Skip to content

Commit e296cf9

Browse files
committed
Merge branch 'master' of github.com:mongodb/mongo-python-driver
2 parents b431154 + 9a7bac7 commit e296cf9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3399
-333
lines changed

.evergreen/generated_configs/tasks.yml

Lines changed: 540 additions & 0 deletions
Large diffs are not rendered by default.

.evergreen/generated_configs/variants.yml

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -817,97 +817,110 @@ buildvariants:
817817
PYTHON_BINARY: /opt/python/3.13/bin/python3
818818

819819
# Ocsp tests
820-
- name: ocsp-rhel8-v4.4-python3.9
820+
- name: ocsp-rhel8-v4.2-python3.9
821821
tasks:
822822
- name: .ocsp
823-
display_name: OCSP RHEL8 v4.4 Python3.9
823+
display_name: OCSP RHEL8 v4.2 Python3.9
824824
run_on:
825825
- rhel87-small
826826
batchtime: 20160
827827
expansions:
828828
AUTH: noauth
829829
SSL: ssl
830830
TOPOLOGY: server
831-
VERSION: "4.4"
831+
VERSION: "4.2"
832832
PYTHON_BINARY: /opt/python/3.9/bin/python3
833-
- name: ocsp-rhel8-v5.0-python3.10
833+
- name: ocsp-rhel8-v4.4-python3.10
834834
tasks:
835835
- name: .ocsp
836-
display_name: OCSP RHEL8 v5.0 Python3.10
836+
display_name: OCSP RHEL8 v4.4 Python3.10
837837
run_on:
838838
- rhel87-small
839839
batchtime: 20160
840840
expansions:
841841
AUTH: noauth
842842
SSL: ssl
843843
TOPOLOGY: server
844-
VERSION: "5.0"
844+
VERSION: "4.4"
845845
PYTHON_BINARY: /opt/python/3.10/bin/python3
846-
- name: ocsp-rhel8-v6.0-python3.11
846+
- name: ocsp-rhel8-v5.0-python3.11
847847
tasks:
848848
- name: .ocsp
849-
display_name: OCSP RHEL8 v6.0 Python3.11
849+
display_name: OCSP RHEL8 v5.0 Python3.11
850850
run_on:
851851
- rhel87-small
852852
batchtime: 20160
853853
expansions:
854854
AUTH: noauth
855855
SSL: ssl
856856
TOPOLOGY: server
857-
VERSION: "6.0"
857+
VERSION: "5.0"
858858
PYTHON_BINARY: /opt/python/3.11/bin/python3
859-
- name: ocsp-rhel8-v7.0-python3.12
859+
- name: ocsp-rhel8-v6.0-python3.12
860860
tasks:
861861
- name: .ocsp
862-
display_name: OCSP RHEL8 v7.0 Python3.12
862+
display_name: OCSP RHEL8 v6.0 Python3.12
863863
run_on:
864864
- rhel87-small
865865
batchtime: 20160
866866
expansions:
867867
AUTH: noauth
868868
SSL: ssl
869869
TOPOLOGY: server
870-
VERSION: "7.0"
870+
VERSION: "6.0"
871871
PYTHON_BINARY: /opt/python/3.12/bin/python3
872-
- name: ocsp-rhel8-v8.0-python3.13
872+
- name: ocsp-rhel8-v7.0-python3.13
873873
tasks:
874874
- name: .ocsp
875-
display_name: OCSP RHEL8 v8.0 Python3.13
875+
display_name: OCSP RHEL8 v7.0 Python3.13
876876
run_on:
877877
- rhel87-small
878878
batchtime: 20160
879879
expansions:
880880
AUTH: noauth
881881
SSL: ssl
882882
TOPOLOGY: server
883-
VERSION: "8.0"
883+
VERSION: "7.0"
884884
PYTHON_BINARY: /opt/python/3.13/bin/python3
885-
- name: ocsp-rhel8-rapid-pypy3.10
885+
- name: ocsp-rhel8-v8.0-pypy3.10
886886
tasks:
887887
- name: .ocsp
888-
display_name: OCSP RHEL8 rapid PyPy3.10
888+
display_name: OCSP RHEL8 v8.0 PyPy3.10
889889
run_on:
890890
- rhel87-small
891891
batchtime: 20160
892892
expansions:
893893
AUTH: noauth
894894
SSL: ssl
895895
TOPOLOGY: server
896-
VERSION: rapid
896+
VERSION: "8.0"
897897
PYTHON_BINARY: /opt/python/pypy3.10/bin/python3
898-
- name: ocsp-rhel8-latest-python3.9
898+
- name: ocsp-rhel8-rapid-python3.9
899899
tasks:
900900
- name: .ocsp
901-
display_name: OCSP RHEL8 latest Python3.9
901+
display_name: OCSP RHEL8 rapid Python3.9
902902
run_on:
903903
- rhel87-small
904904
batchtime: 20160
905905
expansions:
906906
AUTH: noauth
907907
SSL: ssl
908908
TOPOLOGY: server
909-
VERSION: latest
909+
VERSION: rapid
910910
PYTHON_BINARY: /opt/python/3.9/bin/python3
911+
- name: ocsp-rhel8-latest-python3.10
912+
tasks:
913+
- name: .ocsp
914+
display_name: OCSP RHEL8 latest Python3.10
915+
run_on:
916+
- rhel87-small
917+
batchtime: 20160
918+
expansions:
919+
AUTH: noauth
920+
SSL: ssl
921+
TOPOLOGY: server
922+
VERSION: latest
923+
PYTHON_BINARY: /opt/python/3.10/bin/python3
911924
- name: ocsp-win64-v4.4-python3.9
912925
tasks:
913926
- name: .ocsp-rsa !.ocsp-staple
@@ -1066,6 +1079,19 @@ buildvariants:
10661079
PYTHON_BINARY: /opt/python/3.9/bin/python3
10671080

10681081
# Server tests
1082+
- name: test-rhel8-python3.9-cov-no-c
1083+
tasks:
1084+
- name: .standalone .sync_async
1085+
- name: .replica_set .sync_async
1086+
- name: .sharded_cluster .sync_async
1087+
display_name: "* Test RHEL8 Python3.9 cov No C"
1088+
run_on:
1089+
- rhel87-small
1090+
expansions:
1091+
COVERAGE: coverage
1092+
NO_EXT: "1"
1093+
PYTHON_BINARY: /opt/python/3.9/bin/python3
1094+
tags: [coverage_tag]
10691095
- name: test-rhel8-python3.9-cov
10701096
tasks:
10711097
- name: .standalone .sync_async
@@ -1078,6 +1104,19 @@ buildvariants:
10781104
COVERAGE: coverage
10791105
PYTHON_BINARY: /opt/python/3.9/bin/python3
10801106
tags: [coverage_tag]
1107+
- name: test-rhel8-python3.13-cov-no-c
1108+
tasks:
1109+
- name: .standalone .sync_async
1110+
- name: .replica_set .sync_async
1111+
- name: .sharded_cluster .sync_async
1112+
display_name: "* Test RHEL8 Python3.13 cov No C"
1113+
run_on:
1114+
- rhel87-small
1115+
expansions:
1116+
COVERAGE: coverage
1117+
NO_EXT: "1"
1118+
PYTHON_BINARY: /opt/python/3.13/bin/python3
1119+
tags: [coverage_tag]
10811120
- name: test-rhel8-python3.13-cov
10821121
tasks:
10831122
- name: .standalone .sync_async
@@ -1090,6 +1129,19 @@ buildvariants:
10901129
COVERAGE: coverage
10911130
PYTHON_BINARY: /opt/python/3.13/bin/python3
10921131
tags: [coverage_tag]
1132+
- name: test-rhel8-pypy3.10-cov-no-c
1133+
tasks:
1134+
- name: .standalone .sync_async
1135+
- name: .replica_set .sync_async
1136+
- name: .sharded_cluster .sync_async
1137+
display_name: "* Test RHEL8 PyPy3.10 cov No C"
1138+
run_on:
1139+
- rhel87-small
1140+
expansions:
1141+
COVERAGE: coverage
1142+
NO_EXT: "1"
1143+
PYTHON_BINARY: /opt/python/pypy3.10/bin/python3
1144+
tags: [coverage_tag]
10931145
- name: test-rhel8-pypy3.10-cov
10941146
tasks:
10951147
- name: .standalone .sync_async
@@ -1338,6 +1390,7 @@ buildvariants:
13381390
- name: storage-inmemory-rhel8-python3.9
13391391
tasks:
13401392
- name: .standalone .noauth .nossl .4.0 .sync_async
1393+
- name: .standalone .noauth .nossl .4.2 .sync_async
13411394
- name: .standalone .noauth .nossl .4.4 .sync_async
13421395
- name: .standalone .noauth .nossl .5.0 .sync_async
13431396
- name: .standalone .noauth .nossl .6.0 .sync_async

.evergreen/scripts/generate_config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
# Globals
2727
##############
2828

29-
ALL_VERSIONS = ["4.0", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"]
29+
ALL_VERSIONS = ["4.0", "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"]
3030
CPYTHONS = ["3.9", "3.10", "3.11", "3.12", "3.13"]
3131
PYPYS = ["pypy3.10"]
3232
ALL_PYTHONS = CPYTHONS + PYPYS
@@ -279,8 +279,9 @@ def create_server_variants() -> list[BuildVariant]:
279279
host = DEFAULT_HOST
280280
# Prefix the display name with an asterisk so it is sorted first.
281281
base_display_name = "* Test"
282-
for python in [*MIN_MAX_PYTHON, PYPYS[-1]]:
282+
for python, c_ext in product([*MIN_MAX_PYTHON, PYPYS[-1]], C_EXTS):
283283
expansions = dict(COVERAGE="coverage")
284+
handle_c_ext(c_ext, expansions)
284285
display_name = get_display_name(base_display_name, host, python=python, **expansions)
285286
variant = create_variant(
286287
[f".{t} .sync_async" for t in TOPOLOGIES],

CONTRIBUTING.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ documentation including narrative docs, and the [Sphinx docstring format](https:
178178
You can build the documentation locally by running:
179179

180180
```bash
181-
just docs-build
181+
just docs
182182
```
183183

184184
When updating docs, it can be helpful to run the live docs server as:
@@ -261,6 +261,11 @@ To prevent the `synchro` hook from accidentally overwriting code, it first check
261261
of a file is changing and not its async counterpart, and will fail.
262262
In the unlikely scenario that you want to override this behavior, first export `OVERRIDE_SYNCHRO_CHECK=1`.
263263

264+
Sometimes, the `synchro` hook will fail and introduce changes many previously unmodified files. This is due to static
265+
Python errors, such as missing imports, incorrect syntax, or other fatal typos. To resolve these issues,
266+
run `pre-commit run --all-files --hook-stage manual ruff` and fix all reported errors before running the `synchro`
267+
hook again.
268+
264269
## Converting a test to async
265270
The `tools/convert_test_to_async.py` script takes in an existing synchronous test file and outputs a
266271
partially-converted asynchronous version of the same name to the `test/asynchronous` directory.

doc/async-tutorial.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,10 @@ the collection:
420420
DuplicateKeyError: E11000 duplicate key error index: test_database.profiles.$user_id_1 dup key: { : 212 }
421421
422422
.. seealso:: The MongoDB documentation on `indexes <https://www.mongodb.com/docs/manual/indexes/>`_
423+
424+
Task Cancellation
425+
-----------------
426+
`Cancelling <https://docs.python.org/3/library/asyncio-task.html#task-cancellation>`_ an asyncio Task
427+
that is running a PyMongo operation is treated as a fatal interrupt. Any connections, cursors, and transactions
428+
involved in a cancelled Task will be safely closed and cleaned up as part of the cancellation. If those resources are
429+
also used elsewhere, attempting to utilize them after the cancellation will result in an error.

gridfs/asynchronous/grid_file.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ async def get_version(
231231
try:
232232
doc = await anext(cursor)
233233
return AsyncGridOut(self._collection, file_document=doc, session=session)
234-
except StopIteration:
234+
except StopAsyncIteration:
235235
raise NoFile("no version %d for filename %r" % (version, filename)) from None
236236

237237
async def get_last_version(

pymongo/asynchronous/change_stream.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,8 @@ async def try_next(self) -> Optional[_DocumentType]:
391391
if not _resumable(exc) and not exc.timeout:
392392
await self.close()
393393
raise
394-
except Exception:
394+
# Catch KeyboardInterrupt, CancelledError, etc. and cleanup.
395+
except BaseException:
395396
await self.close()
396397
raise
397398

pymongo/asynchronous/client_session.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
2222
.. code-block:: python
2323
24-
with client.start_session(causal_consistency=True) as session:
24+
async with client.start_session(causal_consistency=True) as session:
2525
collection = client.db.collection
2626
await collection.update_one({"_id": 1}, {"$set": {"x": 10}}, session=session)
2727
secondary_c = collection.with_options(read_preference=ReadPreference.SECONDARY)
@@ -53,16 +53,16 @@
5353
5454
orders = client.db.orders
5555
inventory = client.db.inventory
56-
with client.start_session() as session:
57-
async with session.start_transaction():
56+
async with client.start_session() as session:
57+
async with await session.start_transaction():
5858
await orders.insert_one({"sku": "abc123", "qty": 100}, session=session)
5959
await inventory.update_one(
6060
{"sku": "abc123", "qty": {"$gte": 100}},
6161
{"$inc": {"qty": -100}},
6262
session=session,
6363
)
6464
65-
Upon normal completion of ``async with session.start_transaction()`` block, the
65+
Upon normal completion of ``async with await session.start_transaction()`` block, the
6666
transaction automatically calls :meth:`AsyncClientSession.commit_transaction`.
6767
If the block exits with an exception, the transaction automatically calls
6868
:meth:`AsyncClientSession.abort_transaction`.
@@ -113,7 +113,7 @@
113113
.. code-block:: python
114114
115115
# Each read using this session reads data from the same point in time.
116-
with client.start_session(snapshot=True) as session:
116+
async with client.start_session(snapshot=True) as session:
117117
order = await orders.find_one({"sku": "abc123"}, session=session)
118118
inventory = await inventory.find_one({"sku": "abc123"}, session=session)
119119
@@ -619,7 +619,7 @@ async def callback(session):
619619
await inventory.update_one({"sku": "abc123", "qty": {"$gte": 100}},
620620
{"$inc": {"qty": -100}}, session=session)
621621
622-
with client.start_session() as session:
622+
async with client.start_session() as session:
623623
await session.with_transaction(callback)
624624
625625
To pass arbitrary arguments to the ``callback``, wrap your callable
@@ -628,7 +628,7 @@ async def callback(session):
628628
async def callback(session, custom_arg, custom_kwarg=None):
629629
# Transaction operations...
630630
631-
with client.start_session() as session:
631+
async with client.start_session() as session:
632632
await session.with_transaction(
633633
lambda s: callback(s, "custom_arg", custom_kwarg=1))
634634
@@ -697,7 +697,8 @@ async def callback(session, custom_arg, custom_kwarg=None):
697697
)
698698
try:
699699
ret = await callback(self)
700-
except Exception as exc:
700+
# Catch KeyboardInterrupt, CancelledError, etc. and cleanup.
701+
except BaseException as exc:
701702
if self.in_transaction:
702703
await self.abort_transaction()
703704
if (

pymongo/asynchronous/cursor.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1126,7 +1126,8 @@ async def _send_message(self, operation: Union[_Query, _GetMore]) -> None:
11261126
self._killed = True
11271127
await self.close()
11281128
raise
1129-
except Exception:
1129+
# Catch KeyboardInterrupt, CancelledError, etc. and cleanup.
1130+
except BaseException:
11301131
await self.close()
11311132
raise
11321133

pymongo/asynchronous/encryption.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,6 @@ def _wrap_encryption_errors() -> Iterator[None]:
127127
# BSON encoding/decoding errors are unrelated to encryption so
128128
# we should propagate them unchanged.
129129
raise
130-
except asyncio.CancelledError:
131-
raise
132130
except Exception as exc:
133131
raise EncryptionError(exc) from exc
134132

@@ -766,8 +764,6 @@ async def create_encrypted_collection(
766764
await database.create_collection(name=name, **kwargs),
767765
encrypted_fields,
768766
)
769-
except asyncio.CancelledError:
770-
raise
771767
except Exception as exc:
772768
raise EncryptedCollectionError(exc, encrypted_fields) from exc
773769

0 commit comments

Comments
 (0)