Compare commits

...

163 Commits
v3.3 ... master

Author SHA1 Message Date
dependabot[bot]
3cd98d9b94
Update pytest requirement from >=7 to >=9.0.3 (#382)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 19:44:47 -04:00
dependabot[bot]
4fe13c6f25
Bump the actions group with 3 updates (#375)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 15:45:08 -08:00
dependabot[bot]
f45226d02f
Bump github/codeql-action from 4.32.2 to 4.32.3 in the actions group (#373)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-17 07:45:36 -06:00
dependabot[bot]
c869e5cd7d
Bump github/codeql-action from 4.32.0 to 4.32.2 in the actions group (#372)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 06:25:45 -06:00
dependabot[bot]
160dbce99f
Bump the actions group with 2 updates (#371)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 06:28:13 -06:00
Steven Silvester
2433f62fd9
PYTHON-5711 Add zizmor config (#370) 2026-01-30 14:58:55 -06:00
Pablo Castellano
99b56b4633
Fix link in README.md (#369) 2026-01-28 15:06:14 -06:00
dependabot[bot]
bb744b7b5c
Bump the actions group with 2 updates (#368)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 07:56:53 -06:00
dependabot[bot]
94a26fa9da
Bump the actions group across 1 directory with 2 updates (#367)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-20 12:12:43 -06:00
dependabot[bot]
b3b102a89d
Bump zizmorcore/zizmor-action from cb3d8e846e148d1111d90b03375b9c03deceda37 to 706c51b5bce7adb027de71ab36d865f5d3fcc7b7 in the actions group (#365)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 09:16:34 -06:00
dependabot[bot]
9bae93d875
Bump furo from 2025.9.25 to 2025.12.19 (#364)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 07:40:00 -06:00
dependabot[bot]
aab94545dd
Bump github/codeql-action from 4.31.8 to 4.31.9 in the actions group (#363)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 07:39:36 -06:00
dependabot[bot]
ccc861c5c7
Bump the actions group with 4 updates (#362)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 06:33:08 -06:00
dependabot[bot]
87d1c9ed87
Bump github/codeql-action from 4.31.6 to 4.31.7 in the actions group (#361)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 05:31:38 -06:00
dependabot[bot]
77bd8ba755
Bump the actions group with 2 updates (#360)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 08:24:25 -06:00
dependabot[bot]
61c0e5ccf0
Bump the actions group with 4 updates (#359)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 05:54:44 -06:00
dependabot[bot]
156f3b5f16
Bump zizmorcore/zizmor-action from da5ac40c5419dcf7f21630fb2f95e725ae8fb9d5 to 1aba86d8e1245be7a9ca003d46fcc85a76e6aa61 in the actions group (#358)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-11 09:24:02 -06:00
dependabot[bot]
bafa539ed0
Bump github/codeql-action from 4.31.0 to 4.31.2 in the actions group (#357)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 08:22:02 -06:00
Steven Silvester
d0ed67cd80
MOTOR-1478 Update feedback link (#356) 2025-10-29 07:57:51 -05:00
dependabot[bot]
7785176fdc
Bump the actions group with 3 updates (#355)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 07:45:42 -05:00
dependabot[bot]
536b2dd9d8
Bump github/codeql-action from 3.30.6 to 4.30.8 in the actions group (#354)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Steven Silvester <steve.silvester@mongodb.com>
2025-10-20 09:43:22 -05:00
Steven Silvester
224da029d5
MOTOR-1477 Add workaround for missing compression.zstd (#353) 2025-10-07 12:28:47 -05:00
Steven Silvester
52234081fb
MOTOR-1476 Fix linkcheck for JIRA release notes (#352) 2025-10-07 12:27:48 -05:00
dependabot[bot]
b81fd4e181
Bump the actions group with 2 updates (#351)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 16:50:30 -05:00
dependabot[bot]
820ba59552
Update sphinx requirement from <8,>=5.3 to >=5.3,<9 (#348)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Steven Silvester <steven.silvester@ieee.org>
Co-authored-by: Steven Silvester <steve.silvester@mongodb.com>
2025-09-30 10:26:01 -05:00
dependabot[bot]
144c460d8e
Bump furo from 2024.8.6 to 2025.9.25 (#349)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-30 06:29:33 -05:00
dependabot[bot]
d6c688b609
Update sphinx-rtd-theme requirement from <3,>=2 to >=2,<4 (#347)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-30 06:20:09 -05:00
dependabot[bot]
79fd03a68f
Bump the actions group with 7 updates (#350)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Steven Silvester <steve.silvester@mongodb.com>
2025-09-30 06:19:20 -05:00
Steven Silvester
33e4f112c7
MOTOR-1475 Update docs references (#346) 2025-09-15 15:54:53 -05:00
Steven Silvester
12d9d7db05
MOTOR-1474 Update evergreen tests to use RHEL8 (#345) 2025-08-15 13:19:58 -05:00
Noah Stapp
5bcdd4ad6a
MOTOR-1470 - Add Deprecation Warning to Motor (#340) 2025-08-04 14:35:30 -04:00
Steven Silvester
5fc7c41fb5
MOTOR-1473 Add support for Python 3.14 and drop 3.9 support (#344) 2025-07-30 14:55:53 -05:00
Jib
bbada7dace
PYTHON-5438: Create CODEOWNERS for motor (#342)
bypassing docs failures
2025-07-21 13:58:33 -04:00
Steven Silvester
4bb5e32e80
MOTOR-1471 Remove tests for MongoDB 4.0 (#343) 2025-07-15 09:00:32 -05:00
Steven Silvester
8412d20e28
PYTHON-5430 Use the zizmor action (#341) 2025-07-11 16:46:46 -05:00
Noah Stapp
4f1962faf2
MOTOR-1469 - Update the wording of Motor Deprecation Message (#339) 2025-06-26 10:08:37 -04:00
Steven Silvester
edcae5440d
MOTOR-1466 & MOTOR-1467 Skip test and move to MacOS 14 (#338) 2025-06-02 08:22:09 -05:00
mongodb-dbx-release-bot[bot]
71c3cf9dda
BUMP 3.7.2.dev0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2025-05-14 18:57:49 +00:00
Noah Stapp
1b2e255218
Bump version to 3.7.1 for release (#337) 2025-05-14 14:51:33 -04:00
Noah Stapp
c734a3a3f6
Update changelog for 3.7.1 (#336) 2025-05-14 14:34:36 -04:00
Noah Stapp
e20ae5fe5b
PYTHON-5377 - Further update assets to align with GA release of Async PyMongo (#335) 2025-05-14 13:37:35 -04:00
Noah Stapp
f6a70b8c3c
PYTHON-5377 - Update assets to align with GA release of Async PyMongo (#334) 2025-05-13 16:27:42 -04:00
Steven Silvester
83f735ad45
PYTHON-5353 Use pinned sources for GitHub Actions (#333) 2025-04-29 13:56:32 -05:00
Steven Silvester
c291853a13
PYTHON-5348 Fix CodeQL Scanning for GitHub Actions (#332) 2025-04-24 09:27:54 -05:00
Steven Silvester
d5d0995776
MOTOR-1453 & MOTOR-1454 Update expected attributes tests and links (#331) 2025-04-17 09:51:55 -05:00
Steven Silvester
eae1d25c5b
PYTHON-5188 Make version setting a part of the release process (#330) 2025-04-16 10:36:08 -05:00
Steven Silvester
a879ce528a
PYTHON-5131 Migrate off of Ubuntu 20.04 GitHub Actions Runners (#329) 2025-03-05 13:42:02 -06:00
Steven Silvester
6751d66ed3
PYTHON-5136 Add check-json to pre-commit checks (#328) 2025-03-03 12:19:26 -06:00
Steven Silvester
0c8a698fa4
PYTHON-5148 Update SBOM usage for Kondukto (#327) 2025-02-27 10:09:24 -06:00
Scott
e036c6382d
[DOCS] make an explicit warning not to create clients for each request (#324)
Co-authored-by: Steven Silvester <steve.silvester@mongodb.com>
2025-02-26 14:29:57 -08:00
Steven Silvester
e2e967b610
PYTHON-5047 Do not run nightly release check on forks (#325) 2025-02-06 09:23:01 -06:00
mongodb-dbx-release-bot[bot]
9491e32850
BUMP 3.8.0.dev0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2025-01-29 21:13:59 +00:00
mongodb-dbx-release-bot[bot]
251d2a088c
BUMP 3.7.0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2025-01-29 21:08:25 +00:00
Steven Silvester
381fdfbde0
PYTHON-5047 Fix dry run logic in releases yet again (#323) 2025-01-29 15:07:36 -06:00
nfsknight
9cf90a8898
In documentation clarify the purpose behind client creation. (#320)
Co-authored-by: Steven Silvester <steve.silvester@mongodb.com>
Co-authored-by: Steven Silvester <steven.silvester@ieee.org>
2025-01-29 14:03:56 -06:00
Steven Silvester
0259b9f5a8
PYTHON-5047 Improve testing of publish workflows (#322) 2025-01-29 13:39:42 -06:00
Steven Silvester
932bb7382e
MOTOR-1423 Restore compatibility with latest pymongo (#319) 2025-01-29 13:16:09 -06:00
Steven Silvester
1433773db4
PYTHON-5062 Add GitHub Actions CodeQL scanning (#321) 2025-01-27 09:41:00 -06:00
mongodb-dbx-release-bot[bot]
459760f4b4
BUMP 3.6.2.dev0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2025-01-14 18:47:17 +00:00
mongodb-dbx-release-bot[bot]
b9547fa9c2
BUMP 3.6.1
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2025-01-14 18:44:15 +00:00
Steven Silvester
b4f887bf95
Add changelog for 3.6.1 (#317) 2025-01-14 12:43:09 -06:00
Shane Harvey
c1df3a2105
MOTOR-1420 Use generic return type for to_list() (#318) 2025-01-13 12:45:59 -08:00
Ryu Juheon
777d2aadf8
MOTOR-1420 Add detailed return type to to_list method (#316) 2025-01-13 10:29:22 -06:00
Steven Silvester
5cd704ea74
PYTHON-5017 Fix post-publish step (#315) 2025-01-08 16:40:17 -06:00
Steven Silvester
a98698531a
PYTHON-5017 Use a separate PyPI publish step (#314) 2025-01-06 16:24:08 -06:00
Steven Silvester
ff9932ce6f
MOTOR-1413 Install pymongo from v4.9 branch when installing from source (#313) 2024-11-20 11:55:12 -06:00
Steven Silvester
cbef587966
PYTHON-4962 Adopt zizmor GitHub Actions security scanner (#312) 2024-11-12 13:00:11 -06:00
mongodb-dbx-release-bot[bot]
66658d962e
BUMP 3.6.1.dev0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-09-18 16:51:48 +00:00
mongodb-dbx-release-bot[bot]
20015b458e
BUMP 3.6.0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-09-18 16:47:23 +00:00
Steven Silvester
cf7b4cfe53
MOTOR-1351 Bump minimum PyMongo version to 3.9 (#308) 2024-09-18 11:44:43 -05:00
Steven Silvester
e13888b08e
MOTOR-1364 Add support for Python 3.13 (#307) 2024-09-11 14:45:09 -05:00
Steven Silvester
5c77c38016
MOTOR-1177 Add documentation for Type Annotation Support (#306) 2024-09-11 12:13:31 -05:00
Noah Stapp
2a9225d0ce
MOTOR-1363 - Pass correct parameter to AsyncIOMotorClientEncryption (#305) 2024-09-11 12:32:49 -04:00
Steven Silvester
7050d54dc4
MOTOR-1362 Use furo theme in ReadTheDocs (#304) 2024-09-09 10:08:26 -05:00
Steven Silvester
b652685be4
MOTOR-1360 Use AssumeRole for S3 Access in Evergreen Builds (#303) 2024-09-03 14:41:52 -05:00
Steven Silvester
48f34cbe02
MOTOR-1165 Add documentation for CSOT (#302) 2024-08-29 15:41:41 -05:00
Steven Silvester
a08b1aff9c
MOTOR-1260 Add Projection with aggregation expressions example (#301) 2024-08-23 13:43:16 -05:00
Steven Silvester
00f27f3294
MOTOR-940 Improved Bulk Write API (#300) 2024-08-21 14:27:52 -05:00
Bar Hochman
99cc1b5155
MOTOR-1358 Fix AgnosticGridFSBucket collection type (#299)
Co-authored-by: Shane Harvey <shnhrv@gmail.com>
2024-08-20 14:58:23 -05:00
Steven Silvester
633153954d
MOTOR-1353 Update Synchro Tests to support PyMongo 4.9 (#298) 2024-08-20 13:27:29 -05:00
Steven Silvester
972b7ffef9
MOTOR-1352 Update APIs to Support PyMongo 4.9 (#297) 2024-08-17 07:21:45 -05:00
Steven Silvester
72e68b7569
PYTHON-4541 Add attestations for Python Releases (#296) 2024-07-30 08:35:46 -05:00
Steven Silvester
feb49a882c
MOTOR-1337 Test against Python 3.13 beta (#295) 2024-07-11 07:18:07 -05:00
mongodb-dbx-release-bot[bot]
cc6dfabdde
BUMP 3.6.0.dev0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-07-10 20:36:50 +00:00
mongodb-dbx-release-bot[bot]
b1df705d6a
BUMP 3.5.1
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-07-10 20:32:54 +00:00
Steven Silvester
9065e68df8
MOTOR-1336 Add changelog for 3.5.1 (#294) 2024-07-10 13:40:18 -05:00
Steven Silvester
13ca542d5e
MOTOR-1335 AsyncIOMotorClient is not suscriptable (#293) 2024-07-09 11:17:36 -05:00
mongodb-dbx-release-bot[bot]
5146708820
BUMP 3.6.0.dev0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-06-26 11:40:04 +00:00
mongodb-dbx-release-bot[bot]
cd0316acd2
BUMP 3.5.0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-06-26 11:36:37 +00:00
Shane Harvey
73a4cf0e60
PYTHON-4499 Fix test_logging_without_listeners (#292) 2024-06-25 17:40:09 -07:00
mongodb-dbx-release-bot[bot]
fb67b5ff4a
BUMP 3.5.0.dev2
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-06-25 21:41:35 +00:00
mongodb-dbx-release-bot[bot]
0967365505
BUMP 3.5.0rc2
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-06-25 21:38:10 +00:00
Steven Silvester
fd55efee76
MOTOR-1332 Update changelog for 3.5 (#291) 2024-06-25 15:20:43 -05:00
mongodb-dbx-release-bot[bot]
3ff6c13a53
BUMP 3.5.0.dev1
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-06-25 19:21:14 +00:00
mongodb-dbx-release-bot[bot]
0e429b94b0
BUMP 3.5.0rc0
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-06-25 19:12:57 +00:00
Steven Silvester
be2cd387a2
MOTOR-526 Add evergreen link and version handling for SSDLC (#289) 2024-06-25 14:11:48 -05:00
Steven Silvester
9b2b4f07a3
MOTOR-1332 Add changelog for 3.5 (#290) 2024-06-25 10:40:01 -05:00
Steven Silvester
d235a40f5a
MOTOR-526 SSDLC Conformance (#288)
Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
Co-authored-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
2024-06-24 10:29:22 -05:00
Steven Silvester
5ef6a15dc6
MOTOR-1314 Test against 8.0 builds (#287) 2024-06-20 08:02:19 -05:00
Steven Silvester
cac2145109
MOTOR-1299 Add components field to SBOM (#285) 2024-06-07 09:57:41 -05:00
Steven Silvester
96297ac947
MOTOR-1327 Use Hatch as Build Backend (#286) 2024-06-07 08:23:48 -05:00
Steven Silvester
aa6876aa4d
MOTOR-1299 Add SBOM-lite and CodeQL files (#283) 2024-06-05 15:18:23 -05:00
Steven Silvester
888b36a172
MOTOR-1322 Update feature detection imports in synchro (#284) 2024-06-03 16:44:04 -05:00
Steven Silvester
c2d9dc5954
MOTOR-1299 Rename requirements files for dependabot detection (#282) 2024-05-24 12:22:03 -05:00
Steven Silvester
0ace17713d
MOTOR-1299 Set up dependabot config for Python (#281) 2024-05-23 10:41:44 -05:00
Steven Silvester
fd3a60a4f4
MOTOR-1319 Move to RHEL 7.9 Runner (#280) 2024-05-22 16:30:07 -05:00
Steven Silvester
c207caa105
MOTOR-1316 Drop Support for Python 3.7 (#279) 2024-05-15 17:16:06 -05:00
Steven Silvester
6ee75e08f2
MOTOR-1315 Fix compatibility with pytest 8.1 (#278) 2024-05-15 09:51:12 -05:00
Steven Silvester
0248600e38
Revert "MOTOR-1294 Reinstate CODEOWNERS file" (#277) 2024-04-19 10:56:50 -05:00
Steven Silvester
e93b9693d4
MOTOR-1299 Create separate requirements files (#276) 2024-04-18 19:45:31 -05:00
Doekeb
5cf54914f5
MOTOR-1298 Type resume_token as a property (#275) 2024-04-16 11:34:25 -07:00
Steven Silvester
81a5b8ebdf
MOTOR-1294 Reinstate CODEOWNERS file (#274) 2024-04-15 12:16:20 -05:00
Doekeb
96aeb4007a
MOTOR-1285 Add generics to type stubs (#272) 2024-04-01 09:00:51 -05:00
Steven Silvester
b0dda651bf
BUMP 3.5.0.dev0 2024-03-26 12:51:01 -05:00
Steven Silvester
19eeb29b98
BUMP 3.4.0 2024-03-26 12:49:34 -05:00
Steven Silvester
72e8655033
MOTOR-1286 Update Changelog for 3.4.0 (#273) 2024-03-26 12:46:44 -05:00
Casey Clements
0f1a6cdb79
[Motor-1175] Document Network Compression (#271)
* Clean up link to TLC cheat sheet

* Added Network Compression section to Configuration docs page

* Fix typo pymongo -> motor
2024-03-23 11:01:49 -04:00
Steven Silvester
7bcea044a9
MOTOR-1279 Improve Motor Typing (#269) 2024-03-22 17:00:25 -05:00
Steven Silvester
f4832d97a6
MOTOR-1284 Fix Cryptography Install on PyPy3.8 (#270)
* MOTOR-1284 Fix Cryptography Install

* specify install_command

* handle warning

* handle warning
2024-03-22 09:14:31 -05:00
Casey Clements
2d3598796e
[MOTOR-1269] Exclude Synchro test TestUnifiedInterruptInUsePoolClear (#268) 2024-03-20 15:44:45 -04:00
Shane Harvey
1414d0fb02
MOTOR-1282 Stop using filemd5 in docs example for db.command (#267) 2024-03-18 10:59:42 -07:00
Steven Silvester
9d00dd236e
MOTOR-1277 Limit when pull request-specific tasks are run (#266) 2024-03-12 19:08:31 -05:00
Steven Silvester
228e76af48
MOTOR-1271 Use GitHub App to Auto Assign Reviewer (#265)
* MOTOR-1271 Use GitHub App to Auto Assign Reviewer

* Add PR label to more things

* add reviewers file

* fix repo name
2024-03-04 13:26:28 -06:00
Shane Harvey
e8a6d6de3c
MOTOR-1257 key_id can be passed as UUID with pymongo>=4.7 (#264) 2024-02-29 15:15:22 -08:00
Shane Harvey
8442c0a38e
MOTOR-1267 Fix encryption tests that require FLE_AWS_KEY2/FLE_AWS_SECRET2 vars (#263) 2024-02-29 15:13:13 -08:00
Steven Silvester
d902424109
MOTOR-1266 Update path to CSFLE scripts (#262)
* MOTOR-1266 Update path to CSFLE scripts

* update to use secrets_handling script
2024-02-29 04:57:16 -06:00
Shane Harvey
cd6b2bb860
MOTOR-1240 Skip GridFS tests failing due to PYTHON-4146 (#261) 2024-02-20 14:53:04 -08:00
Noah Stapp
928213a715
MOTOR-1247 - Fix typing issue with motor_asyncio types (#260)
* MOTOR-1247 - Typing issue with motor_asyncio types
2024-02-12 12:23:59 -08:00
dependabot[bot]
820aa81ac2
Bump the actions group with 4 updates (#259)
* Bump the actions group with 4 updates

Bumps the actions group with 4 updates: [actions/checkout](https://github.com/actions/checkout), [actions/setup-python](https://github.com/actions/setup-python), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/checkout` from 2 to 4
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v4)

Updates `actions/setup-python` from 3 to 5
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v3...v5)

Updates `actions/upload-artifact` from 3 to 4
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

Updates `actions/download-artifact` from 3 to 4
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update release.yml

* gate the publish

* add concurrency

* do no build in env

* cleanup

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Steven Silvester <steven.silvester@ieee.org>
2024-02-06 11:41:43 -06:00
AS1
c6d436b0c5
Typehints for "to_list" (#246)
* Typehints for "to_list"

Typehints have been added so that code analyzers better understand that None can be passed to a function

* Moved type hints to stub file (.pyi)

---------

Co-authored-by: Casey Clements <caseyclements@users.noreply.github.com>
2024-02-06 09:53:23 -06:00
Jib
dc29694e14
MOTOR-1163: remove AIOHTTP_NO_EXTENSIONS and test support against Python 3.12 (#255) 2024-02-06 09:59:42 -05:00
Steven Silvester
43ea961870
MOTOR-1248 Add Dependabot Config for GitHub Actions (#258) 2024-02-06 08:50:22 -06:00
Steven Silvester
bdf8a31f55
MOTOR-1246 Get CSFLE Secrets from the Vault (#257)
* use csfle scripts

* fix expansion

* fix invocation

* fix server start
2024-02-05 15:20:31 -06:00
Steven Silvester
ba756ca62c
MOTOR-1245 Ensure secrets are not logged in Evergreen (#256)
* MOTOR-1245 Ensure secrets are not logged in Evergreen

* fix handling of enterprise auth

* fix invocation
2024-02-05 13:26:09 -06:00
Jib
e040f7d0c1
MOTOR-1218: Ignore aiohttp==3.8.6 (#253) 2024-02-01 08:49:06 -05:00
Steven Silvester
c5616e091a
MOTOR-1243 Update TLS cheat sheet link (#254) 2024-01-31 14:49:32 -06:00
Jib
b578122363
MOTOR-1218: Upgrade to require aiohttp >=3.8 and avoid 3.8.6 (#251) 2024-01-30 12:20:09 -05:00
Jib
dc8b329adf
MOTOR-1238: remove skip on PYTHON-3389 flagged tests (#252) 2024-01-30 11:01:50 -05:00
kikones34
da50b55d24
MOTOR-1235 - Fix typing for index listing (#249)
Fix typing for index listing
2024-01-16 15:23:10 -06:00
Steven Silvester
2ada94b0e9
MOTOR-1232 Update documentation dependencies (#250)
* MOTOR-1232 Update documentation dependencies

* set min sphinx to 5.3
2024-01-16 13:29:27 -06:00
Jib
2f69643760
MOTOR-1215: Replaced all usage of utcnow with .now(datetime.timezone.utc) (#247) 2024-01-16 11:52:09 -05:00
Steven Silvester
fc84d28fab
MOTOR-1225 Add badges to README (#245)
* MOTOR-1225 Add badges to README

* restore image

* clean up about section
2023-12-15 14:59:36 -06:00
Steven Silvester
3f8314db62
MOTOR-1224 Convert top level docs to Markdown (#244)
* MOTOR-1224 Convert top level docs to Markdown

* add md files

* fix manifest

* address review
2023-12-12 14:12:51 -06:00
Steven Silvester
9a86b616dd
MOTOR-1221 Add pyi lint checks (#242)
* MOTOR-1221 Add pyi lint checks

* cleanup
2023-12-11 12:35:06 -06:00
Kirill Kulikov
597fb405dc
MOTOR-1223 Add ClientSession to __all__ (#243) 2023-11-29 11:28:21 -08:00
Steven Silvester
bb1e4b9ba1 BUMP 3.4.0.dev1 2023-11-14 15:41:14 -06:00
Jib
a1c57b7068
MOTOR-1209: Motor's DriverInfo should not be overwritten (#233) 2023-11-14 15:35:06 -05:00
Steven Silvester
d58545a7a1
MOTOR-1219 Use PyPI Trusted Publishing (#240)
* MOTOR-1219 Use PyPI Trusted Publishing

* Address review
2023-11-14 14:28:13 -06:00
Noah Stapp
593b57f037
MOTOR-1213 Release 3.3.2 changelog update (#241) 2023-11-14 10:21:06 -08:00
Jib
3836246b51
PYTHON-4040 Pin testing to aiohttp<3.8.6 (#239)
only take aiohttp lower than 3.8.6
2023-11-13 14:10:43 -05:00
Steven Silvester
f0c9ff3e48
MOTOR-1187 Fix Motor Types (#237)
* MOTOR-1187 Fix Motor Types

* make searchindexmodel optional

* fix tests
2023-11-13 08:25:44 -06:00
Shane Harvey
c644cdd9a6
MOTOR-1216 Skip change stream test_concurrent_close tests (#238) 2023-11-10 13:53:58 -08:00
Shane Harvey
546f7540f1 MOTOR-1214 Fix catch_warnings on Python 3.7 2023-11-09 18:13:27 -08:00
Shane Harvey
5e6453a266
MOTOR-1182 Attach an error code to workaround PYTHON-4038 (#235) 2023-11-09 17:12:45 -08:00
Shane Harvey
cb050ef3d7
MOTOR-1214 Ignore IOLoop.current deprecation to make synchro pass (#236) 2023-11-09 16:45:47 -08:00
Steven Silvester
4513307899
MOTOR-1212 Update QA Checks to Match PyMongo (#234)
* clean up config

* cleanup

* clean up

* fix pytest config

* clean up typing
2023-11-09 13:00:21 -06:00
Steven Silvester
8f71800c4a
MOTOR-1210 Update pre-commit to match PyMongo Checks (#232)
* MOTOR-1210 Update pre-commit to match PyMongo Checks

* update doctests
2023-11-08 10:13:38 -06:00
Steven Silvester
684279ed0a
MOTOR-1194 Replace flake8 and isort with ruff (#231)
* adopt ruff

* fixups

* cleanup

* update traceback
2023-10-20 13:45:21 -05:00
Steven Silvester
0ed06b65ac
MOTOR-1184 Add changelog entry for 3.3.1 (#229) 2023-09-05 11:58:54 -05:00
Sergi
8a3712cfd1
MOTOR-1183 Typing issue in core.pyi causing MyPy error for to_list … (#226)
Co-authored-by: Sergi Pous <spous@objectvideo.com>
Co-authored-by: Steven Silvester <steven.silvester@ieee.org>
2023-08-31 04:46:51 -05:00
Steven Silvester
47ff33f585
MOTOR-1180 Clean up EVG Output (#225) 2023-08-29 15:42:04 -05:00
Steven Silvester
c391969776
MOTOR-1174 Switch Synchro Tests to Pytest (#224) 2023-08-29 13:44:57 -05:00
Steven Silvester
c2985791cb BUMP 3.4.0.dev0 2023-08-24 15:40:02 -05:00
147 changed files with 3987 additions and 2056 deletions

View File

@ -35,6 +35,7 @@ functions:
params:
working_dir: "src"
script: |
set -o xtrace
# Get the current unique version of this checkout
if [ "${is_patch}" = "true" ]; then
CURRENT_VERSION=$(git describe)-patch-${version_id}
@ -51,37 +52,28 @@ functions:
export PROJECT_DIRECTORY=$(cygpath -m $PROJECT_DIRECTORY)
fi
# Installation of cryptography requires a rust compiler on some machines
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path
export PATH="$HOME/.cargo/bin:$PATH"
rustup toolchain install nightly -c rustc
export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration"
export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin"
export UPLOAD_BUCKET="${project}"
cat <<EOT > expansion.yml
CURRENT_VERSION: "$CURRENT_VERSION"
DRIVERS_TOOLS: "$DRIVERS_TOOLS"
MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME"
MONGODB_BINARIES: "$MONGODB_BINARIES"
UPLOAD_BUCKET: "$UPLOAD_BUCKET"
PROJECT_DIRECTORY: "$PROJECT_DIRECTORY"
PREPARE_SHELL: |
set -o errexit
set -o xtrace
export DRIVERS_TOOLS="$DRIVERS_TOOLS"
export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME"
export MONGODB_BINARIES="$MONGODB_BINARIES"
export UPLOAD_BUCKET="$UPLOAD_BUCKET"
export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
export TMPDIR="$MONGO_ORCHESTRATION_HOME/db"
export PATH="$MONGODB_BINARIES:$HOME/.cargo/bin:$PATH"
export PATH="$MONGODB_BINARIES:$PATH"
export PROJECT="${project}"
export ASYNC_TEST_TIMEOUT=30
export CARGO_NET_GIT_FETCH_WITH_CLI=true
export PIP_QUIET=1
EOT
# See what we've done
# See what we've done.
cat expansion.yml
# Load the expansion file to make an evergreen variable with the current unique version
@ -104,58 +96,6 @@ functions:
fi
echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config
"upload release":
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
local_file: ${project}.tar.gz
remote_file: ${UPLOAD_BUCKET}/${project}-${CURRENT_VERSION}.tar.gz
bucket: mciuploads
permissions: public-read
content_type: ${content_type|application/x-gzip}
# Upload build artifacts that other tasks may depend on
# Note this URL needs to be totally unique, while predictable for the next task
# so it can automatically download the artifacts
"upload build":
# Compress and upload the entire build directory
- command: archive.targz_pack
params:
# Example: mongo_c_driver_releng_9dfb7d741efbca16faa7859b9349d7a942273e43_16_11_08_19_29_52.tar.gz
target: "${build_id}.tar.gz"
source_dir: ${PROJECT_DIRECTORY}/
include:
- "./**"
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
local_file: ${build_id}.tar.gz
# Example: /mciuploads/${UPLOAD_BUCKET}/gcc49/9dfb7d741efbca16faa7859b9349d7a942273e43/debug-compile-nosasl-nossl/mongo_c_driver_releng_9dfb7d741efbca16faa7859b9349d7a942273e43_16_11_08_19_29_52.tar.gz
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${task_name}/${build_id}.tar.gz
bucket: mciuploads
permissions: public-read
content_type: ${content_type|application/x-gzip}
"fetch build":
- command: shell.exec
params:
continue_on_err: true
script: "set -o xtrace && rm -rf ${PROJECT_DIRECTORY}"
- command: s3.get
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${BUILD_NAME}/${build_id}.tar.gz
bucket: mciuploads
local_file: build.tar.gz
- command: shell.exec
params:
continue_on_err: true
# EVG-1105: Use s3.get extract_to: ./
script: "set -o xtrace && cd .. && rm -rf ${PROJECT_DIRECTORY} && mkdir ${PROJECT_DIRECTORY}/ && tar xf build.tar.gz -C ${PROJECT_DIRECTORY}/"
"exec compile script" :
- command: shell.exec
type: test
@ -174,74 +114,10 @@ functions:
${PREPARE_SHELL}
[ -f ${PROJECT_DIRECTORY}/${file} ] && sh ${PROJECT_DIRECTORY}/${file} || echo "${PROJECT_DIRECTORY}/${file} not available, skipping"
"upload docs" :
- command: shell.exec
params:
silent: true
script: |
export AWS_ACCESS_KEY_ID=${aws_key}
export AWS_SECRET_ACCESS_KEY=${aws_secret}
aws s3 cp ${PROJECT_DIRECTORY}/doc/html s3://mciuploads/${UPLOAD_BUCKET}/docs/${CURRENT_VERSION} --recursive --acl public-read --region us-east-1
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
local_file: ${PROJECT_DIRECTORY}/doc/html/index.html
remote_file: ${UPLOAD_BUCKET}/docs/${CURRENT_VERSION}/index.html
bucket: mciuploads
permissions: public-read
content_type: text/html
display_name: "Rendered docs"
"upload coverage" :
- command: shell.exec
params:
silent: true
script: |
export AWS_ACCESS_KEY_ID=${aws_key}
export AWS_SECRET_ACCESS_KEY=${aws_secret}
aws s3 cp ${PROJECT_DIRECTORY}/coverage s3://mciuploads/${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/coverage/ --recursive --acl public-read --region us-east-1
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
local_file: ${PROJECT_DIRECTORY}/coverage/index.html
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/coverage/index.html
bucket: mciuploads
permissions: public-read
content_type: text/html
display_name: "Coverage Report"
"upload scan artifacts" :
- command: shell.exec
type: test
params:
script: |
cd
if find ${PROJECT_DIRECTORY}/scan -name \*.html | grep -q html; then
(cd ${PROJECT_DIRECTORY}/scan && find . -name index.html -exec echo "<li><a href='{}'>{}</a></li>" \;) >> scan.html
else
echo "No issues found" > scan.html
fi
- command: shell.exec
params:
silent: true
script: |
export AWS_ACCESS_KEY_ID=${aws_key}
export AWS_SECRET_ACCESS_KEY=${aws_secret}
aws s3 cp ${PROJECT_DIRECTORY}/scan s3://mciuploads/${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/scan/ --recursive --acl public-read --region us-east-1
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
local_file: ${PROJECT_DIRECTORY}/scan.html
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/scan/index.html
bucket: mciuploads
permissions: public-read
content_type: text/html
display_name: "Scan Build Report"
"upload mo artifacts":
- command: ec2.assume_role
params:
role_arn: ${assume_role_arn}
- command: shell.exec
params:
script: |
@ -249,62 +125,27 @@ functions:
find $MONGO_ORCHESTRATION_HOME -name \*.log | xargs tar czf mongodb-logs.tar.gz
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
aws_key: ${AWS_ACCESS_KEY_ID}
aws_secret: ${AWS_SECRET_ACCESS_KEY}
aws_session_token: ${AWS_SESSION_TOKEN}
local_file: mongodb-logs.tar.gz
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz
bucket: mciuploads
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz
bucket: ${aws_bucket}
permissions: public-read
content_type: ${content_type|application/x-gzip}
display_name: "mongodb-logs.tar.gz"
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
aws_key: ${AWS_ACCESS_KEY_ID}
aws_secret: ${AWS_SECRET_ACCESS_KEY}
aws_session_token: ${AWS_SESSION_TOKEN}
local_file: ${DRIVERS_TOOLS}/.evergreen/orchestration/server.log
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-orchestration.log
bucket: mciuploads
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-orchestration.log
bucket: ${aws_bucket}
permissions: public-read
content_type: ${content_type|text/plain}
display_name: "orchestration.log"
"upload working dir":
- command: archive.targz_pack
params:
target: "working-dir.tar.gz"
source_dir: ${PROJECT_DIRECTORY}/
include:
- "./**"
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
local_file: working-dir.tar.gz
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-working-dir.tar.gz
bucket: mciuploads
permissions: public-read
content_type: ${content_type|application/x-gzip}
display_name: "working-dir.tar.gz"
- command: archive.targz_pack
params:
target: "drivers-dir.tar.gz"
source_dir: ${DRIVERS_TOOLS}
include:
- "./**"
exclude_files:
# Windows cannot read the mongod *.lock files because they are locked.
- "*.lock"
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
local_file: drivers-dir.tar.gz
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-drivers-dir.tar.gz
bucket: mciuploads
permissions: public-read
content_type: ${content_type|application/x-gzip}
display_name: "drivers-dir.tar.gz"
"upload test results":
- command: attach.results
params:
@ -320,7 +161,12 @@ functions:
params:
script: |
${PREPARE_SHELL}
MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} AUTH=${AUTH} SSL=${SSL} STORAGE_ENGINE=${STORAGE_ENGINE} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
MONGODB_VERSION=${VERSION} \
TOPOLOGY=${TOPOLOGY} \
AUTH=${AUTH} \
SSL=${SSL} \
STORAGE_ENGINE=${STORAGE_ENGINE} \
bash ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
# run-orchestration generates expansion file with the MONGODB_URI for the cluster
- command: expansions.update
params:
@ -331,110 +177,72 @@ functions:
params:
script: |
${PREPARE_SHELL}
sh ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh
bash ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh
"run tox":
# If testing FLE, start the KMS mock servers, first create the virtualenv.
- command: shell.exec
- command: ec2.assume_role
params:
script: |
${PREPARE_SHELL}
cd ${DRIVERS_TOOLS}/.evergreen/csfle
. ./activate_venv.sh
# Run in the background so the mock servers don't block the EVG task.
- command: shell.exec
role_arn: ${aws_test_secrets_role}
- command: subprocess.exec
params:
working_dir: "src"
binary: bash
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
args:
- ${DRIVERS_TOOLS}/.evergreen/csfle/setup-secrets.sh
- command: subprocess.exec
params:
working_dir: "src"
binary: bash
background: true
script: |
${PREPARE_SHELL}
cd ${DRIVERS_TOOLS}/.evergreen/csfle
. ./activate_venv.sh
# The -u options forces the stdout and stderr streams to be unbuffered.
# TMPDIR is required to avoid "AF_UNIX path too long" errors.
TMPDIR="$(dirname $DRIVERS_TOOLS)" python -u kms_kmip_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 5698 &
python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 8000 &
python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 8001 &
python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 8002 --require_client_cert &
# Wait up to 10 seconds for the KMIP server to start.
- command: shell.exec
args:
- ${DRIVERS_TOOLS}/.evergreen/csfle/start-servers.sh
- command: subprocess.exec
params:
script: |
${PREPARE_SHELL}
cd ${DRIVERS_TOOLS}/.evergreen/csfle
. ./activate_venv.sh
for i in $(seq 1 1 10); do
sleep 1
if python -u kms_kmip_client.py; then
echo 'KMS KMIP server started!'
exit 0
fi
done
echo 'Failed to start KMIP server!'
exit 1
- command: shell.exec
type: test
params:
silent: true
working_dir: "src"
script: |
cat <<EOT > fle_creds.sh
export FLE_AWS_KEY="${fle_aws_key}"
export FLE_AWS_SECRET="${fle_aws_secret}"
export FLE_AZURE_CLIENTID="${fle_azure_clientid}"
export FLE_AZURE_TENANTID="${fle_azure_tenantid}"
export FLE_AZURE_CLIENTSECRET="${fle_azure_clientsecret}"
export FLE_GCP_EMAIL="${fle_gcp_email}"
export FLE_GCP_PRIVATEKEY="${fle_gcp_privatekey}"
# Needed for generating temporary aws credentials.
export AWS_ACCESS_KEY_ID="${fle_aws_key}"
export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}"
export AWS_DEFAULT_REGION=us-east-1
EOT
binary: bash
args:
- ${DRIVERS_TOOLS}/.evergreen/csfle/await-servers.sh
- command: shell.exec
type: test
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
# Disable xtrace (just in case it was accidentally set).
set +x
. ./fle_creds.sh
rm -f ./fle_creds.sh
set -x
export LIBMONGOCRYPT_URL="${libmongocrypt_url}"
export TEST_ENCRYPTION=1
PYTHON_BINARY="${PYTHON_BINARY}" \
TOX_BINARY="${TOX_BINARY}" \
TOX_ENV="${TOX_ENV}" \
INSTALL_TOX="${INSTALL_TOX}" \
VIRTUALENV="${VIRTUALENV}" \
AUTH="${AUTH}" \
SSL="${SSL}" \
CERT_DIR="${DRIVERS_TOOLS}/.evergreen/x509gen" \
bash ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh
LIBMONGOCRYPT_URL="${libmongocrypt_url}" \
TEST_ENCRYPTION=1 \
PYTHON_BINARY="${PYTHON_BINARY}" \
TOX_ENV="${TOX_ENV}" \
VIRTUALENV="${VIRTUALENV}" \
CHECK_EXCLUDE_PATTERNS=1 \
AUTH="${AUTH}" \
SSL="${SSL}" \
CERT_DIR="${DRIVERS_TOOLS}/.evergreen/x509gen" \
bash ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh
"run enterprise auth tests":
- command: ec2.assume_role
params:
role_arn: ${aws_test_secrets_role}
- command: shell.exec
type: test
params:
working_dir: "src"
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
script: |
bash ${DRIVERS_TOOLS}/.evergreen/secrets_handling/setup-secrets.sh drivers/enterprise_auth
- command: shell.exec
type: test
params:
silent: true
working_dir: "src"
script: |
# DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
set +x # disable xtrace
CLIENT_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem \
CA_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem \
PYTHON_BINARY=${PYTHON_BINARY} \
TOX_ENV=${TOX_ENV} \
SASL_HOST=${sasl_host} \
SASL_PORT=${sasl_port} \
SASL_USER=${sasl_user} \
SASL_PASS=${sasl_pass} \
SASL_DB=${sasl_db} \
PRINCIPAL=${principal} \
GSSAPI_DB=${gssapi_db} \
KEYTAB_BASE64=${keytab_base64} \
PROJECT_DIRECTORY=${PROJECT_DIRECTORY} \
sh ${PROJECT_DIRECTORY}/.evergreen/run-enterprise-auth-tests.sh
CA_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem \
PYTHON_BINARY=${PYTHON_BINARY} \
TOX_ENV=${TOX_ENV} \
PROJECT_DIRECTORY=${PROJECT_DIRECTORY} \
bash ${PROJECT_DIRECTORY}/.evergreen/run-enterprise-auth-tests.sh
"cleanup":
- command: shell.exec
@ -500,8 +308,6 @@ pre:
- func: "install dependencies"
post:
# Disabled, causing timeouts
# - func: "upload working dir"
- func: "upload mo artifacts"
- func: "upload test results"
- func: "stop mongo-orchestration"
@ -540,60 +346,6 @@ tasks:
# Test tasks {{{
- name: "test-3.6-standalone"
tags: ["3.6", "standalone"]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "3.6"
TOPOLOGY: "server"
- func: "run tox"
- name: "test-3.6-replica_set"
tags: ["3.6", "replica_set"]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "3.6"
TOPOLOGY: "replica_set"
- func: "run tox"
- name: "test-3.6-sharded_cluster"
tags: ["3.6", "sharded_cluster"]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "3.6"
TOPOLOGY: "sharded_cluster"
- func: "run tox"
- name: "test-4.0-standalone"
tags: ["4.0", "standalone"]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "4.0"
TOPOLOGY: "server"
- func: "run tox"
- name: "test-4.0-replica_set"
tags: ["4.0", "replica_set"]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "4.0"
TOPOLOGY: "replica_set"
- func: "run tox"
- name: "test-4.0-sharded_cluster"
tags: ["4.0", "sharded_cluster"]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "4.0"
TOPOLOGY: "sharded_cluster"
- func: "run tox"
- name: "test-4.2-standalone"
tags: ["4.2", "standalone"]
commands:
@ -729,6 +481,33 @@ tasks:
TOPOLOGY: "sharded_cluster"
- func: "run tox"
- name: "test-8.0-standalone"
tags: [ "8.0", "standalone" ]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "8.0"
TOPOLOGY: "server"
- func: "run tox"
- name: "test-8.0-replica_set"
tags: [ "8.0", "replica_set" ]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "8.0"
TOPOLOGY: "replica_set"
- func: "run tox"
- name: "test-8.0-sharded_cluster"
tags: [ "8.0", "sharded_cluster" ]
commands:
- func: "bootstrap mongo-orchestration"
vars:
VERSION: "8.0"
TOPOLOGY: "sharded_cluster"
- func: "run tox"
- name: "test-latest-standalone"
tags: ["latest", "standalone"]
commands:
@ -784,6 +563,7 @@ tasks:
- func: "run tox"
- name: "test-enterprise-auth"
tags: ["pr"]
commands:
- func: "bootstrap mongo-orchestration"
vars:
@ -792,12 +572,14 @@ tasks:
- func: "run enterprise auth tests"
- name: "docs"
tags: ["pr"]
commands:
- func: "run tox"
vars:
TOX_ENV: docs
- name: "doctest"
tags: ["pr"]
commands:
- func: "bootstrap mongo-orchestration"
vars:
@ -827,26 +609,10 @@ axes:
- id: tox-env
display_name: "Tox Env RHEL8"
values:
- id: "test-pypy38"
- id: "test-pypy310"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/pypy3.8/bin/python3"
- id: "test-py37"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/3.7/bin/python3"
- id: "test-py37"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/3.7/bin/python3"
- id: "test-py38"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/3.8/bin/python3"
- id: "test-py39"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/3.9/bin/python3"
PYTHON_BINARY: "/opt/python/pypy3.10/bin/python3"
- id: "test-py310"
variables:
TOX_ENV: "test"
@ -859,51 +625,59 @@ axes:
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/3.12/bin/python3"
- id: "test-py313"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/3.13/bin/python3"
- id: "test-py314"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/3.14/bin/python3"
- id: "test-pymongo-4.9"
variables:
TOX_ENV: "test-pymongo-4.9"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
- id: "test-pymongo-4.10"
variables:
TOX_ENV: "test-pymongo-4.10"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
- id: "test-pymongo-4.11"
variables:
TOX_ENV: "test-pymongo-4.11"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
- id: "test-pymongo-latest"
variables:
TOX_ENV: "test-pymongo-latest"
PYTHON_BINARY: "/opt/python/3.7/bin/python3"
- id: "synchro-py37"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
- id: "synchro-py310"
variables:
TOX_ENV: "synchro"
PYTHON_BINARY: "/opt/python/3.7/bin/python3"
- id: "synchro-py312"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
- id: "synchro-py313"
variables:
TOX_ENV: "synchro"
PYTHON_BINARY: "/opt/python/3.12/bin/python3"
PYTHON_BINARY: "/opt/python/3.13/bin/python3"
- id: tox-env-rhel7
display_name: "Tox Env RHEL7"
- id: tox-env-rhel8
display_name: "Tox Env RHEL8"
values:
- id: "test"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/opt/python/3.9/bin/python3"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
# Test Python 3.8 only on Mac.
# Test Python 3.10 only on Mac.
- id: tox-env-osx
display_name: "Tox Env OSX"
values:
- id: "test"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "/Library/Frameworks/Python.framework/Versions/3.8/bin/python3"
PYTHON_BINARY: "/Library/Frameworks/Python.framework/Versions/3.10/bin/python3"
- id: tox-env-win
display_name: "Tox Env Windows"
values:
- id: "test-py37"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "c:/python/Python37/python.exe"
- id: "test-py38"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "c:/python/Python39/python.exe"
- id: "test-py39"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "c:/python/Python39/python.exe"
- id: "test-py310"
variables:
TOX_ENV: "test"
@ -916,22 +690,27 @@ axes:
variables:
TOX_ENV: "test"
PYTHON_BINARY: "c:/python/Python312/python.exe"
- id: "test-py313"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "c:/python/Python313/python.exe"
- id: "test-py314"
variables:
TOX_ENV: "test"
PYTHON_BINARY: "c:/python/Python314/python.exe"
- id: os
display_name: "Operating System"
values:
- id: "rhel84"
display_name: "RHEL 8.4"
- id: "rhel8"
display_name: "RHEL 8.x"
run_on: "rhel84-small"
- id: "rhel76"
display_name: "RHEL 7.6"
run_on: "rhel76-small"
- id: "win"
display_name: "Windows"
run_on: "windows-64-vsMulti-small"
- id: "macos-1100"
display_name: "macOS 11.00"
run_on: "macos-1100"
- id: "macos-1400"
display_name: "macOS 14.00"
run_on: "macos-1400"
buildvariants:
@ -939,31 +718,31 @@ buildvariants:
- matrix_name: "main"
display_name: "${os}-${tox-env}-${ssl}"
matrix_spec:
os: "rhel84"
os: "rhel8"
tox-env: "*"
ssl: "*"
exclude_spec:
# TODO: synchro needs PyMongo's updated SSL test certs,
# which may require Motor test suite changes.
- os: "*"
tox-env: ["synchro-py37", "synchro-py312"]
tox-env: ["synchro-py310", "synchro-py313"]
ssl: "ssl"
tasks:
- ".rapid"
- ".latest"
- ".8.0"
- ".7.0"
- ".6.0"
- ".5.0"
- ".4.4"
- ".4.2"
- ".4.0"
- ".3.6"
- matrix_name: "test-rhel7"
display_name: "${os}-${tox-env-rhel7}-${ssl}"
- matrix_name: "test-rhel8"
display_name: "${os}-${tox-env-rhel8}-${ssl}"
matrix_spec:
os: "rhel76"
tox-env-rhel7: "*"
os: "rhel8"
tox-env-rhel8: "*"
ssl: "*"
tasks:
- ".rapid"
@ -982,6 +761,7 @@ buildvariants:
tasks:
- ".rapid"
- ".latest"
- ".8.0"
- ".7.0"
- ".6.0"
- ".5.0"
@ -993,18 +773,19 @@ buildvariants:
- matrix_name: "test-macos"
display_name: "${os}-${tox-env-osx}-${ssl}"
matrix_spec:
os: "macos-1100"
os: "macos-1400"
tox-env-osx: "*"
ssl: "*"
tasks:
- ".rapid"
- ".latest"
- ".8.0"
- ".7.0"
- ".6.0"
- matrix_name: "enterprise-auth"
display_name: "Enterprise Auth-${tox-env}"
matrix_spec: {"tox-env": ["synchro-py37", "synchro-py312"], ssl: "ssl"}
matrix_spec: {"tox-env": ["synchro-py310", "synchro-py313"], ssl: "ssl"}
run_on:
- "rhel84-small"
tasks:
@ -1016,7 +797,7 @@ buildvariants:
- "rhel84-small"
expansions:
TOX_ENV: "docs"
PYTHON_BINARY: "/opt/python/3.7/bin/python3"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
tasks:
- name: "docs"
@ -1026,6 +807,6 @@ buildvariants:
- "rhel84-small"
expansions:
TOX_ENV: "doctest"
PYTHON_BINARY: "/opt/python/3.7/bin/python3"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
tasks:
- name: "doctest"

View File

@ -2,8 +2,10 @@
# Don't trace to avoid secrets showing up in the logs
set -o errexit
set +x
echo "Running enterprise authentication tests"
source ./secrets-export.sh
export DB_USER="bob"
export DB_PASSWORD="pwd123"
@ -23,7 +25,6 @@ export GSSAPI_PORT=${SASL_PORT}
export GSSAPI_PRINCIPAL=${PRINCIPAL}
# Pass needed env variables to the test environment.
export TOX_TESTENV_PASSENV="*"
export TOX_ENV="enterprise-synchro"
# --sitepackages allows use of pykerberos without a test dep.
/opt/python/3.7/bin/python3 -m tox -m "$TOX_ENV" --sitepackages -- -x test.test_auth
bash ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh

View File

@ -5,8 +5,8 @@ set -o errexit # Exit the script with error if any of the commands fail
# Supported/used environment variables:
# AUTH Set to enable authentication. Defaults to "noauth"
# SSL Set to enable SSL. Defaults to "nossl"
# TOX_ENV Tox environment name, e.g. "tornado5-py37"
# PYTHON_BINARY Path to python
# TOX_ENV Tox environment name, e.g. "synchro", required.
# PYTHON_BINARY Path to python, required.
AUTH=${AUTH:-noauth}
SSL=${SSL:-nossl}
@ -16,6 +16,11 @@ if [ -z $PYTHON_BINARY ]; then
exit 1
fi
if [ -z $TOX_ENV ]; then
echo "TOX_ENV is undefined!"
exit 1
fi
if [ "$AUTH" != "noauth" ]; then
export DB_USER="bob"
export DB_PASSWORD="pwd123"
@ -26,6 +31,9 @@ if [ "$SSL" != "nossl" ]; then
export CA_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem"
fi
if [ -f secrets-export.sh ]; then
source secrets-export.sh
fi
# Usage:
# createvirtualenv /path/to/python /output/path/for/venv
@ -54,22 +62,17 @@ createvirtualenv () {
. $VENVPATH/bin/activate
fi
python -m pip install --upgrade pip
python -m pip install --upgrade setuptools wheel tox
python -m pip install -q --upgrade pip
python -m pip install -q --upgrade tox
}
if $PYTHON_BINARY -m tox --version; then
run_tox() {
$PYTHON_BINARY -m tox -m $TOX_ENV "$@"
}
else # No toolchain present, set up virtualenv before installing tox
createvirtualenv "$PYTHON_BINARY" toxenv
trap "deactivate; rm -rf toxenv" EXIT HUP
python -m pip install tox
run_tox() {
python -m tox -m $TOX_ENV "$@"
}
fi
# Set up a virtualenv and install tox.
createvirtualenv "$PYTHON_BINARY" toxenv
trap "deactivate; rm -rf toxenv" EXIT HUP
python -m pip install tox
run_tox() {
python -m tox -m $TOX_ENV "$@"
}
run_tox "${@:1}"

31
.flake8
View File

@ -1,31 +0,0 @@
[flake8]
max-line-length = 100
enable-extensions = G
extend-ignore =
G200, G202, G001
# black adds spaces around ':'
E203,
# E501 line too long (let black handle line length)
E501
# B305 `.next()` is not a thing on Python 3. Use the `next()` builtin. For Python 2 compatibility, use `six.next()`.
B305
per-file-ignores =
# F841 local variable 'foo' is assigned to but never used
# E731 do not assign a lambda expression, use a def
# F811 redefinition of unused 'foo' from line XXX
test/*/test_examples.py: F841,E731,F811
# F811 redefinition of unused 'foo' from line XXX
# B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError().
test/*: F811,B011
# E402 module level import not at top of file
doc/examples/monitoring_example.py: E402
# F403 'from foo import *' used; unable to detect undefined names
# F401 'foo' imported but unused
synchro/__init__.py: F403,F401
# F401 'foo' imported but unused
motor/__init__.py: F401

1
.github/CODEOWNERS vendored
View File

@ -1,2 +1 @@
# Global owner for repo
* @mongodb/dbx-python

16
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,16 @@
version: 2
updates:
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns:
- "*"
# Python
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"

5
.github/reviewers.txt vendored Normal file
View File

@ -0,0 +1,5 @@
# List of reviewers for auto-assignment of reviews.
caseyclements
blink1073
Jibola
NoahStapp

76
.github/workflows/codeql.yml vendored Normal file
View File

@ -0,0 +1,76 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master", "*" ]
pull_request:
branches: [ "master", "*" ]
schedule:
- cron: '35 23 * * 5'
workflow_call:
inputs:
ref:
required: true
type: string
jobs:
analyze:
name: Analyze ${{ matrix.language }}
runs-on: ubuntu-latest
timeout-minutes: 360
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: python
- language: actions
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.x
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4
with:
languages: ${{ matrix.language }}
build-mode: none
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
queries: security-extended
config: |
paths-ignore:
- 'test/**'
- shell: bash
if: matrix.language == 'python'
run: |
pip install -e .
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4
with:
category: "/language:${{matrix.language}}"

43
.github/workflows/dist.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Python Dist
concurrency:
group: dist-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
workflow_call:
inputs:
ref:
required: true
type: string
pull_request:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+.post[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+[a-b][0-9]+"
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
jobs:
build:
name: "Build Dist"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Install dependencies
run: pip install build
- name: Create packages
run: python -m build .
- name: Store package artifacts
uses: actions/upload-artifact@v7
with:
name: all-dist-${{ github.run_id }}
path: "dist/*"

115
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,115 @@
name: Release
on:
workflow_dispatch:
inputs:
following_version:
description: "The post (dev) version to set"
dry_run:
description: "Dry Run?"
default: false
type: boolean
schedule:
- cron: '30 5 * * *'
env:
# Changes per repo
PRODUCT_NAME: Motor
# Changes per branch
EVERGREEN_PROJECT: motor
# Constant
# inputs will be empty on a scheduled run. so, we only set dry_run
# to 'false' when the input is set to 'false'.
DRY_RUN: ${{ ! contains(inputs.dry_run, 'false') }}
FOLLOWING_VERSION: ${{ inputs.following_version || '' }}
defaults:
run:
shell: bash -eux {0}
jobs:
pre-publish:
environment: release
if: github.repository_owner == 'mongodb' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
outputs:
version: ${{ steps.pre-publish.outputs.version }}
steps:
- uses: mongodb-labs/drivers-github-tools/secure-checkout@v3
with:
app_id: ${{ vars.APP_ID }}
private_key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: mongodb-labs/drivers-github-tools/setup@v3
with:
aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
aws_region_name: ${{ vars.AWS_REGION_NAME }}
aws_secret_id: ${{ secrets.AWS_SECRET_ID }}
- uses: mongodb-labs/drivers-github-tools/python/pre-publish@v3
id: pre-publish
with:
dry_run: ${{ env.DRY_RUN }}
build-dist:
needs: [pre-publish]
uses: ./.github/workflows/dist.yml
with:
ref: ${{ needs.pre-publish.outputs.version }}
static-scan:
needs: [pre-publish]
uses: ./.github/workflows/codeql.yml
with:
ref: ${{ needs.pre-publish.outputs.version }}
publish:
needs: [build-dist, static-scan]
name: Upload release to PyPI
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- name: Download all the dists
uses: actions/download-artifact@v8
with:
name: all-dist-${{ github.run_id }}
path: dist/
- name: Publish package distributions to TestPyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true
attestations: ${{ env.DRY_RUN }}
- name: Publish package distributions to PyPI
if: startsWith(env.DRY_RUN, 'false')
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
post-publish:
needs: [publish]
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: write
attestations: write
security-events: write
steps:
- uses: mongodb-labs/drivers-github-tools/secure-checkout@v3
with:
app_id: ${{ vars.APP_ID }}
private_key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: mongodb-labs/drivers-github-tools/setup@v3
with:
aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
aws_region_name: ${{ vars.AWS_REGION_NAME }}
aws_secret_id: ${{ secrets.AWS_SECRET_ID }}
- uses: mongodb-labs/drivers-github-tools/python/post-publish@v3
with:
following_version: ${{ env.FOLLOWING_VERSION }}
product_name: ${{ env.PRODUCT_NAME }}
evergreen_project: ${{ env.EVERGREEN_PROJECT }}
token: ${{ github.token }}
dry_run: ${{ env.DRY_RUN }}

View File

@ -19,23 +19,26 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
os: [ubuntu-20.04]
python-version: ["3.7", "3.12"]
os: [ubuntu-latest]
python-version: ["3.10", "3.12", "3.14"]
fail-fast: false
name: CPython ${{ matrix.python-version }}-${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
allow-prereleases: true
- name: Start MongoDB with Custom Options
run: |
mkdir data
mongod --fork --dbpath=$(pwd)/data --logpath=$PWD/mongo.log --setParameter enableTestCommands=1
- id: setup-mongodb
uses: mongodb-labs/drivers-evergreen-tools@master
with:
version: "8.0"
topology: replica_set
- name: Install Python dependencies
run: |
python -m pip install -U pip tox
@ -44,12 +47,14 @@ jobs:
tox -m test
lint:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v3
- uses: actions/checkout@v6
with:
python-version: 3.8
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- name: Install Python dependencies
@ -58,15 +63,16 @@ jobs:
- name: Run linters
run: |
tox -m lint-manual
tox -m manifest
docs:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v3
- uses: actions/checkout@v6
with:
python-version: 3.7
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- name: Install Python dependencies
@ -76,20 +82,22 @@ jobs:
run: tox -m docs
- name: Run linkcheck
run: tox -m linkcheck
- name: Start MongoDB with Custom Options
run: |
mkdir data
mongod --fork --dbpath=$(pwd)/data --logpath=$PWD/mongo.log --setParameter enableTestCommands=1
- name: Start MongoDB
uses: supercharge/mongodb-github-action@315db7fe45ac2880b7758f1933e6e5d59afd5e94 # 1.12.1
with:
mongodb-version: 5.0
- name: Run doctest
run: tox -m doctest
release:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v3
- uses: actions/checkout@v6
with:
python-version: 3.7
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- name: Install Python dependencies
@ -102,16 +110,14 @@ jobs:
typing:
name: Typing Tests
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.12']
fail-fast: false
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
- uses: actions/checkout@v6
with:
python-version: ${{ matrix.python-version }}
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.10"
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
allow-prereleases: true

21
.github/workflows/zizmor.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: GitHub Actions Security Analysis with zizmor 🌈
on:
push:
branches: ["master"]
pull_request:
branches: ["**"]
jobs:
zizmor:
name: zizmor latest via Cargo
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d

7
.github/zizmor.yml vendored Normal file
View File

@ -0,0 +1,7 @@
rules:
unpinned-uses:
config:
policies:
actions/*: ref-pin
mongodb-labs/drivers-github-tools/*: ref-pin
mongodb-labs/drivers-evergreen-tools: ref-pin

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ doc/_build/
xunit-results
xunit-synchro-results
.eggs
toxenv

View File

@ -1,12 +1,14 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-toml
- id: check-json
- id: check-yaml
exclude: template.yaml
- id: debug-statements
- id: end-of-file-fixer
exclude: WHEEL
@ -16,44 +18,72 @@ repos:
exclude: .patch
exclude_types: [json]
- repo: https://github.com/psf/black
rev: 22.3.0
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.3
hooks:
- id: black
files: \.(py|pyi)$
args: [--line-length=100]
- id: ruff
args: ["--fix", "--show-fixes"]
- id: ruff-format
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
- repo: https://github.com/adamchainz/blacken-docs
rev: "1.16.0"
hooks:
- id: isort
files: \.(py|pyi)$
args: [--profile=black]
- id: blacken-docs
additional_dependencies:
- black==22.3.0
- repo: https://github.com/pycqa/flake8
rev: 3.9.2
- repo: https://github.com/pre-commit/pygrep-hooks
rev: "v1.10.0"
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear==20.1.4',
'flake8-logging-format==0.6.0',
'flake8-implicit-str-concat==0.2.0',
]
- id: rst-backticks
- id: rst-directive-colons
- id: rst-inline-touching-normal
- repo: https://github.com/rstcheck/rstcheck
rev: v6.2.0
hooks:
- id: rstcheck
additional_dependencies: [sphinx]
args: ["--ignore-directives=doctest,testsetup,todo,automodule,mongodoc,autodoc,testcleanup,autoclass","--ignore-substitutions=release", "--report-level=error"]
exclude: '^doc/migrate-to-motor-3.rst'
# We use the Python version instead of the original version which seems to require Docker
# https://github.com/koalaman/shellcheck-precommit
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.8.0.4
rev: v0.9.0.6
hooks:
- id: shellcheck
name: shellcheck
args: ["--severity=warning"]
stages: [manual]
- repo: https://github.com/PyCQA/doc8
rev: v1.1.1
hooks:
- id: doc8
args: ["--ignore=D001"] # ignore line length
stages: [manual]
- repo: https://github.com/sirosen/check-jsonschema
rev: 0.14.1
rev: 0.29.0
hooks:
- id: check-jsonschema
name: "Check GitHub Workflows"
files: ^\.github/workflows/
types: [yaml]
args: ["--schemafile", "https://json.schemastore.org/github-workflow"]
- id: check-github-workflows
- id: check-github-actions
- id: check-dependabot
- repo: https://github.com/ariebovenberg/slotscheck
rev: v0.17.0
hooks:
- id: slotscheck
files: \.py$
exclude: "^(doc|test)/"
stages: [manual]
args: ["--no-strict-imports"]
- repo: https://github.com/codespell-project/codespell
rev: "v2.2.6"
hooks:
- id: codespell
args: ["-L", "fle"]

View File

@ -15,7 +15,7 @@ python:
# Install motor itself.
- method: pip
path: .
- requirements: doc/docs-requirements.txt
- requirements: requirements/docs.txt
build:
os: ubuntu-22.04

68
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,68 @@
# Contributing to Motor
Contributions are encouraged. Please read these guidelines before
sending a pull request.
## Bugfixes and New Features
Before starting to write code, look for existing tickets or create one
in [Jira](https://jira.mongodb.org/browse/MOTOR) for your specific issue
or feature request.
## Running Tests
Install a recent version of MongoDB and run it on the default port from
a clean data directory. Pass "--setParameter enableTestCommands=1" to
mongod to enable testing MotorCursor's `max_time_ms` method.
Control how the tests connect to MongoDB with these environment
variables:
- `DB_IP`: Defaults to "localhost", can be a domain name or IP
- `DB_PORT`: Defaults to 27017
- `DB_USER`, `DB_PASSWORD`: To test with authentication, create an
admin user and set these environment variables to the username and
password
- `CERT_DIR`: Path with alternate client.pem and ca.pem for testing.
Otherwise the suite uses those in test/certificates/.
Install [tox](https://testrun.org/tox/) and run it from the command line
in the repository directory. You will need a variety of Python
interpreters installed. For a minimal test, ensure you have your desired
Python version on your path, and run:
```bash
tox -m test
```
The doctests pass with Python 3.10+ and a MongoDB 5.0 instance running on
port 27017:
```bash
tox -m doctest
```
## Running Linters
Motor uses [pre-commit](https://pypi.org/project/pre-commit/) for
managing linting of the codebase. `pre-commit` performs various checks
on all files in Motor and uses tools that help follow a consistent code
style within the codebase.
To set up `pre-commit` locally, run:
```bash
pip install pre-commit # or brew install pre-commit for global install.
pre-commit install
```
To run `pre-commit` manually, run:
```bash
tox -m lint
```
## General Guidelines
- Avoid backward breaking changes if at all possible.
- Write inline documentation for new classes and methods.
- Add yourself to doc/contributors.rst :)

View File

@ -1,65 +0,0 @@
Contributing to Motor
=====================
Contributions are encouraged. Please read these guidelines before sending a
pull request.
Bugfixes and New Features
-------------------------
Before starting to write code, look for existing tickets or create one in `Jira
<https://jira.mongodb.org/browse/MOTOR>`_ for your specific issue or feature
request.
Running Tests
-------------
Install a recent version of MongoDB and run it on the default port from a clean
data directory. Pass "--setParameter enableTestCommands=1" to mongod to enable
testing MotorCursor's ``max_time_ms`` method.
Control how the tests connect to MongoDB with these environment variables:
- ``DB_IP``: Defaults to "localhost", can be a domain name or IP
- ``DB_PORT``: Defaults to 27017
- ``DB_USER``, ``DB_PASSWORD``: To test with authentication, create an admin
user and set these environment variables to the username and password
- ``CERT_DIR``: Path with alternate client.pem and ca.pem for testing.
Otherwise the suite uses those in test/certificates/.
Install `tox`_ and run it from the command line in the repository directory.
You will need a variety of Python interpreters installed. For a minimal test,
ensure you have your desired Python version on your path, and run::
> tox -m test
The doctests pass with Python 3.7+ and a MongoDB 5.0 instance running on
port 27017:
> tox -m doctest
.. _tox: https://testrun.org/tox/
Running Linters
---------------
Motor uses `pre-commit <https://pypi.org/project/pre-commit/>`_
for managing linting of the codebase.
``pre-commit`` performs various checks on all files in Motor and uses tools
that help follow a consistent code style within the codebase.
To set up ``pre-commit`` locally, run::
pip install pre-commit
pre-commit install
To run ``pre-commit`` manually, run::
> tox -m lint
General Guidelines
------------------
- Avoid backward breaking changes if at all possible.
- Write inline documentation for new classes and methods.
- Add yourself to doc/contributors.rst :)

View File

@ -1,31 +0,0 @@
include README.rst
include LICENSE
include tox.ini
include pytest.ini
include pyproject.toml
include mypy.ini
include doc/Makefile
include doc/examples/tornado_change_stream_templates/index.html
recursive-include doc *.rst
recursive-include doc *.py
recursive-include doc *.png
recursive-include test *.py
recursive-include test *.pem
recursive-include doc *.conf
recursive-include doc *.css
recursive-include doc *.js
recursive-include doc *.txt
recursive-include doc *.bat
recursive-include synchro *.py
recursive-include motor *.pyi
recursive-include motor *.typed
recursive-include motor *.py
exclude .flake8
exclude .readthedocs.yaml
exclude .git-blame-ignore-revs
exclude .pre-commit-config.yaml
exclude release.sh
exclude RELEASE.rst
exclude CONTRIBUTING.rst
exclude .evergreen/*

209
README.md Normal file
View File

@ -0,0 +1,209 @@
# Motor
[![PyPI Version](https://img.shields.io/pypi/v/motor)](https://pypi.org/project/motor)
[![Python Versions](https://img.shields.io/pypi/pyversions/motor)](https://pypi.org/project/motor)
[![Monthly Downloads](https://static.pepy.tech/badge/motor/month)](https://pepy.tech/project/motor)
[![Documentation Status](https://readthedocs.org/projects/motor/badge/?version=stable)](http://motor.readthedocs.io/en/stable/?badge=stable)
![image](https://raw.github.com/mongodb/motor/master/doc/_static/motor.png)
> [!WARNING]
> As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
> No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
> After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
> We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
> For help transitioning, see the Migrate to PyMongo Async guide: https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/.
## About
Motor is a full-featured, non-blocking [MongoDB](http://mongodb.org/)
driver for Python [asyncio](https://docs.python.org/3/library/asyncio.html) and
[Tornado](http://tornadoweb.org/) applications. Motor presents a coroutine-based API
for non-blocking access to MongoDB.
> "We use Motor in high throughput environments, processing tens of
> thousands of requests per second. It allows us to take full advantage
> of modern hardware, ensuring we utilise the entire capacity of our
> purchased CPUs. This helps us be more efficient with computing power,
> compute spend and minimises the environmental impact of our
> infrastructure as a result."
>
> --*David Mytton, Server Density*
>
> "We develop easy-to-use sensors and sensor systems with open source
> software to ensure every innovator, from school child to laboratory
> researcher, has the same opportunity to create. We integrate Motor
> into our software to guarantee massively scalable sensor systems for
> everyone."
>
> --*Ryan Smith, inXus Interactive*
## Support / Feedback
For issues with, questions about, or feedback for PyMongo, please look
into our [support channels](https://support.mongodb.com/welcome). Please
do not email any of the Motor developers directly with issues or
questions - you're more likely to get an answer on the
[StackOverflow](https://stackoverflow.com/questions/tagged/mongodb)
(using a "mongodb" tag).
## Bugs / Feature Requests
Think you've found a bug? Want to see a new feature in Motor? Please
open a case in our issue management tool, JIRA:
- [Create an account and login](https://jira.mongodb.org).
- Navigate to [the MOTOR
project](https://jira.mongodb.org/browse/MOTOR).
- Click **Create Issue** - Please provide as much information as
possible about the issue type and how to reproduce it.
Bug reports in JIRA for all driver projects (i.e. MOTOR, CSHARP, JAVA)
and the Core Server (i.e. SERVER) project are **public**.
### How To Ask For Help
Please include all of the following information when opening an issue:
- Detailed steps to reproduce the problem, including full traceback, if
possible.
- The exact python version used, with patch level:
```bash
python -c "import sys; print(sys.version)"
```
- The exact version of Motor used, with patch level:
```bash
python -c "import motor; print(motor.version)"
```
- The exact version of PyMongo used, with patch level:
```bash
python -c "import pymongo; print(pymongo.version); print(pymongo.has_c())"
```
- The exact Tornado version, if you are using Tornado:
```bash
python -c "import tornado; print(tornado.version)"
```
- The operating system and version (e.g. RedHat Enterprise Linux 6.4,
OSX 10.9.5, ...)
### Security Vulnerabilities
If you've identified a security vulnerability in a driver or any other
MongoDB project, please report it according to the [instructions
here](https://mongodb.com/docs/manual/tutorial/create-a-vulnerability-report).
## Installation
Motor can be installed with [pip](http://pypi.python.org/pypi/pip):
```bash
pip install motor
```
## Dependencies
Motor works in all the environments officially supported by Tornado or
by asyncio. It requires:
- Unix (including macOS) or Windows.
- [PyMongo](http://pypi.python.org/pypi/pymongo/) >=4.9,<5
- Python 3.10+
Optional dependencies:
Motor supports same optional dependencies as PyMongo. Required
dependencies can be installed along with Motor.
GSSAPI authentication requires `gssapi` extra dependency. The correct
dependency can be installed automatically along with Motor:
```bash
pip install "motor[gssapi]"
```
similarly,
MONGODB-AWS authentication requires `aws` extra dependency:
```bash
pip install "motor[aws]"
```
Support for mongodb+srv:// URIs requires `srv` extra dependency:
```bash
pip install "motor[srv]"
```
OCSP requires `ocsp` extra dependency:
```bash
pip install "motor[ocsp]"
```
Wire protocol compression with snappy requires `snappy` extra
dependency:
```bash
pip install "motor[snappy]"
```
Wire protocol compression with zstandard requires `zstd` extra
dependency:
```bash
pip install "motor[zstd]"
```
Client-Side Field Level Encryption requires `encryption` extra
dependency:
```bash
pip install "motor[encryption]"
```
You can install all dependencies automatically with the following
command:
```bash
pip install "motor[gssapi,aws,ocsp,snappy,srv,zstd,encryption]"
```
See
[requirements](https://motor.readthedocs.io/en/stable/requirements.html)
for details about compatibility.
## Examples
See the [examples on
ReadTheDocs](https://motor.readthedocs.io/en/stable/examples/index.html).
## Documentation
Motor's documentation is on
[ReadTheDocs](https://motor.readthedocs.io/en/stable/).
Build the documentation with Python 3.10+. Install
[sphinx](http://sphinx.pocoo.org/), [Tornado](http://tornadoweb.org/),
and [aiohttp](https://github.com/aio-libs/aiohttp), and do
`cd doc; make html`.
## Learning Resources
- MongoDB Learn - [Python
courses](https://learn.mongodb.com/catalog?labels=%5B%22Language%22%5D&values=%5B%22Python%22%5D).
- [Python Articles on Developer
Center](https://www.mongodb.com/developer/languages/python/).
## Testing
Run `python setup.py test`. Tests are located in the `test/` directory.

View File

@ -1,184 +0,0 @@
=====
Motor
=====
.. image:: https://raw.github.com/mongodb/motor/master/doc/_static/motor.png
:Info: Motor is a full-featured, non-blocking MongoDB_ driver for Python
Tornado_ and asyncio_ applications.
:Documentation: Available at `motor.readthedocs.io <https://motor.readthedocs.io/en/stable/>`_
:Author: A\. Jesse Jiryu Davis
About
=====
Motor presents a coroutine-based API for non-blocking access
to MongoDB. The source is `on GitHub <https://github.com/mongodb/motor>`_
and the docs are on ReadTheDocs_.
"We use Motor in high throughput environments, processing tens of thousands
of requests per second. It allows us to take full advantage of modern
hardware, ensuring we utilise the entire capacity of our purchased CPUs.
This helps us be more efficient with computing power, compute spend and
minimises the environmental impact of our infrastructure as a result."
--*David Mytton, Server Density*
"We develop easy-to-use sensors and sensor systems with open source
software to ensure every innovator, from school child to laboratory
researcher, has the same opportunity to create. We integrate Motor into our
software to guarantee massively scalable sensor systems for everyone."
--*Ryan Smith, inXus Interactive*
Support / Feedback
==================
For issues with, questions about, or feedback for PyMongo, please look into
our `support channels <https://support.mongodb.com/welcome>`_. Please
do not email any of the Motor developers directly with issues or
questions - you're more likely to get an answer on the `StackOverflow <https://stackoverflow.com/questions/tagged/mongodb>`_ (using a "mongodb" tag).
Bugs / Feature Requests
=======================
Think you've found a bug? Want to see a new feature in Motor? Please open a
case in our issue management tool, JIRA:
- `Create an account and login <https://jira.mongodb.org>`_.
- Navigate to `the MOTOR project <https://jira.mongodb.org/browse/MOTOR>`_.
- Click **Create Issue** - Please provide as much information as possible about the issue type and how to reproduce it.
Bug reports in JIRA for all driver projects (i.e. MOTOR, CSHARP, JAVA) and the
Core Server (i.e. SERVER) project are **public**.
How To Ask For Help
-------------------
Please include all of the following information when opening an issue:
- Detailed steps to reproduce the problem, including full traceback, if possible.
- The exact python version used, with patch level::
$ python -c "import sys; print(sys.version)"
- The exact version of Motor used, with patch level::
$ python -c "import motor; print(motor.version)"
- The exact version of PyMongo used, with patch level::
$ python -c "import pymongo; print(pymongo.version); print(pymongo.has_c())"
- The exact Tornado version, if you are using Tornado::
$ python -c "import tornado; print(tornado.version)"
- The operating system and version (e.g. RedHat Enterprise Linux 6.4, OSX 10.9.5, ...)
Security Vulnerabilities
------------------------
If you've identified a security vulnerability in a driver or any other
MongoDB project, please report it according to the `instructions here
<https://mongodb.com/docs/manual/tutorial/create-a-vulnerability-report>`_.
Installation
============
Motor can be installed with `pip <http://pypi.python.org/pypi/pip>`_::
$ pip install motor
Dependencies
============
Motor works in all the environments officially supported by Tornado or by
asyncio. It requires:
* Unix (including macOS) or Windows.
* PyMongo_ >=4.1,<5
* Python 3.7+
Optional dependencies:
Motor supports same optional dependencies as PyMongo. Required dependencies can be installed
along with Motor.
GSSAPI authentication requires ``gssapi`` extra dependency. The correct
dependency can be installed automatically along with Motor::
$ pip install "motor[gssapi]"
similarly,
MONGODB-AWS authentication requires ``aws`` extra dependency::
$ pip install "motor[aws]"
Support for mongodb+srv:// URIs requires ``srv`` extra dependency::
$ pip install "motor[srv]"
OCSP requires ``ocsp`` extra dependency::
$ pip install "motor[ocsp]"
Wire protocol compression with snappy requires ``snappy`` extra dependency::
$ pip install "motor[snappy]"
Wire protocol compression with zstandard requires ``zstd`` extra dependency::
$ pip install "motor[zstd]"
Client-Side Field Level Encryption requires ``encryption`` extra dependency::
$ pip install "motor[encryption]"
You can install all dependencies automatically with the following
command::
$ pip install "motor[gssapi,aws,ocsp,snappy,srv,zstd,encryption]"
See `requirements <https://motor.readthedocs.io/en/stable/requirements.html>`_
for details about compatibility.
Examples
========
See the `examples on ReadTheDocs <https://motor.readthedocs.io/en/stable/examples/index.html>`_.
Documentation
=============
Motor's documentation is on ReadTheDocs_.
Build the documentation with Python 3.7+. Install sphinx_, Tornado_, and aiohttp_,
and do ``cd doc; make html``.
Learning Resources
==================
MongoDB Learn - `Python courses <https://learn.mongodb.com/catalog?labels=%5B%22Language%22%5D&values=%5B%22Python%22%5D>`_.
`Python Articles on Developer Center <https://www.mongodb.com/developer/languages/python/>`_.
Testing
=======
Run ``python setup.py test``.
Tests are located in the ``test/`` directory.
.. _PyMongo: http://pypi.python.org/pypi/pymongo/
.. _MongoDB: http://mongodb.org/
.. _Tornado: http://tornadoweb.org/
.. _asyncio: https://docs.python.org/3/library/asyncio.html
.. _aiohttp: https://github.com/aio-libs/aiohttp
.. _ReadTheDocs: https://motor.readthedocs.io/en/stable/
.. _sphinx: http://sphinx.pocoo.org/

71
RELEASE.md Normal file
View File

@ -0,0 +1,71 @@
# Motor Releases
## Versioning
Motor's version numbers follow [semantic
versioning](http://semver.org/): each version number is structured
"major.minor.patch". Patch releases fix bugs, minor releases add
features (and may fix bugs), and major releases include API changes that
break backwards compatibility (and may add features and fix bugs).
In between releases we add .devN to the version number to denote the
version under development. So if we just released 2.3.0, then the
current dev version might be 2.3.1.dev0 or 2.4.0.dev0. When we make the
next release we replace all instances of 2.x.x.devN in the docs with the
new version number.
<https://www.python.org/dev/peps/pep-0440/>
## Release Process
Motor ships a [pure Python
wheel](https://packaging.python.org/guides/distributing-packages-using-setuptools/#pure-python-wheels)
and a [source
distribution](https://packaging.python.org/guides/distributing-packages-using-setuptools/#source-distributions).
1. Motor is tested on Evergreen. Ensure that the latest commit is
passing CI as expected:
<https://evergreen.mongodb.com/waterfall/motor>.
2. Check JIRA to ensure all the tickets in this version have been
completed.
3. Add release notes to `doc/changelog.rst`. Generally just
summarize/clarify the git log, but you might add some more long form
notes for big changes.
4. Replace the `devN` version number w/ the new version number (see
note above in [Versioning](#versioning)) in `motor/_version.py`.
Commit the change and tag the release. Immediately bump the version
number to `dev0` in a new commit:
$ # Bump to release version number
$ git commit -a -m "BUMP <release version number>"
$ git tag -a "<release version number>" -m "BUMP <release version number>"
$ # Bump to dev version number
$ git commit -a -m "BUMP <dev version number>"
$ git push
$ git push --tags
5. Bump the version number to `<next version>.dev0` in
`motor/_version.py`, commit, then push.
6. Authorize the deployment for the tagged version on the release
GitHub Action and wait for it to successfully publish to PyPI.
7. Make sure the new version appears on
<https://motor.readthedocs.io/>. If the new version does not show up
automatically, trigger a rebuild of "latest":
<https://readthedocs.org/projects/motor/builds/>
8. Publish the release version in Jira and add a brief description
about the reason for the release or the main feature.
9. Announce the release on:
<https://www.mongodb.com/community/forums/c/announcements/driver-releases>
10. Create a GitHub Release for the tag using
<https://github.com/mongodb/motor/releases/new>. The title should be
"Motor X.Y.Z", and the description should contain a link to the
release notes on the the community forum, e.g. "Release notes:
mongodb.com/community/forums/t/motor-2-5-1-released/120313."

View File

@ -1,81 +0,0 @@
==============
Motor Releases
==============
Versioning
----------
Motor's version numbers follow `semantic versioning <http://semver.org/>`_:
each version number is structured "major.minor.patch". Patch releases fix
bugs, minor releases add features (and may fix bugs), and major releases
include API changes that break backwards compatibility (and may add features
and fix bugs).
In between releases we add .devN to the version number to denote the version
under development. So if we just released 2.3.0, then the current dev
version might be 2.3.1.dev0 or 2.4.0.dev0. When we make the next release we
replace all instances of 2.x.x.devN in the docs with the new version number.
https://www.python.org/dev/peps/pep-0440/
Release Process
---------------
Motor ships a `pure Python wheel <https://packaging.python.org/guides/distributing-packages-using-setuptools/#pure-python-wheels>`_
and a `source distribution <https://packaging.python.org/guides/distributing-packages-using-setuptools/#source-distributions>`_.
#. Motor is tested on Evergreen. Ensure that the latest commit is passing CI as
expected: https://evergreen.mongodb.com/waterfall/motor.
#. Check JIRA to ensure all the tickets in this version have been completed.
#. Add release notes to `doc/changelog.rst`. Generally just summarize/clarify
the git log, but you might add some more long form notes for big changes.
#. Replace the `devN` version number w/ the new version number (see
note above in `Versioning`_). Make sure version number is updated in
``motor/_version.py``. Commit the change and tag the release.
Immediately bump the version number to `dev0` in a new commit::
$ # Bump to release version number
$ git commit -a -m "BUMP <release version number>"
$ git tag -a "<release version number>" -m "BUMP <release version number>"
$ # Bump to dev version number
$ git commit -a -m "BUMP <dev version number>"
$ git push
$ git push --tags
#. Build the release packages by running the `release.sh`
script on macOS::
$ git clone git@github.com:mongodb/motor.git
$ cd motor
$ git checkout "<release version number>"
$ ./release.sh
This will create the following distributions::
$ ls dist
motor-<version>.tar.gz
motor-<version>-py3-none-any.whl
#. Upload all the release packages to PyPI with twine::
$ python3 -m twine upload dist/*
#. Make sure the new version appears on https://motor.readthedocs.io/. If the
new version does not show up automatically, trigger a rebuild of "latest":
https://readthedocs.org/projects/motor/builds/
#. Bump the version number to <next version>.dev0 in motor/_version.py,
commit, push.
#. Publish the release version in Jira.
#. Announce the release on:
https://www.mongodb.com/community/forums/c/announcements/driver-releases
#. Create a GitHub Release for the tag using https://github.com/mongodb/motor/releases/new.
The title should be "Motor X.Y.Z", and the description should contain a
link to the release notes on the the community forum, e.g.
"Release notes: mongodb.com/community/forums/t/motor-2-5-1-released/120313."

View File

@ -1,6 +1,12 @@
:mod:`motor.aiohttp` - Integrate Motor with the aiohttp web framework
=====================================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.aiohttp
.. automodule:: motor.aiohttp

View File

@ -1,6 +1,12 @@
asyncio GridFS Classes
======================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_asyncio
Store blobs of data in `GridFS <http://dochub.mongodb.org/core/gridfs>`_.

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_asyncio.AsyncIOMotorChangeStream`
======================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_asyncio
.. autoclass:: AsyncIOMotorChangeStream

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_asyncio.AsyncIOMotorClient` -- Connection to MongoDB
=========================================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. autoclass:: motor.motor_asyncio.AsyncIOMotorClient
:members:

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_asyncio.AsyncIOMotorClientEncryption`
==========================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_asyncio
.. autoclass:: AsyncIOMotorClientEncryption

View File

@ -1,5 +1,11 @@
:class:`~motor.motor_asyncio.AsyncIOMotorClientSession` -- Sequence of operations
=================================================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. autoclass:: motor.motor_asyncio.AsyncIOMotorClientSession
:members:

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_asyncio.AsyncIOMotorCollection`
====================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_asyncio
.. autoclass:: AsyncIOMotorCollection

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_asyncio.AsyncIOMotorDatabase`
==================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_asyncio
.. autoclass:: AsyncIOMotorDatabase

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_asyncio.AsyncIOMotorCursor`
================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_asyncio
.. autoclass:: AsyncIOMotorCursor

View File

@ -1,6 +1,12 @@
Motor asyncio API
=================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. toctree::
asyncio_motor_client

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_tornado.MotorCursor`
=========================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
.. autoclass:: MotorCursor

View File

@ -1,6 +1,12 @@
Motor GridFS Classes
====================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
Store blobs of data in `GridFS <http://dochub.mongodb.org/core/gridfs>`_.

View File

@ -1,6 +1,12 @@
Motor Tornado API
=================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. toctree::
motor_client

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_tornado.MotorChangeStream`
===============================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
.. autoclass:: MotorChangeStream

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_tornado.MotorClient` -- Connection to MongoDB
==================================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
.. autoclass:: MotorClient

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_tornado.MotorClientEncryption`
===================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
.. autoclass:: MotorClientEncryption

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_tornado.MotorClientSession` -- Sequence of operations
==========================================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
.. autoclass:: motor.motor_tornado.MotorClientSession

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_tornado.MotorCollection`
=============================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
.. autoclass:: MotorCollection

View File

@ -1,6 +1,12 @@
:class:`~motor.motor_tornado.MotorDatabase`
===========================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
.. autoclass:: MotorDatabase

View File

@ -1,6 +1,12 @@
:mod:`motor.web` - Integrate Motor with the Tornado web framework
=================================================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.web
.. automodule:: motor.web

View File

@ -3,6 +3,84 @@ Changelog
.. currentmodule:: motor.motor_tornado
Motor 3.8.0
-----------
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
- Add support for Python 3.14.
- Drop support for Python 3.9.
Motor 3.7.1
-----------
The 3.7.1 release contains only documentation changes.
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
Motor 3.7.0
-----------
- Add support for PyMongo 4.10.
- Drop support for Python 3.8.
- Drop support for MongoDB 3.6.
Motor 3.6.1
-----------
- Add return type to to_list method in stub file.
- Fix ability to install pymongo from source while testing.
Motor 3.6.0
-----------
- Add support for MongoDB 8.0 and PyMongo 4.9.
- The length parameter in :meth:`MotorCursor.to_list` is now optional.
.. note::
This is the last planned minor version of Motor. We are sunsetting Motor in favor of native
asyncio support in PyMongo 4.9+. We will continue to provide security releases and bug fixes for
Motor, but it will not gain new features.
Motor 3.5.1
-----------
- Fix runtime behavior of Motor generic class typing, e.g. ``client: AsyncIOMotorClient[Dict[str, Any]]``.
Motor 3.5.0
-----------
- Drop support for Python 3.7.
- Switch to using Hatchling as a build backend and remove ``setup.py``.
- Add Secure Software Development Life Cycle automation to release process.
GitHub Releases for pymongocrypt now include a Software Bill of Materials, and signature
files corresponding to the distribution files released on PyPI.
Motor 3.4.0
-----------
- Type hint bug fixes and improvements. Added typings to classes in ``motor_tornado`` and
``motor_asyncio``.
Motor 3.3.2
-----------
- Fix incorrect type hints for the following:
:meth:`MotorCursor.to_list`,
:meth:`MotorCollection.name`,
:meth:`MotorDatabase.get_collection`,
:meth:`MotorClientSession.with_transaction`
- Fix a bug that caused application-supplied DriverInfo to be overwritten.
Motor 3.3.1
-----------
- Fix a bug in the type hint for :meth:`MotorCursor.to_list`.
Motor 3.3.0
-----------
@ -63,11 +141,11 @@ New features:
The new Queryable Encryption changes that are in beta are:
- The `encrypted_fields` argument to the
- The ``encrypted_fields`` argument to the
:class:`~motor.motor_tornado.MotorCollection` constructor, and the
:meth:`~motor.motor_tornado.MotorDatabase.create_collection`
and :meth:`~motor.motor_tornado.MotorDatabase.drop_collection` methods.
- The `query_type` and `contention_factor` arguments to
- The ``query_type`` and ``contention_factor`` arguments to
:meth:`motor.motor_asyncio.AsyncIOMotorClientEncryption.encrypt` and
:meth:`motor.motor_tornado.MotorClientEncryption.encrypt`.
@ -83,7 +161,7 @@ Motor 3.0
---------
Motor 3.0 adds support for PyMongo 4.0+. It inherits a number
of improvemnts and breaking API changes from PyMongo 4.0+.
of improvements and breaking API changes from PyMongo 4.0+.
See :doc:`migrate-to-motor-3` for more information.
Breaking Changes
@ -163,8 +241,8 @@ Breaking Changes
- Comparing two :class:`~motor.motor_tornado.MotorClient` instances now
uses a set of immutable properties rather than
:attr:`~motor.motor_tornado.MotorClient.address` which can change.
- Removed the `disable_md5` parameter for :class:`~gridfs.GridFSBucket` and
:class:`~gridfs.GridFS`. See :ref:`removed-gridfs-checksum` for details.
- Removed the ``disable_md5`` parameter for :class:`~pymongo.GridFSBucket` and
:class:`~pymongo.GridFS`. See :ref:`removed-gridfs-checksum` for details.
- PyMongoCrypt 1.2.0 or later is now required for client side field level
encryption support.
@ -176,10 +254,10 @@ Notable improvements
- Added the ``maxConnecting`` URI and
:class:`~motor.motor_tornado.MotorClient` keyword argument.
- :class:`~motor.motor_tornado.MotorClient` now accepts a URI and keyword
argument `srvMaxHosts` that limits the number of mongos-like hosts a client
argument ``srvMaxHosts`` that limits the number of mongos-like hosts a client
will connect to. More specifically, when a mongodb+srv:// connection string
resolves to more than `srvMaxHosts` number of hosts, the client will randomly
choose a `srvMaxHosts` sized subset of hosts.
resolves to more than ``srvMaxHosts`` number of hosts, the client will randomly
choose a ``srvMaxHosts`` sized subset of hosts.
- Added :attr:`motor.motor_tornado.MotorClient.options` for read-only access
to a client's configuration options.
- Added support for the ``comment`` parameter to all helpers. For example see
@ -631,9 +709,9 @@ Highlights include:
- Complete support for MongoDB 3.4:
- Unicode aware string comparison using collations. See :ref:`PyMongo's examples for collation <collation-on-operation>`.
- Unicode aware string comparison using collations.
- :class:`MotorCursor` and :class:`MotorGridOutCursor` have a new attribute :meth:`~MotorCursor.collation`.
- Support for the new :class:`~bson.decimal128.Decimal128` BSON type.
- Support for the new :class:`~pymongo.decimal128.Decimal128` BSON type.
- A new maxStalenessSeconds read preference option.
- A username is no longer required for the MONGODB-X509 authentication
mechanism when connected to MongoDB >= 3.4.
@ -662,9 +740,9 @@ Highlights include:
verification.
- TLS compression is now explicitly disabled when possible.
- The Server Name Indication (SNI) TLS extension is used when possible.
- PyMongo's `bson` module provides finer control over JSON encoding/decoding
with :class:`~bson.json_util.JSONOptions`.
- Allow :class:`~bson.code.Code` objects to have a scope of ``None``,
- PyMongo's ``bson`` module provides finer control over JSON encoding/decoding
with :class:`~pymongo.json_util.JSONOptions`.
- Allow :class:`~pymongo.code.Code` objects to have a scope of ``None``,
signifying no scope. Also allow encoding Code objects with an empty scope
(i.e. ``{}``).
@ -687,7 +765,7 @@ Motor 1.0
Motor now depends on PyMongo 3.3 and later. The move from PyMongo 2 to 3 brings
a large number of API changes, read the `the PyMongo 3 changelog`_ carefully.
.. _the PyMongo 3 changelog: https://pymongo.readthedocs.io/en/stable/changelog.html#changes-in-version-3-0
.. _the PyMongo 3 changelog: https://pymongo.readthedocs.io/en/4.0/changelog.html#changes-in-version-3-0
:class:`MotorReplicaSetClient` is removed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -734,8 +812,8 @@ Unix domain socket paths must be quoted with :func:`urllib.parse.quote_plus` (or
.. code-block:: python
path = '/tmp/mongodb-27017.sock'
MotorClient('mongodb://%s' % urllib.parse.quote_plus(path))
path = "/tmp/mongodb-27017.sock"
MotorClient("mongodb://%s" % urllib.parse.quote_plus(path))
:class:`~motor.motor_tornado.MotorCollection` changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -784,9 +862,9 @@ The following find/find_one options have been removed:
- await_data (use the new ``cursor_type`` option instead)
- exhaust (use the new ``cursor_type`` option instead)
- as_class (use :meth:`~motor.motor_tornado.MotorCollection.with_options` with
:class:`~bson.codec_options.CodecOptions` instead)
:class:`~pymongo.codec_options.CodecOptions` instead)
- compile_re (BSON regular expressions are always decoded to
:class:`~bson.regex.Regex`)
:class:`~pymongo.regex.Regex`)
The following find/find_one options are deprecated:
@ -1011,20 +1089,20 @@ Motor 0.6
This is a bugfix release. Fixing these bugs has introduced tiny API changes that
may affect some programs.
`motor_asyncio` and `motor_tornado` submodules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``motor_asyncio`` and ``motor_tornado`` submodules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These modules have been moved from:
- `motor_asyncio.py`
- `motor_tornado.py`
- ``motor_asyncio.py``
- ``motor_tornado.py``
To:
- `motor_asyncio/__init__.py`
- `motor_tornado/__init__.py`
- ``motor_asyncio/__init__.py``
- ``motor_tornado/__init__.py``
Motor had to make this change in order to omit the `motor_asyncio` submodule
Motor had to make this change in order to omit the ``motor_asyncio`` submodule
entirely and avoid a spurious :exc:`SyntaxError` being printed when installing in
Python 2. The change should be invisible to application code.
@ -1078,18 +1156,18 @@ explanation.)
.. _commit message dc19418c: https://github.com/mongodb/motor/commit/dc19418c
`async` and `await`
~~~~~~~~~~~~~~~~~~~
``async`` and ``await``
~~~~~~~~~~~~~~~~~~~~~~~
Motor now supports Python 3.5 native coroutines, written with the `async` and
`await` syntax::
Motor now supports Python 3.5 native coroutines, written with the ``async`` and
``await`` syntax::
async def f():
await collection.insert({'_id': 1})
Cursors from :meth:`~MotorCollection.find`, :meth:`~MotorCollection.aggregate`, or
:meth:`~MotorGridFS.find` can be iterated elegantly and very efficiently in native
coroutines with `async for`::
coroutines with ``async for``::
async def f():
async for doc in collection.find():
@ -1101,7 +1179,7 @@ coroutines with `async for`::
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:meth:`MotorCollection.aggregate` now returns a cursor by default, and the cursor
is returned immediately without a `yield`. The old syntax is no longer
is returned immediately without a ``yield``. The old syntax is no longer
supported::
# Motor 0.4 and older, no longer supported.
@ -1139,7 +1217,7 @@ Deprecations
Motor 0.5 deprecates a large number of APIs that will be removed in version 1.0:
`MotorClient`:
``MotorClient``:
- `~MotorClient.host`
- `~MotorClient.port`
- `~MotorClient.document_class`
@ -1150,7 +1228,7 @@ Motor 0.5 deprecates a large number of APIs that will be removed in version 1.0:
- `~MotorClient.disconnect`
- `~MotorClient.alive`
`MotorReplicaSetClient`:
``MotorReplicaSetClient``:
- `~MotorReplicaSetClient.document_class`
- `~MotorReplicaSetClient.tz_aware`
- `~MotorReplicaSetClient.secondary_acceptable_latency_ms`
@ -1158,12 +1236,12 @@ Motor 0.5 deprecates a large number of APIs that will be removed in version 1.0:
- `~MotorReplicaSetClient.uuid_subtype`
- `~MotorReplicaSetClient.alive`
`MotorDatabase`:
``MotorDatabase``:
- `~MotorDatabase.secondary_acceptable_latency_ms`
- `~MotorDatabase.tag_sets`
- `~MotorDatabase.uuid_subtype`
`MotorCollection`:
``MotorCollection``:
- `~MotorCollection.secondary_acceptable_latency_ms`
- `~MotorCollection.tag_sets`
- `~MotorCollection.uuid_subtype`
@ -1193,7 +1271,7 @@ SSL hostname validation error
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When you use Motor with Tornado and SSL hostname validation fails, Motor used
to raise a :exc:`~pymongo.errors.ConnectionFailure` with a useful messsage like "hostname 'X'
to raise a :exc:`~pymongo.errors.ConnectionFailure` with a useful message like "hostname 'X'
doesn't match 'Y'". The message is now empty and Tornado logs a warning
instead.
@ -1223,8 +1301,7 @@ therefore inheriting
`PyMongo 2.7.2's bug fixes <https://jira.mongodb.org/browse/PYTHON/fixforversion/14005>`_
and
`PyMongo 2.8's bug fixes <https://jira.mongodb.org/browse/PYTHON/fixforversion/14223>`_
and `features
<https://pymongo.readthedocs.io/en/stable/changelog.html#changes-in-version-2-8>`_.
and features.
Fixes `a connection-pool timeout when waitQueueMultipleMS is set
<https://jira.mongodb.org/browse/MOTOR-62>`_ and `two bugs in replica set
@ -1238,8 +1315,6 @@ another, if the remote host requires authentication.
For this, use PyMongo's ``copy_database`` method, or, since PyMongo's
``copy_database`` will be removed in a future release too, use the mongo shell.
.. seealso:: `The "copydb" command <https://mongodb.com/docs/manual/reference/command/copydb/>`_.
Motor 0.3.3
-----------

View File

@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
#
# Motor documentation build configuration file
#
# This file is execfile()d with the current directory set to its containing dir.
import os
import sys
from importlib.metadata import metadata
sys.path[0:0] = [os.path.abspath("..")]
from pymongo import version as pymongo_version # noqa: E402
import motor # noqa: E402
# -- General configuration -----------------------------------------------------
@ -76,7 +73,14 @@ pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
linkcheck_ignore = [r"http://localhost:\d+"]
# Links to release notes in jira give 401 error: unauthorized. MOTOR-1476
linkcheck_ignore = [
r"http://localhost:\d+",
r"https://jira\.mongodb\.org/secure/ReleaseNote\.jspa.*",
]
# Allow for flaky links.
linkcheck_retries = 3
# -- Options for extensions ----------------------------------------------------
autoclass_content = "init"
@ -126,15 +130,20 @@ from motor import MotorClient
html_copy_source = False
# Theme gratefully vendored from CPython source.
html_theme = "pydoctheme"
html_theme_path = ["."]
html_theme_options = {"collapsiblesidebar": True}
html_static_path = ["static"]
try:
import furo # noqa: F401
html_sidebars = {
"index": ["globaltoc.html", "searchbox.html"],
}
html_theme = "furo"
except ImportError:
# Theme gratefully vendored from CPython source.
html_theme = "pydoctheme"
html_theme_path = ["."]
html_theme_options = {"collapsiblesidebar": True}
html_static_path = ["static"]
html_sidebars = {
"index": ["globaltoc.html", "searchbox.html"],
}
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@ -215,11 +224,10 @@ autodoc_default_options = {
"member-order": "groupwise",
}
pymongo_version = metadata("pymongo")["version"]
pymongo_inventory = ("https://pymongo.readthedocs.io/en/%s/" % pymongo_version, None)
intersphinx_mapping = {
"bson": pymongo_inventory,
"gridfs": pymongo_inventory,
"pymongo": pymongo_inventory,
"aiohttp": ("http://aiohttp.readthedocs.io/en/stable/", None),
"tornado": ("http://www.tornadoweb.org/en/stable/", None),

View File

@ -1,6 +1,13 @@
Configuration
=============
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
TLS Protocol Version
''''''''''''''''''''
@ -28,14 +35,14 @@ and executing the following command::
You should see "TLS 1.X" where X is >= 1.
You can read more about TLS versions and their security implications here:
You can read more about TLS versions and their security implications in this `cheat sheet`_.
`<https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html#only-support-strong-protocols>`_
.. _python.org: https://www.python.org/downloads/
.. _homebrew: https://brew.sh/
.. _macports: https://www.macports.org/
.. _requests: https://pypi.python.org/pypi/requests
.. _cheat sheet: https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html#only-support-strong-protocols
Thread Pool Size
''''''''''''''''
@ -46,3 +53,17 @@ system; to override the default set the environment variable ``MOTOR_MAX_WORKERS
Some additional threads are used for monitoring servers and background tasks, so the total
count of threads in your process will be greater.
Network Compression
'''''''''''''''''''
Like PyMongo, Motor supports network compression where network traffic between
the client and MongoDB server are compressed.
Keyword arguments to the Motor clients match those in MongoClient, documented
`here <https://pymongo.readthedocs.io/en/stable/examples/network_compression.html>`_.
By default no compression is used. If you wish to use wire compression,
you will have to install one of the optional dependencies.
Snappy requires `python-snappy <https://pypi.org/project/python-snappy>`_
and zstandard requires `zstandard <https://pypi.org/project/zstandard>`_::
$ python3 -m pip install "motor[snappy, zstd]"

View File

@ -17,3 +17,5 @@ The following is a list of people who have contributed to
- Tushar Singh
- Steven Silvester
- Julius Park
- Doeke Buursma
- Scott Luu

View File

@ -1,10 +1,9 @@
"""Gratefully adapted from aiohttp, provides coroutine support to autodoc."""
from sphinx import addnodes
from sphinx.domains.python import PyFunction, PyMethod
class PyCoroutineMixin(object):
class PyCoroutineMixin:
def handle_signature(self, sig, signode):
ret = super().handle_signature(sig, signode)
signode.insert(0, addnodes.desc_annotation("coroutine ", "coroutine "))

View File

@ -2,6 +2,13 @@
Developer Guide
===============
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
Some explanations for those who would like to contribute to Motor development.
Compatibility
@ -105,5 +112,5 @@ synchronous and Motor is async; how can Motor pass PyMongo's tests?
Synchro is a hacky little module that re-synchronizes all Motor methods using
the Tornado IOLoop's ``run_sync`` method. ``synchrotest.py`` overrides the Python
interpreter's import machinery to allow Synchro to masquerade as PyMongo, and
runs PyMongo's test suite against it. Use ``tox -e synchro37`` to check out
runs PyMongo's test suite against it. Use ``tox -e synchro`` to check out
PyMongo's test suite and run it with Synchro.

View File

@ -4,6 +4,12 @@
Differences between Motor and PyMongo
=====================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. important:: This page describes using Motor with Tornado. Beginning in
version 0.5 Motor can also integrate with asyncio instead of Tornado.
@ -43,8 +49,8 @@ GridFS
- File-like
PyMongo's :class:`~gridfs.grid_file.GridIn` and
:class:`~gridfs.grid_file.GridOut` strive to act like Python's built-in
PyMongo's :class:`~pymongo.grid_file.GridIn` and
:class:`~pymongo.grid_file.GridOut` strive to act like Python's built-in
file objects, so they can be passed to many functions that expect files.
But the I/O methods of :class:`MotorGridIn` and
:class:`MotorGridOut` are asynchronous, so they cannot obey the
@ -53,7 +59,7 @@ GridFS
- Setting properties
In PyMongo, you can set arbitrary attributes on
a :class:`~gridfs.grid_file.GridIn` and they're stored as metadata on
a :class:`~pymongo.grid_file.GridIn` and they're stored as metadata on
the server, even after the ``GridIn`` is closed::
fs = gridfs.GridFSBucket(db)
@ -90,9 +96,9 @@ system_js
PyMongo supports Javascript procedures stored in MongoDB with syntax like:
.. code-block:: python
.. code-block:: pycon
>>> db.system_js.my_func = 'function(x) { return x * x; }'
>>> db.system_js.my_func = "function(x) { return x * x; }"
>>> db.system_js.my_func(2)
4.0
@ -133,17 +139,10 @@ There are two ways to create a capped collection using PyMongo:
.. code-block:: python
# Typical:
db.create_collection(
'collection1',
capped=True,
size=1000)
db.create_collection("collection1", capped=True, size=1000)
# Unusual:
collection = Collection(
db,
'collection2',
capped=True,
size=1000)
collection = Collection(db, "collection2", capped=True, size=1000)
Motor can't do I/O in a constructor, so the unusual style is prohibited and
only the typical style is allowed:
@ -151,7 +150,4 @@ only the typical style is allowed:
.. code-block:: python
async def f():
await db.create_collection(
'collection1',
capped=True,
size=1000)
await db.create_collection("collection1", capped=True, size=1000)

View File

@ -1,5 +0,0 @@
tornado
aiohttp
Sphinx~=4.1
sphinx_rtd_theme~=0.5
readthedocs-sphinx-search~=0.1

View File

@ -31,13 +31,14 @@ async def page_handler(request):
document = await db.pages.find_one(page_name)
if not document:
return web.HTTPNotFound(text="No page named {!r}".format(page_name))
return web.HTTPNotFound(text=f"No page named {page_name!r}")
return web.Response(body=document["body"].encode(), content_type="text/html")
# -- handler-end --
# -- main-start --
async def init_connection():
db = await setup_db()

View File

@ -1,6 +1,13 @@
AIOHTTPGridFS Example
=====================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
Serve pre-compressed static content from GridFS over HTTP. Uses the `aiohttp`_
web framework and :class:`~motor.aiohttp.AIOHTTPGridFS`.

View File

@ -3,6 +3,13 @@
Authentication With Motor
=========================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
This page describes using Motor with Tornado. Beginning in
version 0.5 Motor can also integrate with asyncio instead of Tornado.

View File

@ -74,7 +74,7 @@ async def main():
await create_json_schema_file(kms_providers, key_vault_namespace, key_vault_client)
# Load the JSON Schema and construct the local schema_map option.
with open("jsonSchema.json", "r") as file:
with open("jsonSchema.json") as file:
json_schema_string = file.read()
json_schema = json_util.loads(json_schema_string)
schema_map = {encrypted_namespace: json_schema}
@ -91,10 +91,10 @@ async def main():
await coll.insert_one({"encryptedField": "123456789"})
decrypted_doc = await coll.find_one()
print("Decrypted document: %s" % (decrypted_doc,))
print(f"Decrypted document: {decrypted_doc}")
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name]
encrypted_doc = await unencrypted_coll.find_one()
print("Encrypted document: %s" % (encrypted_doc,))
print(f"Encrypted document: {encrypted_doc}")
if __name__ == "__main__":

View File

@ -5,6 +5,13 @@
Bulk Write Operations
=====================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. testsetup::
client = MotorClient()
@ -31,9 +38,10 @@ bulk insert operations.
.. doctest::
>>> async def f():
... await db.test.insert_many(({'i': i} for i in range(10000)))
... await db.test.insert_many(({"i": i} for i in range(10000)))
... count = await db.test.count_documents({})
... print("Final count: %d" % count)
...
>>>
>>> IOLoop.current().run_sync(f)
Final count: 10000
@ -61,14 +69,17 @@ of operations performed.
>>> from pprint import pprint
>>> from pymongo import InsertOne, DeleteMany, ReplaceOne, UpdateOne
>>> async def f():
... result = await db.test.bulk_write([
... DeleteMany({}), # Remove all documents from the previous example.
... InsertOne({'_id': 1}),
... InsertOne({'_id': 2}),
... InsertOne({'_id': 3}),
... UpdateOne({'_id': 1}, {'$set': {'foo': 'bar'}}),
... UpdateOne({'_id': 4}, {'$inc': {'j': 1}}, upsert=True),
... ReplaceOne({'j': 1}, {'j': 2})])
... result = await db.test.bulk_write(
... [
... DeleteMany({}), # Remove all documents from the previous example.
... InsertOne({"_id": 1}),
... InsertOne({"_id": 2}),
... InsertOne({"_id": 3}),
... UpdateOne({"_id": 1}, {"$set": {"foo": "bar"}}),
... UpdateOne({"_id": 4}, {"$inc": {"j": 1}}, upsert=True),
... ReplaceOne({"j": 1}, {"j": 2}),
... ]
... )
... pprint(result.bulk_api_result)
...
>>> IOLoop.current().run_sync(f)
@ -95,9 +106,10 @@ the failure.
>>> from pymongo.errors import BulkWriteError
>>> async def f():
... requests = [
... ReplaceOne({'j': 2}, {'i': 5}),
... InsertOne({'_id': 4}), # Violates the unique key constraint on _id.
... DeleteOne({'i': 5})]
... ReplaceOne({"j": 2}, {"i": 5}),
... InsertOne({"_id": 4}), # Violates the unique key constraint on _id.
... DeleteOne({"i": 5}),
... ]
... try:
... await db.test.bulk_write(requests)
... except BulkWriteError as bwe:
@ -136,10 +148,11 @@ and fourth operations succeed.
>>> async def f():
... requests = [
... InsertOne({'_id': 1}),
... DeleteOne({'_id': 2}),
... InsertOne({'_id': 3}),
... ReplaceOne({'_id': 4}, {'i': 1})]
... InsertOne({"_id": 1}),
... DeleteOne({"_id": 2}),
... InsertOne({"_id": 3}),
... ReplaceOne({"_id": 4}, {"i": 1}),
... ]
... try:
... await db.test.bulk_write(requests, ordered=False)
... except BulkWriteError as bwe:
@ -181,10 +194,9 @@ after all operations are attempted, regardless of execution order.
>>> from pymongo import WriteConcern
>>> async def f():
... coll = db.get_collection(
... 'test', write_concern=WriteConcern(w=4, wtimeout=1))
... coll = db.get_collection("test", write_concern=WriteConcern(w=4, wtimeout=1))
... try:
... await coll.bulk_write([InsertOne({'a': i}) for i in range(4)])
... await coll.bulk_write([InsertOne({"a": i}) for i in range(4)])
... except BulkWriteError as bwe:
... pprint(bwe.details)
...

View File

@ -3,6 +3,13 @@
Client-Side Field Level Encryption
==================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
Starting in MongoDB 4.2, client-side field level encryption allows an application
to encrypt specific data fields in addition to pre-existing MongoDB
encryption features such as `Encryption at Rest

View File

@ -64,9 +64,9 @@ async def main():
await coll.insert_one({"encryptedField": encrypted_field})
# Automatically decrypts any encrypted fields.
doc = await coll.find_one()
print("Decrypted document: %s" % (doc,))
print(f"Decrypted document: {doc}")
unencrypted_coll = AsyncIOMotorClient().test.coll
print("Encrypted document: %s" % (await unencrypted_coll.find_one(),))
print(f"Encrypted document: {await unencrypted_coll.find_one()}")
# Cleanup resources.
await client_encryption.close()

View File

@ -54,11 +54,11 @@ async def main():
)
await coll.insert_one({"encryptedField": encrypted_field})
doc = await coll.find_one()
print("Encrypted document: %s" % (doc,))
print(f"Encrypted document: {doc}")
# Explicitly decrypt the field:
doc["encryptedField"] = await client_encryption.decrypt(doc["encryptedField"])
print("Decrypted document: %s" % (doc,))
print(f"Decrypted document: {doc}")
# Cleanup resources.
await client_encryption.close()

View File

@ -1,6 +1,13 @@
Motor Examples
==============
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. seealso:: :doc:`../tutorial-tornado`
.. toctree::
@ -12,5 +19,7 @@ Motor Examples
authentication
aiohttp_gridfs_example
encryption
timeouts
type_hints
See also :ref:`example-web-application-aiohttp`.

View File

@ -3,6 +3,13 @@
Application Performance Monitoring (APM)
========================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
Motor implements the same `Command Monitoring`_ and `Topology Monitoring`_ specifications as other MongoDB drivers.
Therefore, you can register callbacks to be notified of every MongoDB query or command your program sends, and the server's reply to each, as well as getting a notification whenever the driver checks a server's status or detects a change in your replica set.

View File

@ -27,25 +27,25 @@ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
class CommandLogger(monitoring.CommandListener):
def started(self, event):
logging.info(
"Command {0.command_name} with request id "
"{0.request_id} started on server "
"{0.connection_id}".format(event)
f"Command {event.command_name} with request id "
f"{event.request_id} started on server "
f"{event.connection_id}"
)
def succeeded(self, event):
logging.info(
"Command {0.command_name} with request id "
"{0.request_id} on server {0.connection_id} "
"succeeded in {0.duration_micros} "
"microseconds".format(event)
f"Command {event.command_name} with request id "
f"{event.request_id} on server {event.connection_id} "
f"succeeded in {event.duration_micros} "
"microseconds"
)
def failed(self, event):
logging.info(
"Command {0.command_name} with request id "
"{0.request_id} on server {0.connection_id} "
"failed in {0.duration_micros} "
"microseconds".format(event)
f"Command {event.command_name} with request id "
f"{event.request_id} on server {event.connection_id} "
f"failed in {event.duration_micros} "
"microseconds"
)
@ -77,22 +77,20 @@ ioloop.IOLoop.current().run_sync(do_insert)
# server logger start
class ServerLogger(monitoring.ServerListener):
def opened(self, event):
logging.info("Server {0.server_address} added to topology {0.topology_id}".format(event))
logging.info(f"Server {event.server_address} added to topology {event.topology_id}")
def description_changed(self, event):
previous_server_type = event.previous_description.server_type
new_server_type = event.new_description.server_type
if new_server_type != previous_server_type:
logging.info(
"Server {0.server_address} changed type from "
"{0.previous_description.server_type_name} to "
"{0.new_description.server_type_name}".format(event)
f"Server {event.server_address} changed type from "
f"{event.previous_description.server_type_name} to "
f"{event.new_description.server_type_name}"
)
def closed(self, event):
logging.warning(
"Server {0.server_address} removed from topology {0.topology_id}".format(event)
)
logging.warning(f"Server {event.server_address} removed from topology {event.topology_id}")
monitoring.register(ServerLogger())
@ -102,21 +100,21 @@ monitoring.register(ServerLogger())
# topology logger start
class TopologyLogger(monitoring.TopologyListener):
def opened(self, event):
logging.info("Topology with id {0.topology_id} opened".format(event))
logging.info(f"Topology with id {event.topology_id} opened")
def description_changed(self, event):
logging.info("Topology description updated for topology id {0.topology_id}".format(event))
logging.info(f"Topology description updated for topology id {event.topology_id}")
previous_topology_type = event.previous_description.topology_type
new_topology_type = event.new_description.topology_type
if new_topology_type != previous_topology_type:
logging.info(
"Topology {0.topology_id} changed type from "
"{0.previous_description.topology_type_name} to "
"{0.new_description.topology_type_name}".format(event)
f"Topology {event.topology_id} changed type from "
f"{event.previous_description.topology_type_name} to "
f"{event.new_description.topology_type_name}"
)
def closed(self, event):
logging.info("Topology with id {0.topology_id} closed".format(event))
logging.info(f"Topology with id {event.topology_id} closed")
monitoring.register(TopologyLogger())
@ -126,18 +124,18 @@ monitoring.register(TopologyLogger())
# heartbeat logger start
class HeartbeatLogger(monitoring.ServerHeartbeatListener):
def started(self, event):
logging.info("Heartbeat sent to server {0.connection_id}".format(event))
logging.info(f"Heartbeat sent to server {event.connection_id}")
def succeeded(self, event):
logging.info(
"Heartbeat to server {0.connection_id} "
f"Heartbeat to server {event.connection_id} "
"succeeded with reply "
"{0.reply.document}".format(event)
f"{event.reply.document}"
)
def failed(self, event):
logging.warning(
"Heartbeat to server {0.connection_id} failed with error {0.reply}".format(event)
f"Heartbeat to server {event.connection_id} failed with error {event.reply}"
)

View File

@ -86,14 +86,14 @@ async def main():
await coll.insert_one({"encryptedField": "123456789"})
decrypted_doc = await coll.find_one()
print("Decrypted document: %s" % (decrypted_doc,))
print(f"Decrypted document: {decrypted_doc}")
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name]
encrypted_doc = await unencrypted_coll.find_one()
print("Encrypted document: %s" % (encrypted_doc,))
print(f"Encrypted document: {encrypted_doc}")
try:
await unencrypted_coll.insert_one({"encryptedField": "123456789"})
except OperationFailure as exc:
print("Unencrypted insert failed: %s" % (exc.details,))
print(f"Unencrypted insert failed: {exc.details}")
if __name__ == "__main__":

View File

@ -3,6 +3,13 @@
Motor Tailable Cursor Example
=============================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
By default, MongoDB will automatically close a cursor when the client has
exhausted all results in the cursor. However, for capped collections you may
use a tailable cursor that remains open after the client exhausts the results
@ -16,11 +23,12 @@ of a replica set member:
from asyncio import sleep
from pymongo.cursor import CursorType
async def tail_oplog_example():
oplog = client.local.oplog.rs
first = await oplog.find().sort('$natural', pymongo.ASCENDING).limit(-1).next()
first = await oplog.find().sort("$natural", pymongo.ASCENDING).limit(-1).next()
print(first)
ts = first['ts']
ts = first["ts"]
while True:
# For a regular capped collection CursorType.TAILABLE_AWAIT is the
@ -30,12 +38,14 @@ of a replica set member:
# can only be used when querying the oplog. Starting in MongoDB 4.4
# this option is ignored by the server as queries against the oplog
# are optimized automatically by the MongoDB query engine.
cursor = oplog.find({'ts': {'$gt': ts}},
cursor_type=CursorType.TAILABLE_AWAIT,
oplog_replay=True)
cursor = oplog.find(
{"ts": {"$gt": ts}},
cursor_type=CursorType.TAILABLE_AWAIT,
oplog_replay=True,
)
while cursor.alive:
async for doc in cursor:
ts = doc['ts']
ts = doc["ts"]
print(doc)
# We end up here if the find() returned no documents or if the
# tailable cursor timed out (no new documents were added to the

87
doc/examples/timeouts.rst Normal file
View File

@ -0,0 +1,87 @@
.. _timeout-example:
Client Side Operation Timeout
=============================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
PyMongo 4.2 introduced :meth:`~pymongo.timeout` and the ``timeoutMS``
URI and keyword argument to :class:`~pymongo.mongo_client.MongoClient`.
These features allow applications to more easily limit the amount of time that
one or more operations can execute before control is returned to the app. This
timeout applies to all of the work done to execute the operation, including
but not limited to server selection, connection checkout, serialization, and
server-side execution.
:meth:`~pymongo.timeout` is asyncio safe; the timeout only applies to current
Task and multiple Tasks can configure different timeouts concurrently.
:meth:`~pymongo.timeout` can be used identically in Motor 3.1+.
For more information and troubleshooting, see the PyMongo docs on
`Client Side Operation Timeout`_.
.. _Client Side Operation Timeout: https://pymongo.readthedocs.io/en/stable/examples/timeouts.html
Basic Usage
-----------
The following example uses :meth:`~pymongo.timeout` to configure a 10-second
timeout for an :meth:`~pymongo.collection.Collection.insert_one` operation::
import pymongo
import motor.motor_asyncio
client = motor.motor_asyncio.AsyncIOMotorClient()
coll = client.test.test
with pymongo.timeout(10):
await coll.insert_one({"name": "Nunu"})
The :meth:`~pymongo.timeout` applies to all pymongo operations within the block.
The following example ensures that both the ``insert`` and the ``find`` complete
within 10 seconds total, or raise a timeout error::
with pymongo.timeout(10):
await coll.insert_one({"name": "Nunu"})
await coll.find_one({"name": "Nunu"})
When nesting :func:`~pymongo.timeout`, the nested deadline is capped by the outer
deadline. The deadline can only be shortened, not extended.
When exiting the block, the previous deadline is restored::
with pymongo.timeout(5):
await coll.find_one() # Uses the 5 second deadline.
with pymongo.timeout(3):
await coll.find_one() # Uses the 3 second deadline.
await coll.find_one() # Uses the original 5 second deadline.
with pymongo.timeout(10):
await coll.find_one() # Still uses the original 5 second deadline.
await coll.find_one() # Uses the original 5 second deadline.
Timeout errors
--------------
When the :meth:`~pymongo.timeout` with-statement is entered, a deadline is set
for the entire block. When that deadline is exceeded, any blocking pymongo operation
will raise a timeout exception. For example::
try:
with pymongo.timeout(10):
await coll.insert_one({"name": "Nunu"})
await asyncio.sleep(10)
# The deadline has now expired, the next operation will raise
# a timeout exception.
await coll.find_one({"name": "Nunu"})
except PyMongoError as exc:
if exc.timeout:
print(f"block timed out: {exc!r}")
else:
print(f"failed with non-timeout error: {exc!r}")
The :attr:`pymongo.errors.PyMongoError.timeout` property (added in PyMongo 4.2)
will be ``True`` when the error was caused by a timeout and ``False`` otherwise.

View File

@ -73,11 +73,8 @@ class ChangesHandler(tornado.websocket.WebSocketHandler):
data = data.encode("utf-8")
html_id = urlsafe_b64encode(data).decode().rstrip("=")
change.pop("_id")
change["html"] = '<div id="change-%s"><pre>%s</pre></div>' % (
html_id,
tornado.escape.xhtml_escape(pformat(change)),
)
change_pre = tornado.escape.xhtml_escape(pformat(change))
change["html"] = f'<div id="change-{html_id}"><pre>{change_pre}</pre></div>'
change["html_id"] = html_id
ChangesHandler.send_change(change)
ChangesHandler.update_cache(change)
@ -97,7 +94,7 @@ async def watch(collection):
def main():
tornado.options.parse_command_line()
if "." not in options.ns:
sys.stderr.write('Invalid ns "%s", must contain a "."' % (options.ns,))
sys.stderr.write(f'Invalid ns "{options.ns}", must contain a "."')
sys.exit(1)
db_name, collection_name = options.ns.split(".", 1)

View File

@ -3,6 +3,13 @@
Tornado Change Stream Example
=============================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
Watch a collection for changes with :meth:`MotorCollection.watch` and display
@ -18,7 +25,7 @@ http://localhost:8888
Open a ``mongo`` shell in the terminal and perform some operations on the
"test" collection in the "test" database:
.. code-block:: none
.. code-block:: text
> use test
switched to db test
@ -29,7 +36,7 @@ Open a ``mongo`` shell in the terminal and perform some operations on the
The application receives each change notification and displays it as JSON on
the web page:
.. code-block:: none
.. code-block:: text
Changes

393
doc/examples/type_hints.rst Normal file
View File

@ -0,0 +1,393 @@
.. _type_hints-example:
Type Hints
==========
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
As of version 3.3.0, Motor ships with `type hints`_. With type hints, Python
type checkers can easily find bugs before they reveal themselves in your code.
If your IDE is configured to use type hints,
it can suggest more appropriate completions and highlight errors in your code.
Some examples include `PyCharm`_, `Sublime Text`_, and `Visual Studio Code`_.
You can also use the `mypy`_ tool from your command line or in Continuous Integration tests.
All of the public APIs in Motor are fully type hinted, and
several of them support generic parameters for the
type of document object returned when decoding BSON documents.
Due to `limitations in mypy`_, the default
values for generic document types are not yet provided (they will eventually be ``Dict[str, any]``).
For a larger set of examples that use types, see the Motor `test_typing module`_.
If you would like to opt out of using the provided types, add the following to
your `mypy config`_: ::
[mypy-motor]
follow_imports = False
Basic Usage
-----------
Note that a type for :class:`~motor.motor_asyncio.AsyncIOMotorClient` must be specified. Here we use the
default, unspecified document type:
.. code-block:: python
from motor.motor_asyncio import AsyncIOMotorClient
async def main():
client: AsyncIOMotorClient = AsyncIOMotorClient()
collection = client.test.test
inserted = await collection.insert_one({"x": 1, "tags": ["dog", "cat"]})
retrieved = await collection.find_one({"x": 1})
assert isinstance(retrieved, dict)
For a more accurate typing for document type you can use:
.. code-block:: python
from typing import Any, Dict
from motor.motor_asyncio import AsyncIOMotorClient
async def main():
client: AsyncIOMotorClient[Dict[str, Any]] = AsyncIOMotorClient()
collection = client.test.test
inserted = await collection.insert_one({"x": 1, "tags": ["dog", "cat"]})
retrieved = await collection.find_one({"x": 1})
assert isinstance(retrieved, dict)
Typed Client
------------
:class:`~motor.motor_asyncio.AsyncIOMotorClient` is generic on the document type used to decode BSON documents.
You can specify a :class:`~pymongo.raw_bson.RawBSONDocument` document type:
.. code-block:: python
from motor.motor_asyncio import AsyncIOMotorClient
from bson.raw_bson import RawBSONDocument
async def main():
client = AsyncIOMotorClient(document_class=RawBSONDocument)
collection = client.test.test
inserted = await collection.insert_one({"x": 1, "tags": ["dog", "cat"]})
result = await collection.find_one({"x": 1})
assert isinstance(result, RawBSONDocument)
Subclasses of :py:class:`collections.abc.Mapping` can also be used, such as :class:`~pymongo.son.SON`:
.. code-block:: python
from bson import SON
from motor.motor_asyncio import AsyncIOMotorClient
async def main():
client = AsyncIOMotorClient(document_class=SON[str, int])
collection = client.test.test
inserted = await collection.insert_one({"x": 1, "y": 2})
result = await collection.find_one({"x": 1})
assert result is not None
assert result["x"] == 1
Note that when using :class:`~pymongo.son.SON`, the key and value types must be given, e.g. ``SON[str, Any]``.
Typed Collection
----------------
You can use :py:class:`~typing.TypedDict` when using a well-defined schema for the data in a
:class:`~motor.motor_asyncio.AsyncIOMotorClient`. Note that all `schema validation`_ for inserts and updates is done on the server.
These methods automatically add an "_id" field.
.. code-block:: python
from typing import TypedDict
from motor.motor_asyncio import AsyncIOMotorClient
from motor.motor_asyncio import AsyncIOMotorCollection
class Movie(TypedDict):
name: str
year: int
async def main():
client: AsyncIOMotorClient = AsyncIOMotorClient()
collection: AsyncIOMotorCollection[Movie] = client.test.test
inserted = await collection.insert_one(Movie(name="Jurassic Park", year=1993))
result = await collection.find_one({"name": "Jurassic Park"})
assert result is not None
assert result["year"] == 1993
# This will raise a type-checking error, despite being present, because it is added by Motor.
assert result["_id"] # type:ignore[typeddict-item]
This same typing scheme works for all of the insert methods (:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.insert_one`,
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.insert_many`, and :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.bulk_write`).
For ``bulk_write`` both :class:`~pymongo.operations.InsertOne` and :class:`~pymongo.operations.ReplaceOne` operators are generic.
.. code-block:: python
from typing import TypedDict
from motor.motor_asyncio import AsyncIOMotorClient
from motor.motor_asyncio import AsyncIOMotorCollection
from pymongo.operations import InsertOne
async def main():
client: AsyncIOMotorClient = AsyncIOMotorClient()
collection: AsyncIOMotorCollection[Movie] = client.test.test
inserted = await collection.bulk_write(
[InsertOne(Movie(name="Jurassic Park", year=1993))]
)
result = await collection.find_one({"name": "Jurassic Park"})
assert result is not None
assert result["year"] == 1993
# This will raise a type-checking error, despite being present, because it is added by Motor.
assert result["_id"] # type:ignore[typeddict-item]
Modeling Document Types with TypedDict
--------------------------------------
You can use :py:class:`~typing.TypedDict` to model structured data.
As noted above, Motor will automatically add an ``_id`` field if it is not present. This also applies to TypedDict.
There are three approaches to this:
1. Do not specify ``_id`` at all. It will be inserted automatically, and can be retrieved at run-time, but will yield a type-checking error unless explicitly ignored.
2. Specify ``_id`` explicitly. This will mean that every instance of your custom TypedDict class will have to pass a value for ``_id``.
3. Make use of :py:class:`~typing.NotRequired`. This has the flexibility of option 1, but with the ability to access the ``_id`` field without causing a type-checking error.
Note: to use :py:class:`~typing.NotRequired` in earlier versions of Python (<3.11), use the ``typing_extensions`` package.
.. code-block:: python
from typing import TypedDict, NotRequired
from motor.motor_asyncio import AsyncIOMotorClient
from motor.motor_asyncio import AsyncIOMotorCollection
from bson import ObjectId
class Movie(TypedDict):
name: str
year: int
class ExplicitMovie(TypedDict):
_id: ObjectId
name: str
year: int
class NotRequiredMovie(TypedDict):
_id: NotRequired[ObjectId]
name: str
year: int
async def main():
client: AsyncIOMotorClient = AsyncIOMotorClient()
collection: AsyncIOMotorCollection[Movie] = client.test.test
inserted = await collection.insert_one(Movie(name="Jurassic Park", year=1993))
result = await collection.find_one({"name": "Jurassic Park"})
assert result is not None
# This will yield a type-checking error, despite being present, because it is added by Motor.
assert result["_id"] # type:ignore[typeddict-item]
collection: AsyncIOMotorCollection[ExplicitMovie] = client.test.test
# Note that the _id keyword argument must be supplied
inserted = await collection.insert_one(
ExplicitMovie(_id=ObjectId(), name="Jurassic Park", year=1993)
)
result = await collection.find_one({"name": "Jurassic Park"})
assert result is not None
# This will not raise a type-checking error.
assert result["_id"]
collection: AsyncIOMotorCollection[NotRequiredMovie] = client.test.test
# Note the lack of _id, similar to the first example
inserted = await collection.insert_one(
NotRequiredMovie(name="Jurassic Park", year=1993)
)
result = await collection.find_one({"name": "Jurassic Park"})
assert result is not None
# This will not raise a type-checking error, despite not being provided explicitly.
assert result["_id"]
Typed Database
--------------
While less common, you could specify that the documents in an entire database
match a well-defined schema using :py:class:`~typing.TypedDict`.
.. code-block:: python
from typing import TypedDict
from motor.motor_asyncio import AsyncIOMotorClient
from motor.motor_asyncio import AsyncIOMotorDatabase
class Movie(TypedDict):
name: str
year: int
async def main():
client: AsyncIOMotorClient = AsyncIOMotorClient()
db: AsyncIOMotorDatabase[Movie] = client.test
collection = db.test
inserted = await collection.insert_one({"name": "Jurassic Park", "year": 1993})
result = await collection.find_one({"name": "Jurassic Park"})
assert result is not None
assert result["year"] == 1993
Typed Command
-------------
When using the :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command`, you can specify the document type by providing a custom :class:`~pymongo.codec_options.CodecOptions`:
.. code-block:: python
from motor.motor_asyncio import AsyncIOMotorClient
from bson.raw_bson import RawBSONDocument
from bson import CodecOptions
async def main():
client: AsyncIOMotorClient = AsyncIOMotorClient()
options = CodecOptions(RawBSONDocument)
result = await client.admin.command("ping", codec_options=options)
assert isinstance(result, RawBSONDocument)
Custom :py:class:`collections.abc.Mapping` subclasses and :py:class:`~typing.TypedDict` are also supported.
For :py:class:`~typing.TypedDict`, use the form: ``options: CodecOptions[MyTypedDict] = CodecOptions(...)``.
Typed BSON Decoding
-------------------
You can specify the document type returned by :mod:`bson` decoding functions by providing :class:`~pymongo.codec_options.CodecOptions`:
.. code-block:: python
from typing import Any, Dict
from bson import CodecOptions, encode, decode
class MyDict(Dict[str, Any]):
pass
def foo(self):
return "bar"
options = CodecOptions(document_class=MyDict)
doc = {"x": 1, "y": 2}
bsonbytes = encode(doc, codec_options=options)
rt_document = decode(bsonbytes, codec_options=options)
assert rt_document.foo() == "bar"
:class:`~pymongo.raw_bson.RawBSONDocument` and :py:class:`~typing.TypedDict` are also supported.
For :py:class:`~typing.TypedDict`, use the form: ``options: CodecOptions[MyTypedDict] = CodecOptions(...)``.
Troubleshooting
---------------
Client Type Annotation
~~~~~~~~~~~~~~~~~~~~~~
If you forget to add a type annotation for a :class:`~motor.motor_asyncio.AsyncIOMotorClient` object you may get the following ``mypy`` error:
.. code-block:: python
from motor.motor_asyncio import AsyncIOMotorClient
client = AsyncIOMotorClient() # error: Need type annotation for "client"
The solution is to annotate the type as ``client: AsyncIOMotorClient`` or ``client: AsyncIOMotorClient[Dict[str, Any]]``. See `Basic Usage`_.
Incompatible Types
~~~~~~~~~~~~~~~~~~
If you use the generic form of :class:`~motor.motor_asyncio.AsyncIOMotorClient` you
may encounter a ``mypy`` error like:
.. code-block:: python
from motor.motor_asyncio import AsyncIOMotorClient
async def main():
client: AsyncIOMotorClient = AsyncIOMotorClient()
await client.test.test.insert_many(
{"a": 1}
) # error: Dict entry 0 has incompatible type "str": "int";
# expected "Mapping[str, Any]": "int"
The solution is to use ``client: AsyncIOMotorClient[Dict[str, Any]]`` as used in
`Basic Usage`_ .
Actual Type Errors
~~~~~~~~~~~~~~~~~~
Other times ``mypy`` will catch an actual error, like the following code:
.. code-block:: python
from motor.motor_asyncio import AsyncIOMotorClient
from typing import Mapping
async def main():
client: AsyncIOMotorClient = AsyncIOMotorClient()
await client.test.test.insert_one(
[{}]
) # error: Argument 1 to "insert_one" of "Collection" has
# incompatible type "List[Dict[<nothing>, <nothing>]]";
# expected "Mapping[str, Any]"
In this case the solution is to use ``insert_one({})``, passing a document instead of a list.
Another example is trying to set a value on a :class:`~pymongo.raw_bson.RawBSONDocument`, which is read-only.:
.. code-block:: python
from bson.raw_bson import RawBSONDocument
from motor.motor_asyncio import AsyncIOMotorClient
async def main():
client = AsyncIOMotorClient(document_class=RawBSONDocument)
coll = client.test.test
doc = {"my": "doc"}
await coll.insert_one(doc)
retrieved = await coll.find_one({"_id": doc["_id"]})
assert retrieved is not None
assert len(retrieved.raw) > 0
retrieved["foo"] = "bar" # error: Unsupported target for indexed assignment
# ("RawBSONDocument") [index]
.. _PyCharm: https://www.jetbrains.com/help/pycharm/type-hinting-in-product.html
.. _Visual Studio Code: https://code.visualstudio.com/docs/languages/python
.. _Sublime Text: https://github.com/sublimelsp/LSP-pyright
.. _type hints: https://docs.python.org/3/library/typing.html
.. _mypy: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html
.. _limitations in mypy: https://github.com/python/mypy/issues/3737
.. _mypy config: https://mypy.readthedocs.io/en/stable/config_file.html
.. _test_typing module: https://github.com/mongodb/motor/blob/master/test/test_typing.py
.. _schema validation: https://www.mongodb.com/docs/manual/core/schema-validation/#when-to-use-schema-validation

View File

@ -4,6 +4,13 @@ Motor Features
.. currentmodule:: motor.motor_tornado
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
Non-Blocking
============
Motor is an asynchronous driver for MongoDB. It can be used from Tornado_ or
@ -20,8 +27,8 @@ Featureful
Motor wraps almost all of PyMongo's API and makes it non-blocking. For the few
PyMongo features not implemented in Motor, see :doc:`differences`.
Convenient With `tornado.gen`
=============================
Convenient With ``tornado.gen``
===============================
The :mod:`tornado.gen` module lets you use coroutines to simplify asynchronous
code. Motor methods return Futures that are convenient to use with coroutines.

View File

@ -4,6 +4,12 @@ Motor: Asynchronous Python driver for MongoDB
.. image:: _static/motor.png
:align: center
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
About
-----
@ -54,7 +60,7 @@ project.
Feature Requests / Feedback
---------------------------
Use our `feedback engine <https://feedback.mongodb.com/forums/924286-drivers>`_
Use our `feedback engine <https://feedback.mongodb.com/?category=7548141816650747033>`_
to send us feature requests and general feedback about PyMongo.
Contributing

View File

@ -1,6 +1,13 @@
Installation
============
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
Install Motor from PyPI_ with pip_::
$ python3 -m pip install motor
@ -19,8 +26,8 @@ Motor works in all the environments officially supported by Tornado or by
asyncio. It requires:
* Unix (including macOS) or Windows.
* PyMongo_ >=4.1,<5
* Python 3.7+
* PyMongo_ >=4.9,<5
* Python 3.10+
Optional dependencies:
@ -34,7 +41,7 @@ dependency can be installed automatically along with Motor::
similarly,
`MONGODB-AWS <https://pymongo.readthedocs.io/en/stable/examples/authentication.html#mongodb-aws>`_
`MONGODB-AWS <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/security/authentication/aws-iam/#std-label-pymongo-mongodb-aws>`_
authentication requires ``aws`` extra dependency::
$ pip install "motor[aws]"

View File

@ -1,6 +1,13 @@
Motor 2.0 Migration Guide
=========================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
Motor 2.0 brings a number of changes to Motor 1.0's API. The major version is
@ -118,6 +125,7 @@ used callbacks:
else:
print(result)
collection.find_one({}, callback=callback)
Callbacks have been largely superseded by a Futures API intended for use with
@ -134,6 +142,7 @@ a parameter:
except Exception as exc:
print(exc)
future = collection.find_one({})
future.add_done_callback(callback)
@ -181,7 +190,7 @@ Or:
.. code-block:: python3
with client.start_session() as session:
doc = client.db.collection.find_one({}, session=session)
doc = client.db.collection.find_one({}, session=session)
To support multi-document transactions, in Motor 2.0
:meth:`MotorClient.start_session` is a coroutine, not a regular method. It must
@ -203,4 +212,4 @@ Or:
.. code-block:: python3
async with client.start_session() as session:
doc = await client.db.collection.find_one({}, session=session)
doc = await client.db.collection.find_one({}, session=session)

View File

@ -1,6 +1,13 @@
Motor 3.0 Migration Guide
=========================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. currentmodule:: motor.motor_tornado
Motor 3.0 brings a number of changes to Motor 2.0's API. The major version is
@ -179,7 +186,7 @@ can be changed to this::
``tz_aware`` defaults to ``False``
..................................
``tz_aware``, an argument for :class:`~bson.json_util.JSONOptions`,
``tz_aware``, an argument for :class:`~pymongo.json_util.JSONOptions`,
now defaults to ``False`` instead of ``True``. ``json_util.loads`` now
decodes datetime as naive by default.
@ -409,10 +416,11 @@ documents in your own code before passing them to PyMongo, and transform
incoming documents after receiving them from PyMongo.
Alternatively, if your application uses the ``SONManipulator`` API to convert
custom types to BSON, the :class:`~bson.codec_options.TypeCodec` and
:class:`~bson.codec_options.TypeRegistry` APIs may be a suitable alternative.
For more information, see the
:external:pymongo:doc:`custom type example <examples/custom_type>`.
custom types to BSON, the :class:`~pymongo.codec_options.TypeCodec` and
:class:`~pymongo.codec_options.TypeRegistry` APIs may be a suitable alternative.
For more information, see the `Custom Types documentation`_.
.. _Custom Types documentation: https://www.mongodb.com/docs/languages/python/pymongo-driver/current/data-formats/custom-types/type-codecs/
GridFS changes
--------------
@ -422,7 +430,7 @@ GridFS changes
disable_md5 parameter is removed
................................
Removed the `disable_md5` option for :class:`~motor.motor_tornado.gridfs.MotorGridFSBucket` and
Removed the ``disable_md5`` option for :class:`~motor.motor_tornado.gridfs.MotorGridFSBucket` and
:class:`~motor.motor_tornado.gridfs.MotorGridFS`. GridFS no longer generates checksums.
Applications that desire a file digest should implement it outside GridFS
and store it with other file metadata. For example::
@ -446,13 +454,12 @@ Removed features with no migration path
Encoding a UUID raises an error by default
..........................................
The default uuid_representation for :class:`~bson.codec_options.CodecOptions`,
:class:`~bson.json_util.JSONOptions`, and
The default uuid_representation for :class:`~pymongo.codec_options.CodecOptions`,
:class:`~pymongo.json_util.JSONOptions`, and
:class:`~motor.motor_tornado.MotorClient` has been changed from
:data:`bson.binary.UuidRepresentation.PYTHON_LEGACY` to
:data:`bson.binary.UuidRepresentation.UNSPECIFIED`. Attempting to encode a
:class:`uuid.UUID` instance to BSON or JSON now produces an error by default.
See :ref:`handling-uuid-data-example` for details.
Upgrade to Motor 3.0

View File

@ -47,7 +47,6 @@ def depart_mongoref_node(self, node):
class MongodocDirective(rst.Directive):
has_content = True
required_arguments = 0
optional_arguments = 0

View File

@ -32,7 +32,7 @@ def has_node_of_type(root, klass):
if isinstance(root, klass):
return True
for child in root.children:
for child in root.children: # noqa: SIM110
if has_node_of_type(child, klass):
return True
@ -90,7 +90,7 @@ def process_motor_nodes(app, doctree):
if desc_content_node.line is None and obj_motor_info["is_pymongo_docstring"]:
maybe_warn_about_code_block(name, desc_content_node)
if obj_motor_info["is_async_method"]:
if obj_motor_info["is_async_method"]: # noqa: SIM102
# Might be a handwritten RST with "coroutine" already.
if not has_coro_annotation(signature_node):
coro_annotation = addnodes.desc_annotation(
@ -125,9 +125,9 @@ def get_motor_attr(motor_class, name, *defargs):
attr = safe_getattr(motor_class, name, *defargs)
# Store some info for process_motor_nodes()
full_name = "%s.%s.%s" % (motor_class.__module__, motor_class.__name__, name)
full_name = f"{motor_class.__module__}.{motor_class.__name__}.{name}"
full_name_legacy = "motor.%s.%s.%s" % (motor_class.__module__, motor_class.__name__, name)
full_name_legacy = f"motor.{motor_class.__module__}.{motor_class.__name__}.{name}"
# These sub-attributes are set in motor.asynchronize()
has_coroutine_annotation = getattr(attr, "coroutine_annotation", False)

View File

@ -1,14 +1,21 @@
Requirements
============
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
The current version of Motor requires:
* CPython 3.7 and later.
* PyMongo_ 4.5 and later.
* CPython 3.10 and later.
* PyMongo_ 4.9 and later.
Motor can integrate with either Tornado or asyncio.
The default authentication mechanism for MongoDB 3.0+ is SCRAM-SHA-1.
The default authentication mechanism for MongoDB is SCRAM-SHA-1.
Building the docs requires `sphinx`_.
@ -27,24 +34,6 @@ Motor and PyMongo
+-------------------+-----------------+
| Motor Version | PyMongo Version |
+===================+=================+
| 1.0 | 3.3+ |
+-------------------+-----------------+
| 1.1 | 3.4+ |
+-------------------+-----------------+
| 1.2 | 3.6+ |
+-------------------+-----------------+
| 1.3 | 3.6+ |
+-------------------+-----------------+
| 2.0 | 3.7+ |
+-------------------+-----------------+
| 2.1 | 3.10+ |
+-------------------+-----------------+
| 2.2 | 3.11+ |
+-------------------+-----------------+
| 2.3 | 3.11+ |
+-------------------+-----------------+
| 2.4 | 3.11+ |
+-------------------+-----------------+
| 2.5 | 3.12+ |
+-------------------+-----------------+
| 3.0 | 4.1+ |
@ -55,43 +44,41 @@ Motor and PyMongo
+-------------------+-----------------+
| 3.3 | 4.5+ |
+-------------------+-----------------+
| 3.4 | 4.5+ |
+-------------------+-----------------+
| 3.5 | 4.5+ |
+-------------------+-----------------+
| 3.6 | 4.9 |
+-------------------+-----------------+
| 3.7 | 4.9+ |
+-------------------+-----------------+
Motor and MongoDB
`````````````````
+---------------------------------------------------------------------------------------------------+
| MongoDB Version |
+=====================+=====+=====+=====+=====+=====+=====+=====+=====+=====+=====+=====+=====+=====+
| | 2.2 | 2.4 | 2.6 | 3.0 | 3.2 | 3.4 | 3.6 | 4.0 | 4.2 | 4.4 | 5.0 | 6.0 | 7.0 |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Motor Version | 1.0 | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.1 | Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.2 |**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.3 |**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.0 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.1 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.2 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.3 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.4 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.5 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.0 |**N**|**N**|**N**|**N**|**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.1 |**N**|**N**|**N**|**N**|**N**|**N**| Y | Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.2 |**N**|**N**|**N**|**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.3 |**N**|**N**|**N**|**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
+---------------------------------------------------------------------+
| MongoDB Version |
+=====================+=====+=====+=====+=====+=====+=====+=====+=====+
| | 3.6 | 4.0 | 4.2 | 4.4 | 5.0 | 6.0 | 7.0 | 8.0 |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Motor Version | 2.5 | Y | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.0 | Y | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.1 | Y | Y | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.2 | Y | Y | Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.3 | Y | Y | Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.4 | Y | Y | Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.5 | Y | Y | Y | Y | Y | Y | Y | Y |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.6 | Y | Y | Y | Y | Y | Y | Y | Y |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.7 | N | Y | Y | Y | Y | Y | Y | Y |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
There is no relationship between PyMongo and MongoDB version numbers, although
the numbers happen to be close or equal in recent releases of PyMongo and MongoDB.
@ -99,7 +86,7 @@ Use `the PyMongo compatibility matrix`_ to determine what MongoDB version is
supported by PyMongo. Use the compatibility matrix above to determine what
MongoDB version Motor supports.
.. _the PyMongo compatibility matrix: https://mongodb.com/docs/drivers/pymongo#mongodb-compatibility
.. _the PyMongo compatibility matrix: https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/compatibility/
Motor and Tornado
`````````````````
@ -110,23 +97,7 @@ known to be incompatible, or have not been tested together.
+---------------------------------------------+
| Tornado Version |
+=====================+=====+=====+=====+=====+
| | 3.x | 4.x | 5.x | 6.x |
+---------------+-----+-----+-----+-----+-----+
| Motor Version | 1.0 | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+
| | 1.1 | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+
| | 1.2 |**N**| Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+
| | 1.3 |**N**| Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+
| | 2.0 |**N**| Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+
| | 2.1 |**N**| Y | Y | Y |
+---------------+-----+-----+-----+-----+-----+
| | 2.2 |**N**|**N**| Y | Y |
+---------------+-----+-----+-----+-----+-----+
| | 2.3 |**N**|**N**| Y | Y |
| Motor Version | 2.5 |**N**|**N**| Y | Y |
+---------------+-----+-----+-----+-----+-----+
| | 3.0 |**N**|**N**|**N**| Y |
+---------------+-----+-----+-----+-----+-----+
@ -136,19 +107,12 @@ known to be incompatible, or have not been tested together.
+---------------+-----+-----+-----+-----+-----+
| | 3.3 |**N**|**N**|**N**| Y |
+---------------+-----+-----+-----+-----+-----+
| | 3.4 |**N**|**N**|**N**| Y |
+---------------+-----+-----+-----+-----+-----+
Motor and Python
````````````````
Motor 1.2 dropped support for the short-lived version of
the "async for" protocol implemented in Python 3.5.0 and 3.5.1. Motor continues
to work with "async for" loops in Python 3.5.2 and later.
Motor 1.2.5 and 1.3.1 add compatibility with Python 3.7, but at the cost of
dropping Python 3.4.3 and older.
Motor 2.2 dropped support for Pythons older than 3.5.2.
Motor 2.5 deprecated support for Python 3.5.
Motor 3.0 dropped support for Pythons older than 3.7.
@ -157,39 +121,55 @@ Motor 3.1.1 added support for Python 3.11.
Motor 3.3 added support for Python 3.12.
+---------------------------------------------------------------+
| Python Version |
+=====================+=====+=====+=====+=====+=====+=====+=====+
| | 3.6 | 3.7 | 3.8 | 3.9 | 3.10| 3.11| 3.12|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| Motor Version | 1.0 | Y |**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.1 | Y |**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.2 | Y | Y |**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.3 | Y | Y |**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.0 | Y | Y |**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.1 | Y | Y | Y |**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.2 | Y | Y | Y |**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.3 | Y | Y | Y |**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.4 | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.5 | Y | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.0 |**N**| Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.1 |**N**| Y | Y | Y | Y |**Y**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.2 |**N**| Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.3 |**N**| Y | Y | Y | Y | Y |**Y**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+
Motor 3.5 dropped support for Python 3.7 and added support for Python 3.13.
Motor 3.7 dropped support for Python 3.8.
Motor 3.8 dropped support for Python 3.9 and added support for Python 3.14.
+---------------------------------------------------------------------------+
| Python Version |
+=====================+=====+=====+=====+=====+=====+=====+=====+=====+=====+
| | 3.6 | 3.7 | 3.8 | 3.9 | 3.10| 3.11| 3.12| 3.13| 3.14|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Motor Version | 1.0 | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.1 | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.2 | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1.3 | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.0 | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.1 | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.2 | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.3 | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.4 | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 2.5 | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.0 |**N**| Y | Y | Y | Y |**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.1 |**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.2 |**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.3 |**N**| Y | Y | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.4 |**N**| Y | Y | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.5 |**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.6 |**N**|**N**| Y | Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.7 |**N**|**N**|**N**| Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| | 3.8 |**N**|**N**|**N**|**N**| Y | Y | Y | Y | Y |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
Not Supported
-------------

View File

@ -3,6 +3,13 @@
Tutorial: Using Motor With :mod:`asyncio`
=========================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. These setups are redundant because I can't figure out how to make doctest
run a common setup *before* the setup for the two groups. A "testsetup:: *"
is the obvious answer, but it's run *after* group-specific setup.
@ -12,6 +19,7 @@ Tutorial: Using Motor With :mod:`asyncio`
import pymongo
import motor.motor_asyncio
import asyncio
client = motor.motor_asyncio.AsyncIOMotorClient()
db = client.test_database
@ -20,14 +28,17 @@ Tutorial: Using Motor With :mod:`asyncio`
import pymongo
import motor.motor_asyncio
import asyncio
client = motor.motor_asyncio.AsyncIOMotorClient()
db = client.test_database
pymongo.MongoClient().test_database.test_collection.insert_many(
[{'i': i} for i in range(2000)])
[{"i": i} for i in range(2000)]
)
.. testcleanup:: *
import pymongo
pymongo.MongoClient().test_database.test_collection.delete_many({})
A guide to using MongoDB and asyncio with Motor.
@ -74,7 +85,9 @@ Motor, like PyMongo, represents data with a 4-level object hierarchy:
Creating a Client
-----------------
You typically create a single instance of :class:`~motor.motor_asyncio.AsyncIOMotorClient` at the time your
Creating a client is what establishes a connection to MongoDB and tells your
app what deployment (i.e. cluster) to connect to. You typically create a single
instance of :class:`~motor.motor_asyncio.AsyncIOMotorClient` at the time your
application starts up.
.. doctest:: before-inserting-2000-docs
@ -87,13 +100,13 @@ specify the host and port like:
.. doctest:: before-inserting-2000-docs
>>> client = motor.motor_asyncio.AsyncIOMotorClient('localhost', 27017)
>>> client = motor.motor_asyncio.AsyncIOMotorClient("localhost", 27017)
Motor also supports `connection URIs`_:
.. doctest:: before-inserting-2000-docs
>>> client = motor.motor_asyncio.AsyncIOMotorClient('mongodb://localhost:27017')
>>> client = motor.motor_asyncio.AsyncIOMotorClient("mongodb://localhost:27017")
Connect to a replica set like:
@ -111,7 +124,7 @@ dot-notation or bracket-notation:
.. doctest:: before-inserting-2000-docs
>>> db = client.test_database
>>> db = client['test_database']
>>> db = client["test_database"]
Creating a reference to a database does no I/O and does not require an
``await`` expression.
@ -126,7 +139,7 @@ collection in Motor works the same as getting a database:
.. doctest:: before-inserting-2000-docs
>>> collection = db.test_collection
>>> collection = db['test_collection']
>>> collection = db["test_collection"]
Just like getting a reference to a database, getting a reference to a
collection does no I/O and doesn't require an ``await`` expression.
@ -140,9 +153,9 @@ store a document in MongoDB, call :meth:`~AsyncIOMotorCollection.insert_one` in
.. doctest:: before-inserting-2000-docs
>>> async def do_insert():
... document = {'key': 'value'}
... document = {"key": "value"}
... result = await db.test_collection.insert_one(document)
... print('result %s' % repr(result.inserted_id))
... print("result %s" % repr(result.inserted_id))
...
>>>
>>> import asyncio
@ -157,23 +170,22 @@ store a document in MongoDB, call :meth:`~AsyncIOMotorCollection.insert_one` in
>>> # Clean up from previous insert
>>> pymongo.MongoClient().test_database.test_collection.delete_many({})
<pymongo.results.DeleteResult ...>
DeleteResult({'n': 1, 'ok': 1.0}, acknowledged=True)
Insert documents in large batches with :meth:`~AsyncIOMotorCollection.insert_many`:
.. doctest:: before-inserting-2000-docs
>>> async def do_insert():
... result = await db.test_collection.insert_many(
... [{'i': i} for i in range(2000)])
... print('inserted %d docs' % (len(result.inserted_ids),))
... result = await db.test_collection.insert_many([{"i": i} for i in range(2000)])
... print("inserted %d docs" % (len(result.inserted_ids),))
...
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_insert())
inserted 2000 docs
Getting a Single Document With `find_one`
-----------------------------------------
Getting a Single Document With ``find_one``
-------------------------------------------
Use :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_one` to get the first document that
matches a query. For example, to get a document where the value for key "i" is
@ -182,7 +194,7 @@ less than 1:
.. doctest:: after-inserting-2000-docs
>>> async def do_find_one():
... document = await db.test_collection.find_one({'i': {'$lt': 1}})
... document = await db.test_collection.find_one({"i": {"$lt": 1}})
... pprint.pprint(document)
...
>>> loop = client.get_io_loop()
@ -209,7 +221,7 @@ To find all documents with "i" less than 5:
.. doctest:: after-inserting-2000-docs
>>> async def do_find():
... cursor = db.test_collection.find({'i': {'$lt': 5}}).sort('i')
... cursor = db.test_collection.find({"i": {"$lt": 5}}).sort("i")
... for document in await cursor.to_list(length=100):
... pprint.pprint(document)
...
@ -233,7 +245,7 @@ You can handle one document at a time in an ``async for`` loop:
>>> async def do_find():
... c = db.test_collection
... async for document in c.find({'i': {'$lt': 2}}):
... async for document in c.find({"i": {"$lt": 2}}):
... pprint.pprint(document)
...
>>> loop = client.get_io_loop()
@ -246,9 +258,9 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
.. doctest:: after-inserting-2000-docs
>>> async def do_find():
... cursor = db.test_collection.find({'i': {'$lt': 4}})
... cursor = db.test_collection.find({"i": {"$lt": 4}})
... # Modify the query before iterating
... cursor.sort('i', -1).skip(1).limit(2)
... cursor.sort("i", -1).skip(1).limit(2)
... async for document in cursor:
... pprint.pprint(document)
...
@ -260,7 +272,7 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
The cursor does not actually retrieve each document from the server
individually; it gets documents efficiently in `large batches`_.
.. _`large batches`: https://mongodb.com/docs/manual/tutorial/iterate-a-cursor/#cursor-batches
.. _`large batches`: https://www.mongodb.com/docs/manual/core/cursors/#cursor-batches
Counting Documents
------------------
@ -272,9 +284,9 @@ that match a query:
>>> async def do_count():
... n = await db.test_collection.count_documents({})
... print('%s documents in collection' % n)
... n = await db.test_collection.count_documents({'i': {'$gt': 1000}})
... print('%s documents where i > 1000' % n)
... print("%s documents in collection" % n)
... n = await db.test_collection.count_documents({"i": {"$gt": 1000}})
... print("%s documents where i > 1000" % n)
...
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_count())
@ -293,13 +305,13 @@ replacement document. The query follows the same syntax as for :meth:`find` or
>>> async def do_replace():
... coll = db.test_collection
... old_document = await coll.find_one({'i': 50})
... print('found document: %s' % pprint.pformat(old_document))
... _id = old_document['_id']
... result = await coll.replace_one({'_id': _id}, {'key': 'value'})
... print('replaced %s document' % result.modified_count)
... new_document = await coll.find_one({'_id': _id})
... print('document is now %s' % pprint.pformat(new_document))
... old_document = await coll.find_one({"i": 50})
... print("found document: %s" % pprint.pformat(old_document))
... _id = old_document["_id"]
... result = await coll.replace_one({"_id": _id}, {"key": "value"})
... print("replaced %s document" % result.modified_count)
... new_document = await coll.find_one({"_id": _id})
... print("document is now %s" % pprint.pformat(new_document))
...
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_replace())
@ -319,10 +331,10 @@ operator to set "key" to "value":
>>> async def do_update():
... coll = db.test_collection
... result = await coll.update_one({'i': 51}, {'$set': {'key': 'value'}})
... print('updated %s document' % result.modified_count)
... new_document = await coll.find_one({'i': 51})
... print('document is now %s' % pprint.pformat(new_document))
... result = await coll.update_one({"i": 51}, {"$set": {"key": "value"}})
... print("updated %s document" % result.modified_count)
... new_document = await coll.find_one({"i": 51})
... print("document is now %s" % pprint.pformat(new_document))
...
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_update())
@ -351,9 +363,9 @@ Deleting Documents
>>> async def do_delete_one():
... coll = db.test_collection
... n = await coll.count_documents({})
... print('%s documents before calling delete_one()' % n)
... result = await db.test_collection.delete_one({'i': {'$gte': 1000}})
... print('%s documents after' % (await coll.count_documents({})))
... print("%s documents before calling delete_one()" % n)
... result = await db.test_collection.delete_one({"i": {"$gte": 1000}})
... print("%s documents after" % (await coll.count_documents({})))
...
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_delete_one())
@ -369,9 +381,9 @@ Deleting Documents
>>> async def do_delete_many():
... coll = db.test_collection
... n = await coll.count_documents({})
... print('%s documents before calling delete_many()' % n)
... result = await db.test_collection.delete_many({'i': {'$gte': 1000}})
... print('%s documents after' % (await coll.count_documents({})))
... print("%s documents before calling delete_many()" % n)
... result = await db.test_collection.delete_many({"i": {"$gte": 1000}})
... print("%s documents after" % (await coll.count_documents({})))
...
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_delete_many())
@ -390,8 +402,7 @@ the :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command` method on
>>> from bson import SON
>>> async def use_distinct_command():
... response = await db.command(SON([("distinct", "test_collection"),
... ("key", "i")]))
... response = await db.command(SON([("distinct", "test_collection"), ("key", "i")]))
...
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(use_distinct_command())
@ -450,11 +461,9 @@ to an :class:`aiohttp.web.Application`:
:start-after: main-start
:end-before: main-end
Note that it is a common mistake to create a new client object for every
request; this comes at a dire performance cost. Create the client
when your application starts and reuse that one client for the lifetime
of the process. You can maintain the client by storing a database handle
from the client on your application object, as shown in this example.
.. warning:: It is a common mistake to create a new client object for every request; this comes at a dire performance cost.
Create the client when your application starts and reuse that one client for the lifetime of the process.
You can maintain the client by storing a database handle from the client on your application object, as shown in this example.
Visit ``localhost:8080/pages/page-one`` and the server responds "Hello!".
At ``localhost:8080/pages/page-two`` it responds "Goodbye." At other URLs it

View File

@ -3,6 +3,13 @@
Tutorial: Using Motor With Tornado
==================================
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
.. These setups are redundant because I can't figure out how to make doctest
run a common setup *before* the setup for the two groups. A "testsetup:: *"
is the obvious answer, but it's run *after* group-specific setup.
@ -14,6 +21,7 @@ Tutorial: Using Motor With Tornado
import tornado.web
from tornado.ioloop import IOLoop
from tornado import gen
db = motor.motor_tornado.MotorClient().test_database
.. testsetup:: after-inserting-2000-docs
@ -23,15 +31,16 @@ Tutorial: Using Motor With Tornado
import tornado.web
from tornado.ioloop import IOLoop
from tornado import gen
db = motor.motor_tornado.MotorClient().test_database
sync_db = pymongo.MongoClient().test_database
sync_db.test_collection.drop()
sync_db.test_collection.insert_many(
[{'i': i} for i in range(2000)])
sync_db.test_collection.insert_many([{"i": i} for i in range(2000)])
.. testcleanup:: *
import pymongo
pymongo.MongoClient().test_database.test_collection.delete_many({})
A guide to using MongoDB and Tornado with Motor.
@ -84,8 +93,9 @@ Motor, like PyMongo, represents data with a 4-level object hierarchy:
Creating a Client
-----------------
You typically create a single instance of :class:`MotorClient` at the time your
application starts up.
Creating a client is what establishes a connection to MongoDB and tells your
app what deployment (i.e. cluster) to connect to. You typically create a single
instance of :class:`MotorClient` at the time your application starts up.
.. doctest:: before-inserting-2000-docs
@ -96,13 +106,13 @@ specify the host and port like:
.. doctest:: before-inserting-2000-docs
>>> client = motor.motor_tornado.MotorClient('localhost', 27017)
>>> client = motor.motor_tornado.MotorClient("localhost", 27017)
Motor also supports `connection URIs`_:
.. doctest:: before-inserting-2000-docs
>>> client = motor.motor_tornado.MotorClient('mongodb://localhost:27017')
>>> client = motor.motor_tornado.MotorClient("mongodb://localhost:27017")
Connect to a replica set like:
@ -120,7 +130,7 @@ dot-notation or bracket-notation:
.. doctest:: before-inserting-2000-docs
>>> db = client.test_database
>>> db = client['test_database']
>>> db = client["test_database"]
Creating a reference to a database does no I/O and does not require an
``await`` expression.
@ -149,10 +159,10 @@ makes it available to request handlers::
def get(self):
db = self.settings['db']
It is a common mistake to create a new client object for every
request; **this comes at a dire performance cost**. Create the client
when your application starts and reuse that one client for the lifetime
of the process, as shown in these examples.
.. warning:: It is a common mistake to create a new client object for every
request; **this comes at a dire performance cost**. Create the client
when your application starts and reuse that one client for the lifetime
of the process, as shown in these examples.
The Tornado :class:`~tornado.httpserver.HTTPServer` class's :meth:`start`
method is a simple way to fork multiple web servers and use all of your
@ -187,7 +197,7 @@ collection in Motor works the same as getting a database:
.. doctest:: before-inserting-2000-docs
>>> collection = db.test_collection
>>> collection = db['test_collection']
>>> collection = db["test_collection"]
Just like getting a reference to a database, getting a reference to a
collection does no I/O and doesn't require an ``await`` expression.
@ -201,9 +211,9 @@ store a document in MongoDB, call :meth:`~MotorCollection.insert_one` in an
.. doctest:: before-inserting-2000-docs
>>> async def do_insert():
... document = {'key': 'value'}
... document = {"key": "value"}
... result = await db.test_collection.insert_one(document)
... print('result %s' % repr(result.inserted_id))
... print("result %s" % repr(result.inserted_id))
...
>>>
>>> IOLoop.current().run_sync(do_insert)
@ -216,7 +226,7 @@ store a document in MongoDB, call :meth:`~MotorCollection.insert_one` in an
>>> # Clean up from previous insert
>>> pymongo.MongoClient().test_database.test_collection.delete_many({})
<pymongo.results.DeleteResult ...>
DeleteResult({'n': 1, 'ok': 1.0}, acknowledged=True)
A typical beginner's mistake with Motor is to insert documents in a loop,
not waiting for each insert to complete before beginning the next::
@ -236,7 +246,7 @@ sequence, use ``await``:
>>> async def do_insert():
... for i in range(2000):
... await db.test_collection.insert_one({'i': i})
... await db.test_collection.insert_one({"i": i})
...
>>> IOLoop.current().run_sync(do_insert)
@ -249,7 +259,7 @@ sequence, use ``await``:
>>> # Clean up from previous insert
>>> pymongo.MongoClient().test_database.test_collection.delete_many({})
<pymongo.results.DeleteResult ...>
DeleteResult({'n': 2000, 'ok': 1.0}, acknowledged=True)
For better performance, insert documents in large batches with
:meth:`~MotorCollection.insert_many`:
@ -257,9 +267,8 @@ For better performance, insert documents in large batches with
.. doctest:: before-inserting-2000-docs
>>> async def do_insert():
... result = await db.test_collection.insert_many(
... [{'i': i} for i in range(2000)])
... print('inserted %d docs' % (len(result.inserted_ids),))
... result = await db.test_collection.insert_many([{"i": i} for i in range(2000)])
... print("inserted %d docs" % (len(result.inserted_ids),))
...
>>> IOLoop.current().run_sync(do_insert)
inserted 2000 docs
@ -273,7 +282,7 @@ less than 1:
.. doctest:: after-inserting-2000-docs
>>> async def do_find_one():
... document = await db.test_collection.find_one({'i': {'$lt': 1}})
... document = await db.test_collection.find_one({"i": {"$lt": 1}})
... pprint.pprint(document)
...
>>> IOLoop.current().run_sync(do_find_one)
@ -302,7 +311,7 @@ To find all documents with "i" less than 5:
.. doctest:: after-inserting-2000-docs
>>> async def do_find():
... cursor = db.test_collection.find({'i': {'$lt': 5}}).sort('i')
... cursor = db.test_collection.find({"i": {"$lt": 5}}).sort("i")
... for document in await cursor.to_list(length=100):
... pprint.pprint(document)
...
@ -325,7 +334,7 @@ You can handle one document at a time in an ``async for`` loop:
>>> async def do_find():
... c = db.test_collection
... async for document in c.find({'i': {'$lt': 2}}):
... async for document in c.find({"i": {"$lt": 2}}):
... pprint.pprint(document)
...
>>> IOLoop.current().run_sync(do_find)
@ -337,9 +346,9 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
.. doctest:: after-inserting-2000-docs
>>> async def do_find():
... cursor = db.test_collection.find({'i': {'$lt': 4}})
... cursor = db.test_collection.find({"i": {"$lt": 4}})
... # Modify the query before iterating
... cursor.sort('i', -1).skip(1).limit(2)
... cursor.sort("i", -1).skip(1).limit(2)
... async for document in cursor:
... pprint.pprint(document)
...
@ -350,7 +359,7 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
The cursor does not actually retrieve each document from the server
individually; it gets documents efficiently in `large batches`_.
.. _`large batches`: https://mongodb.com/docs/manual/tutorial/iterate-a-cursor/#cursor-batches
.. _`large batches`: https://www.mongodb.com/docs/manual/core/cursors/#cursor-batches
Counting Documents
------------------
@ -361,9 +370,9 @@ documents in a collection, or the number of documents that match a query:
>>> async def do_count():
... n = await db.test_collection.count_documents({})
... print('%s documents in collection' % n)
... n = await db.test_collection.count_documents({'i': {'$gt': 1000}})
... print('%s documents where i > 1000' % n)
... print("%s documents in collection" % n)
... n = await db.test_collection.count_documents({"i": {"$gt": 1000}})
... print("%s documents where i > 1000" % n)
...
>>> IOLoop.current().run_sync(do_count)
2000 documents in collection
@ -381,13 +390,13 @@ replacement document. The query follows the same syntax as for :meth:`find` or
>>> async def do_replace():
... coll = db.test_collection
... old_document = await coll.find_one({'i': 50})
... print('found document: %s' % pprint.pformat(old_document))
... _id = old_document['_id']
... result = await coll.replace_one({'_id': _id}, {'key': 'value'})
... print('replaced %s document' % result.modified_count)
... new_document = await coll.find_one({'_id': _id})
... print('document is now %s' % pprint.pformat(new_document))
... old_document = await coll.find_one({"i": 50})
... print("found document: %s" % pprint.pformat(old_document))
... _id = old_document["_id"]
... result = await coll.replace_one({"_id": _id}, {"key": "value"})
... print("replaced %s document" % result.modified_count)
... new_document = await coll.find_one({"_id": _id})
... print("document is now %s" % pprint.pformat(new_document))
...
>>> IOLoop.current().run_sync(do_replace)
found document: {'_id': ObjectId('...'), 'i': 50}
@ -406,10 +415,10 @@ operator to set "key" to "value":
>>> async def do_update():
... coll = db.test_collection
... result = await coll.update_one({'i': 51}, {'$set': {'key': 'value'}})
... print('updated %s document' % result.modified_count)
... new_document = await coll.find_one({'i': 51})
... print('document is now %s' % pprint.pformat(new_document))
... result = await coll.update_one({"i": 51}, {"$set": {"key": "value"}})
... print("updated %s document" % result.modified_count)
... new_document = await coll.find_one({"i": 51})
... print("document is now %s" % pprint.pformat(new_document))
...
>>> IOLoop.current().run_sync(do_update)
updated 1 document
@ -437,9 +446,9 @@ Removing Documents
>>> async def do_delete_one():
... coll = db.test_collection
... n = await coll.count_documents({})
... print('%s documents before calling delete_one()' % n)
... result = await db.test_collection.delete_one({'i': {'$gte': 1000}})
... print('%s documents after' % (await coll.count_documents({})))
... print("%s documents before calling delete_one()" % n)
... result = await db.test_collection.delete_one({"i": {"$gte": 1000}})
... print("%s documents after" % (await coll.count_documents({})))
...
>>> IOLoop.current().run_sync(do_delete_one)
2000 documents before calling delete_one()
@ -454,9 +463,9 @@ Removing Documents
>>> async def do_delete_many():
... coll = db.test_collection
... n = await coll.count_documents({})
... print('%s documents before calling delete_many()' % n)
... result = await db.test_collection.delete_many({'i': {'$gte': 1000}})
... print('%s documents after' % (await coll.count_documents({})))
... print("%s documents before calling delete_many()" % n)
... result = await db.test_collection.delete_many({"i": {"$gte": 1000}})
... print("%s documents after" % (await coll.count_documents({})))
...
>>> IOLoop.current().run_sync(do_delete_many)
1999 documents before calling delete_many()
@ -474,8 +483,7 @@ the :meth:`~motor.motor_tornado.MotorDatabase.command` method on
>>> from bson import SON
>>> async def use_distinct_command():
... response = await db.command(SON([("distinct", "test_collection"),
... ("key", "i")]))
... response = await db.command(SON([("distinct", "test_collection"), ("key", "i")]))
...
>>> IOLoop.current().run_sync(use_distinct_command)

View File

@ -13,18 +13,17 @@
# limitations under the License.
"""Motor, an asynchronous driver for MongoDB."""
from ._version import get_version_string, version, version_tuple # noqa
from ._version import get_version_string, version, version_tuple # noqa: F401
"""Current version of Motor."""
try:
import tornado # type: ignore
import tornado
except ImportError:
tornado = None
tornado = None # type:ignore[assignment]
else:
# For backwards compatibility with Motor 0.4, export Motor's Tornado classes
# at module root. This may change in the future.
from .motor_tornado import * # noqa: F403
from .motor_tornado import __all__
from .motor_tornado import __all__ # noqa: F401

View File

@ -13,14 +13,29 @@
# limitations under the License.
"""Version-related data for motor."""
import re
from typing import Union
version_tuple = (3, 3, 0)
__version__ = "3.7.2.dev0"
def get_version_tuple(version: str) -> tuple[Union[int, str], ...]:
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
match = re.match(pattern, version)
if match:
parts: list[Union[int, str]] = [int(match[part]) for part in ["major", "minor", "patch"]]
if match["rest"]:
parts.append(match["rest"])
elif re.match(r"\d+.\d+", version):
parts = [int(part) for part in version.split(".")]
else:
raise ValueError("Could not parse version")
return tuple(parts)
version_tuple = get_version_tuple(__version__)
version = __version__
def get_version_string() -> str:
if isinstance(version_tuple[-1], str):
return ".".join(map(str, version_tuple[:-1])) + version_tuple[-1]
return ".".join(map(str, version_tuple))
version = get_version_string()
return __version__

View File

@ -28,6 +28,8 @@ import gridfs
from motor.motor_asyncio import AsyncIOMotorDatabase, AsyncIOMotorGridFSBucket
from motor.motor_gridfs import _hash_gridout
# mypy: disable-error-code="no-untyped-def,no-untyped-call"
def get_gridfs_file(bucket, filename, request):
"""Override to choose a GridFS file to serve at a URL.
@ -106,7 +108,6 @@ def set_extra_headers(response, gridout):
- `gridout`: The :class:`~motor.motor_asyncio.AsyncIOMotorGridOut` we
will serve to the client
"""
pass
def _config_error(request):
@ -134,9 +135,9 @@ class AIOHTTPGridFS:
app = aiohttp.web.Application()
# The GridFS URL pattern must have a "{filename}" variable.
resource = app.router.add_resource('/fs/{filename}')
resource.add_route('GET', gridfs_handler)
resource.add_route('HEAD', gridfs_handler)
resource = app.router.add_resource("/fs/{filename}")
resource.add_route("GET", gridfs_handler)
resource.add_route("HEAD", gridfs_handler)
app_handler = app.make_handler()
server = loop.create_server(app_handler, port=80)
@ -188,8 +189,8 @@ class AIOHTTPGridFS:
try:
gridout = await self._get_gridfs_file(self._bucket, filename, request)
except gridfs.NoFile:
raise aiohttp.web.HTTPNotFound(text=request.path)
except gridfs.NoFile as e:
raise aiohttp.web.HTTPNotFound(text=request.path) from e
resp = aiohttp.web.StreamResponse()
@ -247,7 +248,8 @@ class AIOHTTPGridFS:
if cache_time > 0:
resp.headers["Expires"] = (
datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time)
datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
+ datetime.timedelta(seconds=cache_time)
).strftime("%a, %d %b %Y %H:%M:%S GMT")
resp.headers["Cache-Control"] = "max-age=" + str(cache_time)

View File

@ -13,7 +13,6 @@
# limitations under the License.
"""Framework-agnostic core of Motor, an asynchronous driver for MongoDB."""
import functools
import time
import warnings
@ -28,7 +27,8 @@ from pymongo.change_stream import ChangeStream
from pymongo.client_session import ClientSession
from pymongo.collection import Collection
from pymongo.command_cursor import CommandCursor, RawBatchCommandCursor
from pymongo.cursor import _QUERY_OPTIONS, Cursor, RawBatchCursor
from pymongo.cursor import Cursor, RawBatchCursor
from pymongo.cursor_shared import _QUERY_OPTIONS
from pymongo.database import Database
from pymongo.driver_info import DriverInfo
from pymongo.encryption import ClientEncryption
@ -73,7 +73,7 @@ def _max_time_expired_error(exc):
return isinstance(exc, pymongo.errors.OperationFailure) and exc.code == 50
class AgnosticBase(object):
class AgnosticBase:
def __eq__(self, other):
if (
isinstance(other, self.__class__)
@ -87,10 +87,15 @@ class AgnosticBase(object):
self.delegate = delegate
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.delegate)
return f"{self.__class__.__name__}({self.delegate!r})"
class AgnosticBaseProperties(AgnosticBase):
# Allow the use of generics at runtime.
@classmethod
def __class_getitem__(cls, key: str) -> object:
return cls
codec_options = ReadOnlyProperty()
read_preference = ReadOnlyProperty()
read_concern = ReadOnlyProperty()
@ -105,6 +110,7 @@ class AgnosticClient(AgnosticBaseProperties):
arbiters = ReadOnlyProperty()
close = DelegateMethod()
__hash__ = DelegateMethod()
bulk_write = AsyncRead()
drop_database = AsyncCommand().unwrap("MotorDatabase")
options = ReadOnlyProperty()
get_database = DelegateMethod(doc=docstrings.get_database_doc).wrap(Database)
@ -122,6 +128,7 @@ class AgnosticClient(AgnosticBaseProperties):
server_info = AsyncRead()
topology_description = ReadOnlyProperty()
start_session = AsyncCommand(doc=docstrings.start_session_doc).wrap(ClientSession)
_connect = AsyncRead()
def __init__(self, *args, **kwargs):
"""Create a new connection to a single MongoDB instance at *host:port*.
@ -141,13 +148,44 @@ class AgnosticClient(AgnosticBaseProperties):
self._io_loop = io_loop
kwargs.setdefault("connect", False)
kwargs.setdefault(
"driver", DriverInfo("Motor", motor_version, self._framework.platform_info())
)
driver_info = DriverInfo("Motor", motor_version, self._framework.platform_info())
if kwargs.get("driver"):
provided_info = kwargs.get("driver")
if not isinstance(provided_info, DriverInfo):
raise TypeError(
f"Incorrect type for `driver` {type(provided_info)};"
" expected value of type pymongo.driver_info.DriverInfo"
)
added_version = f"|{provided_info.version}" if provided_info.version else ""
added_platform = f"|{provided_info.platform}" if provided_info.platform else ""
driver_info = DriverInfo(
f"{driver_info.name}|{provided_info.name}",
f"{driver_info.version}{added_version}",
f"{driver_info.platform}{added_platform}",
)
kwargs["driver"] = driver_info
delegate = self.__delegate_class__(*args, **kwargs)
super().__init__(delegate)
warnings.warn(
DeprecationWarning(
"""Motor is deprecated as of May 14th, 2025,
in favor of the GA release of the PyMongo Async API.
No new features will be added to Motor,
and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
For help transitioning, see the Migrate to PyMongo Async guide here:
https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/
"""
),
stacklevel=2,
)
@property
def io_loop(self):
if self._io_loop is None:
@ -204,7 +242,7 @@ class AgnosticClient(AgnosticBaseProperties):
to use for the aggregation.
- `start_at_operation_time` (optional): If provided, the resulting
change stream will only return changes that occurred at or after
the specified :class:`~bson.timestamp.Timestamp`. Requires
the specified :class:`~pymongo.timestamp.Timestamp`. Requires
MongoDB >= 4.0.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
@ -213,9 +251,10 @@ class AgnosticClient(AgnosticBaseProperties):
This option and `resume_after` are mutually exclusive.
- `comment` (optional): A user-provided comment to attach to this
command.
- `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change events
may now result in a `fullDocumentBeforeChange` response field.
- `show_expanded_events` (optional): Include expanded events such as DDL events like `dropIndexes`.
- `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change
events may now result in a `fullDocumentBeforeChange` response field.
- `show_expanded_events` (optional): Include expanded events such as DDL events like
`dropIndexes`.
:Returns:
A :class:`~MotorChangeStream`.
@ -260,8 +299,8 @@ class AgnosticClient(AgnosticBaseProperties):
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(
"%s has no attribute %r. To access the %s"
" database, use client['%s']." % (self.__class__.__name__, name, name, name)
f"{self.__class__.__name__} has no attribute {name!r}. To access the {name}"
f" database, use client['{name}']."
)
return self[name]
@ -292,7 +331,7 @@ class AgnosticClient(AgnosticBaseProperties):
return session_class(obj, self)
class _MotorTransactionContext(object):
class _MotorTransactionContext:
"""Internal transaction context manager for start_transaction."""
def __init__(self, session):
@ -321,8 +360,8 @@ class AgnosticClientSession(AgnosticBase):
async with await client.start_session() as s:
async with s.start_transaction():
await collection.delete_one({'x': 1}, session=s)
await collection.insert_one({'x': 2}, session=s)
await collection.delete_one({"x": 1}, session=s)
await collection.insert_one({"x": 2}, session=s)
.. versionadded:: 2.0
"""
@ -388,7 +427,7 @@ class AgnosticClientSession(AgnosticBase):
In the event of an exception, ``with_transaction`` may retry the commit
or the entire transaction, therefore ``coro`` may be awaited
multiple times by a single call to ``with_transaction``. Developers
should be mindful of this possiblity when writing a ``coro`` that
should be mindful of this possibility when writing a ``coro`` that
modifies application state or has any other side-effects.
Note that even when the ``coro`` is invoked multiple times,
``with_transaction`` ensures that the transaction will be committed
@ -500,8 +539,8 @@ class AgnosticClientSession(AgnosticBase):
# Use "await" for start_session, but not for start_transaction.
async with await client.start_session() as s:
async with s.start_transaction():
await collection.delete_one({'x': 1}, session=s)
await collection.insert_one({'x': 2}, session=s)
await collection.delete_one({"x": 1}, session=s)
await collection.insert_one({"x": 2}, session=s)
"""
self.delegate.start_transaction(
@ -669,7 +708,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
to use for the aggregation.
- `start_at_operation_time` (optional): If provided, the resulting
change stream will only return changes that occurred at or after
the specified :class:`~bson.timestamp.Timestamp`. Requires
the specified :class:`~pymongo.timestamp.Timestamp`. Requires
MongoDB >= 4.0.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
@ -678,9 +717,10 @@ class AgnosticDatabase(AgnosticBaseProperties):
This option and `resume_after` are mutually exclusive.
- `comment` (optional): A user-provided comment to attach to this
command.
- `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change events
may now result in a `fullDocumentBeforeChange` response field.
- `show_expanded_events` (optional): Include expanded events such as DDL events like `dropIndexes`.
- `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change
events may now result in a `fullDocumentBeforeChange` response field.
- `show_expanded_events` (optional): Include expanded events such as DDL events like
`dropIndexes`.
:Returns:
A :class:`~MotorChangeStream`.
@ -746,7 +786,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
.. note:: the order of keys in the `command` document is
significant (the "verb" must come first), so commands
which require multiple keys (e.g. `findandmodify`)
should use an instance of :class:`~bson.son.SON` or
should use an instance of :class:`~pymongo.son.SON` or
a string and kwargs instead of a Python `dict`.
- `value` (optional): value to use for the command verb when
@ -757,13 +797,14 @@ class AgnosticDatabase(AgnosticBaseProperties):
read preference configured for the transaction.
Otherwise, defaults to
:attr:`~pymongo.read_preferences.ReadPreference.PRIMARY`.
- `codec_options`: A :class:`~bson.codec_options.CodecOptions`
- `codec_options`: A :class:`~pymongo.codec_options.CodecOptions`
instance.
- `session` (optional): A
:class:`MotorClientSession`.
- `comment` (optional): A user-provided comment to attach to future getMores for this
command.
- `max_await_time_ms` (optional): The number of ms to wait for more data on future getMores for this command.
- `max_await_time_ms` (optional): The number of ms to wait for more data on future
getMores for this command.
- `**kwargs` (optional): additional keyword arguments will
be added to the command document before it is sent
@ -812,8 +853,8 @@ class AgnosticDatabase(AgnosticBaseProperties):
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(
"%s has no attribute %r. To access the %s"
" collection, use database['%s']." % (self.__class__.__name__, name, name, name)
f"{self.__class__.__name__} has no attribute {name!r}. To access the {name}"
" collection, use database['{name}']."
)
return self[name]
@ -830,14 +871,14 @@ class AgnosticDatabase(AgnosticBaseProperties):
client_class_name = self._client.__class__.__name__
if database_name == "open_sync":
raise TypeError(
"%s.open_sync() is unnecessary Motor 0.2, "
"see changelog for details." % client_class_name
f"{client_class_name}.open_sync() is unnecessary Motor 0.2, "
"see changelog for details."
)
raise TypeError(
"MotorDatabase object is not callable. If you meant to "
"call the '%s' method on a %s object it is "
"failing because no such method exists." % (database_name, client_class_name)
f"call the '{database_name}' method on a {client_class_name} object it is "
"failing because no such method exists."
)
def wrap(self, obj):
@ -940,11 +981,10 @@ class AgnosticCollection(AgnosticBaseProperties):
def __getattr__(self, name):
# Dotted collection name, like "foo.bar".
if name.startswith("_"):
full_name = "%s.%s" % (self.name, name)
full_name = f"{self.name}.{name}"
raise AttributeError(
"%s has no attribute %r. To access the %s"
" collection, use database['%s']."
% (self.__class__.__name__, name, full_name, full_name)
f"{self.__class__.__name__} has no attribute {name!r}. To access the {full_name}"
f" collection, use database['{full_name}']."
)
return self[name]
@ -1170,6 +1210,7 @@ class AgnosticCollection(AgnosticBaseProperties):
change_stream = None
async def watch_collection():
global change_stream
@ -1180,17 +1221,20 @@ class AgnosticCollection(AgnosticBaseProperties):
async for change in change_stream:
print(change)
# Tornado
from tornado.ioloop import IOLoop
def main():
loop = IOLoop.current()
# Start watching collection for changes.
try:
loop.run_sync(watch_collection)
except KeyboardInterrupt:
if change_stream:
loop.run_sync(change_stream.close)
try:
loop.run_sync(watch_collection)
except KeyboardInterrupt:
if change_stream:
loop.run_sync(change_stream.close)
# asyncio
try:
@ -1209,14 +1253,14 @@ class AgnosticCollection(AgnosticBaseProperties):
.. code-block:: python3
try:
pipeline = [{'$match': {'operationType': 'insert'}}]
pipeline = [{"$match": {"operationType": "insert"}}]
async with db.collection.watch(pipeline) as stream:
async for change in stream:
print(change)
except pymongo.errors.PyMongoError:
# The ChangeStream encountered an unrecoverable error or the
# resume attempt failed to recreate the cursor.
logging.error('...')
logging.error("...")
For a precise description of the resume process see the
`change streams specification`_.
@ -1250,9 +1294,10 @@ class AgnosticCollection(AgnosticBaseProperties):
This option and `resume_after` are mutually exclusive.
- `comment` (optional): A user-provided comment to attach to this
command.
- `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change events
may now result in a `fullDocumentBeforeChange` response field.
- `show_expanded_events` (optional): Include expanded events such as DDL events like `dropIndexes`.
- `full_document_before_change`: Allowed values: `whenAvailable` and `required`.
Change events may now result in a `fullDocumentBeforeChange` response field.
- `show_expanded_events` (optional): Include expanded events such as DDL events
like `dropIndexes`.
:Returns:
A :class:`~MotorChangeStream`.
@ -1354,6 +1399,11 @@ class AgnosticCollection(AgnosticBaseProperties):
class AgnosticBaseCursor(AgnosticBase):
"""Base class for AgnosticCursor and AgnosticCommandCursor"""
# Allow the use of generics at runtime.
@classmethod
def __class_getitem__(cls, key: str) -> object:
return cls
_async_close = AsyncRead(attr_name="close")
_refresh = AsyncRead()
address = ReadOnlyProperty()
@ -1433,10 +1483,10 @@ class AgnosticBaseCursor(AgnosticBase):
>>> async def f():
... await collection.drop()
... await collection.insert_many([{'_id': i} for i in range(5)])
... await collection.insert_many([{"_id": i} for i in range(5)])
... async for doc in collection.find():
... sys.stdout.write(str(doc['_id']) + ', ')
... print('done')
... sys.stdout.write(str(doc["_id"]) + ", ")
... print("done")
...
>>> IOLoop.current().run_sync(f)
0, 1, 2, 3, 4, done
@ -1449,12 +1499,12 @@ class AgnosticBaseCursor(AgnosticBase):
>>> async def f():
... await collection.drop()
... await collection.insert_many([{'_id': i} for i in range(5)])
... cursor = collection.find().sort([('_id', 1)])
... while (await cursor.fetch_next):
... await collection.insert_many([{"_id": i} for i in range(5)])
... cursor = collection.find().sort([("_id", 1)])
... while await cursor.fetch_next:
... doc = cursor.next_object()
... sys.stdout.write(str(doc['_id']) + ', ')
... print('done')
... sys.stdout.write(str(doc["_id"]) + ", ")
... print("done")
...
>>> IOLoop.current().run_sync(f)
0, 1, 2, 3, 4, done
@ -1462,7 +1512,7 @@ class AgnosticBaseCursor(AgnosticBase):
.. versionchanged:: 2.2
Deprecated.
.. _`large batches`: https://www.mongodb.com/docs/manual/tutorial/iterate-a-cursor/#cursor-batches
.. _`large batches`: https://www.mongodb.com/docs/manual/core/cursors/#cursor-batches
.. _`gen.coroutine`: http://tornadoweb.org/en/stable/gen.html
"""
warnings.warn(
@ -1522,9 +1572,9 @@ class AgnosticBaseCursor(AgnosticBase):
.. testsetup:: each
from tornado.ioloop import IOLoop
MongoClient().test.test_collection.delete_many({})
MongoClient().test.test_collection.insert_many(
[{'_id': i} for i in range(5)])
MongoClient().test.test_collection.insert_many([{"_id": i} for i in range(5)])
collection = MotorClient().test.test_collection
@ -1534,13 +1584,13 @@ class AgnosticBaseCursor(AgnosticBase):
... if error:
... raise error
... elif result:
... sys.stdout.write(str(result['_id']) + ', ')
... sys.stdout.write(str(result["_id"]) + ", ")
... else:
... # Iteration complete
... IOLoop.current().stop()
... print('done')
... print("done")
...
>>> cursor = collection.find().sort([('_id', 1)])
>>> cursor = collection.find().sort([("_id", 1)])
>>> cursor.each(callback=each)
>>> IOLoop.current().start()
0, 1, 2, 3, 4, done
@ -1586,13 +1636,13 @@ class AgnosticBaseCursor(AgnosticBase):
self._framework.call_soon(self.get_io_loop(), functools.partial(callback, None, None))
@coroutine_annotation
def to_list(self, length):
def to_list(self, length=None):
"""Get a list of documents.
.. testsetup:: to_list
MongoClient().test.test_collection.delete_many({})
MongoClient().test.test_collection.insert_many([{'_id': i} for i in range(4)])
MongoClient().test.test_collection.insert_many([{"_id": i} for i in range(4)])
from tornado import ioloop
@ -1602,13 +1652,12 @@ class AgnosticBaseCursor(AgnosticBase):
>>> collection = MotorClient().test.test_collection
>>>
>>> async def f():
... cursor = collection.find().sort([('_id', 1)])
... cursor = collection.find().sort([("_id", 1)])
... docs = await cursor.to_list(length=2)
... while docs:
... print(docs)
... docs = await cursor.to_list(length=2)
...
... print('done')
... print("done")
...
>>> ioloop.IOLoop.current().run_sync(f)
[{'_id': 0}, {'_id': 1}]
@ -1645,7 +1694,12 @@ class AgnosticBaseCursor(AgnosticBase):
else:
the_list = []
self._framework.add_future(
self.get_io_loop(), self._get_more(), self._to_list, length, the_list, future
self.get_io_loop(),
self._get_more(),
self._to_list,
length,
the_list,
future,
)
return future
@ -1733,8 +1787,6 @@ class AgnosticCursor(AgnosticBaseCursor):
comment = MotorCursorChainingMethod()
allow_disk_use = MotorCursorChainingMethod()
_Cursor__die = AsyncRead()
def rewind(self):
"""Rewind this cursor to its unevaluated state."""
self.delegate.rewind()
@ -1752,13 +1804,13 @@ class AgnosticCursor(AgnosticBaseCursor):
return self.__class__(self.delegate.__deepcopy__(memo), self.collection)
def _query_flags(self):
return self.delegate._Cursor__query_flags
return self.delegate._query_flags
def _data(self):
return self.delegate._Cursor__data
return self.delegate._data
def _killed(self):
return self.delegate._Cursor__killed
return self.delegate._killed
class AgnosticRawBatchCursor(AgnosticCursor):
@ -1770,8 +1822,6 @@ class AgnosticCommandCursor(AgnosticBaseCursor):
__motor_class_name__ = "MotorCommandCursor"
__delegate_class__ = CommandCursor
_CommandCursor__die = AsyncRead()
async def try_next(self):
"""Advance the cursor without blocking indefinitely.
@ -1798,10 +1848,10 @@ class AgnosticCommandCursor(AgnosticBaseCursor):
return 0
def _data(self):
return self.delegate._CommandCursor__data
return self.delegate._data
def _killed(self):
return self.delegate._CommandCursor__killed
return self.delegate._killed
class AgnosticRawBatchCommandCursor(AgnosticCommandCursor):
@ -1809,29 +1859,29 @@ class AgnosticRawBatchCommandCursor(AgnosticCommandCursor):
__delegate_class__ = RawBatchCommandCursor
class _LatentCursor(object):
class _LatentCursor:
"""Take the place of a PyMongo CommandCursor until aggregate() begins."""
alive = True
_CommandCursor__data = []
_CommandCursor__id = None
_CommandCursor__killed = False
_CommandCursor__sock_mgr = None
_CommandCursor__session = None
_CommandCursor__explicit_session = None
_data = []
_id = None
_killed = False
_sock_mgr = None
_session = None
_explicit_session = None
cursor_id = None
def __init__(self, collection):
self._CommandCursor__collection = collection.delegate
self._collection = collection.delegate
def _CommandCursor__end_session(self, *args, **kwargs):
def _end_session(self, *args, **kwargs):
pass
def _CommandCursor__die(self, *args, **kwargs):
def _die_lock(self, *args, **kwargs):
pass
def clone(self):
return _LatentCursor(self._CommandCursor__collection)
return _LatentCursor(self._collection)
def rewind(self):
pass
@ -1888,9 +1938,9 @@ class AgnosticLatentCommandCursor(AgnosticCommandCursor):
# Return early if the task was cancelled.
if original_future.done():
return
if self.delegate._CommandCursor__data or not self.delegate.alive:
if self.delegate._data or not self.delegate.alive:
# _get_more is complete.
original_future.set_result(len(self.delegate._CommandCursor__data))
original_future.set_result(len(self.delegate._data))
else:
# Send a getMore.
future = super()._get_more()
@ -1914,6 +1964,11 @@ class AgnosticChangeStream(AgnosticBase):
resume_token = ReadOnlyProperty()
# Allow the use of generics at runtime.
@classmethod
def __class_getitem__(cls, key: str) -> object:
return cls
def __init__(
self,
target,
@ -2105,6 +2160,11 @@ class AgnosticClientEncryption(AgnosticBase):
get_key_by_alt_name = AsyncCommand()
remove_key_alt_name = AsyncCommand()
# Allow the use of generics at runtime.
@classmethod
def __class_getitem__(cls, key: str) -> object:
return cls
def __init__(
self,
kms_providers,
@ -2151,7 +2211,7 @@ class AgnosticClientEncryption(AgnosticBase):
await self.close()
def __enter__(self):
raise RuntimeError('Use {} in "async with", not "with"'.format(self.__class__.__name__))
raise RuntimeError(f'Use {self.__class__.__name__} in "async with", not "with"')
def __exit__(self, exc_type, exc_val, exc_tb):
pass
@ -2173,8 +2233,8 @@ class AgnosticClientEncryption(AgnosticBase):
.. warning::
This function does not update the encryptedFieldsMap in the client's
AutoEncryptionOpts, thus the user must create a new client after calling this function with
the encryptedFields returned.
AutoEncryptionOpts, thus the user must create a new client after calling
this function with the encryptedFields returned.
Normally collection creation is automatic. This method should
only be used to specify options on
@ -2214,10 +2274,12 @@ class AgnosticClientEncryption(AgnosticBase):
All optional `create collection command`_ parameters should be passed
as keyword arguments to this method.
See the documentation for :meth:`~pymongo.database.Database.create_collection` for all valid options.
See the documentation for :meth:`~pymongo.database.Database.create_collection`
for all valid options.
:Raises:
- :class:`~pymongo.errors.EncryptedCollectionError`: When either data-key creation or creating the collection fails.
- :class:`~pymongo.errors.EncryptedCollectionError`: When either data-key creation or
creating the collection fails.
.. versionadded:: 3.2

View File

@ -12,28 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Framework-agnostic type stubs for Motor, an asynchronous driver for MongoDB."""
from __future__ import annotations
from asyncio import Future
from collections.abc import Callable, Coroutine, Iterable, Mapping, MutableMapping, Sequence
from typing import (
Any,
Awaitable,
Callable,
Collection,
Coroutine,
Dict,
FrozenSet,
Iterable,
List,
Mapping,
MutableMapping,
Generic,
NoReturn,
Optional,
Sequence,
Set,
Tuple,
Type,
TypeVar,
Union,
overload,
@ -43,27 +28,34 @@ import pymongo.common
import pymongo.database
import pymongo.errors
import pymongo.mongo_client
import typing_extensions
from bson import Binary, Code, CodecOptions, DBRef, Timestamp
from bson.codec_options import TypeRegistry
from bson.raw_bson import RawBSONDocument
from pymongo import IndexModel, ReadPreference, WriteConcern
from pymongo.change_stream import ChangeStream
from pymongo.client_options import ClientOptions
from pymongo.client_session import _T, ClientSession, SessionOptions, TransactionOptions
from pymongo.collection import ReturnDocument, _IndexKeyHint, _IndexList, _WriteOp
from pymongo.client_session import ClientSession, SessionOptions, TransactionOptions
from pymongo.collection import Collection, ReturnDocument # noqa: F401
from pymongo.command_cursor import CommandCursor, RawBatchCommandCursor
from pymongo.cursor import Cursor, RawBatchCursor, _Hint, _Sort
from pymongo.cursor import Cursor, RawBatchCursor
from pymongo.cursor_shared import _Hint, _Sort
from pymongo.database import Database
from pymongo.encryption import ClientEncryption, RewrapManyDataKeyResult
from pymongo.encryption_options import RangeOpts
from pymongo.operations import _IndexKeyHint, _IndexList
from pymongo.read_concern import ReadConcern
from pymongo.read_preferences import _ServerMode
from pymongo.results import (
BulkWriteResult,
ClientBulkWriteResult,
DeleteResult,
InsertManyResult,
InsertOneResult,
UpdateResult,
)
from pymongo.synchronous.client_session import _T
from pymongo.synchronous.collection import _WriteOp
from pymongo.topology_description import TopologyDescription
from pymongo.typings import (
_Address,
@ -74,9 +66,9 @@ from pymongo.typings import (
)
try:
from pymongo import SearchIndexModel
from pymongo.operations import SearchIndexModel
except ImportError:
SearchIndexModel = Any
SearchIndexModel: typing_extensions.TypeAlias = Any # type:ignore[no-redef]
_WITH_TRANSACTION_RETRY_TIME_LIMIT: int
@ -85,30 +77,29 @@ _CodecDocumentType = TypeVar("_CodecDocumentType", bound=Mapping[str, Any])
def _within_time_limit(start_time: float) -> bool: ...
def _max_time_expired_error(exc: Exception) -> bool: ...
class AgnosticBase(object):
class AgnosticBase:
delegate: Any
def __eq__(self, other: Any) -> bool: ...
def __eq__(self, other: object) -> bool: ...
def __init__(self, delegate: Any) -> None: ...
def __repr__(self) -> str: ...
class AgnosticBaseProperties(AgnosticBase):
codec_options: CodecOptions
class AgnosticBaseProperties(AgnosticBase, Generic[_DocumentType]):
codec_options: CodecOptions[_DocumentType]
read_preference: _ServerMode
read_concern: ReadConcern
write_concern: WriteConcern
class AgnosticClient(AgnosticBaseProperties):
class AgnosticClient(AgnosticBaseProperties[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[pymongo.MongoClient]
__delegate_class__: type[pymongo.MongoClient[_DocumentType]]
def address(self) -> Optional[Tuple[str, int]]: ...
def arbiters(self) -> Set[Tuple[str, int]]: ...
def address(self) -> Optional[tuple[str, int]]: ...
def arbiters(self) -> set[tuple[str, int]]: ...
def close(self) -> None: ...
def __hash__(self) -> int: ...
async def drop_database(
self,
name_or_database: Union[str, AgnosticDatabase],
name_or_database: Union[str, AgnosticDatabase[_DocumentTypeArg]],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
) -> None: ...
@ -120,7 +111,7 @@ class AgnosticClient(AgnosticBaseProperties):
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> Database[_DocumentType]: ...
) -> AgnosticDatabase[_DocumentType]: ...
def get_default_database(
self,
default: Optional[str] = None,
@ -128,7 +119,18 @@ class AgnosticClient(AgnosticBaseProperties):
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> Database[_DocumentType]: ...
) -> AgnosticDatabase[_DocumentType]: ...
async def bulk_write(
self,
models: Sequence[_WriteOp[_DocumentType]],
session: Optional[ClientSession] = None,
ordered: bool = True,
verbose_results: bool = False,
bypass_document_validation: Optional[bool] = None,
comment: Optional[Any] = None,
let: Optional[Mapping] = None,
write_concern: Optional[WriteConcern] = None,
) -> ClientBulkWriteResult: ...
HOST: str
@ -139,20 +141,20 @@ class AgnosticClient(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> AgnosticCommandCursor: ...
) -> AgnosticCommandCursor[dict[str, Any]]: ...
async def list_database_names(
self,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
) -> List[str]: ...
def nodes(self) -> FrozenSet[_Address]: ...
) -> list[str]: ...
def nodes(self) -> frozenset[_Address]: ...
PORT: int
def primary(self) -> Optional[Tuple[str, int]]: ...
def primary(self) -> Optional[tuple[str, int]]: ...
read_concern: ReadConcern
def secondaries(self) -> Set[Tuple[str, int]]: ...
def secondaries(self) -> set[tuple[str, int]]: ...
async def server_info(
self, session: Optional[AgnosticClientSession] = None
) -> Dict[str, Any]: ...
) -> dict[str, Any]: ...
def topology_description(self) -> TopologyDescription: ...
async def start_session(
self,
@ -164,7 +166,16 @@ class AgnosticClient(AgnosticBaseProperties):
_io_loop: Optional[Any]
_framework: Any
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
def __init__(
self,
host: Optional[Union[str, Sequence[str]]] = None,
port: Optional[int] = None,
document_class: Optional[type[_DocumentType]] = None,
tz_aware: Optional[bool] = None,
connect: Optional[bool] = None,
type_registry: Optional[TypeRegistry] = None,
**kwargs: Any,
) -> None: ...
@property
def io_loop(self) -> Any: ...
def get_io_loop(self) -> Any: ...
@ -182,9 +193,9 @@ class AgnosticClient(AgnosticBaseProperties):
comment: Optional[str] = None,
full_document_before_change: Optional[str] = None,
show_expanded_events: Optional[bool] = None,
) -> AgnosticChangeStream: ...
def __getattr__(self, name: str) -> AgnosticDatabase: ...
def __getitem__(self, name: str) -> AgnosticDatabase: ...
) -> AgnosticChangeStream[_DocumentType]: ...
def __getattr__(self, name: str) -> AgnosticDatabase[_DocumentType]: ...
def __getitem__(self, name: str) -> AgnosticDatabase[_DocumentType]: ...
def wrap(self, obj: Any) -> Any: ...
class _MotorTransactionContext:
@ -192,11 +203,11 @@ class _MotorTransactionContext:
def __init__(self, session: AgnosticClientSession): ...
async def __aenter__(self) -> _MotorTransactionContext: ...
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
class AgnosticClientSession(AgnosticBase):
__motor_class_name__: str
__delegate_class__: Type[ClientSession]
__delegate_class__: type[ClientSession]
async def commit_transaction(self) -> None: ...
async def abort_transaction(self) -> None: ...
@ -229,13 +240,13 @@ class AgnosticClientSession(AgnosticBase):
@property
def client(self) -> AgnosticClient: ...
async def __aenter__(self) -> AgnosticClientSession: ...
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
def __enter__(self) -> None: ...
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
class AgnosticDatabase(AgnosticBaseProperties):
class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[Database]
__delegate_class__: type[Database[_DocumentType]]
def __hash__(self) -> int: ...
def __bool__(self) -> int: ...
@ -249,7 +260,34 @@ class AgnosticDatabase(AgnosticBaseProperties):
comment: Optional[Any] = None,
max_await_time_ms: Optional[int] = None,
**kwargs: Any,
) -> AgnosticCommandCursor: ...
) -> AgnosticCommandCursor[_DocumentType]: ...
@overload
async def command(
self,
command: Union[str, MutableMapping[str, Any]],
value: Any = ...,
check: bool = ...,
allowable_errors: Optional[Sequence[Union[str, int]]] = ...,
read_preference: Optional[_ServerMode] = ...,
codec_options: None = ...,
session: Optional[AgnosticClientSession] = ...,
comment: Optional[Any] = ...,
**kwargs: Any,
) -> dict[str, Any]: ...
@overload
async def command(
self,
command: Union[str, MutableMapping[str, Any]],
value: Any = 1,
check: bool = True,
allowable_errors: Optional[Sequence[Union[str, int]]] = None,
read_preference: Optional[_ServerMode] = None,
codec_options: CodecOptions[_CodecDocumentType] = ...,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _CodecDocumentType: ...
@overload
async def command(
self,
command: Union[str, MutableMapping[str, Any]],
@ -261,7 +299,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> Union[Dict[str, Any], _CodecDocumentType]: ...
) -> Union[dict[str, Any], _CodecDocumentType]: ...
async def create_collection(
self,
name: str,
@ -272,7 +310,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
check_exists: Optional[bool] = True,
**kwargs: Any,
) -> AgnosticCollection: ...
) -> AgnosticCollection[_DocumentType]: ...
async def dereference(
self,
dbref: DBRef,
@ -282,57 +320,58 @@ class AgnosticDatabase(AgnosticBaseProperties):
) -> Optional[_DocumentType]: ...
async def drop_collection(
self,
name_or_collection: Union[str, AgnosticCollection],
name_or_collection: Union[str, AgnosticCollection[_DocumentTypeArg]],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
encrypted_fields: Optional[Mapping[str, Any]] = None,
) -> Dict[str, Any]: ...
async def get_collection(
) -> dict[str, Any]: ...
def get_collection(
self,
name: str,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> AgnosticCollection: ...
) -> AgnosticCollection[_DocumentType]: ...
async def list_collection_names(
self,
session: Optional[AgnosticClientSession] = None,
filter: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> List[str]: ...
) -> list[str]: ...
async def list_collections(
self,
session: Optional[AgnosticClientSession] = None,
filter: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> AgnosticCommandCursor: ...
) -> AgnosticCommandCursor[MutableMapping[str, Any]]: ...
@property
def name(self) -> str: ...
async def validate_collection(
self,
name_or_collection: Union[str, AgnosticCollection],
name_or_collection: Union[str, AgnosticCollection[_DocumentTypeArg]],
scandata: bool = False,
full: bool = False,
session: Optional[AgnosticClientSession] = None,
background: Optional[bool] = None,
comment: Optional[Any] = None,
) -> Dict[str, Any]: ...
) -> dict[str, Any]: ...
def with_options(
self,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> AgnosticDatabase: ...
) -> AgnosticDatabase[_DocumentType]: ...
async def _async_aggregate(
self, pipeline: _Pipeline, session: Optional[AgnosticClientSession] = None, **kwargs: Any
) -> AgnosticCommandCursor: ...
def __init__(self, client: AgnosticClient, name: str, **kwargs: Any) -> None: ...
) -> AgnosticCommandCursor[_DocumentType]: ...
def __init__(self, client: AgnosticClient[_DocumentType], name: str, **kwargs: Any) -> None: ...
def aggregate(
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
) -> AgnosticLatentCommandCursor: ...
) -> AgnosticLatentCommandCursor[_DocumentType]: ...
def watch(
self,
pipeline: Optional[_Pipeline] = None,
@ -347,18 +386,18 @@ class AgnosticDatabase(AgnosticBaseProperties):
comment: Optional[Any] = None,
full_document_before_change: Optional[str] = None,
show_expanded_events: Optional[bool] = None,
) -> AgnosticChangeStream: ...
) -> AgnosticChangeStream[_DocumentType]: ...
@property
def client(self) -> AgnosticClient: ...
def __getattr__(self, name: str) -> AgnosticCollection: ...
def __getitem__(self, name: str) -> AgnosticCollection: ...
def client(self) -> AgnosticClient[_DocumentType]: ...
def __getattr__(self, name: str) -> AgnosticCollection[_DocumentType]: ...
def __getitem__(self, name: str) -> AgnosticCollection[_DocumentType]: ...
def __call__(self, *args: Any, **kwargs: Any) -> None: ...
def wrap(self, obj: Any) -> Any: ...
def get_io_loop(self) -> Any: ...
class AgnosticCollection(AgnosticBaseProperties):
class AgnosticCollection(AgnosticBaseProperties[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[Collection]
__delegate_class__: type[Collection[_DocumentType]]
def __hash__(self) -> int: ...
def __bool__(self) -> bool: ...
@ -391,7 +430,7 @@ class AgnosticCollection(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> List[str]: ...
) -> list[str]: ...
async def delete_many(
self,
filter: Mapping[str, Any],
@ -417,7 +456,7 @@ class AgnosticCollection(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> List[Any]: ...
) -> list[Any]: ...
async def drop(
self,
session: Optional[AgnosticClientSession] = None,
@ -461,7 +500,7 @@ class AgnosticCollection(AgnosticBaseProperties):
projection: Optional[Union[Mapping[str, Any], Iterable[str]]] = None,
sort: Optional[_IndexList] = None,
upsert: bool = False,
return_document: bool = ReturnDocument.BEFORE,
return_document: bool = ...,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
@ -475,7 +514,7 @@ class AgnosticCollection(AgnosticBaseProperties):
projection: Optional[Union[Mapping[str, Any], Iterable[str]]] = None,
sort: Optional[_IndexList] = None,
upsert: bool = False,
return_document: bool = ReturnDocument.BEFORE,
return_document: bool = ...,
array_filters: Optional[Sequence[Mapping[str, Any]]] = None,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
@ -502,6 +541,7 @@ class AgnosticCollection(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
) -> InsertOneResult: ...
@property
def name(self) -> str: ...
async def options(
self, session: Optional[AgnosticClientSession] = None, comment: Optional[Any] = None
@ -547,7 +587,7 @@ class AgnosticCollection(AgnosticBaseProperties):
collation: Optional[_CollationIn] = None,
array_filters: Optional[Sequence[Mapping[str, Any]]] = None,
hint: Optional[_IndexKeyHint] = None,
session: Union[Optional[AgnosticClientSession], Optional[AgnosticClientSession]] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
) -> UpdateResult: ...
@ -557,14 +597,14 @@ class AgnosticCollection(AgnosticBaseProperties):
read_preference: Optional[ReadPreference] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> Collection[Mapping[str, Any]]: ...
async def list_search_indexes(
) -> AgnosticCollection[_DocumentType]: ...
def list_search_indexes(
self,
name: Optional[str] = None,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> AgnosticCommandCursor: ...
) -> AgnosticLatentCommandCursor[Mapping[str, Any]]: ...
async def create_search_index(
self,
model: Union[Mapping[str, SearchIndexModel], Any],
@ -574,11 +614,11 @@ class AgnosticCollection(AgnosticBaseProperties):
) -> str: ...
async def create_search_indexes(
self,
models: List[SearchIndexModel],
models: list[SearchIndexModel],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> List[str]: ...
) -> list[str]: ...
async def drop_search_index(
self,
name: str,
@ -605,17 +645,19 @@ class AgnosticCollection(AgnosticBaseProperties):
_delegate: Any = None,
**kwargs: Any,
) -> None: ...
def __getattr__(self, name: str) -> AgnosticCollection: ...
def __getitem__(self, name: str) -> AgnosticCollection: ...
def __getattr__(self, name: str) -> AgnosticCollection[_DocumentType]: ...
def __getitem__(self, name: str) -> AgnosticCollection[_DocumentType]: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
def find(self, *args: Any, **kwargs: Any) -> AgnosticCursor: ...
def find_raw_batches(self, *args: Any, **kwargs: Any) -> AgnosticCursor: ...
def find(self, *args: Any, **kwargs: Any) -> AgnosticCursor[_DocumentType]: ...
def find_raw_batches(
self, *args: Any, **kwargs: Any
) -> AgnosticRawBatchCursor[_DocumentType]: ...
def aggregate(
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
) -> AgnosticCommandCursor: ...
) -> AgnosticCommandCursor[_DocumentType]: ...
def aggregate_raw_batches(
self, pipeline: _Pipeline, **kwargs: Any
) -> AgnosticCommandCursor: ...
) -> AgnosticRawBatchCursor[_DocumentType]: ...
def watch(
self,
pipeline: Optional[_Pipeline] = None,
@ -633,13 +675,17 @@ class AgnosticCollection(AgnosticBaseProperties):
) -> Any: ...
def list_indexes(
self, session: Optional[AgnosticClientSession] = None, **kwargs: Any
) -> AgnosticCommandCursor: ...
) -> AgnosticLatentCommandCursor[MutableMapping[str, Any]]: ...
def wrap(self, obj: Any) -> Any: ...
def get_io_loop(self) -> Any: ...
class AgnosticBaseCursor(AgnosticBase):
class AgnosticBaseCursor(AgnosticBase, Generic[_DocumentType]):
def __init__(
self, cursor: Union[Cursor, CommandCursor, _LatentCursor], collection: AgnosticCollection
self,
cursor: Union[
Cursor[_DocumentType], CommandCursor[_DocumentType], _LatentCursor[_DocumentType]
],
collection: AgnosticCollection[_DocumentType],
) -> None: ...
def address(self) -> Optional[_Address]: ...
def cursor_id(self) -> Optional[int]: ...
@ -651,86 +697,92 @@ class AgnosticBaseCursor(AgnosticBase):
async def next(self) -> _DocumentType: ...
__anext__ = next
async def __aenter__(self) -> Any: ...
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Any: ...
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> Any: ...
def _get_more(self) -> int: ...
@property
def fetch_next(self) -> Any: ...
def fetch_next(self) -> Future[Any]: ...
def next_object(self) -> Any: ...
def each(self, callback: Callable) -> None: ...
def _each_got_more(self, callback: Callable, future: Any) -> None: ...
def to_list(self, length: int) -> List: ...
def _to_list(self, length: int, the_list: List, future: Any, get_more_result: Any) -> None: ...
def to_list(self, length: Optional[int] = ...) -> Future[list[_DocumentType]]: ...
def _to_list(
self, length: Union[int, None], the_list: list, future: Any, get_more_result: Any
) -> None: ...
def get_io_loop(self) -> Any: ...
def batch_size(self, batch_size: int) -> AgnosticBaseCursor: ...
def batch_size(self, batch_size: int) -> AgnosticBaseCursor[_DocumentType]: ...
def _buffer_size(self) -> int: ...
def _query_flags(self) -> Optional[int]: ...
def _data(self) -> None: ...
def _killed(self) -> None: ...
async def close(self) -> None: ...
class AgnosticCursor(AgnosticBaseCursor):
class AgnosticCursor(AgnosticBaseCursor[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[Cursor]
def collation(self, collation: Optional[_CollationIn]) -> AgnosticCursor: ...
async def distinct(self, key: str) -> List: ...
__delegate_class__: type[Cursor]
def collation(self, collation: Optional[_CollationIn]) -> AgnosticCursor[_DocumentType]: ...
async def distinct(self, key: str) -> list: ...
async def explain(self) -> _DocumentType: ...
def add_option(self, mask: int) -> AgnosticCursor: ...
def remove_option(self, mask: int) -> AgnosticCursor: ...
def limit(self, limit: int) -> AgnosticCursor: ...
def skip(self, skip: int) -> AgnosticCursor: ...
def max_scan(self, max_scan: Optional[int]) -> AgnosticCursor: ...
def add_option(self, mask: int) -> AgnosticCursor[_DocumentType]: ...
def remove_option(self, mask: int) -> AgnosticCursor[_DocumentType]: ...
def limit(self, limit: int) -> AgnosticCursor[_DocumentType]: ...
def skip(self, skip: int) -> AgnosticCursor[_DocumentType]: ...
def max_scan(self, max_scan: Optional[int]) -> AgnosticCursor[_DocumentType]: ...
def sort(
self, key_or_list: _Hint, direction: Optional[Union[int, str]] = None
) -> AgnosticCursor: ...
def hint(self, index: Optional[_Hint]) -> AgnosticCursor: ...
def where(self, code: Union[str, Code]) -> AgnosticCursor: ...
def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> AgnosticCursor: ...
def max_time_ms(self, max_time_ms: Optional[int]) -> AgnosticCursor: ...
def min(self, spec: _Sort) -> AgnosticCursor: ...
def max(self, spec: _Sort) -> AgnosticCursor: ...
def comment(self, comment: Any) -> AgnosticCursor: ...
def allow_disk_use(self, allow_disk_use: bool) -> AgnosticCursor: ...
def rewind(self) -> AgnosticCursor: ...
def clone(self) -> AgnosticCursor: ...
def __copy__(self) -> AgnosticCursor: ...
def __deepcopy__(self, memo: Any) -> AgnosticCursor: ...
) -> AgnosticCursor[_DocumentType]: ...
def hint(self, index: Optional[_Hint]) -> AgnosticCursor[_DocumentType]: ...
def where(self, code: Union[str, Code]) -> AgnosticCursor[_DocumentType]: ...
def max_await_time_ms(
self, max_await_time_ms: Optional[int]
) -> AgnosticCursor[_DocumentType]: ...
def max_time_ms(self, max_time_ms: Optional[int]) -> AgnosticCursor[_DocumentType]: ...
def min(self, spec: _Sort) -> AgnosticCursor[_DocumentType]: ...
def max(self, spec: _Sort) -> AgnosticCursor[_DocumentType]: ...
def comment(self, comment: Any) -> AgnosticCursor[_DocumentType]: ...
def allow_disk_use(self, allow_disk_use: bool) -> AgnosticCursor[_DocumentType]: ...
def rewind(self) -> AgnosticCursor[_DocumentType]: ...
def clone(self) -> AgnosticCursor[_DocumentType]: ...
def __copy__(self) -> AgnosticCursor[_DocumentType]: ...
def __deepcopy__(self, memo: Any) -> AgnosticCursor[_DocumentType]: ...
def _query_flags(self) -> int: ...
def _data(self) -> Any: ...
def _killed(self) -> Any: ...
class AgnosticRawBatchCursor(AgnosticCursor):
class AgnosticRawBatchCursor(AgnosticCursor[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[RawBatchCursor]
__delegate_class__: type[RawBatchCursor]
class AgnosticCommandCursor(AgnosticBaseCursor):
class AgnosticCommandCursor(AgnosticBaseCursor[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[CommandCursor]
__delegate_class__: type[CommandCursor]
def _query_flags(self) -> int: ...
def _data(self) -> Any: ...
def _killed(self) -> Any: ...
class AgnosticRawBatchCommandCursor(AgnosticCommandCursor):
class AgnosticRawBatchCommandCursor(AgnosticCommandCursor[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[RawBatchCommandCursor]
__delegate_class__: type[RawBatchCommandCursor]
class _LatentCursor:
def __init__(self, collection: AgnosticCollection): ...
def _CommandCursor__end_session(self, *args: Any, **kwargs: Any) -> None: ...
def _CommandCursor__die(self, *args: Any, **kwargs: Any) -> None: ...
def clone(self) -> _LatentCursor: ...
def rewind(self) -> _LatentCursor: ...
class _LatentCursor(Generic[_DocumentType]):
def __init__(self, collection: AgnosticCollection[_DocumentType]): ...
def _end_session(self, *args: Any, **kwargs: Any) -> None: ...
def clone(self) -> _LatentCursor[_DocumentType]: ...
def rewind(self) -> _LatentCursor[_DocumentType]: ...
class AgnosticLatentCommandCursor(AgnosticCommandCursor):
class AgnosticLatentCommandCursor(AgnosticCommandCursor[_DocumentType]):
__motor_class_name__: str
def __init__(self, collection: AgnosticCollection, start: Any, *args: Any, **kwargs: Any): ...
def __init__(
self, collection: AgnosticCollection[_DocumentType], start: Any, *args: Any, **kwargs: Any
): ...
def _on_started(self, original_future: Any, future: Any) -> None: ...
class AgnosticChangeStream(AgnosticBase):
class AgnosticChangeStream(AgnosticBase, Generic[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[ChangeStream]
__delegate_class__: type[ChangeStream]
async def _close(self) -> None: ...
@property
def resume_token(self) -> Optional[Mapping[str, Any]]: ...
def __init__(
self,
@ -756,22 +808,22 @@ class AgnosticChangeStream(AgnosticBase):
async def next(self) -> _DocumentType: ...
async def try_next(self) -> Optional[_DocumentType]: ...
async def close(self) -> None: ...
def __aiter__(self) -> AgnosticChangeStream: ...
def __aiter__(self) -> AgnosticChangeStream[_DocumentType]: ...
__anext__ = next
async def __aenter__(self) -> AgnosticChangeStream: ...
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
async def __aenter__(self) -> AgnosticChangeStream[_DocumentType]: ...
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
def get_io_loop(self) -> Any: ...
def __enter__(self) -> None: ...
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
class AgnosticClientEncryption(AgnosticBase):
class AgnosticClientEncryption(AgnosticBase, Generic[_DocumentType]):
__motor_class_name__: str
__delegate_class__: Type[ClientEncryption]
__delegate_class__: type[ClientEncryption]
def __init__(
self,
kms_providers: Mapping[str, Any],
key_vault_namespace: str,
key_vault_client: AgnosticClient,
key_vault_client: AgnosticClient[_DocumentTypeArg],
codec_options: CodecOptions,
io_loop: Optional[Any] = None,
kms_tls_options: Optional[Mapping[str, Any]] = None,
@ -821,17 +873,17 @@ class AgnosticClientEncryption(AgnosticBase):
@property
def io_loop(self) -> Any: ...
def get_io_loop(self) -> Any: ...
async def __aenter__(self) -> AgnosticClientEncryption: ...
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
async def __aenter__(self) -> AgnosticClientEncryption[_DocumentType]: ...
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
def __enter__(self) -> NoReturn: ...
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
async def get_keys(self) -> AgnosticCursor: ...
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
async def get_keys(self) -> AgnosticCursor[RawBSONDocument]: ...
async def create_encrypted_collection(
self,
database: AgnosticDatabase,
database: AgnosticDatabase[_DocumentTypeArg],
name: str,
encrypted_fields: Mapping[str, Any],
kms_provider: Optional[str] = None,
master_key: Optional[Mapping[str, Any]] = None,
**kwargs: Any,
) -> Tuple[AgnosticCollection, Mapping[str, Any]]: ...
) -> tuple[AgnosticCollection[_DocumentTypeArg], Mapping[str, Any]]: ...

View File

@ -11,8 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
get_database_doc = """
Get a :class:`MotorDatabase` with the given name and options.
@ -33,7 +31,7 @@ read preference, and/or write concern from this :class:`MotorClient`.
:Parameters:
- `name`: The name of the database - a string.
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`. If ``None`` (the
:class:`~pymongo.codec_options.CodecOptions`. If ``None`` (the
default) the :attr:`codec_options` of this :class:`MotorClient` is
used.
- `read_preference` (optional): The read preference to use. If
@ -68,7 +66,7 @@ based only on the URI in a configuration file.
- `default` (optional): the database name to use if no database name
was provided in the URI.
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`. If ``None`` (the
:class:`~pymongo.codec_options.CodecOptions`. If ``None`` (the
default) the :attr:`codec_options` of this :class:`MotorClient` is
used.
- `read_preference` (optional): The read preference to use. If
@ -113,7 +111,7 @@ For example, to list all non-system collections::
- `comment` (optional): A user-provided comment to attach to this command.
- `**kwargs` (optional): Optional parameters of the
`listCollections
<https://www.mongodb.com/docs/manual/reference/command/listCollections/>`_ comand.
<https://www.mongodb.com/docs/manual/reference/command/listCollections/>`_ command.
can be passed as keyword arguments to this method. The supported
options differ by server version.
@ -189,7 +187,7 @@ This will print something like::
:Returns:
An instance of :class:`~pymongo.results.BulkWriteResult`.
.. seealso:: :ref:`writes-and-ids`
.. seealso:: `Writes and ids <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/crud/insert/#overview>`_
.. note:: `bypass_document_validation` requires server version
**>= 3.2**
@ -335,9 +333,9 @@ collection_name}`` we can do::
result = await db.command("count", collection_name)
For commands that take additional arguments we can use
kwargs. So ``{filemd5: object_id, root: file_root}`` becomes::
kwargs. So ``{count: collection_name, query: query}`` becomes::
result = await db.command("filemd5", object_id, root=file_root)
result = await db.command("count", collection_name, query=query)
:Parameters:
- `command`: document representing the command to be issued,
@ -346,7 +344,7 @@ kwargs. So ``{filemd5: object_id, root: file_root}`` becomes::
.. note:: the order of keys in the `command` document is
significant (the "verb" must come first), so commands
which require multiple keys (e.g. `findandmodify`)
should use an instance of :class:`~bson.son.SON` or
should use an instance of :class:`~pymongo.son.SON` or
a string and kwargs instead of a Python :class:`dict`.
- `value` (optional): value to use for the command verb when
@ -831,7 +829,7 @@ This prints something like::
:Returns:
An instance of :class:`~pymongo.results.InsertManyResult`.
.. seealso:: :ref:`writes-and-ids`
.. seealso:: `Writes and ids <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/crud/insert/#overview>`_
.. note:: `bypass_document_validation` requires server version
**>= 3.2**
@ -868,7 +866,7 @@ This code outputs the new document's ``_id``::
:Returns:
- An instance of :class:`~pymongo.results.InsertOneResult`.
.. seealso:: :ref:`writes-and-ids`
.. seealso:: `Writes and ids <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/crud/insert/#overview>`_
.. note:: `bypass_document_validation` requires server version
**>= 3.2**
@ -892,7 +890,7 @@ response from the server to the `map reduce command`_.
- `reduce`: reduce function (as a JavaScript string)
- `out`: output collection name or `out object` (dict). See
the `map reduce command`_ documentation for available options.
Note: `out` options are order sensitive. :class:`~bson.son.SON`
Note: `out` options are order sensitive. :class:`~pymongo.son.SON`
can be used to specify multiple options.
e.g. SON([('replace', <collection name>), ('db', <database name>)])
- `full_response` (optional): if ``True``, return full response to
@ -1142,17 +1140,17 @@ Pass a field name and a direction, either
.. testsetup:: sort
MongoClient().test.test_collection.drop()
MongoClient().test.test_collection.insert_many([
{'_id': i, 'field1': i % 2, 'field2': i}
for i in range(5)])
MongoClient().test.test_collection.insert_many(
[{"_id": i, "field1": i % 2, "field2": i} for i in range(5)]
)
collection = MotorClient().test.test_collection
.. doctest:: sort
>>> async def f():
... cursor = collection.find().sort('_id', pymongo.DESCENDING)
... docs = await cursor.to_list(None)
... print([d['_id'] for d in docs])
... cursor = collection.find().sort("_id", pymongo.DESCENDING)
... docs = await cursor.to_list()
... print([d["_id"] for d in docs])
...
>>> IOLoop.current().run_sync(f)
[4, 3, 2, 1, 0]
@ -1162,12 +1160,11 @@ To sort by multiple fields, pass a list of (key, direction) pairs:
.. doctest:: sort
>>> async def f():
... cursor = collection.find().sort([
... ('field1', pymongo.ASCENDING),
... ('field2', pymongo.DESCENDING)])
...
... docs = await cursor.to_list(None)
... print([(d['field1'], d['field2']) for d in docs])
... cursor = collection.find().sort(
... [("field1", pymongo.ASCENDING), ("field2", pymongo.DESCENDING)]
... )
... docs = await cursor.to_list()
... print([(d["field1"], d["field2"]) for d in docs])
...
>>> IOLoop.current().run_sync(f)
[(0, 4), (0, 2), (0, 0), (1, 3), (1, 1)]
@ -1177,24 +1174,23 @@ Text search results can be sorted by relevance:
.. testsetup:: sort_text
MongoClient().test.test_collection.drop()
MongoClient().test.test_collection.insert_many([
{'field': 'words'},
{'field': 'words about some words'}])
MongoClient().test.test_collection.insert_many(
[{"field": "words"}, {"field": "words about some words"}]
)
MongoClient().test.test_collection.create_index([('field', 'text')])
MongoClient().test.test_collection.create_index([("field", "text")])
collection = MotorClient().test.test_collection
.. doctest:: sort_text
>>> async def f():
... cursor = collection.find({
... '$text': {'$search': 'some words'}},
... {'score': {'$meta': 'textScore'}})
...
... cursor = collection.find(
... {"$text": {"$search": "some words"}}, {"score": {"$meta": "textScore"}}
... )
... # Sort by 'score' field.
... cursor.sort([('score', {'$meta': 'textScore'})])
... cursor.sort([("score", {"$meta": "textScore"})])
... async for doc in cursor:
... print('%.1f %s' % (doc['score'], doc['field']))
... print("%.1f %s" % (doc["score"], doc["field"]))
...
>>> IOLoop.current().run_sync(f)
1.5 words about some words
@ -1232,11 +1228,10 @@ to initialize it, or an ``async with`` statement.
# Or, use an "async with" statement to end the session
# automatically.
async with await client.start_session() as s:
doc = {'_id': ObjectId(), 'x': 1}
doc = {"_id": ObjectId(), "x": 1}
await collection.insert_one(doc, session=s)
secondary = collection.with_options(
read_preference=ReadPreference.SECONDARY)
secondary = collection.with_options(read_preference=ReadPreference.SECONDARY)
# Sessions are causally consistent by default, so we can read
# the doc we just inserted, even reading from a secondary.
@ -1247,8 +1242,8 @@ to initialize it, or an ``async with`` statement.
async with await client.start_session() as s:
# Note, start_transaction doesn't require "await".
async with s.start_transaction():
await collection.delete_one({'x': 1}, session=s)
await collection.insert_one({'x': 2}, session=s)
await collection.delete_one({"x": 1}, session=s)
await collection.insert_one({"x": 2}, session=s)
# Exiting the "with s.start_transaction()" block while throwing an
# exception automatically aborts the transaction, exiting the block
@ -1257,10 +1252,10 @@ to initialize it, or an ``async with`` statement.
# You can run additional transactions in the same session, so long as
# you run them one at a time.
async with s.start_transaction():
await collection.insert_one({'x': 3}, session=s)
await collection.insert_many({'x': {'$gte': 2}},
{'$inc': {'x': 1}},
session=s)
await collection.insert_one({"x": 3}, session=s)
await collection.insert_many(
{"x": {"$gte": 2}}, {"$inc": {"x": 1}}, session=s
)
Requires MongoDB 3.6.
@ -1282,7 +1277,7 @@ started it.
where_doc = """Adds a `$where`_ clause to this query.
The `code` argument must be an instance of :class:`str`
:class:`~bson.code.Code` containing a JavaScript expression.
:class:`~pymongo.code.Code` containing a JavaScript expression.
This expression will be evaluated for each document scanned.
Only those documents for which the expression evaluates to *true*
will be returned as results. The keyword *this* refers to the object
@ -1298,7 +1293,7 @@ if this :class:`~motor.motor_tornado.MotorCursor` has already been used.
Only the last call to :meth:`where` applied to a
:class:`~motor.motor_tornado.MotorCursor` has any effect.
.. note:: MongoDB 4.4 drops support for :class:`~bson.code.Code`
.. note:: MongoDB 4.4 drops support for :class:`~pymongo.code.Code`
with scope variables. Consider using `$expr`_ instead.
:Parameters:
@ -1342,7 +1337,7 @@ gridfs_delete_doc = """Delete a file's metadata and data chunks from a GridFS bu
b"data I want to store!")
await fs.delete(file_id)
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
Raises :exc:`~pymongo.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be deleted.
@ -1366,7 +1361,7 @@ gridfs_download_to_stream_doc = """Downloads the contents of the stored file spe
file.seek(0)
contents = file.read()
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
Raises :exc:`~pymongo.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be downloaded.
@ -1388,7 +1383,7 @@ gridfs_download_to_stream_by_name_doc = """ Write the contents of `filename
file = open('myfile','wb')
await fs.download_to_stream_by_name("test_file", file)
Raises :exc:`~gridfs.errors.NoFile` if no such version of
Raises :exc:`~pymongo.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
@ -1424,7 +1419,7 @@ gridfs_open_download_stream_doc = """Opens a stream to read the contents of the
grid_out = await fs.open_download_stream(file_id)
contents = await grid_out.read()
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
Raises :exc:`~pymongo.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be downloaded.
@ -1446,7 +1441,7 @@ gridfs_open_download_stream_by_name_doc = """Opens a stream to read the contents
grid_out = await fs.open_download_stream_by_name(file_id)
contents = await grid_out.read()
Raises :exc:`~gridfs.errors.NoFile` if no such version of
Raises :exc:`~pymongo.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` filename is not a string.
@ -1489,7 +1484,7 @@ gridfs_open_upload_stream_doc = """Opens a stream for writing.
Returns an instance of :class:`AsyncIOMotorGridIn`.
Raises :exc:`~gridfs.errors.NoFile` if no such version of
Raises :exc:`~pymongo.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
@ -1533,7 +1528,7 @@ gridfs_open_upload_stream_with_id_doc = """Opens a stream for writing.
Returns an instance of :class:`AsyncIOMotorGridIn`.
Raises :exc:`~gridfs.errors.NoFile` if no such version of
Raises :exc:`~pymongo.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
@ -1565,7 +1560,7 @@ gridfs_rename_doc = """Renames the stored file with the specified file_id.
await fs.rename(file_id, "new_test_name")
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
Raises :exc:`~pymongo.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be renamed.
@ -1586,7 +1581,7 @@ gridfs_upload_from_stream_doc = """Uploads a user file to a GridFS bucket.
b"data I want to store!",
metadata={"contentType": "text/plain"})
Raises :exc:`~gridfs.errors.NoFile` if no such version of
Raises :exc:`~pymongo.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
@ -1621,7 +1616,7 @@ gridfs_upload_from_stream_with_id_doc = """Uploads a user file to a GridFS bucke
b"data I want to store!",
metadata={"contentType": "text/plain"})
Raises :exc:`~gridfs.errors.NoFile` if no such version of
Raises :exc:`~pymongo.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.

View File

@ -16,8 +16,6 @@
See "Frameworks" in the Developer Guide.
"""
import asyncio
import asyncio.tasks
import functools
@ -27,6 +25,8 @@ import warnings
from asyncio import get_event_loop # noqa: F401 - For framework interface.
from concurrent.futures import ThreadPoolExecutor
# mypy: ignore-errors
try:
import contextvars
except ImportError:
@ -68,7 +68,7 @@ _EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
def _reset_global_executor():
"""Re-initialize the global ThreadPoolExecutor"""
global _EXECUTOR
global _EXECUTOR # noqa: PLW0603
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)

View File

@ -32,6 +32,7 @@ try:
except ImportError:
contextvars = None
# mypy: ignore-errors
CLASS_PREFIX = ""
@ -63,7 +64,7 @@ _EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
def _reset_global_executor():
"""Re-initialize the global ThreadPoolExecutor"""
global _EXECUTOR
global _EXECUTOR # noqa: PLW0603
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
@ -148,4 +149,4 @@ def yieldable(future):
def platform_info():
return "Tornado %s" % (tornado_version,)
return f"Tornado {tornado_version}"

View File

@ -13,12 +13,14 @@
# limitations under the License.
"""Dynamic class-creation for Motor."""
import functools
import inspect
from typing import Any, Callable, Dict
from collections.abc import Callable
from typing import Any, TypeVar
_class_cache: Dict[Any, Any] = {}
_class_cache: dict[Any, Any] = {}
# mypy: ignore-errors
def asynchronize(framework, sync_method: Callable, doc=None, wrap_class=None, unwrap_class=None):
@ -121,7 +123,7 @@ def coroutine_annotation(f):
return f
class MotorAttributeFactory(object):
class MotorAttributeFactory:
"""Used by Motor classes to mark attributes that delegate in some way to
PyMongo. At module import time, create_class_with_framework calls
create_attribute() for each attr to create the final class attribute.
@ -265,7 +267,10 @@ class MotorCursorChainingMethod(MotorAttributeFactory):
return return_clone
def create_class_with_framework(cls, framework, module_name):
T = TypeVar("T")
def create_class_with_framework(cls: T, framework: Any, module_name: str) -> T:
motor_class_name = framework.CLASS_PREFIX + cls.__motor_class_name__
cache_key = (cls, motor_class_name, framework)
cached_class = _class_cache.get(cache_key)

View File

@ -13,15 +13,13 @@
# limitations under the License.
"""Asyncio support for Motor, an asynchronous driver for MongoDB."""
from typing import TypeVar
from . import core, motor_gridfs
from .frameworks import asyncio as asyncio_framework
from .metaprogramming import create_class_with_framework
from .metaprogramming import T, create_class_with_framework
__all__ = [
"AsyncIOMotorClient",
"AsyncIOMotorClientEncryption",
"AsyncIOMotorClientSession",
"AsyncIOMotorDatabase",
"AsyncIOMotorCollection",
"AsyncIOMotorCursor",
@ -34,8 +32,6 @@ __all__ = [
"AsyncIOMotorClientEncryption",
]
T = TypeVar("T")
def create_asyncio_class(cls: T) -> T:
return create_class_with_framework(cls, asyncio_framework, "motor.motor_asyncio")

264
motor/motor_asyncio.pyi Normal file
View File

@ -0,0 +1,264 @@
from collections.abc import Mapping, MutableMapping
from typing import Any, Optional, Union
from bson import Code, CodecOptions, Timestamp
from bson.raw_bson import RawBSONDocument
from pymongo.client_session import TransactionOptions
from pymongo.cursor_shared import _Hint, _Sort
from pymongo.read_concern import ReadConcern
from pymongo.read_preferences import ReadPreference, _ServerMode
from pymongo.typings import _CollationIn, _DocumentType, _DocumentTypeArg, _Pipeline
from pymongo.write_concern import WriteConcern
from motor import core, motor_gridfs
__all__: list[str] = [
"AsyncIOMotorClient",
"AsyncIOMotorClientSession",
"AsyncIOMotorDatabase",
"AsyncIOMotorCollection",
"AsyncIOMotorCursor",
"AsyncIOMotorCommandCursor",
"AsyncIOMotorChangeStream",
"AsyncIOMotorGridFSBucket",
"AsyncIOMotorGridIn",
"AsyncIOMotorGridOut",
"AsyncIOMotorGridOutCursor",
"AsyncIOMotorClientEncryption",
"AsyncIOMotorLatentCommandCursor",
]
class AsyncIOMotorClient(core.AgnosticClient[_DocumentType]):
def get_database(
self,
name: Optional[str] = None,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> AsyncIOMotorDatabase[_DocumentType]: ...
def get_default_database(
self,
default: Optional[str] = None,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> AsyncIOMotorDatabase[_DocumentType]: ...
async def list_databases(
self,
session: Optional[core.AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> AsyncIOMotorCommandCursor[dict[str, Any]]: ...
async def start_session(
self,
causal_consistency: Optional[bool] = None,
default_transaction_options: Optional[TransactionOptions] = None,
snapshot: Optional[bool] = False,
) -> AsyncIOMotorClientSession: ...
def watch(
self,
pipeline: Optional[_Pipeline] = None,
full_document: Optional[str] = None,
resume_after: Optional[Mapping[str, Any]] = None,
max_await_time_ms: Optional[int] = None,
batch_size: Optional[int] = None,
collation: Optional[_CollationIn] = None,
start_at_operation_time: Optional[Timestamp] = None,
session: Optional[core.AgnosticClientSession] = None,
start_after: Optional[Mapping[str, Any]] = None,
comment: Optional[str] = None,
full_document_before_change: Optional[str] = None,
show_expanded_events: Optional[bool] = None,
) -> AsyncIOMotorChangeStream[_DocumentType]: ...
def __getattr__(self, name: str) -> AsyncIOMotorDatabase[_DocumentType]: ...
def __getitem__(self, name: str) -> AsyncIOMotorDatabase[_DocumentType]: ...
class AsyncIOMotorClientSession(core.AgnosticClientSession):
@property
def client(self) -> AsyncIOMotorClient: ...
async def __aenter__(self) -> AsyncIOMotorClientSession: ...
class AsyncIOMotorDatabase(core.AgnosticDatabase[_DocumentType]):
async def cursor_command(
self,
command: Union[str, MutableMapping[str, Any]],
value: Any = 1,
read_preference: Optional[_ServerMode] = None,
codec_options: Optional[CodecOptions[core._CodecDocumentType]] = None,
session: Optional[core.AgnosticClientSession] = None,
comment: Optional[Any] = None,
max_await_time_ms: Optional[int] = None,
**kwargs: Any,
) -> AsyncIOMotorCommandCursor[_DocumentType]: ...
async def create_collection(
self,
name: str,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
session: Optional[core.AgnosticClientSession] = None,
check_exists: Optional[bool] = True,
**kwargs: Any,
) -> AsyncIOMotorCollection[_DocumentType]: ...
def get_collection(
self,
name: str,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> AsyncIOMotorCollection[_DocumentType]: ...
async def list_collections(
self,
session: Optional[core.AgnosticClientSession] = None,
filter: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> AsyncIOMotorCommandCursor[MutableMapping[str, Any]]: ...
def with_options(
self,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> AsyncIOMotorDatabase[_DocumentType]: ...
def aggregate(
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
) -> AsyncIOMotorLatentCommandCursor[_DocumentType]: ...
def watch(
self,
pipeline: Optional[_Pipeline] = None,
full_document: Optional[str] = None,
resume_after: Optional[Mapping[str, Any]] = None,
max_await_time_ms: Optional[int] = None,
batch_size: Optional[int] = None,
collation: Optional[_CollationIn] = None,
start_at_operation_time: Optional[Timestamp] = None,
session: Optional[core.AgnosticClientSession] = None,
start_after: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
full_document_before_change: Optional[str] = None,
show_expanded_events: Optional[bool] = None,
) -> AsyncIOMotorChangeStream[_DocumentType]: ...
@property
def client(self) -> AsyncIOMotorClient[_DocumentType]: ...
def __getattr__(self, name: str) -> AsyncIOMotorCollection[_DocumentType]: ...
def __getitem__(self, name: str) -> AsyncIOMotorCollection[_DocumentType]: ...
class AsyncIOMotorCollection(core.AgnosticCollection[_DocumentType]):
def with_options(
self,
codec_options: Optional[CodecOptions] = None,
read_preference: Optional[ReadPreference] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> AsyncIOMotorCollection[_DocumentType]: ...
def list_search_indexes(
self,
name: Optional[str] = None,
session: Optional[core.AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> AsyncIOMotorLatentCommandCursor[Mapping[str, Any]]: ...
def __getattr__(self, name: str) -> AsyncIOMotorCollection[_DocumentType]: ...
def __getitem__(self, name: str) -> AsyncIOMotorCollection[_DocumentType]: ...
def find(self, *args: Any, **kwargs: Any) -> AsyncIOMotorCursor[_DocumentType]: ...
def find_raw_batches(
self, *args: Any, **kwargs: Any
) -> AsyncIOMotorRawBatchCursor[_DocumentType]: ...
def aggregate(
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
) -> AsyncIOMotorCommandCursor[_DocumentType]: ...
def aggregate_raw_batches(
self, pipeline: _Pipeline, **kwargs: Any
) -> AsyncIOMotorRawBatchCursor[_DocumentType]: ...
def list_indexes(
self, session: Optional[core.AgnosticClientSession] = None, **kwargs: Any
) -> AsyncIOMotorLatentCommandCursor[MutableMapping[str, Any]]: ...
class AsyncIOMotorLatentCommandCursor(core.AgnosticLatentCommandCursor[_DocumentType]): ...
class AsyncIOMotorCursor(core.AgnosticCursor[_DocumentType]):
def collation(self, collation: Optional[_CollationIn]) -> AsyncIOMotorCursor[_DocumentType]: ...
def add_option(self, mask: int) -> AsyncIOMotorCursor[_DocumentType]: ...
def remove_option(self, mask: int) -> AsyncIOMotorCursor[_DocumentType]: ...
def limit(self, limit: int) -> AsyncIOMotorCursor[_DocumentType]: ...
def skip(self, skip: int) -> AsyncIOMotorCursor[_DocumentType]: ...
def max_scan(self, max_scan: Optional[int]) -> AsyncIOMotorCursor[_DocumentType]: ...
def sort(
self, key_or_list: _Hint, direction: Optional[Union[int, str]] = None
) -> AsyncIOMotorCursor[_DocumentType]: ...
def hint(self, index: Optional[_Hint]) -> AsyncIOMotorCursor[_DocumentType]: ...
def where(self, code: Union[str, Code]) -> AsyncIOMotorCursor[_DocumentType]: ...
def max_await_time_ms(
self, max_await_time_ms: Optional[int]
) -> AsyncIOMotorCursor[_DocumentType]: ...
def max_time_ms(self, max_time_ms: Optional[int]) -> AsyncIOMotorCursor[_DocumentType]: ...
def min(self, spec: _Sort) -> AsyncIOMotorCursor[_DocumentType]: ...
def max(self, spec: _Sort) -> AsyncIOMotorCursor[_DocumentType]: ...
def comment(self, comment: Any) -> AsyncIOMotorCursor[_DocumentType]: ...
def allow_disk_use(self, allow_disk_use: bool) -> AsyncIOMotorCursor[_DocumentType]: ...
def rewind(self) -> AsyncIOMotorCursor[_DocumentType]: ...
def clone(self) -> AsyncIOMotorCursor[_DocumentType]: ...
def __copy__(self) -> AsyncIOMotorCursor[_DocumentType]: ...
def __deepcopy__(self, memo: Any) -> AsyncIOMotorCursor[_DocumentType]: ...
class AsyncIOMotorRawBatchCursor(core.AgnosticRawBatchCursor[_DocumentType]): ...
class AsyncIOMotorCommandCursor(core.AgnosticCommandCursor[_DocumentType]): ...
class AsyncIOMotorRawBatchCommandCursor(core.AgnosticRawBatchCommandCursor[_DocumentType]): ...
class AsyncIOMotorChangeStream(core.AgnosticChangeStream[_DocumentType]):
def __aiter__(self) -> AsyncIOMotorChangeStream[_DocumentType]: ...
async def __aenter__(self) -> AsyncIOMotorChangeStream[_DocumentType]: ...
class AsyncIOMotorClientEncryption(core.AgnosticClientEncryption[_DocumentType]):
async def __aenter__(self) -> AsyncIOMotorClientEncryption[_DocumentType]: ...
async def get_keys(self) -> AsyncIOMotorCursor[RawBSONDocument]: ...
async def create_encrypted_collection(
self,
database: core.AgnosticDatabase[_DocumentTypeArg],
name: str,
encrypted_fields: Mapping[str, Any],
kms_provider: Optional[str] = None,
master_key: Optional[Mapping[str, Any]] = None,
**kwargs: Any,
) -> tuple[AsyncIOMotorCollection[_DocumentTypeArg], Mapping[str, Any]]: ...
class AsyncIOMotorGridOutCursor(motor_gridfs.AgnosticGridOutCursor):
def next_object(self) -> AsyncIOMotorGridOutCursor: ...
class AsyncIOMotorGridOut(motor_gridfs.AgnosticGridOut):
def __aiter__(self) -> AsyncIOMotorGridOut: ...
class AsyncIOMotorGridIn(motor_gridfs.AgnosticGridIn):
async def __aenter__(self) -> AsyncIOMotorGridIn: ...
class AsyncIOMotorGridFSBucket(motor_gridfs.AgnosticGridFSBucket):
async def open_download_stream_by_name(
self,
filename: str,
revision: int = -1,
session: Optional[core.AgnosticClientSession] = None,
) -> AsyncIOMotorGridOut: ...
async def open_download_stream(
self, file_id: Any, session: Optional[core.AgnosticClientSession] = None
) -> AsyncIOMotorGridOut: ...
def open_upload_stream(
self,
filename: str,
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[core.AgnosticClientSession] = None,
) -> AsyncIOMotorGridIn: ...
def open_upload_stream_with_id(
self,
file_id: Any,
filename: str,
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[core.AgnosticClientSession] = None,
) -> AsyncIOMotorGridIn: ...
def find(self, *args: Any, **kwargs: Any) -> AsyncIOMotorGridOutCursor: ...

View File

@ -13,5 +13,4 @@
# limitations under the License.
"""Common code to support all async frameworks."""
callback_type_error = TypeError("callback must be a callable")

View File

@ -13,7 +13,6 @@
# limitations under the License.
"""GridFS implementation for Motor, an asynchronous driver for MongoDB."""
import hashlib
import warnings
@ -38,9 +37,6 @@ class AgnosticGridOutCursor(AgnosticCursor):
__motor_class_name__ = "MotorGridOutCursor"
__delegate_class__ = gridfs.GridOutCursor
# PyMongo's GridOutCursor inherits __die from Cursor.
_Cursor__die = AsyncCommand()
def next_object(self):
"""**DEPRECATED** - Get next GridOut object from cursor."""
# Note: the super() call will raise a warning for the deprecation.
@ -73,11 +69,11 @@ class MotorGridOutProperty(ReadOnlyProperty):
return property(fget=fget, doc=doc)
class AgnosticGridOut(object):
class AgnosticGridOut:
"""Class to read data out of GridFS.
MotorGridOut supports the same attributes as PyMongo's
:class:`~gridfs.grid_file.GridOut`, such as ``_id``, ``content_type``,
:class:`~pymongo.grid_file.GridOut`, such as ``_id``, ``content_type``,
etc.
You don't need to instantiate this class directly - use the
@ -89,7 +85,6 @@ class AgnosticGridOut(object):
__motor_class_name__ = "MotorGridOut"
__delegate_class__ = gridfs.GridOut
_ensure_file = AsyncCommand()
_id = MotorGridOutProperty()
aliases = MotorGridOutProperty()
chunk_size = MotorGridOutProperty()
@ -99,6 +94,7 @@ class AgnosticGridOut(object):
length = MotorGridOutProperty()
metadata = MotorGridOutProperty()
name = MotorGridOutProperty()
_open = AsyncCommand(attr_name="open")
read = AsyncRead()
readable = DelegateMethod()
readchunk = AsyncRead()
@ -161,7 +157,7 @@ class AgnosticGridOut(object):
:class:`~motor.MotorGridOut` now opens itself on demand, calling
``open`` explicitly is rarely needed.
"""
return self._framework.chain_return_value(self._ensure_file(), self.get_io_loop(), self)
return self._framework.chain_return_value(self._open(), self.get_io_loop(), self)
def get_io_loop(self):
return self.io_loop
@ -180,7 +176,7 @@ class AgnosticGridOut(object):
@tornado.web.asynchronous
@gen.coroutine
def get(self, filename):
db = self.settings['db']
db = self.settings["db"]
fs = await motor.MotorGridFSBucket(db())
try:
gridout = await fs.open_download_stream_by_name(filename)
@ -206,7 +202,7 @@ class AgnosticGridOut(object):
written += len(chunk)
class AgnosticGridIn(object):
class AgnosticGridIn:
__motor_class_name__ = "MotorGridIn"
__delegate_class__ = gridfs.GridIn
@ -259,7 +255,7 @@ Metadata set on the file appears as attributes on a
arguments include:
- ``"_id"``: unique ID for this file (default:
:class:`~bson.objectid.ObjectId`) - this ``"_id"`` must
:class:`~pymongo.objectid.ObjectId`) - this ``"_id"`` must
not have already been used for another file
- ``"filename"``: human name for the file
@ -315,7 +311,7 @@ Metadata set on the file appears as attributes on a
return self.io_loop
class AgnosticGridFSBucket(object):
class AgnosticGridFSBucket:
__motor_class_name__ = "MotorGridFSBucket"
__delegate_class__ = gridfs.GridFSBucket
@ -394,7 +390,7 @@ class AgnosticGridFSBucket(object):
if not isinstance(database, db_class):
raise TypeError(
"First argument to %s must be MotorDatabase, not %r" % (self.__class__, database)
f"First argument to {self.__class__} must be MotorDatabase, not {database!r}"
)
self.io_loop = database.get_io_loop()

View File

@ -12,14 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""GridFS type stubs for Motor, an asynchronous driver for MongoDB."""
import datetime
import os
from typing import Any, Iterable, List, Mapping, NoReturn, Optional, Type
from collections.abc import Iterable, Mapping
from typing import Any, NoReturn, Optional
from bson import ObjectId
from gridfs import DEFAULT_CHUNK_SIZE, GridFSBucket, GridIn, GridOut, GridOutCursor
from gridfs import DEFAULT_CHUNK_SIZE, GridFSBucket, GridIn, GridOut, GridOutCursor # noqa: F401
from pymongo import WriteConcern
from pymongo.read_preferences import _ServerMode
@ -36,15 +35,14 @@ _SEEK_END = os.SEEK_END
class AgnosticGridOutCursor(AgnosticCursor):
__motor_class_name__: str
__delegate_class__ = type[GridOutCursor]
async def _Cursor__die(self, synchronous: bool = False) -> None: ...
__delegate_class__: type[GridOutCursor]
def next_object(self) -> AgnosticGridOutCursor: ...
class AgnosticGridOut(object):
class AgnosticGridOut:
__motor_class_name__: str
__delegate_class__: Type[GridOut]
__delegate_class__: type[GridOut]
_id: Any
aliases: Optional[List[str]]
aliases: Optional[list[str]]
chunk_size: int
filename: Optional[str]
name: Optional[str]
@ -52,13 +50,13 @@ class AgnosticGridOut(object):
length: int
upload_date: datetime.datetime
metadata: Optional[Mapping[str, Any]]
async def _ensure_file(self) -> None: ...
async def _open(self) -> None: ...
def close(self) -> None: ...
async def read(self, size: int = -1) -> NoReturn: ...
def readable(self) -> bool: ...
async def readchunk(self) -> bytes: ...
async def readline(self, size: int = -1) -> bytes: ...
def seek(self, pos: int, whence: int = _SEEK_SET) -> int: ...
def seek(self, pos: int, whence: int = ...) -> int: ...
def seekable(self) -> bool: ...
def tell(self) -> int: ...
def write(self, data: Any) -> None: ...
@ -77,9 +75,9 @@ class AgnosticGridOut(object):
def get_io_loop(self) -> Any: ...
async def stream_to_handler(self, request_handler: Any) -> None: ...
class AgnosticGridIn(object):
class AgnosticGridIn:
__motor_class_name__: str
__delegate_class__: Type[GridIn]
__delegate_class__: type[GridIn]
__getattr__: Any
_id: Any
filename: str
@ -98,22 +96,22 @@ class AgnosticGridIn(object):
async def write(self, data: Any) -> None: ...
def writeable(self) -> bool: ...
async def writelines(self, sequence: Iterable[Any]) -> None: ...
async def _exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Any: ...
async def _exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> Any: ...
async def set(self, name: str, value: Any) -> None: ...
def __init__(
self,
root_collection: AgnosticCollection,
delegate: Any = None,
session: Optional[AgnosticClientSession] = None,
**kwargs: Any
**kwargs: Any,
) -> None: ...
async def __aenter__(self) -> AgnosticGridIn: ...
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
def get_io_loop(self) -> Any: ...
class AgnosticGridFSBucket(object):
class AgnosticGridFSBucket:
__motor_class_name__: str
__delegate_class__: Type[GridFSBucket]
__delegate_class__: type[GridFSBucket]
async def delete(
self, file_id: Any, session: Optional[AgnosticClientSession] = None
) -> None: ...
@ -129,17 +127,17 @@ class AgnosticGridFSBucket(object):
) -> None: ...
async def open_download_stream_by_name(
self, filename: str, revision: int = -1, session: Optional[AgnosticClientSession] = None
) -> GridOut: ...
) -> AgnosticGridOut: ...
async def open_download_stream(
self, file_id: Any, session: Optional[AgnosticClientSession] = None
) -> GridOut: ...
) -> AgnosticGridOut: ...
def open_upload_stream(
self,
filename: str,
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[AgnosticClientSession] = None,
) -> GridIn: ...
) -> AgnosticGridIn: ...
def open_upload_stream_with_id(
self,
file_id: Any,
@ -147,7 +145,7 @@ class AgnosticGridFSBucket(object):
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[AgnosticClientSession] = None,
) -> GridIn: ...
) -> AgnosticGridIn: ...
async def rename(
self, file_id: Any, new_filename: str, session: Optional[AgnosticClientSession] = None
) -> None: ...
@ -172,10 +170,10 @@ class AgnosticGridFSBucket(object):
self,
database: AgnosticDatabase,
bucket_name: str = "fs",
chunk_size_bytes: int = DEFAULT_CHUNK_SIZE,
chunk_size_bytes: int = ...,
write_concern: Optional[WriteConcern] = None,
read_preference: Optional[_ServerMode] = None,
collection: Optional[AgnosticCollection] = None,
collection: Optional[str] = None,
) -> None: ...
def get_io_loop(self) -> Any: ...
def wrap(self, obj: Any) -> Any: ...

View File

@ -13,15 +13,14 @@
# limitations under the License.
"""Tornado support for Motor, an asynchronous driver for MongoDB."""
from typing import TypeVar
from . import core, motor_gridfs
from .frameworks import tornado as tornado_framework
from .metaprogramming import create_class_with_framework
from .metaprogramming import T, create_class_with_framework
__all__ = [
"MotorClient",
"MotorClientEncryption",
"MotorClientSession",
"MotorDatabase",
"MotorCollection",
"MotorCursor",
@ -34,8 +33,6 @@ __all__ = [
"MotorClientEncryption",
]
T = TypeVar("T")
def create_motor_class(cls: T) -> T:
return create_class_with_framework(cls, tornado_framework, "motor.motor_tornado")

259
motor/motor_tornado.pyi Normal file
View File

@ -0,0 +1,259 @@
from collections.abc import Mapping, MutableMapping
from typing import Any, Optional, Union
from bson import Code, CodecOptions, Timestamp
from bson.raw_bson import RawBSONDocument
from pymongo.client_session import TransactionOptions
from pymongo.cursor_shared import _Hint, _Sort
from pymongo.read_concern import ReadConcern
from pymongo.read_preferences import ReadPreference, _ServerMode
from pymongo.typings import _CollationIn, _DocumentType, _DocumentTypeArg, _Pipeline
from pymongo.write_concern import WriteConcern
from motor import core, motor_gridfs
__all__: list[str] = [
"MotorClient",
"MotorClientSession",
"MotorDatabase",
"MotorCollection",
"MotorCursor",
"MotorCommandCursor",
"MotorChangeStream",
"MotorGridFSBucket",
"MotorGridIn",
"MotorGridOut",
"MotorGridOutCursor",
"MotorClientEncryption",
]
class MotorClient(core.AgnosticClient[_DocumentType]):
def get_database(
self,
name: Optional[str] = None,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> MotorDatabase[_DocumentType]: ...
def get_default_database(
self,
default: Optional[str] = None,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> MotorDatabase[_DocumentType]: ...
async def list_databases(
self,
session: Optional[core.AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> MotorCommandCursor[dict[str, Any]]: ...
async def start_session(
self,
causal_consistency: Optional[bool] = None,
default_transaction_options: Optional[TransactionOptions] = None,
snapshot: Optional[bool] = False,
) -> MotorClientSession: ...
def watch(
self,
pipeline: Optional[_Pipeline] = None,
full_document: Optional[str] = None,
resume_after: Optional[Mapping[str, Any]] = None,
max_await_time_ms: Optional[int] = None,
batch_size: Optional[int] = None,
collation: Optional[_CollationIn] = None,
start_at_operation_time: Optional[Timestamp] = None,
session: Optional[core.AgnosticClientSession] = None,
start_after: Optional[Mapping[str, Any]] = None,
comment: Optional[str] = None,
full_document_before_change: Optional[str] = None,
show_expanded_events: Optional[bool] = None,
) -> MotorChangeStream[_DocumentType]: ...
def __getattr__(self, name: str) -> MotorDatabase[_DocumentType]: ...
def __getitem__(self, name: str) -> MotorDatabase[_DocumentType]: ...
class MotorClientSession(core.AgnosticClientSession):
@property
def client(self) -> MotorClient: ...
async def __aenter__(self) -> MotorClientSession: ...
class MotorDatabase(core.AgnosticDatabase[_DocumentType]):
async def cursor_command(
self,
command: Union[str, MutableMapping[str, Any]],
value: Any = 1,
read_preference: Optional[_ServerMode] = None,
codec_options: Optional[CodecOptions[core._CodecDocumentType]] = None,
session: Optional[core.AgnosticClientSession] = None,
comment: Optional[Any] = None,
max_await_time_ms: Optional[int] = None,
**kwargs: Any,
) -> MotorCommandCursor[_DocumentType]: ...
async def create_collection(
self,
name: str,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
session: Optional[core.AgnosticClientSession] = None,
check_exists: Optional[bool] = True,
**kwargs: Any,
) -> MotorCollection[_DocumentType]: ...
def get_collection(
self,
name: str,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> MotorCollection[_DocumentType]: ...
async def list_collections(
self,
session: Optional[core.AgnosticClientSession] = None,
filter: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> MotorCommandCursor[MutableMapping[str, Any]]: ...
def with_options(
self,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> MotorDatabase[_DocumentType]: ...
def aggregate(
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
) -> MotorLatentCommandCursor[_DocumentType]: ...
def watch(
self,
pipeline: Optional[_Pipeline] = None,
full_document: Optional[str] = None,
resume_after: Optional[Mapping[str, Any]] = None,
max_await_time_ms: Optional[int] = None,
batch_size: Optional[int] = None,
collation: Optional[_CollationIn] = None,
start_at_operation_time: Optional[Timestamp] = None,
session: Optional[core.AgnosticClientSession] = None,
start_after: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
full_document_before_change: Optional[str] = None,
show_expanded_events: Optional[bool] = None,
) -> MotorChangeStream[_DocumentType]: ...
@property
def client(self) -> MotorClient[_DocumentType]: ...
def __getattr__(self, name: str) -> MotorCollection[_DocumentType]: ...
def __getitem__(self, name: str) -> MotorCollection[_DocumentType]: ...
class MotorCollection(core.AgnosticCollection[_DocumentType]):
def with_options(
self,
codec_options: Optional[CodecOptions] = None,
read_preference: Optional[ReadPreference] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> MotorCollection[_DocumentType]: ...
def list_search_indexes(
self,
name: Optional[str] = None,
session: Optional[core.AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> MotorLatentCommandCursor[Mapping[str, Any]]: ...
def __getattr__(self, name: str) -> MotorCollection[_DocumentType]: ...
def __getitem__(self, name: str) -> MotorCollection[_DocumentType]: ...
def find(self, *args: Any, **kwargs: Any) -> MotorCursor[_DocumentType]: ...
def find_raw_batches(self, *args: Any, **kwargs: Any) -> MotorRawBatchCursor[_DocumentType]: ...
def aggregate(
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
) -> MotorCommandCursor[_DocumentType]: ...
def aggregate_raw_batches(
self, pipeline: _Pipeline, **kwargs: Any
) -> MotorRawBatchCursor[_DocumentType]: ...
def list_indexes(
self, session: Optional[core.AgnosticClientSession] = None, **kwargs: Any
) -> MotorLatentCommandCursor[MutableMapping[str, Any]]: ...
class MotorLatentCommandCursor(core.AgnosticLatentCommandCursor[_DocumentType]): ...
class MotorCursor(core.AgnosticCursor[_DocumentType]):
def collation(self, collation: Optional[_CollationIn]) -> MotorCursor[_DocumentType]: ...
def add_option(self, mask: int) -> MotorCursor[_DocumentType]: ...
def remove_option(self, mask: int) -> MotorCursor[_DocumentType]: ...
def limit(self, limit: int) -> MotorCursor[_DocumentType]: ...
def skip(self, skip: int) -> MotorCursor[_DocumentType]: ...
def max_scan(self, max_scan: Optional[int]) -> MotorCursor[_DocumentType]: ...
def sort(
self, key_or_list: _Hint, direction: Optional[Union[int, str]] = None
) -> MotorCursor[_DocumentType]: ...
def hint(self, index: Optional[_Hint]) -> MotorCursor[_DocumentType]: ...
def where(self, code: Union[str, Code]) -> MotorCursor[_DocumentType]: ...
def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> MotorCursor[_DocumentType]: ...
def max_time_ms(self, max_time_ms: Optional[int]) -> MotorCursor[_DocumentType]: ...
def min(self, spec: _Sort) -> MotorCursor[_DocumentType]: ...
def max(self, spec: _Sort) -> MotorCursor[_DocumentType]: ...
def comment(self, comment: Any) -> MotorCursor[_DocumentType]: ...
def allow_disk_use(self, allow_disk_use: bool) -> MotorCursor[_DocumentType]: ...
def rewind(self) -> MotorCursor[_DocumentType]: ...
def clone(self) -> MotorCursor[_DocumentType]: ...
def __copy__(self) -> MotorCursor[_DocumentType]: ...
def __deepcopy__(self, memo: Any) -> MotorCursor[_DocumentType]: ...
class MotorRawBatchCursor(core.AgnosticRawBatchCursor[_DocumentType]): ...
class MotorCommandCursor(core.AgnosticCommandCursor[_DocumentType]): ...
class MotorRawBatchCommandCursor(core.AgnosticRawBatchCommandCursor[_DocumentType]): ...
class MotorChangeStream(core.AgnosticChangeStream[_DocumentType]):
def __aiter__(self) -> MotorChangeStream[_DocumentType]: ...
async def __aenter__(self) -> MotorChangeStream[_DocumentType]: ...
class MotorClientEncryption(core.AgnosticClientEncryption[_DocumentType]):
async def __aenter__(self) -> MotorClientEncryption[_DocumentType]: ...
async def get_keys(self) -> MotorCursor[RawBSONDocument]: ...
async def create_encrypted_collection(
self,
database: core.AgnosticDatabase[_DocumentTypeArg],
name: str,
encrypted_fields: Mapping[str, Any],
kms_provider: Optional[str] = None,
master_key: Optional[Mapping[str, Any]] = None,
**kwargs: Any,
) -> tuple[MotorCollection[_DocumentTypeArg], Mapping[str, Any]]: ...
class MotorGridOutCursor(motor_gridfs.AgnosticGridOutCursor):
def next_object(self) -> MotorGridOutCursor: ...
class MotorGridOut(motor_gridfs.AgnosticGridOut):
def __aiter__(self) -> MotorGridOut: ...
class MotorGridIn(motor_gridfs.AgnosticGridIn):
async def __aenter__(self) -> MotorGridIn: ...
class MotorGridFSBucket(motor_gridfs.AgnosticGridFSBucket):
async def open_download_stream_by_name(
self,
filename: str,
revision: int = -1,
session: Optional[core.AgnosticClientSession] = None,
) -> MotorGridOut: ...
async def open_download_stream(
self, file_id: Any, session: Optional[core.AgnosticClientSession] = None
) -> MotorGridOut: ...
def open_upload_stream(
self,
filename: str,
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[core.AgnosticClientSession] = None,
) -> MotorGridIn: ...
def open_upload_stream_with_id(
self,
file_id: Any,
filename: str,
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[core.AgnosticClientSession] = None,
) -> MotorGridIn: ...
def find(self, *args: Any, **kwargs: Any) -> MotorGridOutCursor: ...

View File

@ -13,7 +13,6 @@
# limitations under the License.
"""Utilities for using Motor with Tornado web applications."""
import datetime
import email.utils
import mimetypes
@ -25,6 +24,8 @@ import tornado.web
import motor
from motor.motor_gridfs import _hash_gridout
# mypy: disable-error-code="no-untyped-def,no-untyped-call"
# TODO: this class is not a drop-in replacement for StaticFileHandler.
# StaticFileHandler provides class method make_static_url, which appends
# an checksum of the static file's contents. Templates thus can do
@ -41,9 +42,11 @@ class GridFSHandler(tornado.web.RequestHandler):
.. code-block:: python
db = motor.MotorClient().my_database
application = web.Application([
(r"/static/(.*)", web.GridFSHandler, {"database": db}),
])
application = web.Application(
[
(r"/static/(.*)", web.GridFSHandler, {"database": db}),
]
)
By default, requests' If-Modified-Since headers are honored, but no
specific cache-control timeout is sent to clients. Thus each request for
@ -97,7 +100,7 @@ class GridFSHandler(tornado.web.RequestHandler):
try:
gridout = await self.get_gridfs_file(fs, path, self.request)
except gridfs.NoFile:
raise tornado.web.HTTPError(404)
raise tornado.web.HTTPError(404) from None
# If-Modified-Since header is only good to the second.
modified = gridout.upload_date.replace(microsecond=0)
@ -122,7 +125,9 @@ class GridFSHandler(tornado.web.RequestHandler):
if cache_time > 0:
self.set_header(
"Expires", datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time)
"Expires",
datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
+ datetime.timedelta(seconds=cache_time),
)
self.set_header("Cache-Control", "max-age=" + str(cache_time))
else:
@ -135,6 +140,7 @@ class GridFSHandler(tornado.web.RequestHandler):
ims_value = self.request.headers.get("If-Modified-Since")
if ims_value is not None:
date_tuple = email.utils.parsedate(ims_value)
assert date_tuple is not None
# If our MotorClient is tz-aware, assume the naive ims_value is in
# its time zone.
@ -175,4 +181,3 @@ class GridFSHandler(tornado.web.RequestHandler):
def set_extra_headers(self, path, gridout):
"""For subclass to add extra headers to the response"""
pass

View File

@ -1,11 +0,0 @@
[mypy]
check_untyped_defs = true
disallow_incomplete_defs = true
no_implicit_optional = true
pretty = true
show_error_context = true
show_error_codes = true
strict_equality = true
warn_unused_configs = true
warn_unused_ignores = true
warn_redundant_casts = true

View File

@ -1,14 +1,14 @@
[build-system]
requires = ["setuptools>61.0"]
build-backend = "setuptools.build_meta"
requires = ["hatchling>1.24","hatch-requirements-txt>=0.4.1"]
build-backend = "hatchling.build"
[project]
name = "motor"
dynamic = ["version"]
dynamic = ["version", "dependencies", "optional-dependencies"]
description = "Non-blocking MongoDB driver for Tornado or asyncio"
readme = "README.rst"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.7"
requires-python = ">=3.10"
authors = [
{ name = "A. Jesse Jiryu Davis", email = "jesse@mongodb.com" },
]
@ -33,50 +33,137 @@ classifiers = [
"Typing :: Typed",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"pymongo>=4.5,<5",
]
[project.optional-dependencies]
aws = [
"pymongo[aws]>=4.5,<5",
]
encryption = [
"pymongo[encryption]>=4.5,<5",
]
gssapi = [
"pymongo[gssapi]>=4.5,<5",
]
ocsp = [
"pymongo[ocsp]>=4.5,<5",
]
snappy = [
"pymongo[snappy]>=4.5,<5",
]
srv = [
"pymongo[srv]>=4.5,<5",
]
test = [
"pytest>=7", "mockupdb", "tornado>=5", "aiohttp", "motor[encryption]"
]
zstd = [
"pymongo[zstd]>=4.5,<5",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
[project.urls]
Homepage = "https://github.com/mongodb/motor/"
Homepage = "https://www.mongodb.org"
Documentation = "https://motor.readthedocs.io"
Source = "https://github.com/mongodb/motor"
Tracker = "https://jira.mongodb.org/projects/MOTOR/issues"
[tool.setuptools.dynamic]
version = {attr = "motor._version.version"}
[tool.hatch.version]
path = "motor/_version.py"
validate-bump = false
[tool.setuptools.packages.find]
include = ["motor"]
[tool.hatch.metadata.hooks.requirements_txt]
files = ["requirements.txt"]
[tool.hatch.metadata.hooks.requirements_txt.optional-dependencies]
aws = ["requirements/aws.txt"]
docs = ["requirements/docs.txt"]
encryption = ["requirements/encryption.txt"]
gssapi = ["requirements/gssapi.txt"]
ocsp = ["requirements/ocsp.txt"]
snappy = ["requirements/snappy.txt"]
test = ["requirements/test.txt"]
zstd = ["requirements/zstd.txt"]
[tool.mypy]
python_version = "3.10"
strict = true
pretty = true
show_error_context = true
show_error_codes = true
warn_redundant_casts = true
warn_unreachable = true
disable_error_code = ["type-arg"]
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
[tool.pytest.ini_options]
minversion = "7"
testpaths = ["test"]
xfail_strict = true
log_cli_level = "INFO"
addopts = ["-ra", "--strict-config", "--strict-markers",
"--maxfail=10", "--durations=5", "--junitxml=xunit-results/TEST-results.xml"]
faulthandler_timeout = 1500
filterwarnings = [
"error",
"ignore:Bare functions are deprecated, use async ones:DeprecationWarning",
"ignore:Application.make_handler:DeprecationWarning",
"ignore:unclosed <socket.socket:ResourceWarning",
"ignore:unclosed event loop:ResourceWarning",
"ignore: The fetch_next property is deprecated:DeprecationWarning",
"ignore:The next_object method is deprecated:DeprecationWarning",
"ignore:unclosed <ssl.SSLSocket:ResourceWarning",
"ignore:datetime.datetime.utc:DeprecationWarning",
"ignore:GridOut property 'contentType' is deprecated and will be removed in PyMongo 5.0:DeprecationWarning",
"ignore:GridIn property 'contentType' is deprecated and will be removed in PyMongo 5.0:DeprecationWarning",
"ignore:GridOut property 'aliases' is deprecated and will be removed in PyMongo 5.0:DeprecationWarning",
# From older versions aiohttp.
"ignore:\"@coroutine\" decorator is deprecated since Python 3.8:DeprecationWarning",
"ignore:The loop argument is deprecated since Python 3.8:DeprecationWarning",
# TODO: Remove both of these in https://jira.mongodb.org/browse/PYTHON-4731
"ignore:Unclosed AsyncMongoClient*",
"ignore:Unclosed MongoClient*",
# Deprecation warning now that Motor is officially deprecated
"ignore:Motor is deprecated*",
]
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = [
"E", "F", "W", # flake8
"B", # flake8-bugbear
"I", # isort
"ARG", # flake8-unused-arguments
"C4", # flake8-comprehensions
"EM", # flake8-errmsg
"ICN", # flake8-import-conventions
"G", # flake8-logging-format
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PYI", # flake8-pyi
"PL", # pylint
"PT", # flake8-pytest-style
"PTH", # flake8-use-pathlib
"RUF", # Ruff-specific
"S", # flake8-bandit
"SIM", # flake8-simplify
"T20", # flake8-print
"UP", # pyupgrade
"YTT", # flake8-2020
"EXE", # flake8-executable
]
ignore = [
"ARG001", # Unused function argument
"UP007", # Use `X | Y` for type annotation
"EM101", # Exception must not use a string literal, assign to variable first
"EM102", # Exception must not use an f-string literal, assign to variable first
"G004", # Logging statement uses f-string"
"PLR", # Too many arguments to function call, etc.
"SIM108", # Use ternary operator"
"SIM105", # Use `contextlib.suppress(OSError)` instead of `try`-`except`-`pass`
"ARG002", # Unused method argument:
"S101", # Use of `assert` detected
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
"PYI034", # `__aenter__` methods in classes like `AgnosticGridIn` usually return `self` at runtime
]
unfixable = [
"RUF100", # Unused noqa
"T20", # Removes print statements
"F841", # Removes unused variables
"F401"
]
exclude = []
flake8-unused-arguments.ignore-variadic-names = true
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?)|dummy.*)$"
[tool.ruff.lint.per-file-ignores]
"test/*.py" = ["PT009", "ARG", "E402", "PT027", "UP031",
"B904", "C405", "SIM", "PLR", "PTH", "B018", "C4",
"S", "E501", "T201", "E731", "F841", "F811",
"B011", "PT015", "E721", "PYI"]
"synchro/__init__.py" = ["F403", "B904", "F401"]
"doc/*.py" = [ "T201", "PTH"]
"motor/docstrings.py" = [ "E501", ]
"doc/examples/*.py" = [ "E402", ]

View File

@ -1,15 +0,0 @@
[pytest]
testpaths =
test
addopts = -ra --junitxml=xunit-results/TEST-results.xml
filterwarnings =
error
ignore:Bare functions are deprecated, use async ones:DeprecationWarning
ignore:Application.make_handler:DeprecationWarning
ignore:unclosed <socket.socket:ResourceWarning
ignore:unclosed event loop:ResourceWarning
ignore: The fetch_next property is deprecated:DeprecationWarning
ignore:The next_object method is deprecated:DeprecationWarning
ignore:unclosed <ssl.SSLSocket:ResourceWarning
ignore:datetime.utcfromtimestamp:DeprecationWarning
ignore:datetime.utcnow:DeprecationWarning

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
pymongo>=4.9,<5.0

1
requirements/aws.txt Normal file
View File

@ -0,0 +1 @@
pymongo[aws]>=4.5,<5

Some files were not shown because too many files have changed in this diff Show More