diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml
index eb5b49e6f..8bc758890 100644
--- a/.evergreen/generated_configs/tasks.yml
+++ b/.evergreen/generated_configs/tasks.yml
@@ -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
diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml
index 4ec9f5a3a..673bb111c 100644
--- a/.evergreen/generated_configs/variants.yml
+++ b/.evergreen/generated_configs/variants.yml
@@ -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
diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py
index 8bb4d5b21..9f42fb0a4 100644
--- a/.evergreen/scripts/generate_config.py
+++ b/.evergreen/scripts/generate_config.py
@@ -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
diff --git a/README.md b/README.md
index 962d0d958..9bee92c56 100644
--- a/README.md
+++ b/README.md
@@ -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`.
diff --git a/bson/binary.py b/bson/binary.py
index 48f1f5851..6698e55cc 100644
--- a/bson/binary.py
+++ b/bson/binary.py
@@ -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:
diff --git a/doc/changelog.rst b/doc/changelog.rst
index 87918639c..9b0275dbd 100644
--- a/doc/changelog.rst
+++ b/doc/changelog.rst
@@ -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)
--------------------------------------
diff --git a/doc/faq.rst b/doc/faq.rst
index 7656481d8..cb67ea7fe 100644
--- a/doc/faq.rst
+++ b/doc/faq.rst
@@ -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 `_. See `the official docs `_ for more details.
-PyMongo fully supports :doc:`Gevent `.
-
-To use MongoDB with `asyncio `_
-or `Tornado `_, see the
-`Motor `_ project.
+PyMongo also fully supports :doc:`Gevent `.
For `Twisted `_, see `TxMongo
`_. Its stated mission is to keep feature
diff --git a/doc/tools.rst b/doc/tools.rst
index a3f167d02..5a9297ad6 100644
--- a/doc/tools.rst
+++ b/doc/tools.rst
@@ -163,6 +163,9 @@ These are alternatives to PyMongo.
* `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 `_.
* `TxMongo `_ is an asynchronous Twisted
Python driver for MongoDB.
* `MongoMock `_ is a small
diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py
index f4d5b174f..9a39883fc 100644
--- a/pymongo/asynchronous/pool.py
+++ b/pymongo/asynchronous/pool.py
@@ -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:
diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py
index 99b30fed1..052f91afe 100644
--- a/pymongo/asynchronous/topology.py
+++ b/pymongo/asynchronous/topology.py
@@ -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:
diff --git a/pymongo/network_layer.py b/pymongo/network_layer.py
index 3fa180bf7..6f1bb9a35 100644
--- a/pymongo/network_layer.py
+++ b/pymongo/network_layer.py
@@ -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
diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py
index 44aec31a8..505f58c60 100644
--- a/pymongo/synchronous/pool.py
+++ b/pymongo/synchronous/pool.py
@@ -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:
diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py
index 10d41def6..28370d4ad 100644
--- a/pymongo/synchronous/topology.py
+++ b/pymongo/synchronous/topology.py
@@ -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:
diff --git a/pymongo/write_concern.py b/pymongo/write_concern.py
index ff31c6730..1f9da7af2 100644
--- a/pymongo/write_concern.py
+++ b/pymongo/write_concern.py
@@ -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:
diff --git a/test/asynchronous/utils.py b/test/asynchronous/utils.py
index f653c575e..ca80d1f6d 100644
--- a/test/asynchronous/utils.py
+++ b/test/asynchronous/utils.py
@@ -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
diff --git a/test/bson_binary_vector/packed_bit.json b/test/bson_binary_vector/packed_bit.json
index 3015acba6..a220e7e31 100644
--- a/test/bson_binary_vector/packed_bit.json
+++ b/test/bson_binary_vector/packed_bit.json
@@ -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,
diff --git a/test/test_bson.py b/test/test_bson.py
index 522945d5f..1616c513c 100644
--- a/test/test_bson.py
+++ b/test/test_bson.py
@@ -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
diff --git a/test/test_bson_binary_vector.py b/test/test_bson_binary_vector.py
index afe01f42b..9bfdcbfb9 100644
--- a/test/test_bson_binary_vector.py
+++ b/test/test_bson_binary_vector.py
@@ -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
diff --git a/test/test_topology.py b/test/test_topology.py
index 22e94739e..141b2d7f2 100644
--- a/test/test_topology.py
+++ b/test/test_topology.py
@@ -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):
diff --git a/test/test_write_concern.py b/test/test_write_concern.py
index e22c7e7a8..02a7cb6e5 100644
--- a/test/test_write_concern.py
+++ b/test/test_write_concern.py
@@ -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()
diff --git a/test/utils.py b/test/utils.py
index 3027ed751..25d95d1d3 100644
--- a/test/utils.py
+++ b/test/utils.py
@@ -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