Merge branch 'master' of github.com:mongodb/mongo-python-driver

This commit is contained in:
Steven Silvester 2025-05-14 10:05:01 -05:00
commit 953c06dd46
No known key found for this signature in database
GPG Key ID: B1BF5EC3A8B32F91
21 changed files with 156 additions and 94 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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`.

View File

@ -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:

View File

@ -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)
--------------------------------------

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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()

View File

@ -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