Merge branch 'master' of github.com:mongodb/mongo-python-driver
This commit is contained in:
commit
953c06dd46
@ -178,6 +178,7 @@ tasks:
|
||||
vars:
|
||||
TEST_NAME: kms
|
||||
SUB_TEST_NAME: gcp
|
||||
tags: []
|
||||
- name: test-gcpkms-fail
|
||||
commands:
|
||||
- func: run server
|
||||
@ -185,12 +186,14 @@ tasks:
|
||||
vars:
|
||||
TEST_NAME: kms
|
||||
SUB_TEST_NAME: gcp-fail
|
||||
tags: [pr]
|
||||
- name: test-azurekms
|
||||
commands:
|
||||
- func: run tests
|
||||
vars:
|
||||
TEST_NAME: kms
|
||||
SUB_TEST_NAME: azure
|
||||
tags: []
|
||||
- name: test-azurekms-fail
|
||||
commands:
|
||||
- func: run server
|
||||
@ -198,6 +201,7 @@ tasks:
|
||||
vars:
|
||||
TEST_NAME: kms
|
||||
SUB_TEST_NAME: azure-fail
|
||||
tags: [pr]
|
||||
|
||||
# Mod wsgi tests
|
||||
- name: mod-wsgi-replica-set-python3.9
|
||||
|
||||
@ -22,6 +22,7 @@ buildvariants:
|
||||
VERSION: latest
|
||||
NO_EXT: "1"
|
||||
REQUIRE_FIPS: "1"
|
||||
tags: []
|
||||
- name: other-hosts-rhel8-zseries-latest
|
||||
tasks:
|
||||
- name: .test-no-toolchain
|
||||
@ -32,6 +33,7 @@ buildvariants:
|
||||
expansions:
|
||||
VERSION: latest
|
||||
NO_EXT: "1"
|
||||
tags: []
|
||||
- name: other-hosts-rhel8-power8-latest
|
||||
tasks:
|
||||
- name: .test-no-toolchain
|
||||
@ -42,6 +44,7 @@ buildvariants:
|
||||
expansions:
|
||||
VERSION: latest
|
||||
NO_EXT: "1"
|
||||
tags: []
|
||||
- name: other-hosts-rhel8-arm64-latest
|
||||
tasks:
|
||||
- name: .test-no-toolchain
|
||||
@ -52,6 +55,7 @@ buildvariants:
|
||||
expansions:
|
||||
VERSION: latest
|
||||
NO_EXT: "1"
|
||||
tags: []
|
||||
- name: other-hosts-amazon2023-latest
|
||||
tasks:
|
||||
- name: .test-no-toolchain
|
||||
@ -62,6 +66,7 @@ buildvariants:
|
||||
expansions:
|
||||
VERSION: latest
|
||||
NO_EXT: "1"
|
||||
tags: [pr]
|
||||
|
||||
# Atlas connect tests
|
||||
- name: atlas-connect-rhel8
|
||||
@ -70,6 +75,7 @@ buildvariants:
|
||||
display_name: Atlas connect RHEL8
|
||||
run_on:
|
||||
- rhel87-small
|
||||
tags: [pr]
|
||||
|
||||
# Atlas data lake tests
|
||||
- name: atlas-data-lake-ubuntu-22
|
||||
@ -80,6 +86,7 @@ buildvariants:
|
||||
- ubuntu2204-small
|
||||
expansions:
|
||||
TEST_NAME: data_lake
|
||||
tags: [pr]
|
||||
|
||||
# Aws auth tests
|
||||
- name: auth-aws-ubuntu-20
|
||||
@ -88,18 +95,21 @@ buildvariants:
|
||||
display_name: Auth AWS Ubuntu-20
|
||||
run_on:
|
||||
- ubuntu2004-small
|
||||
tags: []
|
||||
- name: auth-aws-win64
|
||||
tasks:
|
||||
- name: .auth-aws !.auth-aws-ecs
|
||||
display_name: Auth AWS Win64
|
||||
run_on:
|
||||
- windows-64-vsMulti-small
|
||||
tags: []
|
||||
- name: auth-aws-macos
|
||||
tasks:
|
||||
- name: .auth-aws !.auth-aws-web-identity !.auth-aws-ecs !.auth-aws-ec2
|
||||
display_name: Auth AWS macOS
|
||||
run_on:
|
||||
- macos-14
|
||||
tags: [pr]
|
||||
|
||||
# Aws lambda tests
|
||||
- name: faas-lambda
|
||||
@ -288,6 +298,7 @@ buildvariants:
|
||||
- rhel87-small
|
||||
expansions:
|
||||
PYTHON_BINARY: /opt/python/3.13t/bin/python3
|
||||
tags: [pr]
|
||||
- name: free-threaded-macos-python3.13t
|
||||
tasks:
|
||||
- name: .free-threading
|
||||
@ -296,6 +307,7 @@ buildvariants:
|
||||
- macos-14
|
||||
expansions:
|
||||
PYTHON_BINARY: /Library/Frameworks/PythonT.Framework/Versions/3.13/bin/python3t
|
||||
tags: []
|
||||
- name: free-threaded-macos-arm64-python3.13t
|
||||
tasks:
|
||||
- name: .free-threading
|
||||
@ -304,6 +316,7 @@ buildvariants:
|
||||
- macos-14-arm64
|
||||
expansions:
|
||||
PYTHON_BINARY: /Library/Frameworks/PythonT.Framework/Versions/3.13/bin/python3t
|
||||
tags: []
|
||||
|
||||
# Green framework tests
|
||||
- name: green-eventlet-rhel8
|
||||
@ -372,6 +385,7 @@ buildvariants:
|
||||
- rhel87-small
|
||||
expansions:
|
||||
TEST_NAME: mockupdb
|
||||
tags: [pr]
|
||||
|
||||
# Mod wsgi tests
|
||||
- name: mod_wsgi-ubuntu-22
|
||||
@ -398,6 +412,7 @@ buildvariants:
|
||||
display_name: No server RHEL8
|
||||
run_on:
|
||||
- rhel87-small
|
||||
tags: [pr]
|
||||
|
||||
# Ocsp tests
|
||||
- name: ocsp-rhel8
|
||||
@ -427,21 +442,29 @@ buildvariants:
|
||||
# Oidc auth tests
|
||||
- name: auth-oidc-ubuntu-22
|
||||
tasks:
|
||||
- name: .auth_oidc
|
||||
- name: .auth_oidc_remote
|
||||
display_name: Auth OIDC Ubuntu-22
|
||||
run_on:
|
||||
- ubuntu2204-small
|
||||
batchtime: 10080
|
||||
- name: auth-oidc-local-ubuntu-22
|
||||
tasks:
|
||||
- name: "!.auth_oidc_remote .auth_oidc"
|
||||
display_name: Auth OIDC Local Ubuntu-22
|
||||
run_on:
|
||||
- ubuntu2204-small
|
||||
batchtime: 10080
|
||||
tags: [pr]
|
||||
- name: auth-oidc-macos
|
||||
tasks:
|
||||
- name: .auth_oidc !.auth_oidc_remote
|
||||
- name: "!.auth_oidc_remote .auth_oidc"
|
||||
display_name: Auth OIDC macOS
|
||||
run_on:
|
||||
- macos-14
|
||||
batchtime: 10080
|
||||
- name: auth-oidc-win64
|
||||
tasks:
|
||||
- name: .auth_oidc !.auth_oidc_remote
|
||||
- name: "!.auth_oidc_remote .auth_oidc"
|
||||
display_name: Auth OIDC Win64
|
||||
run_on:
|
||||
- windows-64-vsMulti-small
|
||||
|
||||
@ -112,10 +112,13 @@ def create_free_threaded_variants() -> list[BuildVariant]:
|
||||
# TODO: PYTHON-5027
|
||||
continue
|
||||
tasks = [".free-threading"]
|
||||
tags = []
|
||||
if host_name == "rhel8":
|
||||
tags.append("pr")
|
||||
host = HOSTS[host_name]
|
||||
python = "3.13t"
|
||||
display_name = get_variant_name("Free-threaded", host, python=python)
|
||||
variant = create_variant(tasks, display_name, python=python, host=host)
|
||||
variant = create_variant(tasks, display_name, tags=tags, python=python, host=host)
|
||||
variants.append(variant)
|
||||
return variants
|
||||
|
||||
@ -329,7 +332,7 @@ def create_atlas_data_lake_variants():
|
||||
tasks = [".test-no-orchestration"]
|
||||
expansions = dict(TEST_NAME="data_lake")
|
||||
display_name = get_variant_name("Atlas Data Lake", host)
|
||||
return [create_variant(tasks, display_name, host=host, expansions=expansions)]
|
||||
return [create_variant(tasks, display_name, tags=["pr"], host=host, expansions=expansions)]
|
||||
|
||||
|
||||
def create_mod_wsgi_variants():
|
||||
@ -370,9 +373,9 @@ def create_oidc_auth_variants():
|
||||
variants = []
|
||||
for host_name in ["ubuntu22", "macos", "win64"]:
|
||||
if host_name == "ubuntu22":
|
||||
tasks = [".auth_oidc"]
|
||||
tasks = [".auth_oidc_remote"]
|
||||
else:
|
||||
tasks = [".auth_oidc !.auth_oidc_remote"]
|
||||
tasks = ["!.auth_oidc_remote .auth_oidc"]
|
||||
host = HOSTS[host_name]
|
||||
variants.append(
|
||||
create_variant(
|
||||
@ -382,6 +385,18 @@ def create_oidc_auth_variants():
|
||||
batchtime=BATCHTIME_WEEK,
|
||||
)
|
||||
)
|
||||
# Add a specific local test to run on PRs.
|
||||
if host_name == "ubuntu22":
|
||||
tasks = ["!.auth_oidc_remote .auth_oidc"]
|
||||
variants.append(
|
||||
create_variant(
|
||||
tasks,
|
||||
get_variant_name("Auth OIDC Local", host),
|
||||
tags=["pr"],
|
||||
host=host,
|
||||
batchtime=BATCHTIME_WEEK,
|
||||
)
|
||||
)
|
||||
return variants
|
||||
|
||||
|
||||
@ -406,6 +421,7 @@ def create_mockupdb_variants():
|
||||
[".test-no-orchestration"],
|
||||
get_variant_name("MockupDB", host),
|
||||
host=host,
|
||||
tags=["pr"],
|
||||
expansions=expansions,
|
||||
)
|
||||
]
|
||||
@ -430,6 +446,7 @@ def create_atlas_connect_variants():
|
||||
create_variant(
|
||||
[".test-no-orchestration"],
|
||||
get_variant_name("Atlas connect", host),
|
||||
tags=["pr"],
|
||||
host=DEFAULT_HOST,
|
||||
)
|
||||
]
|
||||
@ -469,8 +486,10 @@ def create_aws_auth_variants():
|
||||
for host_name in ["ubuntu20", "win64", "macos"]:
|
||||
expansions = dict()
|
||||
tasks = [".auth-aws"]
|
||||
tags = []
|
||||
if host_name == "macos":
|
||||
tasks = [".auth-aws !.auth-aws-web-identity !.auth-aws-ecs !.auth-aws-ec2"]
|
||||
tags = ["pr"]
|
||||
elif host_name == "win64":
|
||||
tasks = [".auth-aws !.auth-aws-ecs"]
|
||||
host = HOSTS[host_name]
|
||||
@ -478,6 +497,7 @@ def create_aws_auth_variants():
|
||||
tasks,
|
||||
get_variant_name("Auth AWS", host),
|
||||
host=host,
|
||||
tags=tags,
|
||||
expansions=expansions,
|
||||
)
|
||||
variants.append(variant)
|
||||
@ -487,7 +507,7 @@ def create_aws_auth_variants():
|
||||
def create_no_server_variants():
|
||||
host = HOSTS["rhel8"]
|
||||
name = get_variant_name("No server", host=host)
|
||||
return [create_variant([".test-no-orchestration"], name, host=host)]
|
||||
return [create_variant([".test-no-orchestration"], name, host=host, tags=["pr"])]
|
||||
|
||||
|
||||
def create_alternative_hosts_variants():
|
||||
@ -512,14 +532,18 @@ def create_alternative_hosts_variants():
|
||||
expansions = dict(VERSION="latest")
|
||||
handle_c_ext(C_EXTS[0], expansions)
|
||||
host = HOSTS[host_name]
|
||||
tags = []
|
||||
if "fips" in host_name.lower():
|
||||
expansions["REQUIRE_FIPS"] = "1"
|
||||
if "amazon" in host_name.lower():
|
||||
tags.append("pr")
|
||||
variants.append(
|
||||
create_variant(
|
||||
[".test-no-toolchain"],
|
||||
display_name=get_variant_name("Other hosts", host, version=version),
|
||||
batchtime=batchtime,
|
||||
host=host,
|
||||
tags=tags,
|
||||
expansions=expansions,
|
||||
)
|
||||
)
|
||||
@ -693,16 +717,18 @@ def create_kms_tasks():
|
||||
for success in [True, False]:
|
||||
name = f"test-{kms_type}kms"
|
||||
sub_test_name = kms_type
|
||||
tags = []
|
||||
if not success:
|
||||
name += "-fail"
|
||||
sub_test_name += "-fail"
|
||||
tags.append("pr")
|
||||
commands = []
|
||||
if not success:
|
||||
commands.append(FunctionCall(func="run server"))
|
||||
test_vars = dict(TEST_NAME="kms", SUB_TEST_NAME=sub_test_name)
|
||||
test_func = FunctionCall(func="run tests", vars=test_vars)
|
||||
commands.append(test_func)
|
||||
tasks.append(EvgTask(name=name, commands=commands))
|
||||
tasks.append(EvgTask(name=name, tags=tags, commands=commands))
|
||||
return tasks
|
||||
|
||||
|
||||
@ -756,6 +782,7 @@ def create_oidc_tasks():
|
||||
if sub_test != "default":
|
||||
tags.append("auth_oidc_remote")
|
||||
tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func]))
|
||||
|
||||
return tasks
|
||||
|
||||
|
||||
@ -802,6 +829,8 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name):
|
||||
tags = ["ocsp", f"ocsp-{algo}", version]
|
||||
if "disableStapling" not in variant:
|
||||
tags.append("ocsp-staple")
|
||||
if algo == "valid-cert-server-staples" and version == "latest":
|
||||
tags.append("pr")
|
||||
|
||||
task_name = get_task_name(
|
||||
f"test-ocsp-{algo}-{base_task_name}", python=python, version=version
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
The PyMongo distribution contains tools for interacting with MongoDB
|
||||
database from Python. The `bson` package is an implementation of the
|
||||
[BSON format](http://bsonspec.org) for Python. The `pymongo` package is
|
||||
a native Python driver for MongoDB. The `gridfs` package is a
|
||||
a native Python driver for MongoDB, offering both synchronous and asynchronous APIs. The `gridfs` package is a
|
||||
[gridfs](https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.md/)
|
||||
implementation on top of `pymongo`.
|
||||
|
||||
|
||||
@ -462,10 +462,6 @@ class Binary(bytes):
|
||||
raise ValueError(f"{padding=}. It must be in [0,1, ..7].")
|
||||
if padding and not vector:
|
||||
raise ValueError("Empty vector with non-zero padding.")
|
||||
if padding and not (vector[-1] & ((1 << padding) - 1)) == 0: # type: ignore
|
||||
raise ValueError(
|
||||
"If padding p is provided, all bits in the final byte lower than p must be 0."
|
||||
)
|
||||
elif dtype == BinaryVectorDtype.FLOAT32: # pack floats as float32
|
||||
format_str = "f"
|
||||
if padding:
|
||||
@ -494,11 +490,6 @@ class Binary(bytes):
|
||||
dtype = BinaryVectorDtype(dtype)
|
||||
n_values = len(self) - position
|
||||
|
||||
if padding and dtype != BinaryVectorDtype.PACKED_BIT:
|
||||
raise ValueError(
|
||||
f"Corrupt data. Padding ({padding}) must be 0 for all but PACKED_BIT dtypes. ({dtype=})"
|
||||
)
|
||||
|
||||
if dtype == BinaryVectorDtype.INT8:
|
||||
dtype_format = "b"
|
||||
format_string = f"<{n_values}{dtype_format}"
|
||||
@ -522,12 +513,6 @@ class Binary(bytes):
|
||||
dtype_format = "B"
|
||||
format_string = f"<{n_values}{dtype_format}"
|
||||
unpacked_uint8s = list(struct.unpack_from(format_string, self, position))
|
||||
if padding and not n_values:
|
||||
raise ValueError("Corrupt data. Vector has a padding P, but no data.")
|
||||
if padding and n_values and not (unpacked_uint8s[-1] & ((1 << padding) - 1)) == 0:
|
||||
raise ValueError(
|
||||
"Corrupt data. Vector has a padding P, but bits in the final byte lower than P are non-zero."
|
||||
)
|
||||
return BinaryVector(unpacked_uint8s, dtype, padding)
|
||||
|
||||
else:
|
||||
|
||||
@ -1,6 +1,21 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Changes in Version 4.13.0 (2025/05/14)
|
||||
--------------------------------------
|
||||
|
||||
PyMongo 4.13 brings a number of changes including:
|
||||
|
||||
- Fixed a bug where :class:`pymongo.write_concern.WriteConcern` repr was not eval-able
|
||||
when using ``w="majority"``.
|
||||
|
||||
Issues Resolved
|
||||
...............
|
||||
|
||||
See the `PyMongo 4.13 release notes in JIRA`_ for the list of resolved issues
|
||||
in this release.
|
||||
|
||||
.. _PyMongo 4.13 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=42509
|
||||
|
||||
Changes in Version 4.12.1 (2025/04/29)
|
||||
--------------------------------------
|
||||
|
||||
@ -170,12 +170,9 @@ PyMongo supports CPython 3.9+ and PyPy3.10+. See the :doc:`python3` for details.
|
||||
|
||||
Does PyMongo support asynchronous frameworks like Gevent, asyncio, Tornado, or Twisted?
|
||||
---------------------------------------------------------------------------------------
|
||||
As of PyMongo v4.13, PyMongo fully supports asyncio and `Tornado <https://www.tornadoweb.org/>`_. See `the official docs <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_ for more details.
|
||||
|
||||
PyMongo fully supports :doc:`Gevent <examples/gevent>`.
|
||||
|
||||
To use MongoDB with `asyncio <https://docs.python.org/3/library/asyncio.html>`_
|
||||
or `Tornado <https://www.tornadoweb.org/>`_, see the
|
||||
`Motor <https://github.com/mongodb/motor>`_ project.
|
||||
PyMongo also fully supports :doc:`Gevent <examples/gevent>`.
|
||||
|
||||
For `Twisted <https://twistedmatrix.com/>`_, see `TxMongo
|
||||
<https://github.com/twisted/txmongo>`_. Its stated mission is to keep feature
|
||||
|
||||
@ -163,6 +163,9 @@ These are alternatives to PyMongo.
|
||||
|
||||
* `Motor <https://github.com/mongodb/motor>`_ is a full-featured, non-blocking
|
||||
MongoDB driver for Python Tornado applications.
|
||||
As of PyMongo v4.13, Motor's features have been merged into PyMongo via the new AsyncMongoClient API.
|
||||
As a result of this merger, Motor will be officially deprecated on May 14th, 2026.
|
||||
For more information, see `the official PyMongo docs <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
|
||||
* `TxMongo <https://github.com/twisted/txmongo>`_ is an asynchronous Twisted
|
||||
Python driver for MongoDB.
|
||||
* `MongoMock <https://github.com/mongomock/mongomock>`_ is a small
|
||||
|
||||
@ -131,6 +131,7 @@ class AsyncConnection:
|
||||
:param pool: a Pool instance
|
||||
:param address: the server's (host, port)
|
||||
:param id: the id of this socket in it's pool
|
||||
:param is_sdam: SDAM connections do not call hello on creation
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@ -139,11 +140,13 @@ class AsyncConnection:
|
||||
pool: Pool,
|
||||
address: tuple[str, int],
|
||||
id: int,
|
||||
is_sdam: bool,
|
||||
):
|
||||
self.pool_ref = weakref.ref(pool)
|
||||
self.conn = conn
|
||||
self.address = address
|
||||
self.id = id
|
||||
self.is_sdam = is_sdam
|
||||
self.closed = False
|
||||
self.last_checkin_time = time.monotonic()
|
||||
self.performed_handshake = False
|
||||
@ -711,13 +714,13 @@ class Pool:
|
||||
self,
|
||||
address: _Address,
|
||||
options: PoolOptions,
|
||||
handshake: bool = True,
|
||||
is_sdam: bool = False,
|
||||
client_id: Optional[ObjectId] = None,
|
||||
):
|
||||
"""
|
||||
:param address: a (hostname, port) tuple
|
||||
:param options: a PoolOptions instance
|
||||
:param handshake: whether to call hello for each new AsyncConnection
|
||||
:param is_sdam: whether to call hello for each new AsyncConnection
|
||||
"""
|
||||
if options.pause_enabled:
|
||||
self.state = PoolState.PAUSED
|
||||
@ -746,14 +749,14 @@ class Pool:
|
||||
self.pid = os.getpid()
|
||||
self.address = address
|
||||
self.opts = options
|
||||
self.handshake = handshake
|
||||
self.is_sdam = is_sdam
|
||||
# Don't publish events or logs in Monitor pools.
|
||||
self.enabled_for_cmap = (
|
||||
self.handshake
|
||||
not self.is_sdam
|
||||
and self.opts._event_listeners is not None
|
||||
and self.opts._event_listeners.enabled_for_cmap
|
||||
)
|
||||
self.enabled_for_logging = self.handshake
|
||||
self.enabled_for_logging = not self.is_sdam
|
||||
|
||||
# The first portion of the wait queue.
|
||||
# Enforces: maxPoolSize
|
||||
@ -1058,14 +1061,14 @@ class Pool:
|
||||
|
||||
raise
|
||||
|
||||
conn = AsyncConnection(networking_interface, self, self.address, conn_id) # type: ignore[arg-type]
|
||||
conn = AsyncConnection(networking_interface, self, self.address, conn_id, self.is_sdam) # type: ignore[arg-type]
|
||||
async with self.lock:
|
||||
self.active_contexts.add(conn.cancel_context)
|
||||
self.active_contexts.discard(tmp_context)
|
||||
if tmp_context.cancelled:
|
||||
conn.cancel_context.cancel()
|
||||
try:
|
||||
if self.handshake:
|
||||
if not self.is_sdam:
|
||||
await conn.hello()
|
||||
self.is_writable = conn.is_writable
|
||||
if handler:
|
||||
|
||||
@ -985,7 +985,7 @@ class Topology:
|
||||
)
|
||||
|
||||
return self._settings.pool_class(
|
||||
address, monitor_pool_options, handshake=False, client_id=self._topology_id
|
||||
address, monitor_pool_options, is_sdam=True, client_id=self._topology_id
|
||||
)
|
||||
|
||||
def _error_message(self, selector: Callable[[Selection], Selection]) -> str:
|
||||
|
||||
@ -357,7 +357,12 @@ def receive_data(conn: Connection, length: int, deadline: Optional[float]) -> me
|
||||
except socket.timeout:
|
||||
if conn.cancel_context.cancelled:
|
||||
raise _OperationCancelled("operation cancelled") from None
|
||||
if _PYPY:
|
||||
if (
|
||||
_PYPY
|
||||
or not conn.is_sdam
|
||||
and deadline is not None
|
||||
and deadline - time.monotonic() < 0
|
||||
):
|
||||
# We reached the true deadline.
|
||||
raise
|
||||
continue
|
||||
|
||||
@ -131,6 +131,7 @@ class Connection:
|
||||
:param pool: a Pool instance
|
||||
:param address: the server's (host, port)
|
||||
:param id: the id of this socket in it's pool
|
||||
:param is_sdam: SDAM connections do not call hello on creation
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@ -139,11 +140,13 @@ class Connection:
|
||||
pool: Pool,
|
||||
address: tuple[str, int],
|
||||
id: int,
|
||||
is_sdam: bool,
|
||||
):
|
||||
self.pool_ref = weakref.ref(pool)
|
||||
self.conn = conn
|
||||
self.address = address
|
||||
self.id = id
|
||||
self.is_sdam = is_sdam
|
||||
self.closed = False
|
||||
self.last_checkin_time = time.monotonic()
|
||||
self.performed_handshake = False
|
||||
@ -709,13 +712,13 @@ class Pool:
|
||||
self,
|
||||
address: _Address,
|
||||
options: PoolOptions,
|
||||
handshake: bool = True,
|
||||
is_sdam: bool = False,
|
||||
client_id: Optional[ObjectId] = None,
|
||||
):
|
||||
"""
|
||||
:param address: a (hostname, port) tuple
|
||||
:param options: a PoolOptions instance
|
||||
:param handshake: whether to call hello for each new Connection
|
||||
:param is_sdam: whether to call hello for each new Connection
|
||||
"""
|
||||
if options.pause_enabled:
|
||||
self.state = PoolState.PAUSED
|
||||
@ -744,14 +747,14 @@ class Pool:
|
||||
self.pid = os.getpid()
|
||||
self.address = address
|
||||
self.opts = options
|
||||
self.handshake = handshake
|
||||
self.is_sdam = is_sdam
|
||||
# Don't publish events or logs in Monitor pools.
|
||||
self.enabled_for_cmap = (
|
||||
self.handshake
|
||||
not self.is_sdam
|
||||
and self.opts._event_listeners is not None
|
||||
and self.opts._event_listeners.enabled_for_cmap
|
||||
)
|
||||
self.enabled_for_logging = self.handshake
|
||||
self.enabled_for_logging = not self.is_sdam
|
||||
|
||||
# The first portion of the wait queue.
|
||||
# Enforces: maxPoolSize
|
||||
@ -1054,14 +1057,14 @@ class Pool:
|
||||
|
||||
raise
|
||||
|
||||
conn = Connection(networking_interface, self, self.address, conn_id) # type: ignore[arg-type]
|
||||
conn = Connection(networking_interface, self, self.address, conn_id, self.is_sdam) # type: ignore[arg-type]
|
||||
with self.lock:
|
||||
self.active_contexts.add(conn.cancel_context)
|
||||
self.active_contexts.discard(tmp_context)
|
||||
if tmp_context.cancelled:
|
||||
conn.cancel_context.cancel()
|
||||
try:
|
||||
if self.handshake:
|
||||
if not self.is_sdam:
|
||||
conn.hello()
|
||||
self.is_writable = conn.is_writable
|
||||
if handler:
|
||||
|
||||
@ -983,7 +983,7 @@ class Topology:
|
||||
)
|
||||
|
||||
return self._settings.pool_class(
|
||||
address, monitor_pool_options, handshake=False, client_id=self._topology_id
|
||||
address, monitor_pool_options, is_sdam=True, client_id=self._topology_id
|
||||
)
|
||||
|
||||
def _error_message(self, selector: Callable[[Selection], Selection]) -> str:
|
||||
|
||||
@ -127,7 +127,7 @@ class WriteConcern:
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "WriteConcern({})".format(
|
||||
", ".join("{}={}".format(*kvt) for kvt in self.__document.items())
|
||||
", ".join(f"{k}={v!r}" for k, v in self.__document.items())
|
||||
)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
|
||||
@ -159,6 +159,7 @@ class AsyncMockConnection:
|
||||
self.cancel_context = _CancellationContext()
|
||||
self.more_to_come = False
|
||||
self.id = random.randint(0, 100)
|
||||
self.is_sdam = False
|
||||
self.server_connection_id = random.randint(0, 100)
|
||||
|
||||
def close_conn(self, reason):
|
||||
@ -172,7 +173,7 @@ class AsyncMockConnection:
|
||||
|
||||
|
||||
class AsyncMockPool:
|
||||
def __init__(self, address, options, handshake=True, client_id=None):
|
||||
def __init__(self, address, options, is_sdam=False, client_id=None):
|
||||
self.gen = _PoolGeneration()
|
||||
self._lock = _async_create_lock()
|
||||
self.opts = options
|
||||
|
||||
@ -20,24 +20,6 @@
|
||||
"padding": 0,
|
||||
"canonical_bson": "1600000005766563746F7200040000000910007F0700"
|
||||
},
|
||||
{
|
||||
"description": "PACKED_BIT with padding",
|
||||
"valid": true,
|
||||
"vector": [127, 8],
|
||||
"dtype_hex": "0x10",
|
||||
"dtype_alias": "PACKED_BIT",
|
||||
"padding": 3,
|
||||
"canonical_bson": "1600000005766563746F7200040000000910037F0800"
|
||||
},
|
||||
{
|
||||
"description": "PACKED_BIT with inconsistent padding",
|
||||
"valid": false,
|
||||
"vector": [127, 7],
|
||||
"dtype_hex": "0x10",
|
||||
"dtype_alias": "PACKED_BIT",
|
||||
"padding": 3,
|
||||
"canonical_bson": "1600000005766563746F7200040000000910037F0700"
|
||||
},
|
||||
{
|
||||
"description": "Empty Vector PACKED_BIT",
|
||||
"valid": true,
|
||||
@ -47,6 +29,15 @@
|
||||
"padding": 0,
|
||||
"canonical_bson": "1400000005766563746F72000200000009100000"
|
||||
},
|
||||
{
|
||||
"description": "PACKED_BIT with padding",
|
||||
"valid": true,
|
||||
"vector": [127, 7],
|
||||
"dtype_hex": "0x10",
|
||||
"dtype_alias": "PACKED_BIT",
|
||||
"padding": 3,
|
||||
"canonical_bson": "1600000005766563746F7200040000000910037F0700"
|
||||
},
|
||||
{
|
||||
"description": "Overflow Vector PACKED_BIT",
|
||||
"valid": false,
|
||||
|
||||
@ -739,7 +739,7 @@ class TestBSON(unittest.TestCase):
|
||||
"""Tests of subtype 9"""
|
||||
# We start with valid cases, across the 3 dtypes implemented.
|
||||
# Work with a simple vector that can be interpreted as int8, float32, or ubyte
|
||||
list_vector = [127, 8]
|
||||
list_vector = [127, 7]
|
||||
# As INT8, vector has length 2
|
||||
binary_vector = Binary.from_vector(list_vector, BinaryVectorDtype.INT8)
|
||||
vector = binary_vector.as_vector()
|
||||
@ -764,18 +764,18 @@ class TestBSON(unittest.TestCase):
|
||||
uncompressed = ""
|
||||
for val in list_vector:
|
||||
uncompressed += format(val, "08b")
|
||||
assert uncompressed[:-padding] == "0111111100001"
|
||||
assert uncompressed[:-padding] == "0111111100000"
|
||||
|
||||
# It is worthwhile explicitly showing the values encoded to BSON
|
||||
padded_doc = {"padded_vec": padded_vec}
|
||||
assert (
|
||||
encode(padded_doc)
|
||||
== b"\x1a\x00\x00\x00\x05padded_vec\x00\x04\x00\x00\x00\t\x10\x03\x7f\x08\x00"
|
||||
== b"\x1a\x00\x00\x00\x05padded_vec\x00\x04\x00\x00\x00\t\x10\x03\x7f\x07\x00"
|
||||
)
|
||||
# and dumped to json
|
||||
assert (
|
||||
json_util.dumps(padded_doc)
|
||||
== '{"padded_vec": {"$binary": {"base64": "EAN/CA==", "subType": "09"}}}'
|
||||
== '{"padded_vec": {"$binary": {"base64": "EAN/Bw==", "subType": "09"}}}'
|
||||
)
|
||||
|
||||
# FLOAT32 is also implemented
|
||||
|
||||
@ -48,11 +48,11 @@ def create_test(case_spec):
|
||||
def run_test(self):
|
||||
for test_case in case_spec.get("tests", []):
|
||||
description = test_case["description"]
|
||||
vector_exp = test_case.get("vector", None)
|
||||
vector_exp = test_case.get("vector", [])
|
||||
dtype_hex_exp = test_case["dtype_hex"]
|
||||
dtype_alias_exp = test_case.get("dtype_alias")
|
||||
padding_exp = test_case.get("padding", 0)
|
||||
canonical_bson_exp = test_case.get("canonical_bson", None)
|
||||
canonical_bson_exp = test_case.get("canonical_bson")
|
||||
# Convert dtype hex string into bytes
|
||||
dtype_exp = BinaryVectorDtype(int(dtype_hex_exp, 16).to_bytes(1, byteorder="little"))
|
||||
|
||||
@ -85,25 +85,14 @@ def create_test(case_spec):
|
||||
self.assertEqual(cB_obs, canonical_bson_exp, description)
|
||||
|
||||
else:
|
||||
"""
|
||||
#### To prove correct in an invalid case (`valid:false`), one MUST
|
||||
- if the vector field is present, raise an exception when attempting to encode a document from the numeric values,
|
||||
dtype, and padding.
|
||||
- if the canonical_bson field is present, raise an exception when attempting to deserialize it into the corresponding
|
||||
numeric values, as the field contains corrupted data.
|
||||
"""
|
||||
# Tests Binary.from_vector()
|
||||
if vector_exp is not None:
|
||||
with self.assertRaises((struct.error, ValueError), msg=description):
|
||||
Binary.from_vector(vector_exp, dtype_exp, padding_exp)
|
||||
|
||||
# Tests Binary.as_vector()
|
||||
if canonical_bson_exp is not None:
|
||||
with self.assertRaises((struct.error, ValueError), msg=description):
|
||||
cB_exp = binascii.unhexlify(canonical_bson_exp.encode("utf8"))
|
||||
decoded_doc = decode(cB_exp)
|
||||
binary_obs = decoded_doc[test_key]
|
||||
binary_obs.as_vector()
|
||||
with self.assertRaises((struct.error, ValueError), msg=description):
|
||||
# Tests Binary.from_vector
|
||||
Binary.from_vector(vector_exp, dtype_exp, padding_exp)
|
||||
# Tests Binary.as_vector
|
||||
cB_exp = binascii.unhexlify(canonical_bson_exp.encode("utf8"))
|
||||
decoded_doc = decode(cB_exp)
|
||||
binary_obs = decoded_doc[test_key]
|
||||
binary_obs.as_vector()
|
||||
|
||||
return run_test
|
||||
|
||||
|
||||
@ -121,7 +121,7 @@ class TestTopologyConfiguration(TopologyTest):
|
||||
self.assertEqual(1, monitor._pool.opts.socket_timeout)
|
||||
|
||||
# The monitor, not its pool, is responsible for calling hello.
|
||||
self.assertFalse(monitor._pool.handshake)
|
||||
self.assertTrue(monitor._pool.is_sdam)
|
||||
|
||||
|
||||
class TestSingleServerTopology(TopologyTest):
|
||||
|
||||
@ -67,6 +67,19 @@ class TestWriteConcern(unittest.TestCase):
|
||||
_fake_type = collections.namedtuple("NotAWriteConcern", ["document"]) # type: ignore
|
||||
self.assertNotEqual(WriteConcern(j=True), _fake_type({"j": True}))
|
||||
|
||||
def assertRepr(self, obj):
|
||||
new_obj = eval(repr(obj))
|
||||
self.assertEqual(type(new_obj), type(obj))
|
||||
self.assertEqual(repr(new_obj), repr(obj))
|
||||
|
||||
def test_repr(self):
|
||||
concern = WriteConcern(j=True, wtimeout=3000, w="majority", fsync=False)
|
||||
self.assertRepr(concern)
|
||||
self.assertEqual(
|
||||
repr(concern),
|
||||
"WriteConcern(wtimeout=3000, j=True, fsync=False, w='majority')",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@ -157,6 +157,7 @@ class MockConnection:
|
||||
self.cancel_context = _CancellationContext()
|
||||
self.more_to_come = False
|
||||
self.id = random.randint(0, 100)
|
||||
self.is_sdam = False
|
||||
self.server_connection_id = random.randint(0, 100)
|
||||
|
||||
def close_conn(self, reason):
|
||||
@ -170,7 +171,7 @@ class MockConnection:
|
||||
|
||||
|
||||
class MockPool:
|
||||
def __init__(self, address, options, handshake=True, client_id=None):
|
||||
def __init__(self, address, options, is_sdam=False, client_id=None):
|
||||
self.gen = _PoolGeneration()
|
||||
self._lock = _create_lock()
|
||||
self.opts = options
|
||||
|
||||
Loading…
Reference in New Issue
Block a user