Compare commits

...

595 Commits

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
Steven Silvester
5348e5af85
MOTOR-1169 Update PyMongo Deps and Remove Workarounds (#223) 2023-08-24 15:38:08 -05:00
Steven Silvester
f905df8553
MOTOR-1139 Switch to Pytest (#222) 2023-08-23 15:52:07 -05:00
Noah Stapp
70136ad1f2
MOTOR-331 Add python 3 type annotations (#221) 2023-08-18 11:57:56 -07:00
Steven Silvester
9fdb62a350
MOTOR-1136 Ability to create and manage Atlas search indexes in the database (#220) 2023-08-16 15:58:03 -05:00
Steven Silvester
332360be4a
MOTOR-1168 Update broken specification link (#219) 2023-08-08 15:34:03 -05:00
Gleb
424d03e15b
Fix a grammar typo (#218) 2023-08-07 08:39:46 -05:00
Steven Silvester
fd55a40f4a
MOTOR-1160 Test against Python 3.12 (#216) 2023-07-26 12:29:30 -05:00
Steven Silvester
f179495ca3 BUMP 3.3.0.dev0 2023-06-21 16:21:16 -05:00
Steven Silvester
9d38d58189
MOTOR-1144 Release 3.2 (#215) 2023-06-21 16:19:13 -05:00
Steven Silvester
ff05bfc6cd
MOTOR-1143 [Failure] test_async_create_encrypted_collection (#214) 2023-06-21 16:01:19 -05:00
Noah Stapp
4f0ac92f34
PYTHON-3736 Add Noah to code owners for PyMongo, Motor, and PyMongoArrow (#213) 2023-06-14 11:06:44 -07:00
Steven Silvester
6b7d0e930e
MOTOR-1140 Switch to using RHEL only for tests (#212) 2023-06-13 14:28:11 -05:00
Steven Silvester
2c0570f701
MOTOR-1131 Remove "Public Technical Preview" from Queryable Encryption Equality API (#211) 2023-05-16 07:28:04 -05:00
Shane Harvey
3534c33540
MOTOR-1127 Document and test support for MongoDB 7.0 (#209) 2023-05-12 14:15:39 -07:00
Steven Silvester
6a7c84de87
MOTOR-1107 Update readme for Motor driver (#208) 2023-05-12 15:59:11 -05:00
Steven Silvester
462d475972
MOTOR-1089 Drivers should not create the ECC collection in v2 of queryable encryption (#207) 2023-05-12 15:58:42 -05:00
Steven Silvester
440b3dc2c4
MOTOR-1133 [BUILD FAILURE] test.test_client.TestClient (#206) 2023-05-12 13:39:34 -05:00
Steven Silvester
a16d48293d
MOTOR-1100 Update libmongocrypt payloads to new QE protocol (#205) 2023-05-11 16:39:30 -05:00
Steven Silvester
1557700377
MOTOR-1083 Deprecate currentOp/collStats commands by 7.0 (#203) 2023-05-08 13:36:41 -05:00
Steven Silvester
fdc766be7c
MOTOR-1130 Fix RTD Build Failure (#204) 2023-05-08 12:33:53 -05:00
Steven Silvester
6462f1ecbb
MOTOR-1109 Switch to Supported Build Hosts (#202) 2023-04-12 14:30:05 -05:00
Shane Harvey
f9aed97221
MOTOR-1098 Skip synchro test_typing and encrypted coll tests on standalones (#200)
Snapshot session test needs to wait for the documents to be visible in the latest snapshot.
2023-04-06 13:27:06 -07:00
Julius Park
16c476e618
MOTOR-1106 ERROR: module 'os' has no attribute 'fork' (AttributeError) (#199) 2023-03-21 16:59:13 -07:00
Steven Silvester
da92e56829
MOTOR-1104 Windows and MacOS Tox failures (#198) 2023-03-16 15:59:00 -05:00
Julius Park
935c543919
MOTOR-1087 motor_asyncio freezes when using multiprocessing (#196) 2023-03-16 11:20:26 -07:00
Julius Park
f4422ddaaf
MOTOR-969 Support for Range Indexes (#195) 2023-03-15 15:32:46 -07:00
Shashank Nigam
f77fa40ec5
Update aiohttp example (#197)
Co-authored-by: Steven Silvester <steven.silvester@ieee.org>
2023-03-15 10:30:58 -05:00
Julius Park
c8ebda6726
MOTOR-995 Automatically create Queryable Encryption keys (#193) 2023-02-17 10:03:30 -08:00
Steven Silvester
7c2f259bac
MOTOR-1095 Make Rust Compiler available for Cryptography build (#194) 2023-02-15 14:15:34 -06:00
Steven Silvester
8d5a8bc85b
MOTOR-939 Improved change stream event visibility for C2C Replication (#192) 2023-01-11 14:34:23 -06:00
Steven Silvester
8b2c17f2ce
MOTOR-1075 Ubuntu 18.04-synchro37-NoSSL Failing With SynchroMeta Object Error (#191) 2022-12-15 13:54:54 -06:00
Julius Park
04a1d51c13
MOTOR-1072 Test_shared_client failing on multiple variants (#189) 2022-12-13 14:06:27 -08:00
Julius Park
f0f0c09409
MOTOR-1068 Tornado no longer supports Python 3.7 (#188) 2022-12-13 14:04:21 -08:00
Julius Park
0f8dab12da
MOTOR-1073 Ubuntu 22.04 on GitHub Actions does Not Have MongoDB (#190) 2022-12-13 14:04:06 -08:00
Julius Park
a5610d3bc1
MOTOR-923 Language specific examples for AWS Lambda (#187) 2022-11-21 11:07:12 -08:00
Steven Silvester
d25e4bc1c6
MOTOR-1059 Single Source the Package Version (#186) 2022-10-26 13:58:41 -05:00
Steven Silvester
6994a2b53f BUMP 3.1.2.dev0 2022-10-25 15:57:10 -05:00
Steven Silvester
4d2713c7b1 BUMP 3.1.1 2022-10-25 15:49:26 -05:00
Steven Silvester
32bcfe713f
MOTOR-1054 motor.motor_asyncio is broken for Python 3.11 (#185) 2022-10-25 15:45:23 -05:00
Steven Silvester
fb8ea5822f BUMP 3.2.0.dev0 2022-09-13 14:36:35 -05:00
Steven Silvester
c142ccebc6 BUMP 3.1.0 2022-09-13 14:36:00 -05:00
Steven Silvester
b0d7970c4e
MOTOR-1031 Changelog for 3.1 (#184) 2022-09-13 14:30:12 -05:00
Steven Silvester
a624899995
MOTOR-974 Document Queryable Encryption API as "Public Technical Preview" (#182) 2022-09-12 15:34:46 -05:00
Steven Silvester
81f81f6ab0
MOTOR-956 Provide FLE 2.0 API example for docs team (#183) 2022-09-12 14:26:04 -05:00
Julius Park
99e65f85c5
MOTOR-866 Key Management API (#175) 2022-09-10 16:42:33 -07:00
Steven Silvester
6e8e2ee0e1
MOTOR-981 Test Failure - asyncio_tests.test_asyncio_session.TestAsyncIOSession.test_collection (#181) 2022-09-07 18:33:21 -05:00
Steven Silvester
19d12b0f4e
MOTOR-934 Test against latest rapid releases (#180) 2022-09-07 09:15:14 -05:00
Shane Harvey
bed1a56467
MOTOR-1025 Fix test_asyncio_encryption (#179) 2022-09-01 15:50:48 -07:00
Julius Park
555863da5d
MOTOR-878 Change streams support for user-facing PIT pre- and post-images (#178) 2022-09-01 11:34:35 -07:00
Julius Park
f74e740123
MOTOR-1020 Error in Motor Synchro GridFS Tests "test_exception_file_non_existence" (#177) 2022-08-29 14:33:17 -07:00
Julius Park
b9f36c2f7a
MOTOR-1019 Migrate Synchro Tests Away From amazon1-2018 (#176) 2022-08-17 16:06:54 -07:00
Shane Harvey
52fc4267ba MOTOR-1014 Auto format with black 2022-08-17 13:22:08 -07:00
CoderK
4c704fb756
MOTOR-1014 Add __aenter__ and __aexit__ for AgnosticBaseCursor so that cursor can be instantiated using the context manager (#172) 2022-08-17 12:45:21 -07:00
Julius Park
47c84925ac
MOTOR-951 Test against MongoDB 6.0 (#174) 2022-08-11 12:48:03 -07:00
Steven Silvester
39af29282c
MOTOR-1012 Update __all__ to allow partial imports without warnings (#173) 2022-08-09 19:52:14 -05:00
Steven Silvester
78e6a2240b
MOTOR-980 Fix Docs Failure for Missing Reference (#171) 2022-06-16 13:11:09 -05:00
Thomas Grainger
a77db8f0bc
MOTOR-979 Use asyncio.get_event_loop so that DeprecationWarnings are correctly issued (#170) 2022-06-15 19:19:21 -05:00
Steven Silvester
a6b2a79e0c BUMP 3.1.0.dev0 2022-04-28 13:23:52 -05:00
Steven Silvester
6a43c05865 BUMP 3.0.0 2022-04-28 13:22:31 -05:00
Steven Silvester
69e1bae0ba
MOTOR-952 Bump PyMongo minimum to 4.1 (#169) 2022-04-28 13:11:48 -05:00
Steven Silvester
92b4d51ecb
MOTOR-698 Motor 2 to 3 migration guide (#166) 2022-04-27 21:14:13 -05:00
Steven Silvester
19537bbafe
MOTOR-938 Docs for watch() incorrectly call ChangeStream.close() (#163) 2022-04-27 21:13:29 -05:00
Steven Silvester
2f75a4030c
MOTOR-946 Deprecated APIs will not be Removed in Motor 3.0 (#168) 2022-04-26 07:54:00 -05:00
Steven Silvester
c564a02cc4
MOTOR-608 Update outdated API docs (#165) 2022-04-25 18:43:45 -05:00
Steven Silvester
eb3ed1460c
MOTOR-842 Support 'let' option for multiple CRUD commands (#164) 2022-04-25 17:08:06 -05:00
Steven Silvester
7822d938db
MOTOR-936 Mention method delete_one in asyncio tutorial in documentation (#162) 2022-04-22 15:05:34 -05:00
Steven Silvester
ca0b27b207
MOTOR-941 Bump pymongo requirement to 4 (#161) 2022-04-22 14:00:03 -05:00
Steven Silvester
0b0a3602c0
MOTOR-843 Add support for the comment field to all helpers (#160) 2022-04-22 13:43:05 -05:00
Steven Silvester
0c562e940b
MOTOR-902 Since Python 3.10 asyncio.get_event_loop() is deprecated (#155) 2022-04-20 19:47:14 -05:00
Steven Silvester
c16a62d074
MOTOR-900 Fix synchro failures on PyMongo 4.1.1 (#158)
Co-authored-by: Shane Harvey <shnhrv@gmail.com>
2022-04-20 19:22:22 -05:00
Steven Silvester
f5ade74529
MOTOR-894 Snapshot Query Examples for the Manual (#157) 2022-04-14 14:13:47 -05:00
Steven Silvester
690c781066
MOTOR-933 Require Python 3.7+ (#156) 2022-04-14 13:42:56 -05:00
Steven Silvester
588951e51e
MOTOR-922 Update docs.mongodb.com links in source, API & Reference documentation (#154) 2022-04-13 14:10:10 -05:00
Steven Silvester
e2d5fa50ad
MOTOR-912 Update the Release Notes (#153) 2022-03-30 13:33:19 -05:00
Shane Harvey
57dd5773a3
MOTOR-911 Skip version API test for count (#152) 2022-03-18 15:53:26 -07:00
Steven Silvester
276f93e0d0
MOTOR-906 Doc Build and Test are Failing (#150) 2022-03-15 14:37:01 -05:00
Tushar Singh
64fbc8313b
MOTOR-884 Mirror all PyMongo extras (#148) 2022-03-09 15:13:02 -06:00
Alexander Golin
13b82efa1d
Create CODEOWNERS (#147) 2022-03-03 13:46:35 -05:00
Steven Silvester
5e07f66799
MOTOR-897 Set up flake8 (#146) 2022-02-17 15:28:07 -06:00
Steven Silvester
59a3e3229b
MOTOR-896 Set up pre-commit (#145) 2022-02-16 14:25:52 -06:00
Steven Silvester
1e62b868ea
MOTOR-896 Add automatic code formatting (#144) 2022-02-15 20:29:02 -06:00
Steven Silvester
9c8d95e3d1
MOTOR-895 Restore "Versioned API" language in examples (#143) 2022-02-15 17:34:29 -06:00
Steven Silvester
b7dc13fea6
MOTOR-886 Rename "Versioned API" to "Stable API" in documentation (#142) 2022-02-15 06:27:01 -06:00
Steven Silvester
055f5e05ab
MOTOR-643 Support PyMongo 4.0 (#141)
Co-authored-by: Shane Harvey <shnhrv@gmail.com>
2021-12-23 12:33:43 -06:00
Amin Alaee
66fde7189d MOTOR-857 Test Python 3.10 support 2021-11-08 16:18:18 -08:00
Yasser Tahiri
074d3c31be
chore: misc style/pep8 fixes (#139) 2021-11-08 14:37:40 -08:00
Shane Harvey
e4b0591538 MOTOR-852 Use https:// instead of unauthenticated git:// for git clone 2021-11-01 17:35:16 -07:00
Shane Harvey
79488f33fe BUMP 2.5.2.dev0 2021-08-19 13:16:36 -07:00
Shane Harvey
928c92e0b7
BUMP 2.5.1 (#138) 2021-08-19 12:25:09 -07:00
Shane Harvey
401ebf2baf
MOTOR-813 Test macOS with python.org installed Python 3.6 (#137) 2021-08-19 10:50:02 -07:00
Shane Harvey
baa0bf27c7
MOTOR-808 Fix test_watch_with_start_after on MongoDB 5.1+ (#136) 2021-08-12 09:27:40 -07:00
xiaoqiang
bff55222dc
MOTOR-805 Fix bug where cursor.to_list(N) returned more than N documents (#135) 2021-08-11 10:45:06 -07:00
Prashant Mital
6e29325037
MOTOR-784 Fix typo in versioned API migration example for ecosystem docs 2021-07-29 17:20:17 -07:00
Shane Harvey
d8d91a5b65 BUMP 2.5.1.dev0 2021-07-22 16:00:56 -07:00
Shane Harvey
17e25323ae BUMP 2.5.0 2021-07-22 15:58:06 -07:00
Prashant Mital
8ad5aac086
MOTOR-787 Deprecate APIs that were deprecated in PyMongo 3.12 (#133) 2021-07-22 15:52:55 -07:00
Shane Harvey
db1363b272
MOTOR-659 Limit the use of master terminology (#131)
Migrate doctests to MongoDB 5.0.
Remove travis config, motor is tested on Evergreen.
Require tox>=3.18.
2021-07-22 15:26:54 -07:00
Prashant Mital
2a370562c7
MOTOR-784 Versioned API migration example for ecosystem docs (#127) 2021-07-22 15:13:41 -07:00
Prashant Mital
3dec185017
MOTOR-699 Update PyMongo dependency to 3.12+ (#129) 2021-07-22 14:19:59 -07:00
Shane Harvey
0d82d21e00
MOTOR-745 Document let option for aggregate (#130)
MOTOR-654 Document $out/$merge usage for aggregate
2021-07-22 14:19:14 -07:00
Shane Harvey
94416e8d3a
MOTOR-794 Fix support for Sphinx 4 (#128) 2021-07-22 11:41:20 -07:00
Shane Harvey
c81993e015
MOTOR-781 Fix synchro tests for pymongo 3.12 (#125)
Make motor client/db/collection hashable.
Make cursor close wait for killCursors.
2021-07-22 11:26:40 -07:00
Prashant Mital
b0a79e1e30
MOTOR-735 Versioned API connection examples for ecosystem docs (#126)
* MOTOR-699 Update PyMongo dependency to 3.12
2021-07-22 10:01:27 -07:00
Shane Harvey
d6ed164421
MOTOR-730 Stop testing problematic .js content type (#124) 2021-07-20 16:32:01 -07:00
Shane Harvey
0f83e4c8c5 MOTOR-780 Stop testing tornado@git with Python 3.5 2021-07-16 12:48:41 -07:00
Shane Harvey
bdb970378a
MOTOR-782 Better fix for test_fetch_next_exception (#123) 2021-07-16 12:40:08 -07:00
Shane Harvey
882f076ec5
MOTOR-779 Test against MongoDB 5.0 (#122)
Migrate ubuntu-16 testing to amazon1-2018.
2021-07-15 16:45:15 -07:00
Shane Harvey
30c14023ca
MOTOR-769 MOTOR-770 Fix test_fetch_next_exception and test_exhaust (#119)
Fix test_exhaust due to pymongo 3.12 cursor close and del changes.
2021-07-15 14:05:33 -07:00
Shane Harvey
a9dbf9a4fb
MOTOR-771 Use Sphinx 4 on motor.readthedocs.io (#121) 2021-07-15 13:48:45 -07:00
Shane Harvey
0de9d25803
MOTOR-602 Fix doc failures with sphinx 4 (#118) 2021-07-14 17:57:53 -07:00
Shane Harvey
7956514873
MOTOR-707 Add topology_description to MotorClient (#117) 2021-07-14 17:57:38 -07:00
Shane Harvey
96cd21b30c
MOTOR-747 Update test certs from drivers-evergreen-tools (#116) 2021-06-07 12:13:29 -07:00
Prashant Mital
9a03afec12
(docs) Fix broken links in monitoring example (#115) 2021-05-05 15:48:40 -07:00
vlad doster
afabd0079f
(docs) fix broken link for the monitoring example (#114) 2021-05-05 09:38:23 -07:00
Shane Harvey
8837ab095c
MOTOR-719 Remove NPS survey (#113) 2021-05-04 14:19:01 -07:00
Prashant Mital
512b3cc0a9
BUMP 2.5.0.dev0 (#112) 2021-04-08 17:19:02 -05:00
Prashant Mital
7f4203b5b7
BUMP 2.4.0 (#111) 2021-04-08 16:46:32 -05:00
William Zhou
59fe4116fb
MOTOR-708: Update Changelog for Motor 2.4 (#110) 2021-04-07 16:27:47 -07:00
William Zhou
e953978172
MOTOR-702: Motor tests that run agains PyMongo master should use v3.12 branch (#108) 2021-04-07 13:47:32 -07:00
Prashant Mital
5f0d9cff6f
MOTOR-703 Ensure we are testing against latest pymongo v3.12 commit (#109) 2021-04-07 12:15:42 -05:00
William Zhou
abbbde51e0
MOTOR-701: Require MongoDB 4.2+ to run CSFLE tests (#107) 2021-04-06 15:24:35 -07:00
William Zhou
9bff41c276
MOTOR-622: Add Python 3.9 support (#106) 2021-04-05 11:43:11 -07:00
William Zhou
c05d3eda44
MOTOR-476: Update FLE documentation for community version demonstrating explicit encryption/decryption (#104) 2021-04-02 13:05:42 -07:00
William Zhou
1b5a21a202
MOTOR-689: Add async wrapper for pymongo.encryption.ClientEncryption (#103) 2021-03-31 14:20:52 -07:00
William Zhou
8a8313da76
MOTOR-690: Port PyMongo example to Motor for automatic CSFLE (#102) 2021-03-26 14:59:53 -07:00
Jason Haury
f97d911c3b
Fix documentation examples for gridfs open_upload_stream (#101) 2021-03-02 10:11:05 -08:00
Shane Harvey
0e729fb6f3
MOTOR-674 Fix incorrect doc examples for open_upload_stream (#100) 2021-02-12 11:32:37 -08:00
Prashant Mital
f444e21d8d
BUMP 2.4.0.dev0 (#99) 2021-02-03 16:10:54 -08:00
Prashant Mital
b29ecd0335
BUMP 2.3.1 (#98) 2021-02-02 17:36:48 -08:00
Shane Harvey
2ee0d8dc9f
MOTOR-625 Avoid exhausting worker thread pool when many tasks wait on ChangeStream.next (#97) 2021-01-15 11:36:00 -08:00
Shane Harvey
7cf1f12038
MOTOR-633 watch methods must support MotorClientSession classes (#96) 2021-01-15 11:01:16 -08:00
Prashant Mital
e1ae65883f
BUMP 2.4.0.dev0 (#94) 2020-09-24 16:47:34 -07:00
Prashant Mital
c3e51f8c17
BUMP 2.3.0 (#93) 2020-09-24 16:14:57 -07:00
Prashant Mital
6f7ced583c
Document Motor release process (#92) 2020-09-24 16:12:17 -07:00
Prashant Mital
cd8c47aaff
MOTOR-591 Document that Windows is supported (#91) 2020-09-24 11:03:46 -07:00
Prashant Mital
ed02ce5e75
MOTOR-614 Use Python 3 syntax for super() calls (#89) 2020-09-21 13:14:13 -07:00
Shane Harvey
ca6141cb6d
MOTOR-613 Test contextvars support (#90) 2020-09-17 14:04:54 -07:00
Bulat Khasanov
e2d0e18c51
MOTOR-613 Add contextvars support (#88) 2020-09-16 13:21:09 -07:00
Prashant Mital
5053f219d6
BUMP 2.3.0.dev0 (#87) 2020-08-13 18:11:19 -07:00
Prashant Mital
ed7a0a2541
BUMP 2.2.0 (#86) 2020-08-13 17:39:20 -07:00
Prashant Mital
fad68810f9
MOTOR-589 Fix synchrotest failures (#67) 2020-08-13 16:55:47 -07:00
Prashant Mital
84f8a051d0
MOTOR-420 Remove legacy APIs/patterns from documentation (#85) 2020-08-13 16:03:48 -07:00
Prashant Mital
70185d1361
MOTOR-596 Deprecate fsync helper (#84) 2020-08-12 18:18:35 -07:00
Prashant Mital
d72b4f5847
MOTOR-582 Deprecate Cursor.fetch_next and Cursor.next_object (#80) 2020-08-12 18:12:36 -07:00
Prashant Mital
5589020951
MOTOR-598 Fix doctest failures
MOTOR-599 Update documentation with codeblocks for MongoDB 4.4
2020-08-10 17:37:32 -07:00
Shane Harvey
ae9595e9e0
MOTOR-600 Fix AsyncIOMotorGridOut.open (#82) 2020-08-10 16:04:18 -07:00
Shane Harvey
938ff4ee8a
MOTOR-597 Stop using deprecated loop arguments and other improvements (#78)
Stop using deprecated loop argument to asyncio.sleep and asyncio.wait_for
Fix setup.py:28: ResourceWarning: unclosed file <_io.TextIOWrapper name='README.rst' mode='r' encoding='UTF-8'>
  long_description = open("README.rst").read()
Fix RuntimeWarning: coroutine 'TestAsyncIOTests.test_undecorated.<locals>.Test.test_that_should_be_decorated' was never awaited
Migrate to pymongo's new tls arguments.
2020-08-10 15:02:13 -07:00
Prashant Mital
11df507902
MOTOR-505 MOTOR-455 MOTOR-433 Add index hinting to commands (#70) 2020-08-10 14:37:29 -07:00
Shane Harvey
f89b3ca829
MOTOR-496 Use windows-64-vsMulti-small and test asyncio on Windows (#79)
Test asyncio Python 3.7 on Windows.
2020-08-10 11:51:40 -07:00
Prashant Mital
42b48d89a3
MOTOR-595 Add Python 3 back to the list of trove classifiers (#77) 2020-08-06 12:09:28 -07:00
Shane Harvey
3ceb028056
MOTOR-320 Create ChangeStream cursor in async with __aenter__ (#76) 2020-08-06 10:51:42 -07:00
Prashant Mital
114b88c07a
MOTOR-538 Revise issues and help sections of documentation (#75) 2020-08-06 10:43:34 -07:00
Prashant Mital
be31a41106
MOTOR-473 Test asyncio and Tornado against all supported versions of PyPy (#74) 2020-08-05 10:52:09 -07:00
Prashant Mital
25ac11a83b
MOTOR-468 Run asyncio tests with CPython 3.5 and CPython 3.6 (#73) 2020-08-04 18:33:42 -07:00
Prashant Mital
f47846ef5a
MOTOR-590 Test Motor against MongoDB 4.4 in Evergreen (#72) 2020-08-04 18:13:34 -07:00
Prashant Mital
c50625cb89
MOTOR-451 listIndexes no longer includes "ns" as of MongoDB 4.4 (#71) 2020-08-04 17:54:23 -07:00
Prashant Mital
0b031c0b08
MOTOR-594 Update PyMongo dependency to 3.11+ (#69) 2020-08-04 17:25:46 -07:00
Shane Harvey
959c768260
MOTOR-488 Don't call set_result on a cancelled tasks (#66)
Add test case for cancelled async cursor iteration.
Fix AttributeError: '_LatentCursor' object has no attribute '_CommandCursor__address'
2020-08-04 16:20:00 -07:00
Prashant Mital
35bdecfefe
MOTOR-454 Support for allowDiskUse in find() commands (#68) 2020-08-04 11:28:43 -07:00
Prashant Mital
d7e5cb2955
MOTOR-587 Remove py2 compatibility submodule (#65) 2020-07-29 19:17:51 -07:00
Prashant Mital
aa7f55334e
MOTOR-417 Modernize codebase to use async/await syntax (#63) 2020-07-28 19:55:51 -07:00
Prashant Mital
8208420d61
MOTOR-581 Remove __future__ imports (#64) 2020-07-28 17:51:10 -07:00
Prashant Mital
cad5560124
MOTOR-415 Remove quoted text/code for __aenter__ and __aexit__ helpers MOTOR-416 Remove quoted text/code for __aiter__ and __anext__ helpers 2020-07-27 17:21:14 -07:00
Prashant Mital
561e7e1c07
MOTOR-577 Record framework as asyncio (#60) 2020-07-27 13:44:52 -07:00
Prashant Mital
0825ed0a63
MOTOR-419 Drop support for Python versions older than 3.5.2 (#61) 2020-07-27 13:40:31 -07:00
Shane Harvey
7c2978949f MOTOR-517 Update comment in with_transaction example for the docs manual 2020-05-06 11:41:38 -07:00
Wan Bachtiar
3d6ca6b492
Updated discussion links to point to the new MongoDB Community forums (#58) 2020-03-16 16:13:07 -07:00
Shane Harvey
4c7534c620 MOTOR-470 Add NPS Survey to documentation 2020-02-10 14:56:09 -08:00
Prashant Mital
f493bd3772
MOTOR-485 Migrate macOS evergreen builds to macOS-1014 2020-01-23 18:03:27 -08:00
Shane Harvey
dad11a5653 BUMP 2.1.0.dev0 2019-12-11 16:20:55 -08:00
Shane Harvey
0690c83326 BUMP 2.1.0 2019-12-11 16:19:24 -08:00
Shane Harvey
174b8227e5 Update changelog for 2.1 release 2019-12-11 14:04:03 -08:00
Shane Harvey
393b6b2ded MOTOR-478 Do not build universal wheels
Motor's setup.py contains version specific code.
2019-12-11 13:06:35 -08:00
Shane Harvey
167e6a2497 MOTOR-444 Update minimum version to PyMongo 3.10
PyMongo 3.10 provides support for Python 3.8 and the
ClientSession.in_transaction property.
2019-12-11 11:33:33 -08:00
Prashant Mital
47e7498e7c
MOTOR-472 Add PyPI classifier for PyPy implementation 2019-12-06 19:16:10 +05:30
Shane Harvey
e797e6dcd7 MOTOR-471 Document support for Python 3.8 2019-12-04 17:42:30 -08:00
Balint Reczey
bec8fb47e9 MOTOR-458 Run Travis with Python 3.7 and 3.8 (#57) 2019-12-04 17:24:53 -08:00
Shane Harvey
1a4fe95b5e MOTOR-461 Test Python 3.8 in Evergreen 2019-12-04 17:12:11 -08:00
Shane Harvey
0db1890128 MOTOR-460 Fix TypeError when accessing __dict__ on GridFS classes 2019-12-04 17:11:23 -08:00
Shane Harvey
ec8b33ffe7 MOTOR-467 Do not call set_exception on a done Future 2019-12-03 16:04:21 -08:00
Prashant Mital
58f7deb9b4
MOTOR-464 Fix withTransaction example test failure 2019-11-13 16:43:35 -08:00
Prashant Mital
a3c4984c7b
MOTOR-444 Start using PyMongo's ClientSession.in_transaction 2019-11-13 13:45:03 -08:00
Prashant Mital
2c6d34ebe5
MOTOR-462 Add xunit output to evergreen for synchrotests 2019-11-13 13:29:08 -08:00
Prashant Mital
c4169d520d
MOTOR-370 Add example usage for withTransaction API 2019-11-12 12:42:53 -08:00
Prashant Mital
b32a050f68
MOTOR-443 Implement convenient transaction API prose tests 2019-11-11 16:12:03 -08:00
Balint Reczey
f6e69a8bd9 MOTOR-459 tests: Expect asyncio.exceptions.TimeoutError on Python 3.8 (#56) 2019-11-11 11:24:42 -08:00
Prashant Mital
898dc57b64
MOTOR-280 Implement convenient transactions API 2019-11-01 14:55:16 -07:00
Shane Harvey
dd045250c8 MOTOR-442 Always convert ASYNC_TEST_TIMEOUT to float 2019-10-30 15:46:19 -07:00
Shane Harvey
b6bbe99766 MOTOR-439 Make Motor GridFSBucket match PyMongo
Add bucket_name, chunk_size_bytes, write_concern, and read_preference
parameters.
Deprecate the collection but still support it as an alias to
bucket_name.
2019-10-28 17:19:50 -07:00
Shane Harvey
163c56f6cc MOTOR-228 Fix Python 3 module loading
Make sys.meta_path import hook Python 3 compatible.
Make synchro ChangeStream iterable.
2019-10-28 14:49:34 -07:00
Shane Harvey
a300b5e0d5 MOTOR-440 Add session parameter to GridOut class 2019-10-28 14:46:13 -07:00
Shane Harvey
1bc89db074 MOTOR-425 Keep skipping test_custom_types change stream synchro tests 2019-10-22 14:07:08 -07:00
Shane Harvey
84646e0cc3 MOTOR-427 Transaction retry examples for MongoDB Manual 2019-10-17 13:19:03 -07:00
Shane Harvey
c78ab7c0c5 MOTOR-398 Update doc string examples for PyMongo 3.9 2019-10-17 13:18:40 -07:00
Shane Harvey
cf1d3d191e MOTOR-265 Add docs manual example for causal consistency 2019-10-15 15:29:40 -07:00
Shane Harvey
5b094e2640 MOTOR-401 Add client.get_default_database method 2019-10-15 12:11:19 -07:00
Shane Harvey
35f2caaeaf MOTOR-413 Run enterprise auth tests with Python 3.7 2019-10-15 12:10:01 -07:00
Shane Harvey
dc6ae3ed3e MOTOR-418 Test change stream in synchro with Python 3 2019-10-15 12:09:31 -07:00
Shane Harvey
46f849d945 MOTOR-263 Update change stream examples for docs 2019-10-14 17:50:01 -07:00
Shane Harvey
6248419704 Fix synchro tests with PyMongo 3.9
MOTOR-401 Skip get_default_database tests
MOTOR-402 MOTOR-403 Skip internal pymongo tests
MOTOR-404 Add db.aggregate/db.watch/client.watch to synchro tests
MOTOR-407 Add missing private __die attribute on _LatentCursor
MOTOR-409 Skip flakey CMAP test
Skip tests for group
Skip TestCursor.test_min_max_without_hint
Enable faulthandler
Properly implement _SynchroTransactionContext
2019-10-10 16:16:37 -07:00
Shane Harvey
3d204bd02e MOTOR-412 Remove transaction spec test runner
Transactions are tested via the synchro test suite.
2019-10-10 15:01:41 -07:00
Shane Harvey
05101e286f MOTOR-280 Add with_transaction method to fix test_client_session_attrs 2019-10-10 14:57:08 -07:00
Shane Harvey
0d45707f7c MOTOR-228 Run synchro test suite with Python 3.7 2019-10-10 12:06:43 -07:00
crusaderky
8360cf4c5f Align requirements in setup.py to requirements.rst (#45) 2019-10-04 15:54:24 -07:00
Shane Harvey
a88316b8fd MOTOR-280 Ignore missing with_transaction method 2019-10-03 14:01:33 -07:00
Prashant Mital
b01f810cca
MOTOR-339 Set __module__ to actual module path in metaclass 2019-10-03 23:35:49 +05:30
Shane Harvey
ff4cfdfff9 MOTOR-340 Use python3 tox to run python3 tests on macOS
When tox uses python 2 and runs setup.py to build motor it does not
include the asyncio framework which the python 3 tests expect.
2019-10-01 15:39:12 -07:00
Shane Harvey
a815c55318 MOTOR-406 Fix windows path to python 2019-10-01 14:26:13 -07:00
Shane Harvey
5c2da3887d MOTOR-397 Fix import errors in synchro test 2019-09-26 15:31:00 -07:00
Prashant Mital
343926df85
MOTOR-355 Update ChangeStream documentation to use getResumeToken helper 2019-09-26 14:48:59 +05:30
Prashant Mital
6891990e7a
MOTOR-389 Update GridFS classes to implement the io.IOBase API 2019-09-26 01:40:17 +05:30
Shane Harvey
f58789472e MOTOR-392 Fix test_auth_network_error failure caused by retryable reads
Fix versionadded typo.
2019-09-25 10:43:45 -07:00
Prashant Mital
da14373777
MOTOR-318 Document support for readConcern majority in aggregate with $out 2019-09-13 12:36:41 -07:00
Prashant Mital
fab8b51a52
MOTOR-390 Add aggregate and with_options methods to AgnosticDatabase 2019-09-12 12:04:12 -07:00
Prashant Mital
16529e47f1
MOTOR-297 Add startAfter parameter to watch() methods 2019-09-12 11:25:25 -07:00
Shane Harvey
4222501568 MOTOR-387 Fix test failure caused by retryable reads 2019-09-11 14:03:21 -07:00
Prashant Mital
c428f737bb
MOTOR-391 Add retry_reads attribute to MotorClient 2019-09-10 22:05:42 -07:00
Prashant Mital
b38144e40f
MOTOR-376 Add try_next API to Motor's ChangeStream classes 2019-09-10 17:09:12 -07:00
Prashant Mital
640914126b
MOTOR-377 Reinstate removed reconnection test 2019-09-10 15:42:34 -07:00
Prashant Mital
2fcb53de41
MOTOR-385 Fix failing transactions spec tests
MOTOR-344 Add maxCommitTimeMS option to TransactionOptions
2019-09-06 13:21:26 -07:00
Prashant Mital
64adf52236
MOTOR-378 Bump minimum PyMongo version to 3.9
MOTOR-386 Test against Tornado 6 on Python 3.5+
2019-09-06 09:58:00 -07:00
Prashant Mital
34372d1ace
MOTOR-377 Remove reconnection test 2019-09-05 13:31:34 -07:00
Prashant Mital
0b17a0482c
MOTOR-379 Catch both OperationFailure and InvalidOperation exceptions when running a change stream with a pipeline that removes the _id 2019-09-05 13:31:23 -07:00
Prashant Mital
4b1f141447
MOTOR-352 Test MongoDB 4.2 in Evergreen 2019-09-05 13:19:57 -07:00
Marius Kriegerowski
139f4f6340 Fix change stream docs example (#46) 2019-07-12 18:54:06 +02:00
A. Jesse Jiryu Davis
6af2272072 Copy drivers-evergreen-tools certs 2018-09-30 14:02:31 -04:00
A. Jesse Jiryu Davis
67d8cbcbba Skip TestThreadedAuth in synchrotest 2018-09-30 09:31:14 -04:00
A. Jesse Jiryu Davis
b69b91bd51 Tornado 6-compatible tests 2018-09-30 09:31:00 -04:00
A. Jesse Jiryu Davis
c260535810 Use drivers-evergreen-tools certs in tests 2018-09-30 09:30:38 -04:00
fanjindong
24120b9474 fix: fix sample document 2018-09-25 07:40:39 -04:00
A. Jesse Jiryu Davis
9d871fad4e MOTOR-198 Experimental Windows support 2018-07-17 16:00:28 -04:00
A. Jesse Jiryu Davis
b311c199ff Clean up test matrix 2018-07-13 23:00:00 -04:00
A. Jesse Jiryu Davis
ff51fc04c4 MOTOR-248 Update changelog and requirements 2018-07-11 14:27:17 -04:00
A. Jesse Jiryu Davis
949f0b9b28 Version -> dev0 2018-07-11 03:35:37 -04:00
A. Jesse Jiryu Davis
81c570d2e3
BUMP 2.0.0
Signed-off-by: A. Jesse Jiryu Davis <jesse@mongodb.com>
2018-07-11 03:27:57 -04:00
A. Jesse Jiryu Davis
910f7c065d Changelog for Motor 1.2.4 2018-07-09 18:26:22 -07:00
A. Jesse Jiryu Davis
eb8e48f0d0 Changes and requirements for Motor 1.3 2018-07-05 16:04:54 -07:00
A. Jesse Jiryu Davis
3d41e116f2 Refactor tests' use of EventListener 2018-07-05 16:04:54 -07:00
A. Jesse Jiryu Davis
cef5cb9cf6 Whitespace 2018-07-05 16:04:54 -07:00
A. Jesse Jiryu Davis
4d95efaadb Simplify framework function chain_return_value() 2018-07-05 16:04:54 -07:00
A. Jesse Jiryu Davis
ccf88768ee MOTOR-256 Fix error from cmd cursor batch_size, 2 2018-07-05 16:04:54 -07:00
A. Jesse Jiryu Davis
af59ce8f87 MOTOR-172 Raw batches from find/aggregate 2018-07-05 16:04:54 -07:00
A. Jesse Jiryu Davis
78a27f5def Rearrange cursor docs 2018-07-05 16:04:43 -07:00
A. Jesse Jiryu Davis
8a35d4d59f Permit aggregate synchrotests that pass now 2018-07-03 18:08:57 -07:00
A. Jesse Jiryu Davis
c3f579a818 MOTOR-256 Fix error from cmd cursor batch_size 2018-07-03 18:08:57 -07:00
A. Jesse Jiryu Davis
9c8c5cf758 MOTOR-229 Extend change streams for MongoDB 4.0 2018-07-03 18:08:57 -07:00
A. Jesse Jiryu Davis
59b1ccb3b6 MOTOR-242 Update parallelScan test skip reason 2018-07-01 19:35:53 -04:00
A. Jesse Jiryu Davis
7a33bcc32c MOTOR-246 startAtOperationTime for change streams 2018-07-01 19:33:15 -04:00
A. Jesse Jiryu Davis
f722085e76 MOTOR-229 Database and client change streams 2018-07-01 19:33:15 -04:00
A. Jesse Jiryu Davis
0d6b56b40d MOTOR-254 Port doctests to MongoDB 4.0 2018-07-01 19:32:07 -04:00
A. Jesse Jiryu Davis
e18b6ff5db Don't mention Tornado IOLoop in asyncio docs 2018-07-01 18:47:29 -04:00
A. Jesse Jiryu Davis
eb44d970e7 MOTOR-250 Delete callbacks from function docs 2018-07-01 18:47:29 -04:00
A. Jesse Jiryu Davis
4cefdd43a7 MOTOR-250 Delete callback-based async API 2018-07-01 18:47:29 -04:00
A. Jesse Jiryu Davis
58ac0d0c2b MOTOR-225 Use PyMongo 3.7's sphinx inventory 2018-07-01 18:47:29 -04:00
A. Jesse Jiryu Davis
850539ab63 MOTOR-250 Delete callbacks from code examples 2018-07-01 18:47:29 -04:00
A. Jesse Jiryu Davis
865783a747 MOTOR-242 Motor 2 migration guide 2018-07-01 10:11:26 -04:00
A. Jesse Jiryu Davis
d17de0a5d1 MOTOR-242 Test auth without using authenticate()
The MotorDatabase.authenticate() and MotorDatabase.logout() methods were
deprecated and are now removed. Skip the synchrotests that rely on code
in PyMongo's test_auth.py that still calls them (see PYTHON-1601), and
substitute with Motor direct tests.
2018-06-30 19:02:55 -04:00
A. Jesse Jiryu Davis
f2454bfaec MOTOR-242 Stray references to authenticate() 2018-06-30 09:19:45 -04:00
A. Jesse Jiryu Davis
2925db0d5d Unused import 2018-06-30 09:03:42 -04:00
A. Jesse Jiryu Davis
6d56f78c0a Don't test "reIndex" command
It's been removed from mongos, SERVER-35580.
2018-06-30 08:58:54 -04:00
A. Jesse Jiryu Davis
aad24aea46 Typo in test_environment.py 2018-06-30 08:50:58 -04:00
A. Jesse Jiryu Davis
077e6a448a Quoting style in synchrotest 2018-06-30 08:48:34 -04:00
A. Jesse Jiryu Davis
fddb25215a MOTOR-225 Test with PyMongo from PyPI 2018-06-30 08:45:15 -04:00
A. Jesse Jiryu Davis
ac6064f868 Install MockupDB from PyPI
MockupDB's OP_MSG support has been released.
2018-06-30 08:42:41 -04:00
A. Jesse Jiryu Davis
0dbcfbbda6 MOTOR-239 Test MongoDB 4.0 in Evergreen 2018-06-29 12:48:54 -04:00
A. Jesse Jiryu Davis
297b7f37f8 MOTOR-242 Remove "count" helpers
MotorCollection.count and MotorCursor.count are replaced by
MotorCollection.count_documents and estimated_document_count.
2018-06-29 12:09:11 -04:00
A. Jesse Jiryu Davis
e54ac09626 MOTOR-242 Don't test deprecated socketKeepAlive 2018-06-29 12:09:11 -04:00
A. Jesse Jiryu Davis
84c607c37a Delete some slow tests 2018-06-29 12:09:11 -04:00
A. Jesse Jiryu Davis
e862c3b432 MOTOR-253 Change streams use new async iter style 2018-06-29 12:09:11 -04:00
A. Jesse Jiryu Davis
e6c367c8ef MOTOR-249 Support Python 3.7 2018-06-29 12:09:11 -04:00
A. Jesse Jiryu Davis
8610b9fc18 MOTOR-205 Test Tornado 5 in Evergreen
I added tox configuration for testing Tornado 5, and copied the Tornado
4 build variants in config.yml but forgot to update the copied variants
to test with Tornado 5 instead of 4.
2018-06-29 12:09:11 -04:00
A. Jesse Jiryu Davis
11b21db7de MOTOR-249 Move most testing from RHEL to Ubuntu
Our Ubuntu 16.04 distro has Python 3.7; test Python 3.7 there and move
most other tests as well. I'm having trouble getting Mongo Orchestration
to start MongoDB 3.0 (the oldest supported) on Ubuntu, however, so leave
MongoDB 3.0 variants on RHEL 6.2.
2018-06-29 12:09:11 -04:00
A. Jesse Jiryu Davis
345798b72a MOTOR-252 Drop MongoDB 2.6 2018-06-28 16:25:01 -04:00
A. Jesse Jiryu Davis
08f882190c MOTOR-234 Obsolete code in aggregate() 2018-06-28 09:53:14 -04:00
A. Jesse Jiryu Davis
9a88d92dad MOTOR-248 Don't use "async" as variable name
In Python 3.7, "async" becomes a full-fledged keyword.
2018-06-28 07:49:21 -04:00
A. Jesse Jiryu Davis
21c36dd279 MOTOR-242 Remove 4 deprecated methods
MotorCollection.database_names is deprecated for list_database_names and collection_names for list_collection_names. Remove parallel_scan and
close_cursor.
2018-06-28 07:49:21 -04:00
A. Jesse Jiryu Davis
2614ae83ec MOTOR-242 Remove "group", rewrite changelog prose 2018-06-27 22:48:01 -04:00
A. Jesse Jiryu Davis
0b564cb392 MOTOR-242 Rename motor_py3_compat to py2_compat 2018-06-27 22:48:01 -04:00
A. Jesse Jiryu Davis
6af5bbaf21 MOTOR-242 Drop old CRUD methods 2018-06-27 22:48:01 -04:00
A. Jesse Jiryu Davis
d8be367d13 MOTOR-229 Client/db watch not yet implemented 2018-06-27 22:48:01 -04:00
A. Jesse Jiryu Davis
899c8f9b11 MOTOR-242 Delete ensure_index() 2018-06-27 22:48:01 -04:00
A. Jesse Jiryu Davis
3021767038 MOTOR-225 Update tests for PyMongo 3.7 2018-06-27 22:48:01 -04:00
A. Jesse Jiryu Davis
79295cb92a MOTOR-242 Drop 13 deprecated functions
Take the opportunity of the Motor 2.0 release to drop any deprecated
APIs scheduled for removal in PyMongo. Start with the following:

Collection.initialize_unordered_bulk_op and initialize_ordered_bulk_op
Database.authenticate and logout
Database.eval (database.SystemJS never supported)
Helpers for getLastError and friends
MongoClient.get_default_database and kill_cursors
SON manipulators
2018-06-27 22:38:33 -04:00
A. Jesse Jiryu Davis
2024c7c97c MOTOR-227 Add Motor info to handshake metadata
Motor adds its name, version, and Tornado's version (if appropriate) to
the client data logged by the MongoDB server when Motor connects, in
addition to the data added by PyMongo.
2018-06-27 22:38:33 -04:00
A. Jesse Jiryu Davis
69b70a281b MOTOR-209 Simpler start_session implementation, 2 2018-06-27 22:38:33 -04:00
A. Jesse Jiryu Davis
bee3d63d79 Unused import 2018-06-21 11:13:10 -04:00
A. Jesse Jiryu Davis
85dcfdce0f MOTOR-224 Transaction example for MongoDB Manual 2018-06-21 10:09:41 -04:00
A. Jesse Jiryu Davis
a56618e42a MOTOR-209 Simpler start_session implementation
I didn't realize that "async with await client.start_session():" was
valid Python 3. It is; so users have a convenient way both to await
start_session and to enter an async context management block, so there's
no need to support "async with client.start_session()" with fancy hacks.
2018-06-21 09:59:49 -04:00
A. Jesse Jiryu Davis
391f7a799d MOTOR-245 Test txns and errorLabels 2018-06-20 22:11:18 -04:00
A. Jesse Jiryu Davis
adb8d123ec MOTOR-232 Use Python's full path on Evergreen Mac 2018-06-12 01:51:36 -04:00
A. Jesse Jiryu Davis
45f31dc7b5 MOTOR-128 Remove old GridFS classes 2018-06-10 08:10:40 -04:00
A. Jesse Jiryu Davis
b1d972b969 MOTOR-204 Typo in changelog 2018-06-10 06:53:19 -04:00
A. Jesse Jiryu Davis
6664abad00 MOTOR-243 Fix option inheritance for subcollections 2018-06-10 06:51:55 -04:00
A. Jesse Jiryu Davis
76c328e546 MOTOR-204 Fix attributes test 2018-06-09 16:52:06 -04:00
A. Jesse Jiryu Davis
1b3b69f9cc MOTOR-204 Delete add_user and remove_user 2018-06-09 16:42:58 -04:00
A. Jesse Jiryu Davis
fa52a17cf0 Rename Motor 1.3 to 2.0 2018-06-09 16:34:19 -04:00
A. Jesse Jiryu Davis
c3a6e29a23 Delete racy test of old authenticate method 2018-06-09 16:29:59 -04:00
A. Jesse Jiryu Davis
4f25c98124 MOTOR-243 Skip failing synchro test for now 2018-06-09 16:29:59 -04:00
A. Jesse Jiryu Davis
80fb63767c MOTOR-241 New count API 2018-06-09 16:29:59 -04:00
A. Jesse Jiryu Davis
2cbf0abb3b MOTOR-209 Update transactions tests 2018-06-09 16:29:59 -04:00
A. Jesse Jiryu Davis
ad22c768ea MOTOR-131 Test list_indexes with Tornado
Filling in tests from an old bugfix.
2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
3b30240ab9 MOTOR-209 Support "async with start_session()" 2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
3591cb4b8a MOTOR-225 Fix docs while awaiting PyMongo 3.7 2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
55a3838e4a Developer guide typo 2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
61754b8ba7 MOTOR-209 Test transactions with Motor and Tornado 2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
c4382ea1a4 Simpler MotorClient constructor 2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
bba885f338 MOTOR-209 Wrap ClientSession in Motor class
In Motor 1.2 we supported logical sessions using the ClientSession class
from PyMongo - there was no need to wrap it because it does no I/O and
has no async methods of its own.

Now, closing a session that is in a transaction sends a command that
commits or aborts the transaction, and the application must await the
outcome. Therefore, I wrap ClientSession in a new MotorClientSession
class and apps must use it in "async with" instead of "with".

This incompatible change motivates Motor 2.0.
2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
7c58cbfa4e Update comments in Synchro 2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
c1de57d96d MOTOR-209 Remove Wrap/Unwrap property types
These were useful when only a few Motor methods must wrap or unwrap
PyMongo classes. But in preparation for transactions, I'll wrap
ClientSession in MotorClientSession and nearly all Motor methods will
have to unwrap it before passing to PyMongo. Therefore, move unwrap
logic to Motor's general method decorator, and move wrap logic with it
for simplicity.
2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
606e0ba855 MOTOR-225 Synchrotest fixes for PyMongo 3.7 2018-06-09 16:14:37 -04:00
A. Jesse Jiryu Davis
3fc852e822 MOTOR-232 Use "python" from path on Mac in Evergreen 2018-05-31 10:35:58 -04:00
A. Jesse Jiryu Davis
714f88cfc7 MOTOR-225 Test with PyMongo master on Travis 2018-05-30 10:52:04 -04:00
A. Jesse Jiryu Davis
c3d65ed502 MOTOR-232 Disable Mac tests on Evergreen 2018-05-30 10:49:17 -04:00
A. Jesse Jiryu Davis
a2c406f616 MOTOR-232 Try to install tox on Evergreen Macs 2018-05-30 10:49:17 -04:00
A. Jesse Jiryu Davis
19778a7622 MOTOR-225 Restore PyMongo link in README 2018-05-30 08:22:51 -04:00
A. Jesse Jiryu Davis
ebd2e24298 MOTOR-195 Aggregate, command, index examples 2018-05-30 08:21:22 -04:00
A. Jesse Jiryu Davis
4202093554 Obsolete comment in tox.ini 2018-05-30 08:21:22 -04:00
A. Jesse Jiryu Davis
26193a6108 MOTOR-228 Disable change stream synchrotests
But start implementing ChangeStream in Synchro. It will be completed
once I port Synchro to Python 3.
2018-05-30 08:21:22 -04:00
A. Jesse Jiryu Davis
ff9d25dfce Allow synchrotest selection via tox 2018-05-30 08:21:21 -04:00
A. Jesse Jiryu Davis
709da4cca1 Exit with code 1 if synchrotests fail
Motor's synchrotests had exited with code 0, and appeared to succeed on
Evergreen, even when they failed.
2018-05-30 08:21:21 -04:00
A. Jesse Jiryu Davis
f3da17eab9 MOTOR-225 Depend on PyMongo 3.7 2018-05-30 08:21:21 -04:00
A. Jesse Jiryu Davis
28c2a29454 MOTOR-205 Support Tornado 5 2018-05-30 08:20:50 -04:00
A. Jesse Jiryu Davis
173a78feb3 MOTOR-192 Add disable_md5 GridFS option 2018-05-30 08:20:50 -04:00
Zhong Jin
186dc7ffa0 Update README.rst
motor 1.2 needs to work with Pymongo 3.6 or later
2018-05-26 18:39:58 -04:00
A. Jesse Jiryu Davis
0b56b0718d MOTOR-221 Fix command cursor doc: it DOESN'T allow "explain" 2018-05-22 06:33:47 -04:00
A. Jesse Jiryu Davis
071d651306 Changelog for 1.2.3 2018-05-21 15:06:55 -04:00
A. Jesse Jiryu Davis
c34b865251 Changelog for 1.2.2 2018-05-21 14:53:45 -04:00
Shane Harvey
8ebbb810bd MOTOR-223 Motor 1.2 requires PyMongo 3.6+ (#39) 2018-05-21 11:05:24 -07:00
A. Jesse Jiryu Davis
d35ce7ce36 MOTOR-221 Improve docs for aggregate explain 2018-05-10 15:15:23 -04:00
A. Jesse Jiryu Davis
13bc0d548e Update extension for latest Sphinx 2018-02-27 12:14:56 -05:00
A. Jesse Jiryu Davis
90e816fffc MOTOR-203 - Fix auth tests for MongoDB 4 2018-02-27 12:14:18 -05:00
A. Jesse Jiryu Davis
d26085c805 MOTOR-202 - Drop aiohttp 2, support aiohttp 3 2018-02-27 11:39:32 -05:00
A. Jesse Jiryu Davis
3cc3de3ff3 Update changelog 2018-02-26 21:32:05 -05:00
A. Jesse Jiryu Davis
352f5c78d4 MOTOR-200 - Warn about old TLS versions 2018-02-26 21:29:41 -05:00
A. Jesse Jiryu Davis
6cec3beb8f Install Tornado on Travis 2018-02-21 17:02:09 -05:00
A. Jesse Jiryu Davis
82f3f6e979 Add .travis.yml 2018-02-21 15:52:59 -05:00
A. Jesse Jiryu Davis
e55e879efc Obsolete TODOs 2018-02-21 15:45:59 -05:00
A. Jesse Jiryu Davis
ca746b261c Fix get_version_string() 2018-01-18 06:05:51 -05:00
A. Jesse Jiryu Davis
931614b081 Update docs copyright notice 2018-01-17 13:58:11 -05:00
A. Jesse Jiryu Davis
bb32ef2998 MOTOR-178 Disable synchrotest on Mac again
The synchrotests hang during startup on MongoDB 3.6+ on Mac, only on
Evergreen, cause unknown. I'd thought the bug had gone away so I enabled
the tests, but in fact it still hangs so I'm disabling them again,
probably forever.
2018-01-17 13:48:11 -05:00
A. Jesse Jiryu Davis
d404bae4e3 MOTOR-186 Standard driver API examples 2018-01-17 13:07:38 -05:00
A. Jesse Jiryu Davis
bd19b56109 MOTOR-185 Example clean shutdown w/ change stream 2018-01-11 16:03:45 -05:00
A. Jesse Jiryu Davis
70b18139a2 MOTOR-185 Allow clean shutdown with change stream 2018-01-11 08:08:04 -05:00
A. Jesse Jiryu Davis
cee794adff MOTOR-185 Deadlock closing Change Stream 2018-01-10 09:25:56 -05:00
A. Jesse Jiryu Davis
3cecac6d19 MOTOR-178 enable synchro test on mac 2018-01-04 17:10:06 -05:00
A. Jesse Jiryu Davis
c97a40a119 Version -> + 2017-12-18 15:35:21 -06:00
A. Jesse Jiryu Davis
d091db8dbd
BUMP 1.2.0
Signed-off-by: A. Jesse Jiryu Davis <jesse@mongodb.com>
2017-12-18 15:30:12 -06:00
A. Jesse Jiryu Davis
f0f01e73e7 MOTOR-175 Change streams support "async with" 2017-12-14 21:00:41 -05:00
A. Jesse Jiryu Davis
f27bf6ddd7 Test with both SSL and auth, or neither
Reduces the matrix size nearly by half.
2017-12-14 16:49:05 -05:00
A. Jesse Jiryu Davis
ee588cfb5d Longer timeouts for all async tests 2017-12-14 11:47:51 -05:00
A. Jesse Jiryu Davis
84c67f8923 Longer test_nest_callbacks timeout 2017-12-14 11:44:31 -05:00
A. Jesse Jiryu Davis
12f71628c8 MOTOR-183 Test MongoDB 3.6 in Evergreen 2017-12-14 10:14:09 -05:00
A. Jesse Jiryu Davis
8c20e165af MOTOR-174 Drop Python 3.5.0 and 3.5.1 in GridFS 2017-12-14 08:23:39 -05:00
A. Jesse Jiryu Davis
6f4ca447de MOTOR-174 Stray Python 3.5.0 and 3.5.1 checks 2017-12-13 23:03:16 -05:00
A. Jesse Jiryu Davis
c025306f64 MOTOR-156 Skip change stream tests with one check 2017-12-13 21:39:29 -05:00
A. Jesse Jiryu Davis
47b3cc411d Use insert_one in causal consistency example 2017-12-13 12:43:00 -05:00
Shane Harvey
988ed7e02f Fix causal consistency example. 2017-12-13 12:39:46 -05:00
A. Jesse Jiryu Davis
86c007f117 MOTOR-182 Drop Python 3.3 2017-12-13 11:00:47 -05:00
A. Jesse Jiryu Davis
3813ec65c3 Requirements for building docs 2017-12-12 23:31:29 -05:00
A. Jesse Jiryu Davis
779f286f4b post-release bump 2017-12-12 23:16:57 -05:00
A. Jesse Jiryu Davis
f7b3fdf7b4 BUMP 1.2rc0 2017-12-12 23:09:28 -05:00
A. Jesse Jiryu Davis
6f8e7c1382 Longer test timeouts 2017-12-11 16:07:28 -05:00
A. Jesse Jiryu Davis
92cf753fa9 Switch from Ubuntu to RHEL for testing 2017-12-11 16:07:28 -05:00
A. Jesse Jiryu Davis
973d4e4e53 Simpler auth test setup 2017-12-11 16:07:28 -05:00
A. Jesse Jiryu Davis
41fd54f839 Fix Kerberos/GSSAPI auth test in Evergreen 2017-12-11 14:26:16 -05:00
A. Jesse Jiryu Davis
b69c3d781e MOTOR-178 Disable synchrotest on Mac 2017-12-11 14:26:16 -05:00
A. Jesse Jiryu Davis
d0009b8bfd Unused function in config.yml 2017-12-11 14:26:16 -05:00
A. Jesse Jiryu Davis
d49ff6551a MOTOR-174 Drop Python 3.5.0 and 3.5.1 2017-12-11 14:26:16 -05:00
A. Jesse Jiryu Davis
d0de7315aa MOTOR-176 Drop MongoDB 2.4 support 2017-12-11 14:25:11 -05:00
A. Jesse Jiryu Davis
ded0f055c2 MOTOR-170 Update synchrotests for PyMongo 3.6 2017-12-11 14:25:10 -05:00
A. Jesse Jiryu Davis
6fc92a9ecf Fix Synchro's check for unused skip-test patterns
In synchrotest.py I skip PyMongo tests that match certain patterns, and
I have code to check if patterns have become obsolete as PyMongo's test
suite evolves. That check never ran because the tests quit as soon as
they succeed or fail, this change allows the check to run.
2017-12-11 14:25:09 -05:00
A. Jesse Jiryu Davis
a776d2b15d MOTOR-170 Use PyMongo 3.6 tests with Synchro 2017-12-11 14:25:08 -05:00
A. Jesse Jiryu Davis
219c135179 Update instructions for testing 2017-12-11 14:25:07 -05:00
A. Jesse Jiryu Davis
c08de7c748 Check that doctests are run with correct MongoDB 2017-12-11 14:25:06 -05:00
A. Jesse Jiryu Davis
45f5fcd673 Don't use aiohttp's old finish_connections in test 2017-12-11 14:25:05 -05:00
A. Jesse Jiryu Davis
99723a3e4b MOTOR-156 Skip change stream for old MongoDB 2017-12-11 14:25:04 -05:00
A. Jesse Jiryu Davis
99d8db76c8 Simpler MongoDB server version checks in tests 2017-12-11 14:25:03 -05:00
A. Jesse Jiryu Davis
a8509c92b2 MOTOR-176 Use latest MockupDB for tests
MockupDB 1.2.1 or great is required to simulate MongoDB 2.6+. Older
MockupDB is incompatible with PyMongo 3.6, which requires MongoDB 2.6+.
2017-12-11 14:25:02 -05:00
A. Jesse Jiryu Davis
aea3843af8 MOTOR-177 collection_names & list_collection_names 2017-12-11 14:25:01 -05:00
A. Jesse Jiryu Davis
5fa28a25a5 MOTOR-176 Remove MongoDB 2.4 test code 2017-12-11 14:25:00 -05:00
A. Jesse Jiryu Davis
a87828f561 Unused imports 2017-12-11 14:24:59 -05:00
A. Jesse Jiryu Davis
0d659ed029 MOTOR-156 Change stream methods take no callback 2017-12-11 14:24:57 -05:00
A. Jesse Jiryu Davis
9e687d1634 MOTOR-173 Don't document "callback" param twice 2017-12-11 14:24:57 -05:00
A. Jesse Jiryu Davis
ec9b313500 MOTOR-156 Change stream example 2017-12-11 14:24:56 -05:00
A. Jesse Jiryu Davis
fa2087469a MOTOR-156 Test change streams with asyncio 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
e0d21419a8 MOTOR-156 Begin change stream support 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
de97b8f01f MOTOR-159 Document "session" parameter 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
6bd838d657 MOTOR-157 Document array_filters param 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
b647267a0c MOTOR-166 Document maxTimeMS option for create_indexes 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
277aa0e2dd Don't copy RST files into HTML out dir 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
ea4a8c82b0 MOTOR-158 Add MotorClient.retry_writes property 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
2b921e7d99 MOTOR-158 Note retryable writes in changelog 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
e6886ba96f MOTOR-161 Note mongodb+srv support in changelog 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
e6d8922d86 MOTOR-159 Sessions API 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
8596131341 Allow parallel Sphinx builds 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
13938ecded MOTOR-165 Database enumeration 2017-12-09 12:32:12 -05:00
A. Jesse Jiryu Davis
3fb37ffe06 MOTOR-170 Update changelog 2017-12-06 22:05:04 -05:00
A. Jesse Jiryu Davis
627a2df292 MOTOR-170 Update tests for PyMongo 3.6
PyMongo 3.6 dropped support for MongoDB 2.4, so the MockupDB tests that
simulate MongoDB 2.4 had to be updated to simulate modern MongoDB.

Also, PyMongo's socketKeepAlive now defaults to True.
2017-12-06 22:05:04 -05:00
A. Jesse Jiryu Davis
052faeab6f MOTOR-170 Pin synchrotest to PyMongo 3.4 for now
I'll update to PyMongo 3.6 once I port Motor to it.
2017-12-06 22:05:04 -05:00
A. Jesse Jiryu Davis
9d3fb6965c MOTOR-170 Motor doesn't have PyMongo 3.6 attrs yet 2017-12-06 13:08:03 -05:00
A. Jesse Jiryu Davis
eaaa7688e8 Changelog for 1.2 2017-12-05 22:51:06 -05:00
A. Jesse Jiryu Davis
e7fcf8a741 Merge pull request #36 from karolhor/bug/MOTOR-154-collation-cursor
allow AgnosticCursor.collation to be called as chained method
2017-09-15 04:22:53 -04:00
Karol Horowski
bb9c168f29 skip test_to_list_with_chained_collation if run with mongo < 3.4 2017-09-15 10:18:41 +02:00
Karol Horowski
9f79dded5c allow AgnosticCursor.collation to be called as chained method 2017-09-15 09:15:36 +02:00
A. Jesse Jiryu Davis
f4c5389b4e Sphinx 1.6 compatibility, simpler 2017-07-01 09:31:19 -04:00
A. Jesse Jiryu Davis
c4cc546e1c Sphinx 1.6 compatibility 2017-06-26 22:21:09 -04:00
A. Jesse Jiryu Davis
9e9d9743e7 MOTOR-153 Use macOS 10.12 on Evergreen 2017-06-26 15:26:37 -04:00
A. Jesse Jiryu Davis
645042f0c4 Skip some cursor tests in Synchro 2017-06-26 14:24:51 -04:00
A. Jesse Jiryu Davis
268efd887b Merge pull request #35 from amongouser/patch-1
add support for universal wheels
2017-06-26 10:49:30 -04:00
amongouser
929711253d add support for universal wheels 2017-06-26 10:55:03 +01:00
A. Jesse Jiryu Davis
aa6bc79351 Ensure we test the latest Tornado 2017-06-10 20:12:04 -04:00
A. Jesse Jiryu Davis
38e0a8478a Skip concurrent SSL tests in 2.4 2017-04-26 15:52:26 -05:00
A. Jesse Jiryu Davis
7d70d1e756 MOTOR-151 Delete obsolete class motor.Op 2017-04-23 23:10:39 -05:00
A. Jesse Jiryu Davis
894067faa6 PEP8, order of imports in motor_asyncio 2017-04-23 23:09:09 -05:00
A. Jesse Jiryu Davis
f0048c9de6 MOTOR-138 Del __all__ workaround for Python 2.6 2017-04-23 23:08:31 -05:00
A. Jesse Jiryu Davis
2c91fa5fcc MOTOR-83 Drop Tornado 3 2017-04-23 23:00:05 -05:00
A. Jesse Jiryu Davis
60ac8cb4bc Update frameworks/tornado docstring 2017-04-23 22:47:05 -05:00
A. Jesse Jiryu Davis
1851fdb6fa MOTOR-138 Drop Python 2.6 2017-04-23 22:43:04 -05:00
A. Jesse Jiryu Davis
ebe6f5c25d Stray references to check_optional_callback 2017-04-23 22:24:42 -05:00
A. Jesse Jiryu Davis
1c97061c13 Unused test helper 2017-04-23 21:52:44 -05:00
A. Jesse Jiryu Davis
e3efeb122f MOTOR-133 Show how to init MotorClient after fork 2017-04-23 21:52:44 -05:00
A. Jesse Jiryu Davis
e59bc31ac9 Unused imports 2017-04-23 21:52:44 -05:00
A. Jesse Jiryu Davis
3d38802446 MOTOR-136 Support aiohttp 2.x, drop 1.x 2017-04-23 21:52:44 -05:00
A. Jesse Jiryu Davis
4321ae9f20 Skip cursor delete tests in PyPy 2017-04-23 21:52:43 -05:00
A. Jesse Jiryu Davis
0481d0560c Merge pull request #34 from jwilk/re.sub
Fix incorrect use of regexp flags
2017-04-23 19:49:56 -05:00
A. Jesse Jiryu Davis
e1ab4cda4b MOTOR-137 Delete unreliable find_is_async test 2017-04-23 14:34:42 -05:00
Jakub Wilk
b1909af52f Fix incorrect use of regexp flags
The regexp sub() method doesn't take flags as an argument.
The flags have to be supplied at compile time, and another part of code
already does that.
2017-04-23 20:53:34 +02:00
A. Jesse Jiryu Davis
7f4ce59913 MOTOR-148 PyPy3.5-compatible tests 2017-04-23 11:29:26 -05:00
A. Jesse Jiryu Davis
a9431627f3 MOTOR-150 Skip test_system_certs_config_error in Synchro 2017-04-23 10:32:35 -05:00
A. Jesse Jiryu Davis
f510c6d953 Merge pull request #33 from jwilk/readme
Fix formatting in README
2017-04-23 10:19:55 -04:00
Jakub Wilk
33c8dfb73c Fix formatting in README 2017-04-23 13:15:35 +02:00
Bernie Hackett
88e95fa70a MOTOR-141 - Enable pypy3 testing 2017-04-21 09:50:18 -07:00
181 changed files with 18671 additions and 10928 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,6 @@
set -o xtrace # Write all commands first to stderr
set -o errexit # Exit the script with error if any of the commands fail
# Copy Motor's test certificates over driver-evergreen-tools'
cp ${PROJECT_DIRECTORY}/test/certificates/* ${DRIVERS_TOOLS}/.evergreen/x509gen/
# Replace MongoOrchestration's client certificate.
cp ${PROJECT_DIRECTORY}/test/certificates/client.pem ${MONGO_ORCHESTRATION_HOME}/lib/client.pem
if [ -w /etc/hosts ]; then
SUDO=""
else

View File

@ -2,12 +2,18 @@
# 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"
# BUILD-3830
touch ${PROJECT_DIRECTORY}/.evergreen/krb5.conf.empty
export KRB5_CONFIG=${PROJECT_DIRECTORY}/.evergreen/krb5.conf.empty
echo "Writing keytab"
echo ${KEYTAB_BASE64} | base64 -d > ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab
echo "Running kinit"
@ -19,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.6/bin/python3 -m tox -e "$TOX_ENV" --sitepackages -- -x test.test_auth
bash ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh

View File

@ -1,15 +1,26 @@
#!/bin/sh
#!/bin/bash
set -o xtrace # Write all commands first to stderr
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. "tornado4-py36"
# TOX_ENV Tox environment name, e.g. "synchro", required.
# PYTHON_BINARY Path to python, required.
AUTH=${AUTH:-noauth}
SSL=${SSL:-nossl}
if [ -z $PYTHON_BINARY ]; then
echo "PYTHON_BINARY is undefined!"
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"
@ -20,5 +31,48 @@ if [ "$SSL" != "nossl" ]; then
export CA_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem"
fi
# Run the tests, and store the results in Evergreen compatible XUnit XML
${TOX_BINARY} -e ${TOX_ENV}
if [ -f secrets-export.sh ]; then
source secrets-export.sh
fi
# Usage:
# createvirtualenv /path/to/python /output/path/for/venv
# * param1: Python binary to use for the virtualenv
# * param2: Path to the virtualenv to create
createvirtualenv () {
PYTHON=$1
VENVPATH=$2
if $PYTHON -m virtualenv --version; then
VIRTUALENV="$PYTHON -m virtualenv"
elif $PYTHON -m venv -h > /dev/null; then
# System virtualenv might not be compatible with the python3 on our path
VIRTUALENV="$PYTHON -m venv"
else
echo "Cannot test without virtualenv"
exit 1
fi
# Workaround for bug in older versions of virtualenv.
$VIRTUALENV $VENVPATH || $PYTHON -m venv $VENVPATH
if [ "Windows_NT" = "$OS" ]; then
# Workaround https://bugs.python.org/issue32451:
# mongovenv/Scripts/activate: line 3: $'\r': command not found
dos2unix $VENVPATH/Scripts/activate || true
. $VENVPATH/Scripts/activate
else
. $VENVPATH/bin/activate
fi
python -m pip install -q --upgrade pip
python -m pip install -q --upgrade tox
}
# 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}"

2
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,2 @@
# Initial pre-commit reformat
1e62b868ea58afeb42b3d0346e33776561c16ab6

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
* @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 }}

129
.github/workflows/test-python.yml vendored Normal file
View File

@ -0,0 +1,129 @@
name: Python Tests
on:
push:
pull_request:
concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash -eux {0}
jobs:
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
matrix:
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@v6
with:
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
allow-prereleases: true
- 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
- name: Run tests
run: |
tox -m test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- name: Install Python dependencies
run: |
python -m pip install -U pip tox
- name: Run linters
run: |
tox -m lint-manual
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- name: Install Python dependencies
run: |
python -m pip install -U pip tox
- name: Run docs
run: tox -m docs
- name: Run linkcheck
run: tox -m linkcheck
- 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@v6
with:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- name: Install Python dependencies
run: |
python -m pip install -U pip
- name: Run the release script
run: |
bash release.sh
typing:
name: Typing Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
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
- name: Install dependencies
run: |
python -m pip install -U pip tox
- name: Run mypy
run: |
tox -m typecheck-mypy

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

3
.gitignore vendored
View File

@ -12,3 +12,6 @@ setup.cfg
doc/_build/
.idea/
xunit-results
xunit-synchro-results
.eggs
toxenv

89
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,89 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
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
exclude_types: [json]
- id: forbid-new-submodules
- id: trailing-whitespace
exclude: .patch
exclude_types: [json]
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.3
hooks:
- id: ruff
args: ["--fix", "--show-fixes"]
- id: ruff-format
- repo: https://github.com/adamchainz/blacken-docs
rev: "1.16.0"
hooks:
- id: blacken-docs
additional_dependencies:
- black==22.3.0
- repo: https://github.com/pre-commit/pygrep-hooks
rev: "v1.10.0"
hooks:
- 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.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.29.0
hooks:
- 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"]

23
.readthedocs.yaml Normal file
View File

@ -0,0 +1,23 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the doc/ directory with Sphinx
sphinx:
configuration: doc/conf.py
# Set the version of Python and requirements required to build the docs.
python:
install:
# Install motor itself.
- method: pip
path: .
- requirements: requirements/docs.txt
build:
os: ubuntu-22.04
tools:
python: "3.11"

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,50 +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``: If auth is enabled the test suite creates an admin user by
default, or logs in to the admin database with the username provided
- ``DB_PASSWORD``: If auth is enabled the test suite creates an admin user by
default, or logs in to the admin database with the username provided
- ``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 Python 2.6 and 3.5, and run::
> tox -e tornado4-py26-min,tornado4-py35-min
The doctests pass with Python 3.5 and a MongoDB 3.2 instance running on
port 27017:
> tox -e py3-sphinx-doctest
.. _tox: https://testrun.org/tox/
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

@ -199,4 +199,3 @@
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.

View File

@ -1,8 +0,0 @@
include README.rst
include LICENSE
recursive-include doc *.rst
recursive-include doc *.py
recursive-include test *.py
recursive-include doc *.conf
recursive-include doc *.css
recursive-include doc *.js

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,120 +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.
:Author: A\. Jesse Jiryu Davis
About
=====
Motor presents a callback- or Future-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*
Installation
============
$ pip install motor
Dependencies
============
Motor works in all the environments officially supported by Tornado or by
asyncio. It requires:
* Unix, including Mac OS X. Windows is not supported.
* PyMongo_ 3.4 or later.
* Python 2.6 or later.
* `futures`_ on Python 2.6.
* `backports.pbkdf2`_ for faster authentication with MongoDB 3.0+,
especially on Python older than 2.7.8, or on Python 3 before Python 3.4.
See `requirements <https://motor.readthedocs.io/en/stable/requirements.html>`_
for details about compatibility.
How To Ask For Help
===================
Issues with, questions about, or feedback for Motor should be sent to the
`mongodb-user list on Google Groups`_.
For confirmed issues or feature requests,
open a case in `Jira <http://jira.mongodb.org>`_ in the "MOTOR" project.
Please include all of the following information:
- Detailed steps to reproduce the problem, including your code and a full
traceback, if possible.
- What you expected to happen, and what actually happened.
- The exact python version used, with patch level::
$ python -c "import sys; print(sys.version)"
- The exact version of PyMongo used:
$ 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, ...)
Documentation
=============
Motor's documentation is on ReadTheDocs_.
Build the documentation with Python 3.5. Install sphinx, Tornado, and aiohttp,
and do ``cd doc; make html``.
Examples
========
See the `examples on ReadTheDocs <https://motor.readthedocs.io/en/latest/examples/index.html>`_.
Testing
=======
Run ``python setup.py test``.
Tests are located in the ``test/`` directory.
In Python 2.6, unittest2_ is automatically installed.
.. _PyMongo: http://pypi.python.org/pypi/pymongo/
.. _MongoDB: http://mongodb.org/
.. _Tornado: http://tornadoweb.org/
.. _asyncio: https://docs.python.org/3/library/asyncio.html
.. _futures: https://pypi.python.org/pypi/futures
.. _backports.pbkdf2: https://pypi.python.org/pypi/backports.pbkdf2/
.. _ReadTheDocs: https://motor.readthedocs.io/
.. _mongodb-user list on Google Groups:
https://groups.google.com/forum/?fromgroups#!forum/mongodb-user
.. _sphinx: http://sphinx.pocoo.org/
.. _unittest2: https://pypi.python.org/pypi/unittest2

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

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>`_.
@ -8,421 +14,8 @@ Store blobs of data in `GridFS <http://dochub.mongodb.org/core/gridfs>`_.
.. seealso:: :ref:`Differences between PyMongo's and Motor's GridFS APIs
<gridfs-differences>`.
.. class:: AsyncIOMotorGridFSBucket
Create a new instance of :class:`AsyncIOMotorGridFSBucket`.
Raises :exc:`TypeError` if `database` is not an instance of
:class:`AsyncIOMotorDatabase`.
Raises :exc:`~pymongo.errors.ConfigurationError` if `write_concern`
is not acknowledged.
:Parameters:
- `database`: database to use.
- `bucket_name` (optional): The name of the bucket. Defaults to 'fs'.
- `chunk_size_bytes` (optional): The chunk size in bytes. Defaults
to 255KB.
- `write_concern` (optional): The
:class:`~pymongo.write_concern.WriteConcern` to use. If ``None``
(the default) db.write_concern is used.
- `read_preference` (optional): The read preference to use. If
``None`` (the default) db.read_preference is used.
.. mongodoc:: gridfs
.. coroutinemethod:: delete(self, file_id)
Delete a file's metadata and data chunks from a GridFS bucket::
async def delete():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
# Get _id of file to delete
file_id = await fs.upload_from_stream("test_file",
b"data I want to store!")
await fs.delete(file_id)
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be deleted.
.. coroutinemethod:: download_to_stream(self, file_id, destination)
Downloads the contents of the stored file specified by file_id and
writes the contents to `destination`::
async def download():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
# Get _id of file to read
file_id = await fs.upload_from_stream("test_file",
b"data I want to store!")
# Get file to write to
file = open('myfile','wb+')
await fs.download_to_stream(file_id, file)
file.seek(0)
contents = file.read()
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be downloaded.
- `destination`: a file-like object implementing :meth:`write`.
.. coroutinemethod:: download_to_stream_by_name(self, filename, destination, revision=-1)
Write the contents of `filename` (with optional `revision`) to
`destination`.
For example::
async def download_by_name():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
# Get file to write to
file = open('myfile','wb')
await fs.download_to_stream_by_name("test_file", file)
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
:Parameters:
- `filename`: The name of the file to read from.
- `destination`: A file-like object that implements :meth:`write`.
- `revision` (optional): Which revision (documents with the same
filename and different uploadDate) of the file to retrieve.
Defaults to -1 (the most recent revision).
:Note: Revision numbers are defined as follows:
- 0 = the original stored file
- 1 = the first revision
- 2 = the second revision
- etc...
- -2 = the second most recent revision
- -1 = the most recent revision
.. method:: find(self, *args, **kwargs)
Find and return the files collection documents that match ``filter``.
Returns a cursor that iterates across files matching
arbitrary queries on the files collection. Can be combined
with other modifiers for additional control.
For example::
async def find():
cursor = fs.find({"filename": "lisa.txt"},
no_cursor_timeout=True)
async for grid_data in cursor:
data = grid_data.read()
iterates through all versions of "lisa.txt" stored in GridFS.
Setting no_cursor_timeout may be important to
prevent the cursor from timing out during long multi-file processing
work.
As another example, the call::
most_recent_three = fs.find().sort("uploadDate", -1).limit(3)
returns a cursor to the three most recently uploaded files in GridFS.
Follows a similar interface to :meth:`~AsyncIOMotorCollection.find`
in :class:`AsyncIOMotorCollection`.
:Parameters:
- `filter`: Search query.
- `batch_size` (optional): The number of documents to return per
batch.
- `limit` (optional): The maximum number of documents to return.
- `no_cursor_timeout` (optional): The server normally times out idle
cursors after an inactivity period (10 minutes) to prevent excess
memory use. Set this option to True prevent that.
- `skip` (optional): The number of documents to skip before
returning.
- `sort` (optional): The order by which to sort results. Defaults to
None.
.. coroutinemethod:: open_download_stream(self, file_id)
Opens a stream to read the contents of the stored file specified by file_id::
async def download_stream():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
# get _id of file to read.
file_id = await fs.upload_from_stream("test_file",
b"data I want to store!")
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.
:Parameters:
- `file_id`: The _id of the file to be downloaded.
Returns a :class:`AsyncIOMotorGridOut`.
.. coroutinemethod:: open_download_stream_by_name(self, filename, revision=-1)
Opens a stream to read the contents of `filename` and optional `revision`::
async def download_by_name():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
# get _id of file to read.
file_id = await fs.upload_from_stream("test_file",
b"data I want to store!")
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
that file exists.
Raises :exc:`~ValueError` filename is not a string.
:Parameters:
- `filename`: The name of the file to read from.
- `revision` (optional): Which revision (documents with the same
filename and different uploadDate) of the file to retrieve.
Defaults to -1 (the most recent revision).
Returns a :class:`AsyncIOMotorGridOut`.
:Note: Revision numbers are defined as follows:
- 0 = the original stored file
- 1 = the first revision
- 2 = the second revision
- etc...
- -2 = the second most recent revision
- -1 = the most recent revision
.. method:: open_upload_stream(self, filename, chunk_size_bytes=None, metadata=None)
Opens a stream for writing.
Specify the filename, and add any additional information in the metadata
field of the file document or modify the chunk size::
async def upload():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
grid_in, file_id = fs.open_upload_stream(
"test_file", chunk_size_bytes=4,
metadata={"contentType": "text/plain"})
await grid_in.write(b"data I want to store!")
await grid_in.close() # uploaded on close
Returns an instance of :class:`AsyncIOMotorGridIn`.
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
In a Python 3.5 native coroutine, the "async with" statement calls
:meth:`~AsyncIOMotorGridIn.close` automatically::
async def upload():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
async with await fs.new_file() as gridin:
await gridin.write(b'First part\n')
await gridin.write(b'Second part')
# gridin is now closed automatically.
:Parameters:
- `filename`: The name of the file to upload.
- `chunk_size_bytes` (options): The number of bytes per chunk of this
file. Defaults to the chunk_size_bytes in :class:`AsyncIOMotorGridFSBucket`.
- `metadata` (optional): User data for the 'metadata' field of the
files collection document. If not provided the metadata field will
be omitted from the files collection document.
.. method:: open_upload_stream_with_id(self, file_id, filename, chunk_size_bytes=None, metadata=None)
Opens a stream for writing.
Specify the filed_id and filename, and add any additional information in
the metadata field of the file document, or modify the chunk size::
async def upload():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
grid_in, file_id = fs.open_upload_stream_with_id(
ObjectId(),
"test_file",
chunk_size_bytes=4,
metadata={"contentType": "text/plain"})
await grid_in.write(b"data I want to store!")
await grid_in.close() # uploaded on close
Returns an instance of :class:`AsyncIOMotorGridIn`.
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
:Parameters:
- `file_id`: The id to use for this file. The id must not have
already been used for another file.
- `filename`: The name of the file to upload.
- `chunk_size_bytes` (options): The number of bytes per chunk of this
file. Defaults to the chunk_size_bytes in :class:`AsyncIOMotorGridFSBucket`.
- `metadata` (optional): User data for the 'metadata' field of the
files collection document. If not provided the metadata field will
be omitted from the files collection document.
.. coroutinemethod:: rename(self, file_id, new_filename)
Renames the stored file with the specified file_id.
For example::
async def rename():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
# get _id of file to read.
file_id = await fs.upload_from_stream("test_file",
b"data I want to store!")
await fs.rename(file_id, "new_test_name")
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be renamed.
- `new_filename`: The new name of the file.
.. coroutinemethod:: upload_from_stream(self, filename, source, chunk_size_bytes=None, metadata=None)
Uploads a user file to a GridFS bucket.
Reads the contents of the user file from `source` and uploads
it to the file `filename`. Source can be a string or file-like object.
For example::
async def upload_from_stream():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
file_id = await fs.upload_from_stream(
"test_file",
b"data I want to store!",
chunk_size_bytes=4,
metadata={"contentType": "text/plain"})
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
:Parameters:
- `filename`: The name of the file to upload.
- `source`: The source stream of the content to be uploaded. Must be
a file-like object that implements :meth:`read` or a string.
- `chunk_size_bytes` (options): The number of bytes per chunk of this
file. Defaults to the chunk_size_bytes of :class:`AsyncIOMotorGridFSBucket`.
- `metadata` (optional): User data for the 'metadata' field of the
files collection document. If not provided the metadata field will
be omitted from the files collection document.
Returns the _id of the uploaded file.
.. coroutinemethod:: upload_from_stream_with_id(self, file_id, filename, source, chunk_size_bytes=None, metadata=None)
Uploads a user file to a GridFS bucket with a custom file id.
Reads the contents of the user file from `source` and uploads
it to the file `filename`. Source can be a string or file-like object.
For example::
async def upload_from_stream_with_id():
my_db = AsyncIOMotorClient().test
fs = AsyncIOMotorGridFSBucket(my_db)
file_id = await fs.upload_from_stream_with_id(
ObjectId(),
"test_file",
b"data I want to store!",
chunk_size_bytes=4,
metadata={"contentType": "text/plain"})
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
:Parameters:
- `file_id`: The id to use for this file. The id must not have
already been used for another file.
- `filename`: The name of the file to upload.
- `source`: The source stream of the content to be uploaded. Must be
a file-like object that implements :meth:`read` or a string.
- `chunk_size_bytes` (options): The number of bytes per chunk of this
file. Defaults to the chunk_size_bytes of :class:`AsyncIOMotorGridFSBucket`.
- `metadata` (optional): User data for the 'metadata' field of the
files collection document. If not provided the metadata field will
be omitted from the files collection document.
.. autoclass:: AsyncIOMotorGridFS
.. autoclass:: AsyncIOMotorGridFSBucket
:members:
:exclude-members: find_one, put
.. coroutinemethod:: find_one(self, filter=None, *args, **kwargs)
Get a single file from gridfs.
All arguments to :meth:`find` are also valid arguments for
:meth:`find_one`, although any `limit` argument will be
ignored. Returns a single :class:`AsyncIOMotorGridOut`,
or ``None`` if no matching file is found. For example::
file = await fs.find_one({"filename": "lisa.txt"})
:Parameters:
- `filter` (optional): a dictionary specifying
the query to be performing OR any other type to be used as
the value for a query for ``"_id"`` in the file collection.
- `*args` (optional): any additional positional arguments are
the same as the arguments to :meth:`find`.
- `**kwargs` (optional): any additional keyword arguments
are the same as the arguments to :meth:`find`.
.. coroutinemethod:: put(self, data, **kwargs)
Put data in GridFS as a new file.
Equivalent to doing::
try:
f = await fs.new_file(**kwargs)
await f.write(data)
finally:
await f.close()
`data` can be a :class:`bytes` instance or a file-like object providing a :meth:`read` method.
If an `encoding` keyword argument is passed, `data` can also be a
:class:`str`, which will be encoded as `encoding` before being written. Any keyword arguments
will be passed through to the created file - see
:class:`AsyncIOMotorGridIn` for possible arguments. Returns the
``"_id"`` of the created file.
If the ``"_id"`` of the file is manually specified, it must
not already exist in GridFS. Otherwise
:class:`~gridfs.errors.FileExists` is raised.
:Parameters:
- `data`: data to be written as a file.
- `**kwargs` (optional): keyword arguments for file creation
.. autoclass:: AsyncIOMotorGridIn
:members:

View File

@ -0,0 +1,13 @@
: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
:members:

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

@ -0,0 +1,13 @@
: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
:members:

View File

@ -0,0 +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,11 +1,16 @@
: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
:members:
:exclude-members: create_index, inline_map_reduce
.. describe:: c[name] || c.name
@ -18,110 +23,3 @@
The :class:`AsyncIOMotorDatabase` that this
:class:`AsyncIOMotorCollection` is a part of.
.. coroutinemethod:: create_index(self, keys, **kwargs)
Creates an index on this collection.
Takes either a single key or a list of (key, direction) pairs.
The key(s) must be an instance of :class:`basestring`
(:class:`str` in python 3), and the direction(s) must be one of
(:data:`~pymongo.ASCENDING`, :data:`~pymongo.DESCENDING`,
:data:`~pymongo.GEO2D`, :data:`~pymongo.GEOHAYSTACK`,
:data:`~pymongo.GEOSPHERE`, :data:`~pymongo.HASHED`,
:data:`~pymongo.TEXT`).
To create a single key ascending index on the key ``'mike'`` we just
use a string argument::
await my_collection.create_index("mike")
For a compound index on ``'mike'`` descending and ``'eliot'``
ascending we need to use a list of tuples::
await my_collection.create_index([("mike", pymongo.DESCENDING),
("eliot", pymongo.ASCENDING)])
All optional index creation parameters should be passed as
keyword arguments to this method. For example::
await my_collection.create_index([("mike", pymongo.DESCENDING)],
background=True)
Valid options include, but are not limited to:
- `name`: custom name to use for this index - if none is
given, a name will be generated.
- `unique`: if ``True`` creates a uniqueness constraint on the index.
- `background`: if ``True`` this index should be created in the
background.
- `sparse`: if ``True``, omit from the index any documents that lack
the indexed field.
- `bucketSize`: for use with geoHaystack indexes.
Number of documents to group together within a certain proximity
to a given longitude and latitude.
- `min`: minimum value for keys in a :data:`~pymongo.GEO2D`
index.
- `max`: maximum value for keys in a :data:`~pymongo.GEO2D`
index.
- `expireAfterSeconds`: <int> Used to create an expiring (TTL)
collection. MongoDB will automatically delete documents from
this collection after <int> seconds. The indexed field must
be a UTC datetime or the data will not expire.
- `partialFilterExpression`: A document that specifies a filter for
a partial index.
- `collation` (optional): An instance of
:class:`~pymongo.collation.Collation`. This option is only supported
on MongoDB 3.4 and above.
See the MongoDB documentation for a full list of supported options by
server version.
.. warning:: `dropDups` is not supported by MongoDB 3.0 or newer. The
option is silently ignored by the server and unique index builds
using the option will fail if a duplicate value is detected.
.. note:: `partialFilterExpression` requires server version **>= 3.2**
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
this collection is automatically applied to this operation when using
MongoDB >= 3.4.
:Parameters:
- `keys`: a single key or a list of (key, direction)
pairs specifying the index to create
- `**kwargs` (optional): any additional index creation
options (see the above list) should be passed as keyword
arguments
.. mongodoc:: indexes
.. coroutinemethod:: inline_map_reduce(self, map, reduce, full_response=False, **kwargs)
Perform an inline map/reduce operation on this collection.
Perform the map/reduce operation on the server in RAM. A result
collection is not created. The result set is returned as a list
of documents.
If `full_response` is ``False`` (default) returns the
result documents in a list. Otherwise, returns the full
response from the server to the `map reduce command`_.
The :meth:`inline_map_reduce` method obeys the :attr:`read_preference`
of this :class:`Collection`.
:Parameters:
- `map`: map function (as a JavaScript string)
- `reduce`: reduce function (as a JavaScript string)
- `full_response` (optional): if ``True``, return full response to
this command - otherwise just return the result collection
- `**kwargs` (optional): additional arguments to the
`map reduce command`_ may be passed as keyword arguments to this
helper method, e.g.::
await db.test.inline_map_reduce(map, reduce, limit=2)
.. _map reduce command: http://docs.mongodb.org/manual/reference/command/mapReduce/
.. mongodoc:: mapreduce

View File

@ -1,7 +0,0 @@
:class:`~motor.motor_asyncio.AsyncIOMotorCursor`
================================================
.. currentmodule:: motor.motor_asyncio
.. autoclass:: AsyncIOMotorCursor
:members:

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

@ -0,0 +1,23 @@
: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
:members:
:inherited-members:
:class:`~motor.motor_asyncio.AsyncIOMotorCommandCursor`
=======================================================
.. currentmodule:: motor.motor_asyncio
.. autoclass:: AsyncIOMotorCommandCursor
:members:
:inherited-members:

View File

@ -1,12 +1,21 @@
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
asyncio_motor_client_session
asyncio_motor_database
asyncio_motor_collection
asyncio_motor_cursor
asyncio_motor_change_stream
asyncio_motor_client_encryption
cursors
asyncio_gridfs
aiohttp
@ -14,4 +23,3 @@ Motor asyncio API
This page describes using Motor with asyncio. For Tornado integration, see
:doc:`../api-tornado/index`.

View File

@ -0,0 +1,23 @@
: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
:members:
:inherited-members:
:class:`~motor.motor_tornado.MotorCommandCursor`
================================================
.. currentmodule:: motor.motor_tornado
.. autoclass:: MotorCommandCursor
:members:
:inherited-members:

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>`_.
@ -10,470 +16,8 @@ Store blobs of data in `GridFS <http://dochub.mongodb.org/core/gridfs>`_.
.. seealso:: :doc:`web`
.. class:: MotorGridFSBucket
Create a new instance of :class:`MotorGridFSBucket`.
Raises :exc:`TypeError` if `database` is not an instance of
:class:`MotorDatabase`.
Raises :exc:`~pymongo.errors.ConfigurationError` if `write_concern`
is not acknowledged.
:Parameters:
- `database`: database to use.
- `bucket_name` (optional): The name of the bucket. Defaults to 'fs'.
- `chunk_size_bytes` (optional): The chunk size in bytes. Defaults
to 255KB.
- `write_concern` (optional): The
:class:`~pymongo.write_concern.WriteConcern` to use. If ``None``
(the default) db.write_concern is used.
- `read_preference` (optional): The read preference to use. If
``None`` (the default) db.read_preference is used.
.. mongodoc:: gridfs
.. coroutinemethod:: delete(self, file_id, callback=None)
Delete a file's metadata and data chunks from a GridFS bucket::
@gen.coroutine
def delete():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
# Get _id of file to delete
file_id = yield fs.upload_from_stream("test_file",
b"data I want to store!")
yield fs.delete(file_id)
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be deleted.
- `callback`: (optional): function taking (result, error), executed
when operation completes
If a callback is passed, returns None, else returns a Future.
.. coroutinemethod:: download_to_stream(self, file_id, destination, callback=None)
Downloads the contents of the stored file specified by file_id and
writes the contents to `destination`::
@gen.coroutine
def download():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
# Get _id of file to read
file_id = yield fs.upload_from_stream("test_file",
b"data I want to store!")
# Get file to write to
file = open('myfile','wb+')
yield fs.download_to_stream(file_id, file)
file.seek(0)
contents = file.read()
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be downloaded.
- `destination`: a file-like object implementing :meth:`write`.
- `callback`: (optional): function taking (result, error), executed
when operation completes
If a callback is passed, returns None, else returns a Future.
.. coroutinemethod:: download_to_stream_by_name(self, filename, destination, revision=-1, callback=None)
Write the contents of `filename` (with optional `revision`) to
`destination`.
For example::
@gen.coroutine
def download_by_name():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
# Get file to write to
file = open('myfile','wb')
yield fs.download_to_stream_by_name("test_file", file)
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
:Parameters:
- `filename`: The name of the file to read from.
- `destination`: A file-like object that implements :meth:`write`.
- `revision` (optional): Which revision (documents with the same
filename and different uploadDate) of the file to retrieve.
Defaults to -1 (the most recent revision).
:Note: Revision numbers are defined as follows:
- 0 = the original stored file
- 1 = the first revision
- 2 = the second revision
- etc...
- -2 = the second most recent revision
- -1 = the most recent revision
.. method:: find(self, *args, **kwargs)
Find and return the files collection documents that match ``filter``.
Returns a cursor that iterates across files matching
arbitrary queries on the files collection. Can be combined
with other modifiers for additional control.
For example::
@gen.coroutine
def find():
cursor = fs.find({"filename": "lisa.txt"},
no_cursor_timeout=True)
while (yield cursor.fetch_next):
grid_data = cursor.next_object()
data = grid_data.read()
iterates through all versions of "lisa.txt" stored in GridFS.
Setting no_cursor_timeout may be important to
prevent the cursor from timing out during long multi-file processing
work.
As another example, the call::
most_recent_three = fs.find().sort("uploadDate", -1).limit(3)
returns a cursor to the three most recently uploaded files in GridFS.
Follows a similar interface to :meth:`~MotorCollection.find`
in :class:`MotorCollection`.
:Parameters:
- `filter`: Search query.
- `batch_size` (optional): The number of documents to return per
batch.
- `limit` (optional): The maximum number of documents to return.
- `no_cursor_timeout` (optional): The server normally times out idle
cursors after an inactivity period (10 minutes) to prevent excess
memory use. Set this option to True prevent that.
- `skip` (optional): The number of documents to skip before
returning.
- `sort` (optional): The order by which to sort results. Defaults to
None.
- `callback`: (optional): function taking (result, error), executed
when operation completes
If a callback is passed, returns None, else returns a Future.
.. coroutinemethod:: open_download_stream(self, file_id, callback=None)
Opens a stream to read the contents of the stored file specified by file_id::
@gen.coroutine
def download_stream():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
# get _id of file to read.
file_id = yield fs.upload_from_stream("test_file",
b"data I want to store!")
grid_out = yield fs.open_download_stream(file_id)
contents = yield grid_out.read()
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be downloaded.
- `callback`: (optional): function taking (result, error), executed
when operation completes
If a callback is passed, returns None, else returns a Future that resolves
to a :class:`MotorGridOut`.
.. coroutinemethod:: open_download_stream_by_name(self, filename, revision=-1, callback=None)
Opens a stream to read the contents of `filename` and optional `revision`::
@gen.coroutine
def download_by_name():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
# get _id of file to read.
file_id = yield fs.upload_from_stream("test_file",
b"data I want to store!")
grid_out = yield fs.open_download_stream_by_name(file_id)
contents = yield grid_out.read()
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` filename is not a string.
:Parameters:
- `filename`: The name of the file to read from.
- `revision` (optional): Which revision (documents with the same
filename and different uploadDate) of the file to retrieve.
Defaults to -1 (the most recent revision).
- `callback`: (optional): function taking (result, error), executed
when operation completes
If a callback is passed, returns None, else returns a Future that resolves
to a :class:`MotorGridOut`.
:Note: Revision numbers are defined as follows:
- 0 = the original stored file
- 1 = the first revision
- 2 = the second revision
- etc...
- -2 = the second most recent revision
- -1 = the most recent revision
.. method:: open_upload_stream(self, filename, chunk_size_bytes=None, metadata=None)
Opens a stream for writing.
Specify the filename, and add any additional information in the metadata
field of the file document or modify the chunk size::
@gen.coroutine
def upload():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
grid_in, file_id = fs.open_upload_stream(
"test_file", chunk_size_bytes=4,
metadata={"contentType": "text/plain"})
yield grid_in.write(b"data I want to store!")
yield grid_in.close() # uploaded on close
Returns an instance of :class:`MotorGridIn`.
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
In a Python 3.5 native coroutine, the "async with" statement calls
:meth:`~MotorGridIn.close` automatically::
async def upload():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
async with await fs.new_file() as gridin:
await gridin.write(b'First part\n')
await gridin.write(b'Second part')
# gridin is now closed automatically.
:Parameters:
- `filename`: The name of the file to upload.
- `chunk_size_bytes` (options): The number of bytes per chunk of this
file. Defaults to the chunk_size_bytes in :class:`MotorGridFSBucket`.
- `metadata` (optional): User data for the 'metadata' field of the
files collection document. If not provided the metadata field will
be omitted from the files collection document.
.. method:: open_upload_stream_with_id(self, file_id, filename, chunk_size_bytes=None, metadata=None)
Opens a stream for writing.
Specify the filed_id and filename, and add any additional information in
the metadata field of the file document, or modify the chunk size::
@gen.coroutine
def upload():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
grid_in, file_id = fs.open_upload_stream_with_id(
ObjectId(),
"test_file",
chunk_size_bytes=4,
metadata={"contentType": "text/plain"})
yield grid_in.write(b"data I want to store!")
yield grid_in.close() # uploaded on close
Returns an instance of :class:`MotorGridIn`.
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
:Parameters:
- `file_id`: The id to use for this file. The id must not have
already been used for another file.
- `filename`: The name of the file to upload.
- `chunk_size_bytes` (options): The number of bytes per chunk of this
file. Defaults to the chunk_size_bytes in :class:`MotorGridFSBucket`.
- `metadata` (optional): User data for the 'metadata' field of the
files collection document. If not provided the metadata field will
be omitted from the files collection document.
.. coroutinemethod:: rename(self, file_id, new_filename, callback=None)
Renames the stored file with the specified file_id.
For example::
@gen.coroutine
def rename():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
# get _id of file to read.
file_id = yield fs.upload_from_stream("test_file",
b"data I want to store!")
yield fs.rename(file_id, "new_test_name")
Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists.
:Parameters:
- `file_id`: The _id of the file to be renamed.
- `new_filename`: The new name of the file.
- `callback`: (optional): function taking (result, error), executed
when operation completes
If a callback is passed, returns None, else returns a Future.
.. coroutinemethod:: upload_from_stream(self, filename, source, chunk_size_bytes=None, metadata=None, callback=None)
Uploads a user file to a GridFS bucket.
Reads the contents of the user file from `source` and uploads
it to the file `filename`. Source can be a string or file-like object.
For example::
@gen.coroutine
def upload_from_stream():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
file_id = yield fs.upload_from_stream(
"test_file",
b"data I want to store!",
chunk_size_bytes=4,
metadata={"contentType": "text/plain"})
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
:Parameters:
- `filename`: The name of the file to upload.
- `source`: The source stream of the content to be uploaded. Must be
a file-like object that implements :meth:`read` or a string.
- `chunk_size_bytes` (options): The number of bytes per chunk of this
file. Defaults to the chunk_size_bytes of :class:`MotorGridFSBucket`.
- `metadata` (optional): User data for the 'metadata' field of the
files collection document. If not provided the metadata field will
be omitted from the files collection document.
- `callback`: (optional): function taking (result, error), executed
when operation completes
If a callback is passed, returns None, else returns a Future that resolves
to the _id of the uploaded file.
.. coroutinemethod:: upload_from_stream_with_id(self, file_id, filename, source, chunk_size_bytes=None, metadata=None, callback=None)
Uploads a user file to a GridFS bucket with a custom file id.
Reads the contents of the user file from `source` and uploads
it to the file `filename`. Source can be a string or file-like object.
For example::
@gen.coroutine
def upload_from_stream_with_id():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
file_id = yield fs.upload_from_stream_with_id(
ObjectId(),
"test_file",
b"data I want to store!",
chunk_size_bytes=4,
metadata={"contentType": "text/plain"})
Raises :exc:`~gridfs.errors.NoFile` if no such version of
that file exists.
Raises :exc:`~ValueError` if `filename` is not a string.
:Parameters:
- `file_id`: The id to use for this file. The id must not have
already been used for another file.
- `filename`: The name of the file to upload.
- `source`: The source stream of the content to be uploaded. Must be
a file-like object that implements :meth:`read` or a string.
- `chunk_size_bytes` (options): The number of bytes per chunk of this
file. Defaults to the chunk_size_bytes of :class:`MotorGridFSBucket`.
- `metadata` (optional): User data for the 'metadata' field of the
files collection document. If not provided the metadata field will
be omitted from the files collection document.
- `callback`: (optional): function taking (result, error), executed
when operation completes
If a callback is passed, returns None, else returns a Future.
.. autoclass:: MotorGridFS
.. autoclass:: MotorGridFSBucket
:members:
:exclude-members: find_one, put
.. coroutinemethod:: find_one(self, filter=None, *args, callback=None, **kwargs)
Get a single file from gridfs.
All arguments to :meth:`find` are also valid arguments for
:meth:`find_one`, although any `limit` argument will be
ignored. Returns a single :class:`MotorGridOut`,
or ``None`` if no matching file is found. For example::
file = yield fs.find_one({"filename": "lisa.txt"})
:Parameters:
- `filter` (optional): a dictionary specifying
the query to be performing OR any other type to be used as
the value for a query for ``"_id"`` in the file collection.
- `*args` (optional): any additional positional arguments are
the same as the arguments to :meth:`find`.
- `callback`: (optional): function taking (result, error), executed
when operation completes
- `**kwargs` (optional): any additional keyword arguments
are the same as the arguments to :meth:`find`.
If a callback is passed, returns None, else returns a Future.
.. coroutinemethod:: put(self, data, callback=None, **kwargs)
Put data in GridFS as a new file.
Equivalent to doing::
try:
f = yield fs.new_file(**kwargs)
yield f.write(data)
finally:
yield f.close()
`data` can be a :class:`bytes` instance or a file-like object providing a :meth:`read` method.
If an `encoding` keyword argument is passed, `data` can also be a
:class:`unicode` (:class:`str` in python 3) instance, which will
be encoded as `encoding` before being written. Any keyword arguments
will be passed through to the created file - see
:class:`MotorGridIn` for possible arguments. Returns the
``"_id"`` of the created file.
If the ``"_id"`` of the file is manually specified, it must
not already exist in GridFS. Otherwise
:class:`~gridfs.errors.FileExists` is raised.
:Parameters:
- `data`: data to be written as a file.
- `callback`: (optional): function taking (result, error), executed
when operation completes
- `**kwargs` (optional): keyword arguments for file creation
If a callback is passed, returns None, else returns a Future.
.. autoclass:: MotorGridIn
:members:

View File

@ -1,12 +1,21 @@
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
motor_client_session
motor_database
motor_collection
motor_cursor
motor_change_stream
motor_client_encryption
cursors
gridfs
web

View File

@ -0,0 +1,13 @@
: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
:members:

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

@ -0,0 +1,13 @@
: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
:members:

View File

@ -0,0 +1,13 @@
: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
:members:

View File

@ -1,11 +1,16 @@
: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
:members:
:exclude-members: create_index, inline_map_reduce
.. describe:: c[name] || c.name
@ -18,118 +23,3 @@
The :class:`MotorDatabase` that this
:class:`MotorCollection` is a part of.
.. coroutinemethod:: create_index(self, keys, callback=None, **kwargs)
Creates an index on this collection.
Takes either a single key or a list of (key, direction) pairs.
The key(s) must be an instance of :class:`basestring`
(:class:`str` in python 3), and the direction(s) must be one of
(:data:`~pymongo.ASCENDING`, :data:`~pymongo.DESCENDING`,
:data:`~pymongo.GEO2D`, :data:`~pymongo.GEOHAYSTACK`,
:data:`~pymongo.GEOSPHERE`, :data:`~pymongo.HASHED`,
:data:`~pymongo.TEXT`).
To create a single key ascending index on the key ``'mike'`` we just
use a string argument::
yield my_collection.create_index("mike")
For a compound index on ``'mike'`` descending and ``'eliot'``
ascending we need to use a list of tuples::
yield my_collection.create_index([("mike", pymongo.DESCENDING),
("eliot", pymongo.ASCENDING)])
All optional index creation parameters should be passed as
keyword arguments to this method. For example::
yield my_collection.create_index([("mike", pymongo.DESCENDING)],
background=True)
Valid options include, but are not limited to:
- `name`: custom name to use for this index - if none is
given, a name will be generated.
- `unique`: if ``True`` creates a uniqueness constraint on the index.
- `background`: if ``True`` this index should be created in the
background.
- `sparse`: if ``True``, omit from the index any documents that lack
the indexed field.
- `bucketSize`: for use with geoHaystack indexes.
Number of documents to group together within a certain proximity
to a given longitude and latitude.
- `min`: minimum value for keys in a :data:`~pymongo.GEO2D`
index.
- `max`: maximum value for keys in a :data:`~pymongo.GEO2D`
index.
- `expireAfterSeconds`: <int> Used to create an expiring (TTL)
collection. MongoDB will automatically delete documents from
this collection after <int> seconds. The indexed field must
be a UTC datetime or the data will not expire.
- `partialFilterExpression`: A document that specifies a filter for
a partial index.
- `collation` (optional): An instance of
:class:`~pymongo.collation.Collation`. This option is only supported
on MongoDB 3.4 and above.
See the MongoDB documentation for a full list of supported options by
server version.
.. warning:: `dropDups` is not supported by MongoDB 3.0 or newer. The
option is silently ignored by the server and unique index builds
using the option will fail if a duplicate value is detected.
.. note:: `partialFilterExpression` requires server version **>= 3.2**
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
this collection is automatically applied to this operation when using
MongoDB >= 3.4.
:Parameters:
- `keys`: a single key or a list of (key, direction)
pairs specifying the index to create
- `callback`: (optional): function taking (result, error), executed
when operation completes
- `**kwargs` (optional): any additional index creation
options (see the above list) should be passed as keyword
arguments
If a callback is passed, returns None, else returns a Future.
.. mongodoc:: indexes
.. coroutinemethod:: inline_map_reduce(self, map, reduce, full_response=False, callback=None, **kwargs)
Perform an inline map/reduce operation on this collection.
Perform the map/reduce operation on the server in RAM. A result
collection is not created. The result set is returned as a list
of documents.
If `full_response` is ``False`` (default) returns the
result documents in a list. Otherwise, returns the full
response from the server to the `map reduce command`_.
The :meth:`inline_map_reduce` method obeys the :attr:`read_preference`
of this :class:`Collection`.
:Parameters:
- `map`: map function (as a JavaScript string)
- `reduce`: reduce function (as a JavaScript string)
- `full_response` (optional): if ``True``, return full response to
this command - otherwise just return the result collection
- `callback`: (optional): function taking (result, error), executed
when operation completes
- `**kwargs` (optional): additional arguments to the
`map reduce command`_ may be passed as keyword arguments to this
helper method, e.g.::
yield db.test.inline_map_reduce(map, reduce, limit=2)
If a callback is passed, returns None, else returns a Future.
.. _map reduce command: http://docs.mongodb.org/manual/reference/command/mapReduce/
.. mongodoc:: mapreduce

View File

@ -1,7 +0,0 @@
:class:`~motor.motor_tornado.MotorCursor`
=========================================
.. currentmodule:: motor.motor_tornado
.. autoclass:: MotorCursor
:members:

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,9 +3,691 @@ 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
-----------
- Add support for PyMongo 4.4+.
- Add support for Python 3.12.
- Add inline type hints for public APIs.
- Added new helper methods for Atlas Search Index (requires MongoDB Server 7.0+):
:meth:`~motor.motor_tornado.MotorCollection.list_search_indexes`,
:meth:`~motor.motor_tornado.MotorCollection.create_search_index`,
:meth:`~motor.motor_tornado.MotorCollection.create_search_indexes`,
:meth:`~motor.motor_tornado.MotorCollection.drop_search_index`,
:meth:`~motor.motor_tornado.MotorCollection.update_search_index`
- Added :meth:`~motor.motor_tornado.MotorDatabase.cursor_command`
and :meth:`~motor.motor_tornado.MotorCommandCursor.try_next` to support
executing an arbitrary command that returns a cursor.
Motor 3.2.0
-----------
- Add support for MongoDB 7.0 and PyMongo 4.4+.
- Add support for Queryable Encryption helpers :meth:`~motor.core.MotorClientEncryption.create_encrypted_collection` and
:meth:`~motor.core.MotorClientEncryption.encrypt_expression`.
Backwards-breaking changes may be made before the final release.
- pymongocrypt 1.6.0 or later is now required for Client Side Field Level
Encryption (CSFLE) and Queryable Encryption (QE) support. MongoDB Server 7.0
introduced a backwards breaking change to the QE protocol. Users taking
advantage of the QE must now upgrade to MongoDB 7.0+ and Motor 3.2+.
Motor 3.1.1
-----------
Motor 3.1.1 adds support for Python 3.11 and fixes a bug that caused an
``ImportError`` in Python 3.11.0.
Motor 3.1
---------
Motor 3.1 adds support for PyMongo 4.2+ and the Queryable Encryption beta
with MongoDB 6.0.
Note that backwards-breaking changes may be made before the final release.
New features:
- Support for MongoDB 6.0.
- Added the following key management APIs to :class:`~motor.core.MotorClientEncryption`:
:meth:`~motor.core.MotorClientEncryption.get_key`
:meth:`~motor.core.MotorClientEncryption.get_keys`
:meth:`~motor.core.MotorClientEncryption.delete_key`
:meth:`~motor.core.MotorClientEncryption.add_key_alt_name`
:meth:`~motor.core.MotorClientEncryption.get_key_by_alt_name`
:meth:`~motor.core.MotorClientEncryption.remove_key_alt_name`
:meth:`~motor.core.MotorClientEncryption.rewrap_many_data_key`
- Change streams support for user-facing PIT pre- and post-images using
the new ``full_document_before_change`` argument to
:meth:`~motor.core.MotorClient.watch` and :meth:`~motor.core.MotorCollection.watch`.
- Allow cursor to be used in async with-statement.
The new Queryable Encryption changes that are in beta are:
- 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
:meth:`motor.motor_asyncio.AsyncIOMotorClientEncryption.encrypt` and
:meth:`motor.motor_tornado.MotorClientEncryption.encrypt`.
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 3.1 release notes in JIRA`_ for the list of resolved issues
in this release.
.. _Motor 3.1 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=33421
Motor 3.0
---------
Motor 3.0 adds support for PyMongo 4.0+. It inherits a number
of improvements and breaking API changes from PyMongo 4.0+.
See :doc:`migrate-to-motor-3` for more information.
Breaking Changes
~~~~~~~~~~~~~~~~
- Requires PyMongo 4.0+.
- Removed support for Python 3.5 and 3.6. Python 3.7+ is now required.
- Removed the ``socketKeepAlive`` keyword argument to
:class:`~motor.motor_tornado.MotorClient`.
- Removed :meth:`motor.motor_tornado.MotorClient.fsync`,
:meth:`motor.motor_tornado.MotorClient.unlock`, and
:attr:`motor.motor_tornado.MotorClient.is_locked`.
- Removed :attr:`motor.motor_tornado.MotorClient.max_bson_size`.
- Removed :attr:`motor.motor_tornado.MotorClient.max_message_size`.
- Removed :attr:`motor.motor_tornado.MotorClient.max_write_batch_size`.
- Removed :attr:`motor.motor_tornado.MotorClient.event_listeners`.
- Removed :attr:`motor.motor_tornado.MotorClient.max_pool_size`.
- Removed :attr:`motor.motor_tornado.MotorClient.max_idle_time_ms`.
- Removed :attr:`motor.motor_tornado.MotorClient.local_threshold_ms`.
- Removed :attr:`motor.motor_tornado.MotorClient.server_selection_timeout`.
- Removed :attr:`motor.motor_tornado.MotorClient.retry_writes`.
- Removed :attr:`motor.motor_tornado.MotorClient.retry_reads`.
- Removed support for database profiler helpers
:meth:`~motor.motor_tornado.MotorDatabase.profiling_level`,
:meth:`~motor.motor_tornado.MotorDatabase.set_profiling_level`,
and :meth:`~motor.motor_tornado.MotorDatabase.profiling_info`. Instead, users
should run the profile command with the
:meth:`~motor.motor_tornado.MotorDatabase.command` helper directly.
- Removed :attr:`pymongo.OFF`, :attr:`pymongo.SLOW_ONLY`, and
:attr:`pymongo.ALL`.
- Removed :meth:`motor.motor_tornado.MotorCollection.map_reduce` and
:meth:`motor.motor_tornado.MotorCollection.inline_map_reduce`.
- Removed the ``useCursor`` option for
:meth:`~motor.motor_tornado.MotorCollection.aggregate`.
- Removed :mod:`pymongo.son_manipulator`,
:meth:`motor.motor_tornado.MotorDatabase.add_son_manipulator`,
:attr:`motor.motor_tornado.MotorDatabase.outgoing_copying_manipulators`,
:attr:`motor.motor_tornado.MotorDatabase.outgoing_manipulators`,
:attr:`motor.motor_tornado.MotorDatabase.incoming_copying_manipulators`, and
:attr:`motor.motor_tornado.MotorDatabase.incoming_manipulators`.
- Removed the ``manipulate`` and ``modifiers`` parameters from
:meth:`~motor.motor_tornado.MotorCollection.find`,
:meth:`~motor.motor_tornado.MotorCollection.find_one`,
:meth:`~motor.motor_tornado.MotorCollection.find_raw_batches`, and
:meth:`~motor.motor_tornado.MotorCursor`.
- ``directConnection`` URI option and keyword argument to :class:`~motor.motor_tornado.MotorClient`
defaults to ``False`` instead of ``None``, allowing for the automatic
discovery of replica sets. This means that if you
want a direct connection to a single server you must pass
``directConnection=True`` as a URI option or keyword argument.
- The ``hint`` option is now required when using ``min`` or ``max`` queries
with :meth:`~motor.motor_tornado.MotorCollection.find`.
- When providing a "mongodb+srv://" URI to
:class:`~motor.motor_tornado.MotorClient` constructor you can now use the
``srvServiceName`` URI option to specify your own SRV service name.
- :class:`~motor.motor_tornado.MotorCollection` and :class:`motor.motor_tornado.MotorDatabase`
now raises an error upon evaluating as a Boolean, please use the
syntax ``if collection is not None:`` or ``if database is not None:`` as
opposed to
the previous syntax which was simply ``if collection:`` or ``if database:``.
You must now explicitly compare with None.
- :class:`~motor.motor_tornado.MotorClient` cannot execute any operations
after being closed. The previous behavior would simply reconnect. However,
now you must create a new instance.
- Empty projections (eg {} or []) for
:meth:`~motor.motor_tornado.MotorCollection.find`, and
:meth:`~motor.motor_tornado.MotorCollection.find_one`
are passed to the server as-is rather than the previous behavior which
substituted in a projection of ``{"_id": 1}``. This means that an empty
projection will now return the entire document, not just the ``"_id"`` field.
- :class:`~motor.motor_tornado.MotorClient` now raises a
:exc:`~pymongo.errors.ConfigurationError` when more than one URI is passed
into the ``hosts`` argument.
- :class:`~motor.motor_tornado.MotorClient`` now raises an
:exc:`~pymongo.errors.InvalidURI` exception
when it encounters unescaped percent signs in username and password when
parsing MongoDB URIs.
- 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:`~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.
Notable improvements
~~~~~~~~~~~~~~~~~~~~
- Enhanced connection pooling to create connections more efficiently and
avoid connection storms.
- 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
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.
- 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
:meth:`~motor.motor_tornado.MotorCollection.insert_one`.
- Added support for the ``let`` parameter to
:meth:`~motor.motor_tornado.MotorCollection.update_one`,
:meth:`~motor.motor_tornado.MotorCollection.update_many`,
:meth:`~motor.motor_tornado.MotorCollection.delete_one`,
:meth:`~motor.motor_tornado.MotorCollection.delete_many`,
:meth:`~motor.motor_tornado.MotorCollection.replace_one`,
:meth:`~motor.motor_tornado.MotorCollection.aggregate`,
:meth:`~motor.motor_tornado.MotorCollection.find_one_and_delete`,
:meth:`~motor.motor_tornado.MotorCollection.find_one_and_replace`,
:meth:`~motor.motor_tornado.MotorCollection.find_one_and_update`,
:meth:`~motor.motor_tornado.MotorCollection.find`,
:meth:`~motor.motor_tornado.MotorCollection.find_one`,
and :meth:`~motor.motor_tornado.MotorCollection.bulk_write`.
``let`` is a map of parameter names and values.
Parameters can then be accessed as variables in an aggregate expression
context.
- :meth:`~motor.motor_tornado.MotorCollection.aggregate` now supports
$merge and $out executing on secondaries on MongoDB >=5.0.
aggregate() now always obeys the collection's :attr:`read_preference` on
MongoDB >= 5.0.
- :meth:`gridfs.grid_file.GridOut.seek` now returns the new position in the file, to
conform to the behavior of :meth:`io.IOBase.seek`.
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 3.0 release notes in JIRA`_ for the list of resolved issues
in this release.
.. _Motor 3.0 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=29710
Motor 2.5.1
-----------
Motor 2.5.1 fixes a bug where :meth:`MotorCursor.to_list` could return more
than ``length`` documents.
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 2.5.1 release notes in JIRA`_ for the complete list of resolved
issues in this release.
.. _Motor 2.5.1 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=31791
Motor 2.5
---------
Motor 2.5 adds support for MongoDB 5.0. It depends on PyMongo 3.12 or
later.
New features:
- Added support for MongoDB 5.0.
- Support for MongoDB Stable API, see :class:`~pymongo.server_api.ServerApi`.
- Support for snapshot reads on secondaries via the new ``snapshot`` option to
:meth:`~motor.motor_asyncio.AsyncIOMotorClient.start_session`.
- Support for Azure and GCP KMS providers for client side field level
encryption. See the examples in :doc:`examples/encryption`.
- Support AWS authentication with temporary credentials when connecting to KMS
in client side field level encryption.
- Support for connecting to load balanced MongoDB clusters via the new
``loadBalanced`` URI option.
- Support for creating timeseries collections via the ``timeseries`` and
``expireAfterSeconds`` arguments to
:meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.create_collection`.
- Added :attr:`motor.motor_asyncio.AsyncIOMotorClient.topology_description`.
- Added hash support to :class:`motor.motor_asyncio.AsyncIOMotorClient`,
:class:`motor.motor_asyncio.AsyncIOMotorDatabase`, and
:class:`motor.motor_asyncio.AsyncIOMotorCollection` classes.
- Added session and read concern support to
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_raw_batches`
and :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.aggregate_raw_batches`.
Deprecations:
- Deprecated support for Python 3.5.
- Deprecated :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.profiling_info`,
:meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.profiling_level`, and
:meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.set_profiling_level`.
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 2.5 release notes in JIRA`_ for the complete list of resolved
issues in this release.
.. _Motor 2.5 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=30895
Motor 2.4
---------
Motor 2.4 adds support for client-side field-level encryption
and Python 3.9.
New Features:
- Added the :class:`motor.motor_asyncio.AsyncIOMotorClientEncryption` class,
with the same interface as the corresponding PyMongo class.
See :doc:`examples/encryption` for examples.
- Added support for Python 3.9
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 2.4 release notes in JIRA`_ for the complete list of resolved
issues in this release.
.. _Motor 2.4 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=29749
Motor 2.3.1
-----------
Motor 2.3.1 fixes two bugs related to change streams.
Bug-fixes:
- The :meth:`motor.motor_asyncio.AsyncIOMotorCollection.watch`,
:meth:`motor.motor_asyncio.AsyncIOMotorDatabase.watch`, and
:meth:`motor.motor_asyncio.AsyncIOMotorClient.watch` methods now properly
support passing :class:`~motor.motor_asyncio.AsyncIOMotorClientSession` via
the ``session`` argument.
- Avoid exhausting Motor's worker thread pool when many change streams are
being iterated simultaneously.
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 2.3.1 release notes in JIRA`_ for the complete list of resolved
issues in this release.
.. _Motor 2.3.1 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=30136
Motor 2.3
---------
Motor 2.3 adds support for contextvars.
New features:
- Added supported for the contextvars module. Specifically, it is now possible
to access context variables inside
:class:`~pymongo.monitoring.CommandListener` callbacks.
Bug-fixes:
- Fixed a bug that prohibited users from subclassing the
:class:`motor.motor_asyncio.AsyncIOMotorClient`,
:class:`motor.motor_asyncio.AsyncIOMotorDatabase`, and
:class:`motor.motor_asyncio.AsyncIOMotorCollection` classes.
- Updated the documentation to indicate full support for Windows.
Previously, the documentation stated that Windows support was
experimental.
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 2.3 release notes in JIRA`_ for the complete list of resolved
issues in this release.
.. _Motor 2.3 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=29836
Motor 2.2
---------
Motor 2.2 adds support for MongoDB 4.4 features. It depends on PyMongo 3.11 or
later. Motor continues to support MongoDB 3.0 and later. Motor 2.2 also drops
support for Python 2.7 and Python 3.4.
New features:
- Added the ``AsyncIOMotorCursor`` method
:meth:`~motor.motor_asyncio.AsyncIOMotorCursor.next` that advances the
cursor one document at a time, similar to to the
``AsyncIOMotorChangeStream`` method
:meth:`~motor.motor_asyncio.AsyncIOMotorChangeStream.next`.
- Added index-hinting support to the
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.replace_one`,
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.update_one`,
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.update_many`,
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.delete_one`,
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.delete_many`,
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_one_and_replace`,
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_one_and_update`, and
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_one_and_delete`
methods.
- Added support for the ``allow_disk_use`` parameter to
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find`.
- Modified the :meth:`~motor.motor_asyncio.AsyncIOMotorChangeStream` class'
async context manager such that the change stream cursor is now created
during the call to ``async with``. Previously, the cursor was only created
when the application iterated the
:meth:`~motor.motor_asyncio.AsyncIOMotorChangeStream` object which could
result in the application missing some changes.
- Motor now advertises the framework used by the application to
the MongoDB server as ``asyncio`` or ``Tornado``. Previously, no framework
information was reported if the application used ``asyncio``.
Bug-fixes:
- Fixed a bug that caused calls to the
:meth:`~motor.motor_asyncio.AsyncIOMotorGridOut.open()` method to raise
:exc:`AttributeError`.
- Fixed a bug that sometimes caused :meth:`~asyncio.Future.set_result` to be
called on a cancelled :meth:`~asyncio.Future` when iterating a
:meth:`~motor.motor_asyncio.AsyncIOMotorCommandCursor`.
Deprecations:
- Deprecated ``AsyncIOMotorCursor`` method
:meth:`~motor.motor_asyncio.AsyncIOMotorCursor.next_object` and
property :attr:`~motor.motor_asyncio.AsyncIOMotorCursor.fetch_next`.
Applications should use ``async for`` to iterate over cursors instead.
- Deprecated the :meth:`~motor.motor_asyncio.AsyncIOMotorClient.fsync`
method. Applications should run the
`fsync command <https://mongodb.com/docs/manual/reference/command/fsync/>`_
directly with :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command`
instead.
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 2.2 release notes in JIRA`_ for the complete list of resolved
issues in this release.
.. _Motor 2.2 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=24884
Motor 2.1
---------
Motor 2.1 adds support for MongoDB 4.2 features. It depends on PyMongo 3.10 or
later. Motor continues to support MongoDB 3.0 and later. Motor 2.1 also adds
support for Python 3.8.
Motor now offers experimental support for Windows when it is using the asyncio
event loop. This means it supports Windows exclusively with Python 3, either
integrating with asyncio directly or with Tornado 5 or later: starting in
version 5, Tornado uses the asyncio event loop on Python 3 by default.
Additional changes:
- Support for MongoDB 4.2 sharded transactions. Sharded transactions have
the same API as replica set transactions.
- New method :meth:`~motor.motor_asyncio.AsyncIOMotorClientSession.with_transaction`
to support conveniently running a transaction in a session with automatic
retries and at-most-once semantics.
- Added the ``max_commit_time_ms`` parameter to
:meth:`~motor.motor_asyncio.AsyncIOMotorClientSession.start_transaction`.
- The ``retryWrites`` URI option now defaults to ``True``. Supported write
operations that fail with a retryable error will automatically be retried one
time, with at-most-once semantics.
- Support for retryable reads and the ``retryReads`` URI option which is
enabled by default. See the :class:`~pymongo.mongo_client.MongoClient`
documentation for details. Now that supported operations are retried
automatically and transparently, users should consider adjusting any custom
retry logic to prevent an application from inadvertently retrying for too
long.
- Support zstandard for wire protocol compression.
- Support for periodically polling DNS SRV records to update the mongos proxy
list without having to change client configuration.
- New method :meth:`motor.motor_asyncio.AsyncIOMotorDatabase.aggregate` to
support running database level aggregations.
- Change stream enhancements for MongoDB 4.2:
- Resume tokens can now be accessed from a ``AsyncIOMotorChangeStream`` cursor
using the :attr:`~motor.motor_asyncio.AsyncIOMotorChangeStream.resume_token`
attribute.
- New ``AsyncIOMotorChangeStream`` method
:meth:`~motor.motor_asyncio.AsyncIOMotorChangeStream.try_next` and
attribute :attr:`~motor.motor_asyncio.AsyncIOMotorChangeStream.alive`.
- New parameter ``start_after`` for change stream
:meth:`motor.motor_asyncio.AsyncIOMotorCollection.watch`,
:meth:`motor.motor_asyncio.AsyncIOMotorDatabase.watch`, and
:meth:`motor.motor_asyncio.AsyncIOMotorClient.watch` methods.
- New parameters ``bucket_name``, ``chunk_size_bytes``, ``write_concern``, and
``read_preference`` for :class:`motor.motor_asyncio.AsyncIOMotorGridFSBucket`.
Issues Resolved
~~~~~~~~~~~~~~~
See the `Motor 2.1 release notes in JIRA`_ for the complete list of resolved
issues in this release.
.. _Motor 2.1 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=20187
Motor 2.0
---------
Motor 2.0 drops support for MongoDB 2.6 and adds supports MongoDB 4.0 features,
including multi-document transactions, and change stream notifications on entire
databases or entire MongoDB servers. It adds support for Python 3.7. This
version of Motor requires PyMongo 3.7 or later.
This is a major release that removes previously deprecated APIs.
To support multi-document transactions, Motor had to make breaking changes to
the session API and release a major version bump. Since this is a major release
it also deletes many helper methods and APIs that had been deprecated over the
time since Motor 1.0, most notably the old CRUD methods ``insert``, ``update``,
``remove``, and ``save``, and the original callback-based API. Read the
:doc:`migrate-to-motor-2` carefully to upgrade your existing Motor application.
Documentation is updated to warn about obsolete TLS versions, see
:doc:`configuration`. Motor is now tested on Travis in addition to MongoDB's
`Evergreen <https://github.com/evergreen-ci/evergreen>`_ system.
Added support for `aiohttp`_ 3.0 and later, and dropped older aiohttp versions.
The aiohttp integration now requires Python 3.5+.
The ``MotorDatabase.add_user`` and ``MotorDatabase.remove_user`` methods are
deleted. Manage user accounts with four database commands: createUser_,
usersInfo_, updateUser_, and dropUser_. You can run any database command with
the :meth:`MotorDatabase.command` method.
.. _createUser: https://mongodb.com/docs/manual/reference/command/createUser/
.. _usersInfo: https://mongodb.com/docs/manual/reference/command/usersInfo/
.. _updateUser: https://mongodb.com/docs/manual/reference/command/updateUser/
.. _dropUser: https://mongodb.com/docs/manual/reference/command/createUser/
The deprecated GridFS classes ``MotorGridFS`` and ``AsyncIOMotorGridFS`` are
deleted in favor of :class:`~motor.motor_tornado.MotorGridFSBucket` and
:class:`~motor.motor_asyncio.AsyncIOMotorGridFSBucket`, which conform to driver
specs for GridFS.
Additional changes:
- New methods for retrieving batches of raw BSON:
- :meth:`MotorCollection.find_raw_batches`
- :meth:`MotorCollection.aggregate_raw_batches`
- Motor adds its name, version, and Tornado's version (if appropriate) to the
client data logged by the MongoDB server when Motor connects, in addition to
the data added by PyMongo.
- Calling :meth:`~MotorCommandCursor.batch_size` on a cursor returned from
:meth:`~MotorCollection.aggregate` no longer raises ``AttributeError``.
Motor 1.3.1
-----------
Fix a Python 3.7 compatibility bug caused by importing "async", which is a
keyword in Python 3.7. Drop support for Python 3.4.3 and older.
Motor 1.3.0
-----------
Deprecate Motor's old callback-based async API in preparation for removing it in
Motor 2.0. Raise ``DeprecationWarning`` whenever a callback is passed.
See the :doc:`migrate-to-motor-2`.
Motor 1.2.5
-----------
Fix a Python 3.7 compatibility bug caused by importing "async", which is a
keyword in Python 3.7. Drop support for Python 3.4.3 and older.
Motor 1.2.4
-----------
Fix a Python 3.7 compatibility bug in the :class:`MotorChangeStream` class
returned by :meth:`MotorCollection.watch`. It is now possible to use change
streams in ``async for`` loops in Python 3.7.
Motor 1.2.3
-----------
Compatibility with latest Sphinx and document how to use the latest TLS
protocols.
Motor 1.2.2
-----------
Motor 1.2.0 requires PyMongo 3.6 or later. The dependency was properly
documented, but not enforced in ``setup.py``. PyMongo 3.6 is now an install-time
requirement; thanks to Shane Harvey for the fix.
Motor 1.2.1
-----------
An asyncio application that created a Change Stream with
:meth:`MotorCollection.watch` and shut down while the Change Stream was open
would print several errors. I have rewritten :meth:`MotorChangeStream.next`
and some Motor internals to allow clean shutdown with asyncio.
Motor 1.2
---------
Motor 1.2 drops support for MongoDB 2.4 and adds support for MongoDB 3.6
features. It depends on PyMongo 3.6 or later. Motor continues to support MongoDB
2.6 and later.
Dropped support for Python 2.6 and 3.3. Motor continues to support Python 2.7,
and 3.4+.
Dropped support for Tornado 3. A recent version of Tornado 4 is required.
Dropped support for the `Python 3.5.0 and Python 3.5.1 "async for" protocol
<https://python.org/dev/peps/pep-0492/#api-design-and-implementation-revisions>`_.
Motor allows "async for" with cursors in Python 3.5.2 and later.
See the :ref:`Compatibility Matrix <compatibility-matrix>` for the relationships
among Motor, Python, Tornado, and MongoDB versions.
Added support for `aiohttp`_ 2.0 and later, and dropped older aiohttp versions.
Highlights include:
- New method :meth:`MotorCollection.watch` to acquire a Change Stream on a
collection.
- New Session API to support causal consistency, see
:meth:`MotorClient.start_session`.
- Support for array_filters in
:meth:`~MotorCollection.update_one`,
:meth:`~MotorCollection.update_many`,
:meth:`~MotorCollection.find_one_and_update`,
:meth:`~MotorCollection.bulk_write`.
- :meth:`MotorClient.list_databases` and :meth:`MotorClient.list_database_names`.
- Support for mongodb+srv:// URIs. See
:class:`~pymongo.mongo_client.MongoClient` for details.
- Support for retryable writes and the ``retryWrites`` URI option. See
:class:`~pymongo.mongo_client.MongoClient` for details.
The maximum number of workers in the thread pool can be overridden with an
environment variable, see :doc:`configuration`.
@ -14,6 +696,8 @@ and read_concern arguments. This is rarely needed; you typically create a
:class:`MotorCollection` from a :class:`MotorDatabase`, not by calling its
constructor directly.
Deleted obsolete class ``motor.Op``.
Motor 1.1
---------
@ -25,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.
@ -56,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. ``{}``).
@ -79,13 +763,12 @@ 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 :doc:`migrate-to-motor-1` and
`the PyMongo 3 changelog`_ carefully.
a large number of API changes, read the `the PyMongo 3 changelog`_ carefully.
.. _the PyMongo 3 changelog: http://api.mongodb.com/python/current/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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Motor 1.0, :class:`MotorClient` is the only class. Connect to a replica set with
a "replicaSet" URI option or parameter::
@ -99,8 +782,7 @@ New features
New classes :class:`~motor.motor_tornado.MotorGridFSBucket` and :class:`~motor.motor_asyncio.AsyncIOMotorGridFSBucket`
conform to the `GridFS API Spec <https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.rst>`_
for MongoDB drivers. These classes supersede the old
:class:`~motor.motor_tornado.MotorGridFS` and
:class:`~motor.motor_asyncio.AsyncIOMotorGridFS`. See `GridFS`_ changes below,
``MotorGridFS`` and ``AsyncIOMotorGridFS``. See `GridFS`_ changes below,
especially note the **breaking change** in
:class:`~motor.motor_web.GridFSHandler`.
@ -130,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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -180,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:
@ -212,9 +894,10 @@ Removed:
GridFS
~~~~~~
The old GridFS classes :class:`~motor.motor_tornado.MotorGridFS` and
:class:`~motor.motor_asyncio.AsyncIOMotorGridFS` are deprecated in favor of
:class:`~motor.motor_tornado.MotorGridFSBucket` and :class:`~motor.motor_asyncio.AsyncIOMotorGridFSBucket`,
The old GridFS classes ``MotorGridFS`` and
``AsyncIOMotorGridFS`` are deprecated in favor of
:class:`~motor.motor_tornado.MotorGridFSBucket` and
:class:`~motor.motor_asyncio.AsyncIOMotorGridFSBucket`,
which comply with MongoDB's cross-language driver spec for GridFS.
The old classes are still supported, but will be removed in Motor 2.0.
@ -249,7 +932,8 @@ In a Python 3.5 native coroutine, the "async with" statement calls
async def upload():
my_db = MotorClient().test
fs = MotorGridFSBucket(my_db)
async with await fs.new_file() as gridin:
async with await fs.open_upload_stream(
"test_file", metadata={"contentType": "text/plain"}) as gridin:
await gridin.write(b'First part\n')
await gridin.write(b'Second part')
@ -290,8 +974,7 @@ This version updates the PyMongo dependency from 2.8.0 to 2.9.x, and wraps
PyMongo 2.9's new APIs.
Most of Motor 1.0's API is now implemented, and APIs that will be removed in
Motor 1.0 are now deprecated and raise warnings. See the
:doc:`/migrate-to-motor-1` to prepare your code for Motor 1.0.
Motor 1.0 are now deprecated and raise warnings.
:class:`MotorClient` changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -406,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.
@ -445,7 +1128,7 @@ accessible, Motor collections now allow dict-style access, the same as Motor
clients and databases always have::
# New in Motor 0.6
subcollection = collection['_subcollection']
subcollection = collection['_subcollection']
These changes solve problems with iPython code completion and the Python 3
:class:`ABC` abstract base class.
@ -473,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():
@ -496,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.
@ -534,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`
@ -545,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`
@ -553,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`
@ -582,13 +1265,13 @@ Then instead, write::
The negative limit ensures the server closes the cursor after one result,
saving Motor the work of closing it. See `cursor.limit
<http://docs.mongodb.org/v3.0/reference/method/cursor.limit/>`_.
<https://mongodb.com/docs/v3.0/reference/method/cursor.limit/>`_.
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.
@ -618,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
<http://api.mongodb.org/python/current/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
@ -630,13 +1312,9 @@ used it, see `MOTOR-56 <https://jira.mongodb.org/browse/MOTOR-56>`_.
You can still use the :meth:`MotorDatabase.command` method directly.
The only scenario not supported is copying a database from one host to
another, if the remote host requires authentication.
For this, use PyMongo's `copy_database`_ method, or, since PyMongo's
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.
.. _copy_database: http://api.mongodb.org/python/current/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.copy_database
.. seealso:: `The "copydb" command <http://docs.mongodb.org/manual/reference/command/copydb/>`_.
Motor 0.3.3
-----------
@ -722,7 +1400,7 @@ If it's important to test that MongoDB is available before continuing
your application's startup, use ``IOLoop.run_sync``::
loop = tornado.ioloop.IOLoop.current()
client = motor.MotorClient(host, port)
client = motor.motor_tornado.MotorClient(host, port)
try:
loop.run_sync(client.open)
except pymongo.errors.ConnectionFailure:
@ -908,7 +1586,7 @@ returns a Future instead of accepting a callback.
.. _Greenlet: http://pypi.python.org/pypi/greenlet/
.. _asynchronous resolver interface: http://www.tornadoweb.org/en/stable/netutil.html#tornado.netutil.Resolver
.. _pycares: https://pypi.python.org/pypi/pycares
.. _fsyncLock: http://docs.mongodb.org/manual/reference/method/db.fsyncLock/
.. _fsyncLock: https://mongodb.com/docs/manual/reference/method/db.fsyncLock/
New Features
~~~~~~~~~~~~
@ -927,7 +1605,7 @@ PyMongo 2.6 and 2.7:
with :meth:`~MotorCursor.max_time_ms`.
* A new :meth:`MotorGridFS.find` method for querying GridFS.
.. _authentication examples: http://api.mongodb.org/python/current/examples/authentication.html
.. _authentication examples: https://pymongo.readthedocs.io/en/stable/examples/authentication.html
Bugfixes
~~~~~~~~

View File

@ -1,37 +1,44 @@
# -*- 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
import sys, os
sys.path[0:0] = [os.path.abspath('..')]
sys.path[0:0] = [os.path.abspath("..")]
from pymongo import version as pymongo_version
import motor
import motor # noqa: E402
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage',
'sphinx.ext.todo', 'doc.mongo_extensions', 'doc.motor_extensions',
'sphinx.ext.intersphinx', 'doc.coroutine_annotation']
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.coverage",
"sphinx.ext.todo",
"doc.mongo_extensions",
"doc.motor_extensions",
"sphinx.ext.intersphinx",
"doc.coroutine_annotation",
]
primary_domain = 'py'
primary_domain = "py"
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = ".rst"
# The master toctree document.
master_doc = 'index'
# The root toctree document.
root_doc = "index"
# General information about the project.
project = u'Motor'
copyright = u'2016 MongoDB, Inc.'
project = "Motor"
copyright = "2016-present MongoDB, Inc."
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@ -47,33 +54,42 @@ unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']
exclude_trees = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# default_role = None
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# 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'
autoclass_content = "init"
doctest_path = [os.path.abspath('..')]
doctest_path = [os.path.abspath("..")]
# Don't test examples pulled from PyMongo's docstrings just because they start
# with '>>>'
doctest_test_doctest_blocks = ''
doctest_test_doctest_blocks = ""
doctest_global_setup = """
import pprint
@ -85,7 +101,24 @@ from tornado.ioloop import IOLoop
import pymongo
from pymongo.mongo_client import MongoClient
sync_client = MongoClient()
hello = sync_client.admin.command('hello')
server_info = sync_client.server_info()
if 'setName' in hello:
raise Exception(
"Run doctests with standalone MongoDB 5.0 server, not a replica set")
if hello.get('msg') == 'isdbgrid':
raise Exception(
"Run doctests with standalone MongoDB 5.0 server, not mongos")
if server_info['versionArray'][:2] != [5, 0]:
raise Exception(
"Run doctests with standalone MongoDB 5.0 server, not %s" % (
server_info['version'], ))
sync_client.drop_database("doctest_test")
db = sync_client.doctest_test
@ -95,102 +128,108 @@ from motor import MotorClient
# -- Options for HTML output ---------------------------------------------------
# Theme gratefully vendored from CPython source.
html_theme = "pydoctheme"
html_theme_path = ["."]
html_theme_options = {'collapsiblesidebar': True}
html_static_path = ['static']
html_copy_source = False
html_sidebars = {
'index': ['globaltoc.html', 'searchbox.html'],
}
try:
import furo # noqa: F401
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".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# html_static_path = ['_static']
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'Motor' + release.replace('.', '_')
htmlhelp_basename = "Motor" + release.replace(".", "_")
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Motor.tex', u'Motor Documentation',
u'A. Jesse Jiryu Davis', 'manual'),
("index", "Motor.tex", "Motor Documentation", "A. Jesse Jiryu Davis", "manual"),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
# latex_use_modindex = True
autodoc_default_flags = ['inherited-members']
autodoc_member_order = 'groupwise'
autodoc_default_options = {
"inherited-members": True,
"member-order": "groupwise",
}
pymongo_inventory = ('http://api.mongodb.com/python/%s/' % pymongo_version,
None)
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),
'python': ('https://docs.python.org/3/', None),
"pymongo": pymongo_inventory,
"aiohttp": ("http://aiohttp.readthedocs.io/en/stable/", None),
"tornado": ("http://www.tornadoweb.org/en/stable/", None),
"python": ("https://docs.python.org/3/", None),
}

View File

@ -1,9 +1,69 @@
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
''''''''''''''''''''
Industry best practices, and some regulations, require the use
of TLS 1.1 or newer. Though no application changes are required for
Motor to make use of the newest protocols, some operating systems or
versions may not provide an OpenSSL version new enough to support them.
Users of macOS older than 10.13 (High Sierra) will need to install Python
from `python.org`_, `homebrew`_, `macports`_, or another similar source.
Users of Linux or other non-macOS Unix can check their OpenSSL version like
this::
$ openssl version
If the version number is less than 1.0.1 support for TLS 1.1 or newer is not
available. Contact your operating system vendor for a solution or upgrade to
a newer distribution.
You can check your Python interpreter by installing the `requests`_ module
and executing the following command::
python -c "import requests; print(requests.get('https://www.howsmyssl.com/a/check', verify=False).json()['tls_version'])"
You should see "TLS 1.X" where X is >= 1.
You can read more about TLS versions and their security implications in this `cheat sheet`_.
.. _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
''''''''''''''''
Motor uses the Python standard library's :class:`~concurrent.futures.ThreadPoolExecutor` to defer network
operations to threads. By default, the executor uses at most five threads per CPU core on your
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

@ -10,3 +10,12 @@ The following is a list of people who have contributed to
- Rémi Jolin
- Andrew Svetlov
- Nikolay Novik
- Prashant Mital
- Shane Harvey
- Bulat Khasanov
- William Zhou
- Tushar Singh
- Steven Silvester
- Julius Park
- Doeke Buursma
- Scott Luu

View File

@ -1,29 +1,28 @@
"""Gratefully adapted from aiohttp, provides coroutine support to autodoc."""
from sphinx.domains.python import PyModulelevel, PyClassmember
from sphinx import addnodes
from sphinx.domains.python import PyFunction, PyMethod
class PyCoroutineMixin(object):
class PyCoroutineMixin:
def handle_signature(self, sig, signode):
ret = super(PyCoroutineMixin, self).handle_signature(sig, signode)
signode.insert(0, addnodes.desc_annotation('coroutine ', 'coroutine '))
ret = super().handle_signature(sig, signode)
signode.insert(0, addnodes.desc_annotation("coroutine ", "coroutine "))
return ret
class PyCoroutineFunction(PyCoroutineMixin, PyModulelevel):
class PyCoroutineFunction(PyCoroutineMixin, PyFunction):
def run(self):
self.name = 'py:function'
return PyModulelevel.run(self)
self.name = "py:function"
return PyFunction.run(self)
class PyCoroutineMethod(PyCoroutineMixin, PyClassmember):
class PyCoroutineMethod(PyCoroutineMixin, PyMethod):
def run(self):
self.name = 'py:method'
return PyClassmember.run(self)
self.name = "py:method"
return PyMethod.run(self)
def setup(app):
app.add_directive_to_domain('py', 'coroutinefunction', PyCoroutineFunction)
app.add_directive_to_domain('py', 'coroutinemethod', PyCoroutineMethod)
return {'version': '1.0', 'parallel_read_safe': True}
app.add_directive_to_domain("py", "coroutinefunction", PyCoroutineFunction)
app.add_directive_to_domain("py", "coroutinemethod", PyCoroutineMethod)
return {"version": "1.0", "parallel_read_safe": True}

View File

@ -2,47 +2,58 @@
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
-------------
Motor is single-source compatible with all supported Python versions, although
there are some tricks for Python 3. There is some code for the ``async``
and ``await`` features of Python 3.5+ that is conditionally compiled with ``eval``
in ``core.py``.
Motor supports the asyncio module in the standard library of Python 3.5.3 and
later.
Motor also works with Tornado 5.0 and later along with all the Python versions
it supports.
In ``setup.py`` there are tricks to conditionally import tests depending on
Python version. ``setup.py`` also avoids installing the ``frameworks/asyncio``
directory in a Python 2 environment.
Motor supports a range of Tornado versions, and all the Python versions those
Tornado versions work with. Motor still supports Tornado 3 because New Relic
has `only begun to support Tornado 4`_, and therefore Motor continues to
support Python 2.6.
Each new Motor feature release depends on the latest PyMongo minor version release
or newer, up to the next PyMongo major version release. For example, if 3.10
is the latest available PyMongo version when Motor 2.1 is being released, Motor 2.1
will require 3.10<=PyMongo<4.
Frameworks
----------
Motor abstracts the differences between Tornado and asyncio by wrapping each in a "framework" interface. A Motor framework
is a module implementing these properties and functions:
Motor abstracts the differences between Tornado and asyncio by wrapping each in a "framework" interface.
A Motor framework is a module implementing these properties and functions:
- ``CLASS_PREFIX``
- ``add_future``
- ``call_soon``
- ``chain_future``
- ``chain_return_value``
- ``check_event_loop``
- ``coroutine``
- ``future_or_callback``
- ``coroutine`` (**DEPRECATED**)
- ``get_event_loop``
- ``get_future``
- ``is_event_loop``
- ``is_future``
- ``platform_info``
- ``pymongo_class_wrapper``
- ``run_on_executor``
- ``yieldable``
- ``yieldable`` (**DEPRECATED**)
See the ``frameworks/tornado`` and ``frameworks/asyncio`` modules.
.. note:: Starting in Motor 2.2, the functions marked **DEPRECATED** in the
list above are not used internally in Motor. Instead of being removed
from the codebase, they have been left in a deprecated state to avoid
breaking any libraries built on top of Motor. These deprecated functions
may be removed in a future major release.
A framework-specific class, like ``MotorClient`` for Tornado or
``AsyncIOMotorClient`` for asyncio, is created by the
``create_class_with_framework`` function, which combines a framework with a
@ -84,8 +95,8 @@ framework to:
- create a ``Future`` that will be resolved by the event loop when the thread finishes
- returns the ``Future`` to the caller
This is what allows Tornado or asyncio coroutines to call Motor methods with
``yield``, ``yield from``, or ``await`` to await I/O without blocking the event loop.
This is what allows Tornado or asyncio awaitables to call Motor methods with
``await`` to await I/O without blocking the event loop.
Synchro
-------
@ -102,6 +113,4 @@ 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 synchro`` to check out
PyMongo test suite and run it with Synchro.
.. _only begun to support Tornado 4: https://docs.newrelic.com/docs/agents/python-agent/hosting-mechanisms/introductory-tornado-4-support
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.
@ -18,73 +24,11 @@ Motor provides a single client class, :class:`MotorClient`. Unlike PyMongo's
not begin connecting in the background when it is instantiated. Instead it
connects on demand, when you first attempt an operation.
Callbacks and Futures
---------------------
Coroutines
----------
Motor supports nearly every method PyMongo does, but Motor methods that
do network I/O take an optional callback function. The callback must accept two
parameters:
.. code-block:: python
def callback(result, error):
pass
Motor's asynchronous methods return immediately, and execute the
callback, with either a result or an error, when the operation has completed.
For example, :meth:`~pymongo.collection.Collection.find_one` is used in PyMongo
like:
.. code-block:: python
db = MongoClient().test
user = db.users.find_one({'name': 'Jesse'})
print user
But Motor's :meth:`~MotorCollection.find_one` method is asynchronous:
.. code-block:: python
db = MotorClient().test
def got_user(user, error):
if error:
print 'error getting user!', error
else:
print user
db.users.find_one({'name': 'Jesse'}, callback=got_user)
The callback must be passed as a keyword argument, not a positional argument.
To find multiple documents, Motor provides :meth:`~MotorCursor.to_list`:
.. code-block:: python
def got_users(users, error):
if error:
print 'error getting users!', error
else:
for user in users:
print user
db.users.find().to_list(length=10, callback=got_users)
.. seealso:: :meth:`~MotorCursor.fetch_next`
If you pass no callback to an asynchronous method, it returns a Future for use
in a :func:`coroutine <tornado.gen.coroutine>`:
.. code-block:: python
from tornado import gen
@gen.coroutine
def f():
result = yield motor_db.collection.insert_one({'name': 'Randall'})
doc = yield motor_db.collection.find_one()
See :ref:`the coroutine example <coroutine-example>`.
do network I/O are *coroutines*. See :doc:`tutorial-tornado`.
Threading and forking
---------------------
@ -105,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
@ -115,25 +59,24 @@ 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)
grid_in, file_id = fs.open_upload_stream('test_file')
grid_in = fs.open_upload_stream('test_file')
grid_in.close()
grid_in.my_field = 'my_value' # Sends update to server.
Updating metadata on a :class:`MotorGridIn` is asynchronous, so
the API is different::
@gen.coroutine
def f():
async def f():
fs = motor.motor_tornado.MotorGridFSBucket(db)
grid_in, file_id = fs.open_upload_stream('test_file')
yield grid_in.close()
grid_in = fs.open_upload_stream('test_file')
await grid_in.close()
# Sends update to server.
yield grid_in.set('my_field', 'my_value')
await grid_in.set('my_field', 'my_value')
.. seealso:: :doc:`../api-tornado/gridfs`.
@ -145,7 +88,7 @@ In PyMongo ``is_locked`` is a property of
server has been fsyncLocked requires I/O, Motor has no such convenience method.
The equivalent in Motor is::
result = yield client.admin.current_op()
result = await client.admin.current_op()
locked = bool(result.get('fsyncLock', None))
system_js
@ -153,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
@ -164,7 +107,7 @@ Motor does not.
Cursor slicing
--------------
In Pymongo, the following raises an ``IndexError`` if the collection has fewer
In PyMongo, the following raises an ``IndexError`` if the collection has fewer
than 101 documents:
.. code-block:: python
@ -176,13 +119,12 @@ In Motor, however, no exception is raised. The query simply has no results:
.. code-block:: python
@gen.coroutine
def f():
async def f():
cursor = db.collection.find()[100]
# Iterates zero or one times.
while (yield cursor.fetch_next):
doc = cursor.next_object()
# Iterates zero or one time.
async for doc in cursor:
print(doc)
The difference arises because the PyMongo :class:`~pymongo.cursor.Cursor`'s
slicing operator blocks until it has queried the MongoDB server, and determines
@ -197,26 +139,15 @@ 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:
.. code-block:: python
@gen.coroutine
def f():
yield db.create_collection(
'collection1',
capped=True,
size=1000)
async def f():
await db.create_collection("collection1", capped=True, size=1000)

View File

@ -1,51 +1,59 @@
# These comments let tutorial-asyncio.rst include this code in sections.
# -- setup-start --
import asyncio
from aiohttp import web
from motor.motor_asyncio import AsyncIOMotorClient
@asyncio.coroutine
def setup_db():
async def setup_db():
db = AsyncIOMotorClient().test
yield from db.pages.drop()
html = '<html><body>{}</body></html>'
yield from db.pages.insert_one({'_id': 'page-one',
'body': html.format('Hello!')})
await db.pages.drop()
html = "<html><body>{}</body></html>"
await db.pages.insert_one({"_id": "page-one", "body": html.format("Hello!")})
yield from db.pages.insert_one({'_id': 'page-two',
'body': html.format('Goodbye.')})
await db.pages.insert_one({"_id": "page-two", "body": html.format("Goodbye.")})
return db
# -- setup-end --
# -- handler-start --
@asyncio.coroutine
def page_handler(request):
async def page_handler(request):
# If the visitor gets "/pages/page-one", then page_name is "page-one".
page_name = request.match_info.get('page_name')
page_name = request.match_info.get("page_name")
# Retrieve the long-lived database handle.
db = request.app['db']
db = request.app["db"]
# Find the page by its unique id.
document = yield from db.pages.find_one(page_name)
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")
return web.Response(body=document['body'].encode(),
content_type='text/html')
# -- handler-end --
# -- main-start --
loop = asyncio.get_event_loop()
db = loop.run_until_complete(setup_db())
app = web.Application()
app['db'] = db
# Route requests to the page_handler() coroutine.
app.router.add_get('/pages/{page_name}', page_handler)
web.run_app(app)
async def init_connection():
db = await setup_db()
app = web.Application()
app["db"] = db
# Route requests to the page_handler() coroutine.
app.router.add_get("/pages/{page_name}", page_handler)
return app
def main():
app = init_connection()
web.run_app(app)
if __name__ == "__main__":
main()
# -- main-end --

View File

@ -1,5 +1,7 @@
"""Serve pre-compressed static content from GridFS with aiohttp.
Requires Python 3.5 or later and aiohttp 3.0 or later.
Start a MongoDB server on its default port, run this script, and visit:
http://localhost:8080/fs/my_file
@ -12,7 +14,7 @@ import tempfile
import aiohttp.web
from motor.aiohttp import AIOHTTPGridFS
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorGridFS
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorGridFSBucket
client = AsyncIOMotorClient()
@ -20,33 +22,33 @@ client = AsyncIOMotorClient()
# Use Motor to put compressed data in GridFS, with filename "my_file".
async def put_gridfile():
with tempfile.TemporaryFile() as tmp:
with gzip.GzipFile(mode='wb', fileobj=tmp) as gzfile:
with gzip.GzipFile(mode="wb", fileobj=tmp) as gzfile:
for _ in range(10):
gzfile.write(b'Nonesuch nonsense\n')
gzfile.write(b"Nonesuch nonsense\n")
gfs = AsyncIOMotorGridFS(client.my_database)
gfs = AsyncIOMotorGridFSBucket(client.my_database)
tmp.seek(0)
await gfs.put(tmp,
filename='my_file',
content_type='text',
metadata={'compressed': True})
await gfs.upload_from_stream(
filename="my_file", source=tmp, metadata={"contentType": "text", "compressed": True}
)
asyncio.get_event_loop().run_until_complete(put_gridfile())
asyncio.run(put_gridfile())
# Add "Content-Encoding: gzip" header for compressed data.
def gzip_header(response, gridout):
if gridout.metadata.get('compressed'):
response.headers['Content-Encoding'] = 'gzip'
if gridout.metadata.get("compressed"):
response.headers["Content-Encoding"] = "gzip"
gridfs_handler = AIOHTTPGridFS(client.my_database,
set_extra_headers=gzip_header)
gridfs_handler = AIOHTTPGridFS(client.my_database, set_extra_headers=gzip_header)
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)
aiohttp.web.run_app(app)

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.
@ -11,10 +18,8 @@ replica sets or sharded clusters, ``--keyFile``. Create an admin user and
optionally normal users or read-only users.
.. seealso:: `MongoDB Authentication
<http://docs.mongodb.org/manual/tutorial/control-access-to-mongodb-with-authentication/>`_
<https://mongodb.com/docs/manual/tutorial/control-access-to-mongodb-with-authentication/>`_
Authentication at Startup
-------------------------
To create an authenticated connection use a `MongoDB connection URI`_::
uri = "mongodb://user:pass@localhost:27017/database_name"
@ -22,19 +27,4 @@ To create an authenticated connection use a `MongoDB connection URI`_::
Motor logs in to the server on demand, when you first attempt an operation.
Asynchronous Authentication
---------------------------
Use the non-blocking :meth:`~MotorDatabase.authenticate` method to log
in after starting the IOLoop::
client = motor.motor_tornado.MotorClient('localhost', 27017)
@gen.coroutine
def login(c):
yield c.my_database.authenticate("user", "pass")
After you've logged in to a database with a given :class:`MotorClient`, all further
operations on that database using that client will already be authenticated
until you call :meth:`~MotorDatabase.logout`.
.. _MongoDB connection URI: http://docs.mongodb.org/manual/reference/connection-string/
.. _MongoDB connection URI: https://mongodb.com/docs/manual/reference/connection-string/

View File

@ -0,0 +1,101 @@
import asyncio
import os
from bson import json_util
from bson.codec_options import CodecOptions
from pymongo.encryption import Algorithm
from pymongo.encryption_options import AutoEncryptionOpts
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorClientEncryption
async def create_json_schema_file(kms_providers, key_vault_namespace, key_vault_client):
client_encryption = AsyncIOMotorClientEncryption(
kms_providers,
key_vault_namespace,
key_vault_client,
# The CodecOptions class used for encrypting and decrypting.
# This should be the same CodecOptions instance you have configured
# on MotorClient, Database, or Collection. We will not be calling
# encrypt() or decrypt() in this example so we can use any
# CodecOptions.
CodecOptions(),
)
# Create a new data key and json schema for the encryptedField.
# https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
data_key_id = await client_encryption.create_data_key(
"local", key_alt_names=["pymongo_encryption_example_1"]
)
schema = {
"properties": {
"encryptedField": {
"encrypt": {
"keyId": [data_key_id],
"bsonType": "string",
"algorithm": Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic,
}
}
},
"bsonType": "object",
}
# Use CANONICAL_JSON_OPTIONS so that other drivers and tools will be
# able to parse the MongoDB extended JSON file.
json_schema_string = json_util.dumps(schema, json_options=json_util.CANONICAL_JSON_OPTIONS)
with open("jsonSchema.json", "w") as file:
file.write(json_schema_string)
async def main():
# The MongoDB namespace (db.collection) used to store the
# encrypted documents in this example.
encrypted_namespace = "test.coll"
# This must be the same master key that was used to create
# the encryption key.
local_master_key = os.urandom(96)
kms_providers = {"local": {"key": local_master_key}}
# The MongoDB namespace (db.collection) used to store
# the encryption data keys.
key_vault_namespace = "encryption.__pymongoTestKeyVault"
key_vault_db_name, key_vault_coll_name = key_vault_namespace.split(".", 1)
# The MotorClient used to access the key vault (key_vault_namespace).
key_vault_client = AsyncIOMotorClient()
key_vault = key_vault_client[key_vault_db_name][key_vault_coll_name]
# Ensure that two data keys cannot share the same keyAltName.
await key_vault.drop()
await key_vault.create_index(
"keyAltNames", unique=True, partialFilterExpression={"keyAltNames": {"$exists": True}}
)
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") as file:
json_schema_string = file.read()
json_schema = json_util.loads(json_schema_string)
schema_map = {encrypted_namespace: json_schema}
auto_encryption_opts = AutoEncryptionOpts(
kms_providers, key_vault_namespace, schema_map=schema_map
)
client = AsyncIOMotorClient(auto_encryption_opts=auto_encryption_opts)
db_name, coll_name = encrypted_namespace.split(".", 1)
coll = client[db_name][coll_name]
# Clear old data
await coll.drop()
await coll.insert_one({"encryptedField": "123456789"})
decrypted_doc = await coll.find_one()
print(f"Decrypted document: {decrypted_doc}")
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name]
encrypted_doc = await unencrypted_coll.find_one()
print(f"Encrypted document: {encrypted_doc}")
if __name__ == "__main__":
asyncio.run(main())

View File

@ -1,8 +1,17 @@
.. currentmodule:: motor.motor_tornado
.. _bulk-write-tutorial:
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()
@ -28,11 +37,11 @@ bulk insert operations.
.. doctest::
>>> @gen.coroutine
... def f():
... yield db.test.insert_many(({'i': i} for i in range(10000)))
... count = yield db.test.count()
>>> async def f():
... 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
@ -40,11 +49,9 @@ bulk insert operations.
Mixed Bulk Write Operations
---------------------------
.. versionadded:: 0.2
Motor also supports executing mixed bulk write operations. A batch
of insert, update, and delete operations can be executed together using
the Bulk API.
of insert, update, and remove operations can be executed together using
the bulk write operations API.
.. _ordered_bulk:
@ -52,26 +59,28 @@ Ordered Bulk Write Operations
.............................
Ordered bulk write operations are batched and sent to the server in the
order provided for serial execution. The return value is a document
describing the type and count of operations performed.
order provided for serial execution. The return value is an instance of
:class:`~pymongo.results.BulkWriteResult` describing the type and count
of operations performed.
.. doctest::
:options: +NORMALIZE_WHITESPACE
>>> from pprint import pprint
>>>
>>> @gen.coroutine
... def f():
... bulk = db.test.initialize_ordered_bulk_op()
... # Remove all documents from the previous example.
... bulk.find({}).remove()
... bulk.insert({'_id': 1})
... bulk.insert({'_id': 2})
... bulk.insert({'_id': 3})
... bulk.find({'_id': 1}).update({'$set': {'foo': 'bar'}})
... bulk.find({'_id': 4}).upsert().update({'$inc': {'j': 1}})
... bulk.find({'j': 1}).replace_one({'j': 2})
... result = yield bulk.execute()
... pprint(result)
>>> 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}),
... ]
... )
... pprint(result.bulk_api_result)
...
>>> IOLoop.current().run_sync(f)
{'nInserted': 3,
@ -84,28 +93,28 @@ describing the type and count of operations performed.
'writeErrors': []}
The first write failure that occurs (e.g. duplicate key error) aborts the
remaining operations, and Motor raises :class:`~pymongo.errors.BulkWriteError`.
The :attr:`details` attibute of the exception instance provides the execution
results up until the failure occurred and details about the failure - including
the operation that caused the failure.
remaining operations, and Motor raises
:class:`~pymongo.errors.BulkWriteError`. The :attr:`details` attribute of
the exception instance provides the execution results up until the failure
occurred and details about the failure - including the operation that caused
the failure.
.. doctest::
:options: +NORMALIZE_WHITESPACE
>>> from pymongo import InsertOne, DeleteOne, ReplaceOne
>>> from pymongo.errors import BulkWriteError
>>>
>>> @gen.coroutine
... def f():
... bulk = db.test.initialize_ordered_bulk_op()
... bulk.find({'j': 2}).replace_one({'i': 5})
... # Violates the unique key constraint on _id.
...
... bulk.insert({'_id': 4})
... bulk.find({'i': 5}).remove_one()
>>> async def f():
... requests = [
... ReplaceOne({"j": 2}, {"i": 5}),
... InsertOne({"_id": 4}), # Violates the unique key constraint on _id.
... DeleteOne({"i": 5}),
... ]
... try:
... yield bulk.execute()
... except BulkWriteError as err:
... pprint(err.details)
...
... await db.test.bulk_write(requests)
... except BulkWriteError as bwe:
... pprint(bwe.details)
...
>>> IOLoop.current().run_sync(f)
{'nInserted': 0,
'nMatched': 1,
@ -117,6 +126,8 @@ the operation that caused the failure.
'writeErrors': [{'code': 11000,
'errmsg': '... duplicate key error ...',
'index': 1,
'keyPattern': {'_id': 1},
'keyValue': {'_id': 4},
'op': {'_id': 4}}]}
.. _unordered_bulk:
@ -133,19 +144,20 @@ constraint on _id. Since we are doing unordered execution the second
and fourth operations succeed.
.. doctest::
:options: +NORMALIZE_WHITESPACE
>>> @gen.coroutine
... def f():
... bulk = db.test.initialize_unordered_bulk_op()
... bulk.insert({'_id': 1})
... bulk.find({'_id': 2}).remove_one()
... bulk.insert({'_id': 3})
... bulk.find({'_id': 4}).replace_one({'i': 1})
>>> async def f():
... requests = [
... InsertOne({"_id": 1}),
... DeleteOne({"_id": 2}),
... InsertOne({"_id": 3}),
... ReplaceOne({"_id": 4}, {"i": 1}),
... ]
... try:
... yield bulk.execute()
... except BulkWriteError as err:
... pprint(err.details)
...
... await db.test.bulk_write(requests, ordered=False)
... except BulkWriteError as bwe:
... pprint(bwe.details)
...
>>> IOLoop.current().run_sync(f)
{'nInserted': 0,
'nMatched': 1,
@ -157,41 +169,37 @@ and fourth operations succeed.
'writeErrors': [{'code': 11000,
'errmsg': '... duplicate key error ...',
'index': 0,
'keyPattern': {'_id': 1},
'keyValue': {'_id': 1},
'op': {'_id': 1}},
{'code': 11000,
'errmsg': '... duplicate key error ...',
'index': 2,
'keyPattern': {'_id': 1},
'keyValue': {'_id': 3},
'op': {'_id': 3}}]}
Write Concern
.............
By default bulk operations are executed with the
:meth:`~MotorCollection.write_concern` of the collection they are
executed against, typically the default write concern ``{w: 1}``. A custom
write concern can be passed to the
:meth:`~MotorBulkOperationBuilder.execute` method. Write concern
errors (e.g. wtimeout) will be reported after all operations are attempted,
regardless of execution order.
Bulk operations are executed with the
:attr:`~pymongo.collection.Collection.write_concern` of the collection they
are executed against. Write concern errors (e.g. wtimeout) will be reported
after all operations are attempted, regardless of execution order.
.. doctest::
:options: +SKIP
.. Standalone MongoDB raises "can't use w>1" with this example, so skip it.
>>> @gen.coroutine
... def f():
... bulk = db.test.initialize_ordered_bulk_op()
... bulk.insert({'a': 0})
... bulk.insert({'a': 1})
... bulk.insert({'a': 2})
... bulk.insert({'a': 3})
>>> from pymongo import WriteConcern
>>> async def f():
... coll = db.get_collection("test", write_concern=WriteConcern(w=4, wtimeout=1))
... try:
... # Times out if the replica set has fewer than four members.
... yield bulk.execute({'w': 4, 'wtimeout': 1})
... except BulkWriteError as err:
... pprint(err.details)
...
... await coll.bulk_write([InsertOne({"a": i}) for i in range(4)])
... except BulkWriteError as bwe:
... pprint(bwe.details)
...
>>> IOLoop.current().run_sync(f)
{'nInserted': 4,
'nMatched': 0,

View File

@ -1,201 +0,0 @@
.. currentmodule:: motor.motor_tornado
Examples With Callbacks And Coroutines
======================================
Programming with Motor is far easier with Tornado coroutines than with
raw callbacks. Here's an example that shows the difference.
This page describes using Motor with Tornado. Beginning in
version 0.5 Motor can also integrate with asyncio instead of Tornado.
.. contents::
With callbacks
--------------
An application that can create and display short messages:
.. code-block:: python
import tornado.web, tornado.ioloop
import motor
class NewMessageHandler(tornado.web.RequestHandler):
def get(self):
"""Show a 'compose message' form."""
self.write('''
<form method="post">
<input type="text" name="msg">
<input type="submit">
</form>''')
# Method exits before the HTTP request completes, thus "asynchronous"
@tornado.web.asynchronous
def post(self):
"""Insert a message."""
msg = self.get_argument('msg')
# Async insert; callback is executed when insert completes
self.settings['db'].messages.insert_one(
{'msg': msg},
callback=self._on_response)
def _on_response(self, result, error):
if error:
raise tornado.web.HTTPError(500, error)
else:
self.redirect('/')
class MessagesHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
"""Display all messages."""
self.write('<a href="/compose">Compose a message</a><br>')
self.write('<ul>')
db = self.settings['db']
db.messages.find().sort([('_id', -1)]).each(self._got_message)
def _got_message(self, message, error):
if error:
raise tornado.web.HTTPError(500, error)
elif message:
self.write('<li>%s</li>' % message['msg'])
else:
# Iteration complete
self.write('</ul>')
self.finish()
db = motor.motor_tornado.MotorClient().test
application = tornado.web.Application(
[
(r'/compose', NewMessageHandler),
(r'/', MessagesHandler)
],
db=db
)
print('Listening on http://localhost:8888')
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
The call to :meth:`~MotorCursor.each` could be
replaced with :meth:`~MotorCursor.to_list`, which is easier to use
with templates because the callback receives the entire result at once:
.. code-block:: python
from tornado import template
messages_template = template.Template('''<ul>
{% for message in messages %}
<li>{{ message['msg'] }}</li>
{% end %}
</ul>''')
class MessagesHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
"""Display all messages
"""
self.write('<a href="/compose">Compose a message</a><br>')
self.write('<ul>')
db = self.settings['db']
(db.messages.find()
.sort([('_id', -1)])
.limit(10)
.to_list(length=10, callback=self._got_messages))
def _got_messages(self, messages, error):
if error:
raise tornado.web.HTTPError(500, error)
elif messages:
self.write(messages_template.generate(messages=messages))
self.finish()
To protect you from buffering huge numbers of documents in memory, ``to_list``
requires a maximum ``length`` argument.
.. _coroutine-example:
With coroutines
---------------
Motor's asynchronous methods return `Futures <tornado.concurrent.Future>`.
Yield a Future to resolve it into a result or an exception:
.. code-block:: python
from tornado import gen
class NewMessageHandler(tornado.web.RequestHandler):
@gen.coroutine
def post(self):
"""Insert a message."""
msg = self.get_argument('msg')
db = self.settings['db']
# insert_one() returns a Future. Yield the Future to get the result.
result = yield db.messages.insert_one({'msg': msg})
# Success
self.redirect('/')
class MessagesHandler(tornado.web.RequestHandler):
@gen.coroutine
def get(self):
"""Display all messages."""
self.write('<a href="/compose">Compose a message</a><br>')
self.write('<ul>')
db = self.settings['db']
cursor = db.messages.find().sort([('_id', -1)])
while (yield cursor.fetch_next):
message = cursor.next_object()
self.write('<li>%s</li>' % message['msg'])
# Iteration complete
self.write('</ul>')
self.finish()
One can parallelize operations and wait for all to complete. To query for
two messages at once and wait for both:
.. code-block:: python
msg = yield db.messages.find_one({'_id': msg_id})
# Start getting the previous. find_one returns a Future.
prev_future = db.messages.find_one({'_id': {'$lt': msg_id}})
# Start getting the next.
next_future = db.messages.find_one({'_id': {'$gt': msg_id}})
# Wait for both to complete by yielding the Futures.
previous_msg, next_msg = yield [prev_future, next_future]
`async` and `await`
-------------------
Python 3.5 introduces `native coroutines`_ that use the `async` and `await`
keywords. Starting in Python 3.5, you can use `async def` instead of the
`gen.coroutine` decorator, and replace the while-loop with `async for`:
.. code-block:: python
class MessagesHandler(tornado.web.RequestHandler):
async def get(self):
"""Display all messages."""
self.write('<a href="/compose">Compose a message</a><br>')
self.write('<ul>')
db = self.settings['db']
# New in Python 3.5:
async for message in db.messages.find().sort([('_id', -1)]):
self.write('<li>%s</li>' % message['msg'])
self.write('</ul>')
self.finish()
This version of the code is dramatically faster.
.. _native coroutines: https://www.python.org/dev/peps/pep-0492/

131
doc/examples/encryption.rst Normal file
View File

@ -0,0 +1,131 @@
.. _Client-Side Field Level Encryption:
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
<https://dochub.mongodb.org/core/security-encryption-at-rest>`_ and
`TLS/SSL (Transport Encryption)
<https://dochub.mongodb.org/core/security-tls-transport-encryption>`_.
With field level encryption, applications can encrypt fields in documents
*prior* to transmitting data over the wire to the server. Client-side field
level encryption supports workloads where applications must guarantee that
unauthorized parties, including server administrators, cannot read the
encrypted data.
.. mongodoc:: client-side-field-level-encryption
Dependencies
------------
To get started using client-side field level encryption in your project,
you will need to install the
`pymongocrypt <https://pypi.org/project/pymongocrypt/>`_ library
as well as the driver itself. Install both the driver and a compatible
version of pymongocrypt like this::
$ python -m pip install 'motor[encryption]'
Note that installing on Linux requires pip 19 or later for manylinux2010 wheel
support. For more information about installing pymongocrypt see
`the installation instructions on the project's PyPI page
<https://pypi.org/project/pymongocrypt/>`_.
mongocryptd
-----------
The ``mongocryptd`` binary is required for automatic client-side encryption
and is included as a component in the `MongoDB Enterprise Server package
<https://dochub.mongodb.org/core/install-mongodb-enterprise>`_. For more
information on this binary, see the `PyMongo documentation on mongocryptd
<https://pymongo.readthedocs.io/en/stable/examples/encryption.html>`_.
Automatic Client-Side Field Level Encryption
--------------------------------------------
Automatic client-side field level encryption is enabled by creating a
:class:`~motor.motor_asyncio.AsyncIOMotorClient` with the ``auto_encryption_opts``
option set to an instance of
:class:`~pymongo.encryption_options.AutoEncryptionOpts`. The following
examples show how to setup automatic client-side field level encryption
using :class:`~motor.motor_asyncio.AsyncIOMotorClientEncryption` to create a new
encryption data key.
.. note:: Automatic client-side field level encryption requires MongoDB 4.2+
enterprise or a MongoDB 4.2+ Atlas cluster. The community version of the
server supports automatic decryption as well as
:ref:`explicit-client-side-encryption`.
Providing Local Automatic Encryption Rules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following example shows how to specify automatic encryption rules via the
``schema_map`` option. The automatic encryption rules are expressed using a
`strict subset of the JSON Schema syntax
<https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules>`_.
Supplying a ``schema_map`` provides more security than relying on
JSON Schemas obtained from the server. It protects against a
malicious server advertising a false JSON Schema, which could trick
the client into sending unencrypted data that should be encrypted.
JSON Schemas supplied in the ``schema_map`` only apply to configuring
automatic client-side field level encryption. Other validation
rules in the JSON schema will not be enforced by the driver and
will result in an error.
.. literalinclude:: auto_csfle_example.py
:language: python3
Server-Side Field Level Encryption Enforcement
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The MongoDB 4.2+ server supports using schema validation to enforce encryption
of specific fields in a collection. This schema validation will prevent an
application from inserting unencrypted values for any fields marked with the
``"encrypt"`` JSON schema keyword.
The following example shows how to setup automatic client-side field level
encryption using
:class:`~motor.motor_asyncio.AsyncIOMotorClientEncryption` to create a new encryption
data key and create a collection with the
`Automatic Encryption JSON Schema Syntax
<https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules>`_.
.. literalinclude:: server_fle_enforcement_example.py
:language: python3
.. _explicit-client-side-encryption:
Explicit Encryption
~~~~~~~~~~~~~~~~~~~
Explicit encryption is a MongoDB community feature and does not use the
``mongocryptd`` process. Explicit encryption is provided by the
:class:`~motor.motor_asyncio.AsyncIOMotorClientEncryption` class, for example:
.. literalinclude:: explicit_encryption_example.py
:language: python3
Explicit Encryption with Automatic Decryption
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Although automatic encryption requires MongoDB 4.2 enterprise or a
MongoDB 4.2 Atlas cluster, automatic *decryption* is supported for all users.
To configure automatic *decryption* without automatic *encryption* set
``bypass_auto_encryption=True`` in
:class:`~pymongo.encryption_options.AutoEncryptionOpts`:
.. literalinclude:: explicit_encryption_automatic_decryption_example.py
:language: python3

View File

@ -0,0 +1,76 @@
import asyncio
import os
from pymongo.encryption import Algorithm
from pymongo.encryption_options import AutoEncryptionOpts
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorClientEncryption
async def main():
# This must be the same master key that was used to create
# the encryption key.
local_master_key = os.urandom(96)
kms_providers = {"local": {"key": local_master_key}}
# The MongoDB namespace (db.collection) used to store
# the encryption data keys.
key_vault_namespace = "encryption.__pymongoTestKeyVault"
key_vault_db_name, key_vault_coll_name = key_vault_namespace.split(".", 1)
# bypass_auto_encryption=True disable automatic encryption but keeps
# the automatic _decryption_ behavior. bypass_auto_encryption will
# also disable spawning mongocryptd.
auto_encryption_opts = AutoEncryptionOpts(
kms_providers, key_vault_namespace, bypass_auto_encryption=True
)
client = AsyncIOMotorClient(auto_encryption_opts=auto_encryption_opts)
coll = client.test.coll
# Clear old data
await coll.drop()
# Set up the key vault (key_vault_namespace) for this example.
key_vault = client[key_vault_db_name][key_vault_coll_name]
# Ensure that two data keys cannot share the same keyAltName.
await key_vault.drop()
await key_vault.create_index(
"keyAltNames", unique=True, partialFilterExpression={"keyAltNames": {"$exists": True}}
)
client_encryption = AsyncIOMotorClientEncryption(
kms_providers,
key_vault_namespace,
# The MotorClient to use for reading/writing to the key vault.
# This can be the same MotorClient used by the main application.
client,
# The CodecOptions class used for encrypting and decrypting.
# This should be the same CodecOptions instance you have configured
# on MotorClient, Database, or Collection.
coll.codec_options,
)
# Create a new data key for the encryptedField.
_ = await client_encryption.create_data_key(
"local", key_alt_names=["pymongo_encryption_example_4"]
)
# Explicitly encrypt a field:
encrypted_field = await client_encryption.encrypt(
"123456789",
Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic,
key_alt_name="pymongo_encryption_example_4",
)
await coll.insert_one({"encryptedField": encrypted_field})
# Automatically decrypts any encrypted fields.
doc = await coll.find_one()
print(f"Decrypted document: {doc}")
unencrypted_coll = AsyncIOMotorClient().test.coll
print(f"Encrypted document: {await unencrypted_coll.find_one()}")
# Cleanup resources.
await client_encryption.close()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -0,0 +1,68 @@
import asyncio
import os
from pymongo.encryption import Algorithm
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorClientEncryption
async def main():
# This must be the same master key that was used to create
# the encryption key.
local_master_key = os.urandom(96)
kms_providers = {"local": {"key": local_master_key}}
# The MongoDB namespace (db.collection) used to store
# the encryption data keys.
key_vault_namespace = "encryption.__pymongoTestKeyVault"
key_vault_db_name, key_vault_coll_name = key_vault_namespace.split(".", 1)
# The MotorClient used to read/write application data.
client = AsyncIOMotorClient()
coll = client.test.coll
# Clear old data
await coll.drop()
# Set up the key vault (key_vault_namespace) for this example.
key_vault = client[key_vault_db_name][key_vault_coll_name]
# Ensure that two data keys cannot share the same keyAltName.
await key_vault.drop()
await key_vault.create_index(
"keyAltNames", unique=True, partialFilterExpression={"keyAltNames": {"$exists": True}}
)
client_encryption = AsyncIOMotorClientEncryption(
kms_providers,
key_vault_namespace,
# The Motorlient to use for reading/writing to the key vault.
# This can be the same MotorClient used by the main application.
client,
# The CodecOptions class used for encrypting and decrypting.
# This should be the same CodecOptions instance you have configured
# on MotorClient, Database, or Collection.
coll.codec_options,
)
# Create a new data key for the encryptedField.
data_key_id = await client_encryption.create_data_key(
"local", key_alt_names=["pymongo_encryption_example_3"]
)
# Explicitly encrypt a field:
encrypted_field = await client_encryption.encrypt(
"123456789", Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic, key_id=data_key_id
)
await coll.insert_one({"encryptedField": encrypted_field})
doc = await coll.find_one()
print(f"Encrypted document: {doc}")
# Explicitly decrypt the field:
doc["encryptedField"] = await client_encryption.decrypt(doc["encryptedField"])
print(f"Decrypted document: {doc}")
# Cleanup resources.
await client_encryption.close()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -1,15 +1,25 @@
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::
callbacks-and-coroutines
bulk
monitoring
tailable-cursors
tornado_change_stream_example
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.
@ -80,12 +87,10 @@ Further Information
See also:
* PyMongo's :mod:`~pymongo.monitoring` module
* `The Command Monitoring Spec`_
* `The Topology Monitoring Spec`_
* The `monitoring.py`_ example file in the Motor repository
* The `Command Monitoring`_ Spec
* The `Topology Monitoring`_ Spec
* The `monitoring_example.py`_ example file in the Motor repository
.. _The Command Monitoring Spec:
.. _Command Monitoring: https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst
.. _The Topology Monitoring Spec:
.. _Topology Monitoring: https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring-monitoring.rst
.. _monitoring.py: https://github.com/mongodb/motor/blob/master/doc/examples/monitoring.py
.. _Command Monitoring: https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.rst
.. _Topology Monitoring: https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring-logging-and-monitoring.rst
.. _monitoring_example.py: https://github.com/mongodb/motor/blob/master/doc/examples/monitoring_example.py

View File

@ -26,21 +26,29 @@ 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))
logging.info(
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))
logging.info(
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))
logging.info(
f"Command {event.command_name} with request id "
f"{event.request_id} on server {event.connection_id} "
f"failed in {event.duration_micros} "
"microseconds"
)
# command logger end
# command logger register start
@ -49,13 +57,14 @@ monitoring.register(CommandLogger())
# motorclient start
from tornado import gen, ioloop
from motor import MotorClient
client = MotorClient()
async def do_insert():
await client.test.collection.insert({'message': 'hi!'})
await client.test.collection.insert_one({"message": "hi!"})
# For this example, wait 10 seconds for more monitoring events to fire.
await gen.sleep(10)
@ -64,68 +73,70 @@ async def do_insert():
ioloop.IOLoop.current().run_sync(do_insert)
# motorclient end
# 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())
# server logger end
# 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())
# topology logger end
# 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} "
"succeeded with reply "
"{0.reply.document}".format(event))
logging.info(
f"Heartbeat to server {event.connection_id} "
"succeeded with reply "
f"{event.reply.document}"
)
def failed(self, event):
logging.warning("Heartbeat to server {0.connection_id} "
"failed with error {0.reply}".format(event))
logging.warning(
f"Heartbeat to server {event.connection_id} failed with error {event.reply}"
)
monitoring.register(HeartbeatLogger())

View File

@ -0,0 +1,100 @@
import asyncio
import os
from bson.binary import STANDARD
from bson.codec_options import CodecOptions
from pymongo.encryption import Algorithm
from pymongo.encryption_options import AutoEncryptionOpts
from pymongo.errors import OperationFailure
from pymongo.write_concern import WriteConcern
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorClientEncryption
async def main():
# The MongoDB namespace (db.collection) used to store the
# encrypted documents in this example.
encrypted_namespace = "test.coll"
# This must be the same master key that was used to create
# the encryption key.
local_master_key = os.urandom(96)
kms_providers = {"local": {"key": local_master_key}}
# The MongoDB namespace (db.collection) used to store
# the encryption data keys.
key_vault_namespace = "encryption.__pymongoTestKeyVault"
key_vault_db_name, key_vault_coll_name = key_vault_namespace.split(".", 1)
# The MotorClient used to access the key vault (key_vault_namespace).
key_vault_client = AsyncIOMotorClient()
key_vault = key_vault_client[key_vault_db_name][key_vault_coll_name]
# Ensure that two data keys cannot share the same keyAltName.
await key_vault.drop()
await key_vault.create_index(
"keyAltNames", unique=True, partialFilterExpression={"keyAltNames": {"$exists": True}}
)
client_encryption = AsyncIOMotorClientEncryption(
kms_providers,
key_vault_namespace,
key_vault_client,
# The CodecOptions class used for encrypting and decrypting.
# This should be the same CodecOptions instance you have configured
# on MotorClient, Database, or Collection. We will not be calling
# encrypt() or decrypt() in this example so we can use any
# CodecOptions.
CodecOptions(),
)
# Create a new data key and json schema for the encryptedField.
data_key_id = await client_encryption.create_data_key(
"local", key_alt_names=["pymongo_encryption_example_2"]
)
json_schema = {
"properties": {
"encryptedField": {
"encrypt": {
"keyId": [data_key_id],
"bsonType": "string",
"algorithm": Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic,
}
}
},
"bsonType": "object",
}
auto_encryption_opts = AutoEncryptionOpts(kms_providers, key_vault_namespace)
client = AsyncIOMotorClient(auto_encryption_opts=auto_encryption_opts)
db_name, coll_name = encrypted_namespace.split(".", 1)
db = client[db_name]
# Clear old data
await db.drop_collection(coll_name)
# Create the collection with the encryption JSON Schema.
await db.create_collection(
coll_name,
# uuid_representation=STANDARD is required to ensure that any
# UUIDs in the $jsonSchema document are encoded to BSON Binary
# with the standard UUID subtype 4. This is only needed when
# running the "create" collection command with an encryption
# JSON Schema.
codec_options=CodecOptions(uuid_representation=STANDARD),
write_concern=WriteConcern(w="majority"),
validator={"$jsonSchema": json_schema},
)
coll = client[db_name][coll_name]
await coll.insert_one({"encryptedField": "123456789"})
decrypted_doc = await coll.find_one()
print(f"Decrypted document: {decrypted_doc}")
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name]
encrypted_doc = await unencrypted_coll.find_one()
print(f"Encrypted document: {encrypted_doc}")
try:
await unencrypted_coll.insert_one({"encryptedField": "123456789"})
except OperationFailure as exc:
print(f"Unencrypted insert failed: {exc.details}")
if __name__ == "__main__":
asyncio.run(main())

View File

@ -3,27 +3,53 @@
Motor Tailable Cursor Example
=============================
This example describes using Motor with Tornado. Beginning in
version 0.5 Motor can also integrate with asyncio instead of 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/>`_.
A cursor on a capped collection can be tailed using :meth:`~MotorCursor.fetch_next`:
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
in the initial cursor.
The following is a basic example of using a tailable cursor to tail the oplog
of a replica set member:
.. code-block:: python
@gen.coroutine
def tail_example():
results = []
collection = db.my_capped_collection
cursor = collection.find(cursor_type=CursorType.TAILABLE, await_data=True)
while True:
if not cursor.alive:
now = datetime.datetime.utcnow()
# While collection is empty, tailable cursor dies immediately
yield gen.Task(loop.add_timeout, datetime.timedelta(seconds=1))
cursor = collection.find(cursor_type=CursorType.TAILABLE, await_data=True)
from asyncio import sleep
from pymongo.cursor import CursorType
if (yield cursor.fetch_next):
results.append(cursor.next_object())
print results
async def tail_oplog_example():
oplog = client.local.oplog.rs
first = await oplog.find().sort("$natural", pymongo.ASCENDING).limit(-1).next()
print(first)
ts = first["ts"]
while True:
# For a regular capped collection CursorType.TAILABLE_AWAIT is the
# only option required to create a tailable cursor. When querying the
# oplog, the oplog_replay option enables an optimization to quickly
# find the 'ts' value we're looking for. The oplog_replay option
# 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,
)
while cursor.alive:
async for doc in cursor:
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
# collection for more than 1 second).
await sleep(1)
.. seealso:: `Tailable cursors <http://docs.mongodb.org/manual/tutorial/create-tailable-cursor/>`_

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

@ -0,0 +1,117 @@
import logging
import os
import sys
from base64 import urlsafe_b64encode
from pprint import pformat
import tornado.escape
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
from bson import json_util # Installed with PyMongo.
from tornado.options import define, options
from motor.motor_tornado import MotorClient
define("port", default=8888, help="run on the given port", type=int)
define("debug", default=False, help="reload on source changes")
define("mongo", default="mongodb://localhost", help="MongoDB URI")
define("ns", default="test.test", help="database and collection name")
class Application(tornado.web.Application):
def __init__(self):
handlers = [(r"/", MainHandler), (r"/socket", ChangesHandler)]
templates = os.path.join(os.path.dirname(__file__), "tornado_change_stream_templates")
super().__init__(
handlers, template_path=templates, template_whitespace="all", debug=options.debug
)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", changes=ChangesHandler.cache)
class ChangesHandler(tornado.websocket.WebSocketHandler):
waiters = set()
cache = []
cache_size = 5
def open(self):
ChangesHandler.waiters.add(self)
def on_close(self):
ChangesHandler.waiters.remove(self)
@classmethod
def update_cache(cls, change):
cls.cache.append(change)
if len(cls.cache) > cls.cache_size:
cls.cache = cls.cache[-cls.cache_size :]
@classmethod
def send_change(cls, change):
change_json = json_util.dumps(change)
for waiter in cls.waiters:
try:
waiter.write_message(change_json)
except Exception as exc:
logging.exception(exc)
@classmethod
def on_change(cls, change):
logging.info("got change of type '%s'", change.get("operationType"))
# Each change notification has a binary _id. Use it to make an HTML
# element id, then remove it.
data = change["_id"]["_data"]
if not isinstance(data, bytes):
data = data.encode("utf-8")
html_id = urlsafe_b64encode(data).decode().rstrip("=")
change.pop("_id")
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)
change_stream = None
async def watch(collection):
global change_stream
async with collection.watch() as change_stream:
async for change in change_stream:
ChangesHandler.on_change(change)
def main():
tornado.options.parse_command_line()
if "." not in options.ns:
sys.stderr.write(f'Invalid ns "{options.ns}", must contain a "."')
sys.exit(1)
db_name, collection_name = options.ns.split(".", 1)
client = MotorClient(options.mongo)
collection = client[db_name][collection_name]
app = Application()
app.listen(options.port)
loop = tornado.ioloop.IOLoop.current()
# Start watching collection for changes.
try:
loop.run_sync(lambda: watch(collection))
except KeyboardInterrupt:
if change_stream:
loop.run_sync(change_stream.close)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,61 @@
.. _tornado_change_stream_example:
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
each change notification on a web page using web sockets.
Instructions
------------
Start a MongoDB server on its default port and run this script. Then visit:
http://localhost:8888
Open a ``mongo`` shell in the terminal and perform some operations on the
"test" collection in the "test" database:
.. code-block:: text
> use test
switched to db test
> db.test.insertOne({})
> db.test.updateOne({}, {$set: {x: 1}})
> db.test.deleteOne({})
The application receives each change notification and displays it as JSON on
the web page:
.. code-block:: text
Changes
{'documentKey': {'_id': ObjectId('5a2a6967ea2dcf7b1c721cfb')},
'fullDocument': {'_id': ObjectId('5a2a6967ea2dcf7b1c721cfb')},
'ns': {'coll': 'test', 'db': 'test'},
'operationType': 'insert'}
{'documentKey': {'_id': ObjectId('5a2a6967ea2dcf7b1c721cfb')},
'ns': {'coll': 'test', 'db': 'test'},
'operationType': 'update',
'updateDescription': {'removedFields': [], 'updatedFields': {'x': 1.0}}}
{'documentKey': {'_id': ObjectId('5a2a6967ea2dcf7b1c721cfb')},
'ns': {'coll': 'test', 'db': 'test'},
'operationType': 'delete'}
Display change notifications over a web socket
----------------------------------------------
.. literalinclude:: tornado_change_stream_example.py
:language: python3

View File

@ -0,0 +1,51 @@
<html>
<head>
<style type="text/css">
#changes div {
border-bottom: 1px solid gray;
margin-bottom: 1em;
}
</style>
</head>
<body>
<div id="body">
<div id="changes">
<h4>Changes</h4>
{% for change in changes %}
{% raw change['html'] %}
{% end %}
</div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
updater.start();
});
var updater = {
socket: null,
start: function() {
var url = "ws://" + location.host + "/socket";
updater.socket = new WebSocket(url);
updater.socket.onmessage = function(event) {
updater.showMessage(JSON.parse(event.data));
}
},
showMessage: function(change) {
var container = $("#changes");
var existing = $("#change-" + change.id);
if (existing.length > 0) return;
while (container.find('div').length >= 5) {
container.find('div:first').remove();
}
var node = $(change.html);
container.append(node);
}
};
</script>
</body>
</html>

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,11 +27,10 @@ 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.
See :ref:`the coroutine example <coroutine-example>`.
Configurable IOLoops
====================

View File

@ -4,10 +4,16 @@ 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
-----
Motor presents a callback- or Future-based API for non-blocking access to
Motor presents a coroutine-based API for non-blocking access to
MongoDB from Tornado_ or asyncio_.
The `source is on GitHub <https://github.com/mongodb/motor>`_ and
@ -36,16 +42,38 @@ Install with::
.. _asyncio: https://docs.python.org/3/library/asyncio.html
.. _PyMongo: http://pypi.python.org/pypi/pymongo/
Getting Help
------------
If you're having trouble or have questions about Motor, ask your question on
our `MongoDB Community Forum <https://developer.mongodb.com/community/forums/tags/c/drivers-odms-connectors/7/motor-driver>`_.
You may also want to consider a
`commercial support subscription <https://support.mongodb.com/welcome>`_.
Once you get an answer, it'd be great if you could work it back into this
documentation and contribute!
How To Ask For Help
-------------------
Issues
------
All issues should be reported (and can be tracked / voted for /
commented on) at the main `MongoDB JIRA bug tracker
<http://jira.mongodb.org/browse/MOTOR>`_, in the "Motor"
project.
Post questions about Motor to the
`mongodb-user list on Google Groups
<https://groups.google.com/forum/?fromgroups#!forum/mongodb-user>`_.
For confirmed issues or feature requests, open a case in
`Jira <http://jira.mongodb.org>`_ in the "MOTOR" project.
Feature Requests / Feedback
---------------------------
Use our `feedback engine <https://feedback.mongodb.com/?category=7548141816650747033>`_
to send us feature requests and general feedback about PyMongo.
Contributing
------------
**Motor** has a large :doc:`community <contributors>` and
contributions are always encouraged. Contributions can be as simple as
minor tweaks to this documentation. To contribute, fork the project on
`GitHub <http://github.com/mongodb/motor/>`_ and send a
pull request.
Changes
-------
See the :doc:`changelog` for a full list of changes to Motor.
Contents
--------
@ -62,7 +90,8 @@ Contents
tutorial-asyncio
examples/index
changelog
migrate-to-motor-1
migrate-to-motor-2
migrate-to-motor-3
developer-guide
contributors

View File

@ -1,13 +1,84 @@
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_::
$ python -m pip install motor
$ python3 -m pip install motor
Pip automatically installs Motor's prerequisite packages.
See :doc:`requirements`.
To install Motor from sources, you can clone its git repository and do::
$ python3 -m pip install .
Dependencies
------------
Motor works in all the environments officially supported by Tornado or by
asyncio. It requires:
* Unix (including macOS) or Windows.
* 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::
$ pip install "motor[gssapi]"
similarly,
`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]"
Support for mongodb+srv:// URIs requires ``srv`` extra dependency::
$ pip install "motor[srv]"
`OCSP <https://pymongo.readthedocs.io/en/stable/examples/tls.html#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
<https://pymongo.readthedocs.io/en/stable/examples/encryption.html#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.
.. _PyPI: http://pypi.python.org/pypi/motor
.. _pip: http://pip-installer.org
.. _PyMongo: http://pypi.python.org/pypi/pymongo/

View File

@ -1,400 +0,0 @@
Motor 1.0 Migration Guide
=========================
.. currentmodule:: motor.motor_tornado
Motor 1.0 brings a number of backward breaking changes to the pre-1.0 API.
Follow this guide to migrate an existing application that had used an older
version of Motor.
Removed features with no migration path
---------------------------------------
:class:`MotorReplicaSetClient` is removed
..........................................
In Motor 1.0, :class:`MotorClient` is the only class. Connect to a replica set with
a "replicaSet" URI option or parameter::
MotorClient("mongodb://localhost/?replicaSet=my-rs")
MotorClient(host, port, replicaSet="my-rs")
The "compile_re" option is removed
..................................
In Motor 1.0 regular expressions are never compiled to Python `re.match`
objects.
Motor 0.7
---------
The first step in migrating to Motor 1.0 is to upgrade to at least Motor 0.7.
If your project has a
requirements.txt file, add the line::
motor >= 0.7, < 1.0
Most of the key new
methods and options from Motor 1.0 are backported in Motor 0.7 making
migration much easier.
Enable Deprecation Warnings
---------------------------
Starting with Motor 0.7, :exc:`DeprecationWarning` is raised by most methods
removed in Motor 1.0. Make sure you enable runtime warnings to see
where deprecated functions and methods are being used in your application::
python -Wd <your application>
Warnings can also be changed to errors::
python -Wd -Werror <your application>
Not all deprecated features raise `DeprecationWarning` when
used. For example, `~motor.motor_tornado.MotorReplicaSetClient` will be
removed in Motor 1.0 but it does not raise `DeprecationWarning`
in Motor 0.7. See also `Removed features with no migration path`_.
CRUD API
--------
Changes to find() and find_one()
................................
"spec" renamed "filter"
~~~~~~~~~~~~~~~~~~~~~~~
The ``spec`` option has been renamed to ``filter``. Code like this::
cursor = collection.find(spec={"a": 1})
can be changed to this with Motor 0.7 or later::
cursor = collection.find(filter={"a": 1})
or this with any version of Motor::
cursor = collection.find({"a": 1})
"fields" renamed "projection"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``fields`` option has been renamed to ``projection``. Code like this::
cursor = collection.find({"a": 1}, fields={"_id": False})
can be changed to this with Motor 0.7 or later::
cursor = collection.find({"a": 1}, projection={"_id": False})
or this with any version of Motor::
cursor = collection.find({"a": 1}, {"_id": False})
"partial" renamed "allow_partial_results"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``partial`` option has been renamed to ``allow_partial_results``. Code like
this::
cursor = collection.find({"a": 1}, partial=True)
can be changed to this with Motor 0.7 or later::
cursor = collection.find({"a": 1}, allow_partial_results=True)
"timeout" replaced by "no_cursor_timeout"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``timeout`` option has been replaced by ``no_cursor_timeout``. Code like this::
cursor = collection.find({"a": 1}, timeout=False)
can be changed to this with Motor 0.7 or later::
cursor = collection.find({"a": 1}, no_cursor_timeout=True)
"snapshot" and "max_scan" replaced by "modifiers"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``snapshot`` and ``max_scan`` options have been removed. They can now be set,
along with other $ query modifiers, through the ``modifiers`` option. Code like
this::
cursor = collection.find({"a": 1}, snapshot=True)
can be changed to this with Motor 0.7 or later::
cursor = collection.find({"a": 1}, modifiers={"$snapshot": True})
or with any version of Motor::
cursor = collection.find({"$query": {"a": 1}, "$snapshot": True})
"network_timeout" is removed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``network_timeout`` option has been removed. This option was always the
wrong solution for timing out long running queries and should never be used
in production. Starting with **MongoDB 2.6** you can use the $maxTimeMS query
modifier. Code like this::
# Set a 5 second select() timeout.
cursor = collection.find({"a": 1}, network_timeout=5)
can be changed to this with Motor 0.7 or later::
# Set a 5 second (5000 millisecond) server side query timeout.
cursor = collection.find({"a": 1}, modifiers={"$maxTimeMS": 5000})
or with any version of Motor::
cursor = collection.find({"$query": {"a": 1}, "$maxTimeMS": 5000})
.. seealso:: `$maxTimeMS
<http://docs.mongodb.org/manual/reference/operator/meta/maxTimeMS/>`_
Tailable cursors
~~~~~~~~~~~~~~~~
The ``tailable`` and ``await_data`` options have been replaced by ``cursor_type``.
Code like this::
cursor = collection.find({"a": 1}, tailable=True)
cursor = collection.find({"a": 1}, tailable=True, await_data=True)
can be changed to this with Motor 0.7 or later::
from pymongo import CursorType
cursor = collection.find({"a": 1}, cursor_type=CursorType.TAILABLE)
cursor = collection.find({"a": 1}, cursor_type=CursorType.TAILABLE_AWAIT)
Other removed options
~~~~~~~~~~~~~~~~~~~~~
The ``read_preference``, ``tag_sets``,
and ``secondary_acceptable_latency_ms`` options have been removed. See the `Read
Preferences`_ section for solutions.
Read Preferences
----------------
The "read_preference" attribute is immutable
............................................
Code like this::
from pymongo import ReadPreference
db = client.my_database
db.read_preference = ReadPreference.SECONDARY
can be changed to this with Motor 0.7 or later::
db = client.get_database("my_database",
read_preference=ReadPreference.SECONDARY)
Code like this::
cursor = collection.find({"a": 1},
read_preference=ReadPreference.SECONDARY)
can be changed to this with Motor 0.7 or later::
coll2 = collection.with_options(read_preference=ReadPreference.SECONDARY)
cursor = coll2.find({"a": 1})
.. seealso:: :meth:`~MotorDatabase.get_collection`
The "tag_sets" option and attribute are removed
...............................................
The ``tag_sets`` MotorClient option is removed. The ``read_preference``
option can be used instead. Code like this::
client = MotorClient(
read_preference=ReadPreference.SECONDARY,
tag_sets=[{"dc": "ny"}, {"dc": "sf"}])
can be changed to this with Motor 0.7 or later::
from pymongo.read_preferences import Secondary
client = MotorClient(read_preference=Secondary([{"dc": "ny"}]))
To change the tags sets for a MotorDatabase or MotorCollection, code like this::
db = client.my_database
db.read_preference = ReadPreference.SECONDARY
db.tag_sets = [{"dc": "ny"}]
can be changed to this with Motor 0.7 or later::
db = client.get_database("my_database",
read_preference=Secondary([{"dc": "ny"}]))
Code like this::
cursor = collection.find(
{"a": 1},
read_preference=ReadPreference.SECONDARY,
tag_sets=[{"dc": "ny"}])
can be changed to this with Motor 0.7 or later::
from pymongo.read_preferences import Secondary
coll2 = collection.with_options(
read_preference=Secondary([{"dc": "ny"}]))
cursor = coll2.find({"a": 1})
.. seealso:: :meth:`~MotorDatabase.get_collection`
The "secondary_acceptable_latency_ms" option and attribute are removed
......................................................................
Motor 0.x supports ``secondary_acceptable_latency_ms`` as an option to methods
throughout the driver, but mongos only supports a global latency option.
Motor 1.0 has changed to match the behavior of mongos, allowing migration
from a single server, to a replica set, to a sharded cluster without a
surprising change in server selection behavior. A new option,
``localThresholdMS``, is available through MotorClient and should be used in
place of ``secondaryAcceptableLatencyMS``. Code like this::
client = MotorClient(readPreference="nearest",
secondaryAcceptableLatencyMS=100)
can be changed to this with Motor 0.7 or later::
client = MotorClient(readPreference="nearest",
localThresholdMS=100)
Write Concern
-------------
The "write_concern" attribute is immutable
..........................................
The ``write_concern`` attribute is immutable in Motor 1.0. Code like this::
client = MotorClient()
client.write_concern = {"w": "majority"}
can be changed to this with any version of Motor::
client = MotorClient(w="majority")
Code like this::
db = client.my_database
db.write_concern = {"w": "majority"}
can be changed to this with Motor 0.7 or later::
from pymongo import WriteConcern
db = client.get_database("my_database",
write_concern=WriteConcern(w="majority"))
The new CRUD API write methods do not accept write concern options. Code like
this::
oid = collection.insert({"a": 2}, w="majority")
can be changed to this with Motor 0.7 or later::
from pymongo import WriteConcern
coll2 = collection.with_options(
write_concern=WriteConcern(w="majority"))
oid = coll2.insert({"a": 2})
.. seealso:: :meth:`~MotorDatabase.get_collection`
Codec Options
-------------
The "document_class" attribute is removed
.........................................
Code like this::
from bson.son import SON
client = MotorClient()
client.document_class = SON
can be replaced by this in any version of Motor::
from bson.son import SON
client = MotorClient(document_class=SON)
or to change the ``document_class`` for a :class:`MotorDatabase`
with Motor 0.7 or later::
from bson.codec_options import CodecOptions
from bson.son import SON
db = client.get_database("my_database", CodecOptions(SON))
.. seealso:: :meth:`~MotorDatabase.get_collection` and
:meth:`~MotorCollection.with_options`
The "uuid_subtype" option and attribute are removed
...................................................
Code like this::
from bson.binary import JAVA_LEGACY
db = client.my_database
db.uuid_subtype = JAVA_LEGACY
can be replaced by this with Motor 0.7 or later::
from bson.binary import JAVA_LEGACY
from bson.codec_options import CodecOptions
db = client.get_database("my_database",
CodecOptions(uuid_representation=JAVA_LEGACY))
.. seealso:: :meth:`~MotorDatabase.get_collection` and
:meth:`~MotorCollection.with_options`
MotorClient
-----------
The ``open`` method
...................
The :meth:`~MotorClient.open` method is removed in Motor 1.0.
Motor clients have opened themselves on demand since Motor 0.2.
The max_pool_size parameter is removed
......................................
Motor 1.0 replaced the max_pool_size parameter with support for the MongoDB URI
``maxPoolSize`` option. Code like this::
client = MotorClient(max_pool_size=10)
can be replaced by this with Motor 0.7 or later::
client = MotorClient(maxPoolSize=10)
client = MotorClient("mongodb://localhost:27017/?maxPoolSize=10")
The "disconnect" method is removed
..................................
Code like this::
client.disconnect()
can be replaced by this::
client.close()
The host and port attributes are removed
........................................
Code like this::
host = client.host
port = client.port
can be replaced by this with Motor 0.7 or later::
address = client.address
host, port = address or (None, None)

215
doc/migrate-to-motor-2.rst Normal file
View File

@ -0,0 +1,215 @@
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
required in order to update the session API to support multi-document
transactions, introduced in MongoDB 4.0; this feature is so valuable that it
motivated me to make the breaking change and bump the version number to 2.0.
Since this is the first major version number in almost two years, it removes a
large number of APIs that have been deprecated in the time since Motor 1.0.
Follow this guide to migrate an existing application that had used Motor 1.x.
Check compatibility
-------------------
Read the :doc:`requirements` page and ensure your MongoDB server and Python
interpreter are compatible, and your Tornado version if you are using Tornado.
If you use aiohttp, upgrade to at least 3.0.
Upgrade to Motor 1.3
--------------------
The first step in migrating to Motor 2.0 is to upgrade to at least Motor 1.3.
If your project has a
requirements.txt file, add the line::
motor >= 1.3, < 2.0
Enable Deprecation Warnings
---------------------------
Starting with Motor 1.3, :exc:`DeprecationWarning` is raised by most methods
removed in Motor 2.0. Make sure you enable runtime warnings to see where
deprecated functions and methods are being used in your application::
python -Wd <your application>
Warnings can also be changed to errors::
python -Wd -Werror <your application>
Migrate from deprecated APIs
----------------------------
The following features are deprecated by PyMongo and scheduled for removal;
they are now deleted from Motor:
- ``MotorClient.kill_cursors`` and ``close_cursor``.
Allow :class:`MotorCursor` to handle its own cleanup.
- ``MotorClient.get_default_database``. Call :meth:`MotorClient.get_database`
with a database name of ``None`` for the same effect.
- ``MotorDatabase.add_son_manipulator``. Transform documents to and from
their MongoDB representations in your application code instead.
- The server command ``getLastError`` and related commands are deprecated,
their helper functions are deleted from Motor:
- ``MotorDatabase.last_status``
- ``MotorDatabase.error``
- ``MotorDatabase.previous_error``
- ``MotorDatabase.reset_error_history``
Use acknowledged writes and rely on Motor to raise exceptions.
- The server command ``parallelCollectionScan`` is deprecated and
``MotorCollection.parallel_scan`` is removed. Use a regular
:meth:`MotorCollection.find` cursor.
- ``MotorClient.database_names``. Use
:meth:`~MotorClient.list_database_names`.
- ``MotorDatabase.eval``. The server command is deprecated but
still available with ``MotorDatabase.command("eval", ...)``.
- ``MotorDatabase.group``. The server command is deprecated but
still available with ``MotorDatabase.command("group", ...)``.
- ``MotorDatabase.authenticate`` and ``MotorDatabase.logout``. Add credentials
to the URI or ``MotorClient`` options instead of calling ``authenticate``.
To authenticate as multiple users on the same database, instead of using
``authenticate`` and ``logout`` use a separate client for each user.
- ``MotorCollection.initialize_unordered_bulk_op``,
``initialize_unordered_bulk_op``, and ``MotorBulkOperationBuilder``. Use
:meth:`MotorCollection.bulk_write``, see :ref:`Bulk Writes Tutorial
<bulk-write-tutorial>`.
- ``MotorCollection.count``. Use :meth:`~MotorCollection.count_documents` or
:meth:`~MotorCollection.estimated_document_count`.
- ``MotorCollection.ensure_index``. Use
:meth:`MotorCollection.create_indexes`.
- Deprecated write methods have been deleted from :class:`MotorCollection`.
- ``save`` and ``insert``: Use :meth:`~MotorCollection.insert_one` or
:meth:`~MotorCollection.insert_many`.
- ``update``: Use :meth:`~MotorCollection.update_one`,
:meth:`~MotorCollection.update_many`, or
:meth:`~MotorCollection.replace_one`.
- ``remove``: Use :meth:`~MotorCollection.delete_one` or
:meth:`~MotorCollection.delete_many`.
- ``find_and_modify``: Use :meth:`~MotorCollection.find_one_and_update`,
:meth:`~MotorCollection.find_one_and_replace`, or
:meth:`~MotorCollection.find_one_and_delete`.
- ``MotorCursor.count`` and ``MotorGridOutCursor.count``. Use
:meth:`MotorCollection.count_documents` or
:meth:`MotorCollection.estimated_document_count`.
Migrate from the original callback API
--------------------------------------
Motor was first released before Tornado had introduced Futures, generator-based
coroutines, and the ``yield`` syntax, and long before the async features
developed during Python 3's career. Therefore Motor's original asynchronous API
used callbacks:
.. code-block:: python3
def callback(result, error):
if error:
print(error)
else:
print(result)
collection.find_one({}, callback=callback)
Callbacks have been largely superseded by a Futures API intended for use with
coroutines, see :doc:`tutorial-tornado`. You can still use callbacks with Motor when
appropriate but you must add the callback to a Future instead of passing it as
a parameter:
.. code-block:: python3
def callback(future):
try:
result = future.result()
print(result)
except Exception as exc:
print(exc)
future = collection.find_one({})
future.add_done_callback(callback)
The :meth:`~asyncio.Future.add_done_callback` call can be placed on the same
line:
.. code-block:: python3
collection.find_one({}).add_done_callback(callback)
In almost all cases the modern coroutine API is more readable and provides
better exception handling:
.. code-block:: python3
async def do_find():
try:
result = await collection.find_one({})
print(result)
except Exception as exc:
print(exc)
Upgrade to Motor 2.0
--------------------
Once your application runs without deprecation warnings with Motor 1.3, upgrade
to Motor 2.0. Update any calls in your code to
:meth:`MotorClient.start_session` or
:meth:`~pymongo.client_session.ClientSession.end_session` to handle the
following change.
:meth:`MotorClient.start_session` is a coroutine
------------------------------------------------
In the past, you could use a client session like:
.. code-block:: python3
session = client.start_session()
doc = await client.db.collection.find_one({}, session=session)
session.end_session()
Or:
.. code-block:: python3
with client.start_session() as 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
be used like ``await client.start_session()`` or ``async with await
client.start_session()``. The coroutine now returns a new class
:class:`~motor.motor_tornado.MotorClientSession`, not PyMongo's
:class:`~pymongo.client_session.ClientSession`. The ``end_session`` method on
the returned :class:`~motor.motor_tornado.MotorClientSession` is also now a
coroutine instead of a regular method. Use it like:
.. code-block:: python3
session = await client.start_session()
doc = await client.db.collection.find_one({}, session=session)
await session.end_session()
Or:
.. code-block:: python3
async with client.start_session() as session:
doc = await client.db.collection.find_one({}, session=session)

469
doc/migrate-to-motor-3.rst Normal file
View File

@ -0,0 +1,469 @@
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
required in order to bring support for PyMongo 4.0+.
To add compatibility with PyMongo 4, several methods were removed, as detailed below.
Some of the underlying behaviors and method arguments have changed in PyMongo
4.0 as well.
Follow this guide to migrate an existing application that had used Motor 2.x.
Check compatibility
-------------------
Read the :doc:`requirements` page and ensure your MongoDB server and Python
interpreter are compatible, and your Tornado version if you are using Tornado.
Upgrade to Motor 2.5
--------------------
The first step in migrating to Motor 3.0 is to upgrade to at least Motor 2.5.
If your project has a
``requirements.txt`` file, add the line::
motor >= 2.5, < 3.0
Python 3.7+
-----------
Motor 3.0 drops support for Python 3.5 and 3.6. Users who wish to upgrade to 3.x must first upgrade to Python 3.7+.
Enable Deprecation Warnings
---------------------------
A :exc:`DeprecationWarning` is raised by most changes made in PyMongo 4.0.
Make sure you enable runtime warnings to see where
deprecated functions and methods are being used in your application::
python -Wd <your application>
Warnings can also be changed to errors::
python -Wd -Werror <your application>
Note that there are some deprecation warnings raised by Motor itself for
APIs that are deprecated but not yet removed, like :meth:`~motor.motor_tornado.MotorCursor.fetch_next`.
MotorClient
-----------
``directConnection`` defaults to False
......................................
``directConnection`` URI option and keyword argument to :class:`~motor
.MotorClient` defaults to ``False`` instead of ``None``,
allowing for the automatic discovery of replica sets. This means that if you
want a direct connection to a single server you must pass
``directConnection=True`` as a URI option or keyword argument.
Renamed URI options
...................
Several deprecated URI options have been renamed to the standardized
option names defined in the
`URI options specification <https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.rst>`_.
The old option names and their renamed equivalents are summarized in the table
below. Some renamed options have different semantics from the option being
replaced as noted in the 'Migration Notes' column.
+--------------------+-------------------------------+--------------------------------------------------------+
| Old URI Option | Renamed URI Option | Migration Notes |
+====================+===============================+========================================================+
| ssl_pem_passphrase | tlsCertificateKeyFilePassword | - |
+--------------------+-------------------------------+--------------------------------------------------------+
| ssl_ca_certs | tlsCAFile | - |
+--------------------+-------------------------------+--------------------------------------------------------+
| ssl_crlfile | tlsCRLFile | - |
+--------------------+-------------------------------+--------------------------------------------------------+
| ssl_match_hostname | tlsAllowInvalidHostnames | ``ssl_match_hostname=True`` is equivalent to |
| | | ``tlsAllowInvalidHostnames=False`` and vice-versa. |
+--------------------+-------------------------------+--------------------------------------------------------+
| ssl_cert_reqs | tlsAllowInvalidCertificates | Instead of ``ssl.CERT_NONE``, ``ssl.CERT_OPTIONAL`` |
| | | and ``ssl.CERT_REQUIRED``, the new option expects |
| | | a boolean value - ``True`` is equivalent to |
| | | ``ssl.CERT_NONE``, while ``False`` is equivalent to |
| | | ``ssl.CERT_REQUIRED``. |
+--------------------+-------------------------------+--------------------------------------------------------+
| ssl_certfile | tlsCertificateKeyFile | Instead of using ``ssl_certfile`` and ``ssl_keyfile`` |
| | | to specify the certificate and private key files |
+--------------------+ | respectively, use ``tlsCertificateKeyFile`` to pass |
| ssl_keyfile | | a single file containing both the client certificate |
| | | and the private key. |
+--------------------+-------------------------------+--------------------------------------------------------+
| j | journal | - |
+--------------------+-------------------------------+--------------------------------------------------------+
| wtimeout | wTimeoutMS | - |
+--------------------+-------------------------------+--------------------------------------------------------+
MotorClient.fsync is removed
............................
Removed :meth:`~motor.motor_tornado.MotorClient.fsync`. Run the
`fsync command`_ directly with :meth:`~motor.motor_tornado.MotorDatabase.command`
instead. For example::
await client.admin.command('fsync', lock=True)
.. _fsync command: https://mongodb.com/docs/manual/reference/command/fsync/
MotorClient.unlock is removed
.............................
Removed :meth:`~motor.motor_tornado.MotorClient.unlock`. Run the
`fsyncUnlock command`_ directly with
:meth:`~motor.motor_tornado.MotorDatabase.command` instead. For example::
await client.admin.command('fsyncUnlock')
.. _fsyncUnlock command: https://mongodb.com/docs/manual/reference/command/fsyncUnlock/
MotorClient.max_bson_size/max_message_size/max_write_batch_size are removed
...........................................................................
Removed :attr:`~motor.motor_tornado.MotorClient.max_bson_size`,
:attr:`~motor.motor_tornado.MotorClient.max_message_size`, and
:attr:`~motor.motor_tornado.MotorClient.max_write_batch_size`. These helpers
were incorrect when in ``loadBalanced=true mode`` and ambiguous in clusters
with mixed versions. Use the `hello command`_ to get the authoritative
value from the remote server instead. Code like this::
max_bson_size = client.max_bson_size
max_message_size = client.max_message_size
max_write_batch_size = client.max_write_batch_size
can be changed to this::
doc = await client.admin.command('hello')
max_bson_size = doc['maxBsonObjectSize']
max_message_size = doc['maxMessageSizeBytes']
max_write_batch_size = doc['maxWriteBatchSize']
.. _hello command: https://mongodb.com/docs/manual/reference/command/hello/
MotorClient.event_listeners and other configuration option helpers are removed
..............................................................................
The following client configuration option helpers are removed:
- :attr:`~motor.motor_tornado.MotorClient.event_listeners`.
- :attr:`~motor.motor_tornado.MotorClient.max_pool_size`.
- :attr:`~motor.motor_tornado.MotorClient.min_pool_size`.
- :attr:`~motor.motor_tornado.MotorClient.max_idle_time_ms`.
- :attr:`~motor.motor_tornado.MotorClient.local_threshold_ms`.
- :attr:`~motor.motor_tornado.MotorClient.server_selection_timeout`.
- :attr:`~motor.motor_tornado.MotorClient.retry_writes`.
- :attr:`~motor.motor_tornado.MotorClient.retry_reads`.
These helpers have been replaced by
:attr:`~motor.motor_tornado.MotorClient.options`. Code like this::
client.event_listeners
client.local_threshold_ms
client.server_selection_timeout
client.max_pool_size
client.min_pool_size
client.max_idle_time_ms
can be changed to this::
client.options.event_listeners
client.options.local_threshold_ms
client.options.server_selection_timeout
client.options.pool_options.max_pool_size
client.options.pool_options.min_pool_size
client.options.pool_options.max_idle_time_seconds
``tz_aware`` defaults to ``False``
..................................
``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.
MotorClient cannot execute operations after ``close()``
.......................................................
:class:`~motor.motor_tornado.MotorClient` cannot execute any operations
after being closed. The previous behavior would simply reconnect. However,
now you must create a new instance.
MotorClient raises exception when given more than one URI
.........................................................
:class:`~motor.motor_tornado.MotorClient` now raises a :exc:`~pymongo.errors.ConfigurationError`
when more than one URI is passed into the ``hosts`` argument.
MotorClient raises exception when given unescaped percent sign in login info
............................................................................
:class:`~motor.motor_tornado.MotorClient` now raises an
:exc:`~pymongo.errors.InvalidURI` exception
when it encounters unescaped percent signs in username and password.
Database
--------
MotorDatabase.current_op is removed
...................................
Removed :meth:`~motor.motor_tornado.MotorDatabase.current_op`. Use
:meth:`~motor.motor_tornado.MotorDatabase.aggregate` instead with the
`$currentOp aggregation pipeline stage`_. Code like
this::
ops = client.admin.current_op()['inprog']
can be changed to this::
ops = await client.admin.aggregate([{'$currentOp': {}}]).to_list()
.. _$currentOp aggregation pipeline stage: https://mongodb.com/docs/manual/reference/operator/aggregation/currentOp/
MotorDatabase.profiling_level is removed
........................................
Removed :meth:`~motor.motor_tornado.MotorDatabase.profiling_level` which was deprecated in
PyMongo 3.12. Use the `profile command`_ instead. Code like this::
level = db.profiling_level()
Can be changed to this::
profile = await db.command('profile', -1)
level = profile['was']
.. _profile command: https://mongodb.com/docs/manual/reference/command/profile/
MotorDatabase.set_profiling_level is removed
............................................
Removed :meth:`~motor.motor_tornado.MotorDatabase.set_profiling_level` which was deprecated in
PyMongo 3.12. Use the `profile command`_ instead. Code like this::
db.set_profiling_level(pymongo.ALL, filter={'op': 'query'})
Can be changed to this::
res = await db.command('profile', 2, filter={'op': 'query'})
MotorDatabase.profiling_info is removed
.......................................
Removed :meth:`~motor.motor_tornado.MotorDatabase.profiling_info` which was deprecated in
PyMongo 3.12. Query the `'system.profile' collection`_ instead. Code like this::
profiling_info = db.profiling_info()
Can be changed to this::
profiling_info = await db['system.profile'].find().to_list()
.. _'system.profile' collection: https://mongodb.com/docs/manual/reference/database-profiler/
MotorDatabase.__bool__ raises NotImplementedError
.................................................
:class:`~motor.motor_tornado.MotorDatabase` now raises an error upon evaluating as a
Boolean. Code like this::
if database:
Can be changed to this::
if database is not None:
You must now explicitly compare with None.
MotorCollection
---------------
MotorCollection.map_reduce and MotorCollection.inline_map_reduce are removed
............................................................................
Removed :meth:`~motor.motor_tornado.MotorCollection.map_reduce` and
:meth:`~motor.motor_tornado.MotorCollection.inline_map_reduce`.
Migrate to :meth:`~motor.motor_tornado.MotorCollection.aggregate` or run the
`mapReduce command`_ directly with :meth:`~motor.motor_tornado.MotorDatabase.command`
instead. For more guidance on this migration see:
- https://mongodb.com/docs/manual/reference/map-reduce-to-aggregation-pipeline/
- https://mongodb.com/docs/manual/reference/aggregation-commands-comparison/
.. _mapReduce command: https://mongodb.com/docs/manual/reference/command/mapReduce/
MotorCollection.reindex is removed
..................................
Removed :meth:`motor.motor_tornado.MotorCollection.reindex`. Run the
`reIndex command`_ directly instead. Code like this::
>>> result = await database.my_collection.reindex()
can be changed to this::
>>> result = await database.command('reIndex', 'my_collection')
.. _reIndex command: https://mongodb.com/docs/manual/reference/command/reIndex/
The modifiers parameter is removed
..................................
Removed the ``modifiers`` parameter from
:meth:`~motor.motor_tornado.MotorCollection.find`,
:meth:`~motor.motor_tornado.MotorCollection.find_one`,
:meth:`~motor.motor_tornado.MotorCollection.find_raw_batches`, and
:meth:`~motor.motor_tornado.MotorCursor`. Pass the options directly to the method
instead. Code like this::
cursor = await coll.find({}, modifiers={
"$comment": "comment",
"$hint": {"_id": 1},
"$min": {"_id": 0},
"$max": {"_id": 6},
"$maxTimeMS": 6000,
"$returnKey": False,
"$showDiskLoc": False,
})
can be changed to this::
cursor = await coll.find(
{},
comment="comment",
hint={"_id": 1},
min={"_id": 0},
max={"_id": 6},
max_time_ms=6000,
return_key=False,
show_record_id=False,
)
The hint parameter is required with min/max
...........................................
The ``hint`` option is now required when using ``min`` or ``max`` queries
with :meth:`~motor.motor_tornado.MotorCollection.find` to ensure the query utilizes
the correct index. For example, code like this::
cursor = await coll.find({}, min={'x', min_value})
can be changed to this::
cursor = await coll.find({}, min={'x', min_value}, hint=[('x', ASCENDING)])
MotorCollection.__bool__ raises NotImplementedError
...................................................
:class:`~motor.motor_tornado.MotorCollection` now raises an error upon evaluating
as a Boolean. Code like this::
if collection:
Can be changed to this::
if collection is not None:
You must now explicitly compare with None.
MotorCollection.find returns entire document with empty projection
..................................................................
Empty projections (eg {} or []) for
:meth:`~motor.motor_tornado.MotorCollection.find`, and
:meth:`~motor.motor_tornado.MotorCollection.find_one`
are passed to the server as-is rather than the previous behavior which
substituted in a projection of ``{"_id": 1}``. This means that an empty
projection will now return the entire document, not just the ``"_id"`` field.
To ensure that behavior remains consistent, code like this::
await coll.find({}, projection={})
Can be changed to this::
await coll.find({}, projection={"_id":1})
SONManipulator is removed
-------------------------
PyMongo 4.0 removed :mod:`pymongo.son_manipulator`.
Motor 3.0 removed :meth:`motor.MotorDatabase.add_son_manipulator`,
:attr:`motor.MotorDatabase.outgoing_copying_manipulators`,
:attr:`motor.MotorDatabase.outgoing_manipulators`,
:attr:`motor.MotorDatabase.incoming_copying_manipulators`, and
:attr:`motor.MotorDatabase.incoming_manipulators`.
Removed the ``manipulate`` parameter from
:meth:`~motor.motor_tornado.MotorCollection.find`,
:meth:`~motor.motor_tornado.MotorCollection.find_one`, and
:meth:`~motor.motor_tornado.MotorCursor`.
The :class:`pymongo.son_manipulator.SONManipulator` API has limitations as a
technique for transforming your data and was deprecated in PyMongo 3.0.
Instead, it is more flexible and straightforward to transform outgoing
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:`~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
--------------
.. _removed-gridfs-checksum:
disable_md5 parameter is removed
................................
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::
import hashlib
my_db = MotorClient().test
fs = GridFSBucket(my_db)
grid_in = fs.open_upload_stream("test_file")
file_data = b'...'
sha356 = hashlib.sha256(file_data).hexdigest()
await grid_in.write(file_data)
grid_in.sha356 = sha356 # Set the custom 'sha356' field
await grid_in.close()
Note that for large files, the checksum may need to be computed in chunks
to avoid the excessive memory needed to load the entire file at once.
Removed features with no migration path
---------------------------------------
Encoding a UUID raises an error by default
..........................................
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.
Upgrade to Motor 3.0
--------------------
Once your application runs without deprecation warnings with Motor 2.5, upgrade
to Motor 3.0.

View File

@ -1,4 +1,4 @@
# Copyright 2009-2015 MongoDB, Inc.
# Copyright 2009-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -15,9 +15,8 @@
"""MongoDB specific extensions to Sphinx."""
from docutils import nodes
from docutils.parsers import rst
from sphinx import addnodes
from sphinx.util.compat import (Directive,
make_admonition)
class mongodoc(nodes.Admonition, nodes.Element):
@ -29,7 +28,7 @@ class mongoref(nodes.reference):
def visit_mongodoc_node(self, node):
self.visit_admonition(node)
self.visit_admonition(node, "seealso")
def depart_mongodoc_node(self, node):
@ -37,20 +36,17 @@ def depart_mongodoc_node(self, node):
def visit_mongoref_node(self, node):
atts = {"class": "reference external",
"href": node["refuri"],
"name": node["name"]}
self.body.append(self.starttag(node, 'a', '', **atts))
atts = {"class": "reference external", "href": node["refuri"], "name": node["name"]}
self.body.append(self.starttag(node, "a", "", **atts))
def depart_mongoref_node(self, node):
self.body.append('</a>')
self.body.append("</a>")
if not isinstance(node.parent, nodes.TextElement):
self.body.append('\n')
self.body.append("\n")
class MongodocDirective(Directive):
class MongodocDirective(rst.Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
@ -58,11 +54,11 @@ class MongodocDirective(Directive):
option_spec = {}
def run(self):
return make_admonition(mongodoc, self.name,
['See general MongoDB documentation'],
self.options, self.content, self.lineno,
self.content_offset, self.block_text,
self.state, self.state_machine)
node = mongodoc()
title = "The MongoDB documentation on"
node += nodes.title(title, title)
self.state.nested_parse(self.content, self.content_offset, node)
return [node]
def process_mongodoc_nodes(app, doctree, fromdocname):
@ -76,7 +72,7 @@ def process_mongodoc_nodes(app, doctree, fromdocname):
anchor = name["ids"][0]
break
for para in node.traverse(nodes.paragraph):
tag = str(para.traverse()[1])
tag = str(list(para.traverse())[1])
link = mongoref("", "")
link["refuri"] = "http://dochub.mongodb.org/core/%s" % tag
link["name"] = anchor
@ -87,13 +83,13 @@ def process_mongodoc_nodes(app, doctree, fromdocname):
def setup(app):
app.add_node(mongodoc,
html=(visit_mongodoc_node, depart_mongodoc_node),
latex=(visit_mongodoc_node, depart_mongodoc_node),
text=(visit_mongodoc_node, depart_mongodoc_node))
app.add_node(mongoref,
html=(visit_mongoref_node, depart_mongoref_node))
app.add_node(
mongodoc,
html=(visit_mongodoc_node, depart_mongodoc_node),
latex=(visit_mongodoc_node, depart_mongodoc_node),
text=(visit_mongodoc_node, depart_mongodoc_node),
)
app.add_node(mongoref, html=(visit_mongoref_node, depart_mongoref_node))
app.add_directive("mongodoc", MongodocDirective)
app.connect("doctree-resolved", process_mongodoc_nodes)

View File

@ -1,4 +1,4 @@
# Copyright 2012-2014 MongoDB, Inc.
# Copyright 2012-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -14,35 +14,25 @@
"""Motor specific extensions to Sphinx."""
import inspect
import re
from itertools import chain
from docutils.nodes import field, list_item, paragraph, title_reference, literal
from docutils.nodes import field_list, field_body, bullet_list, Text, field_name
from docutils.nodes import literal_block, doctest_block
from docutils.nodes import doctest_block, literal_block
from sphinx import addnodes
from sphinx.addnodes import (desc, desc_content, versionmodified,
desc_signature, seealso, pending_xref)
from sphinx.addnodes import desc, desc_content, desc_signature, seealso, versionmodified
from sphinx.util.inspect import safe_getattr
import motor
import motor.core
# This is a place to store info while parsing, to be used before generating.
motor_info = {}
def is_asyncio_api(name):
return 'motor_asyncio.' in name
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
@ -62,57 +52,6 @@ def find_by_path(root, classes):
return rv
def get_parameter_names(parameters_node):
parameter_names = []
# Most PyMongo methods have bullet lists.
for list_item_node in find_by_path(parameters_node, [list_item]):
title_ref_nodes = find_by_path(
list_item_node, [paragraph, (title_reference, pending_xref)])
parameter_names.append(title_ref_nodes[0].astext())
# Some are just paragraphs.
for title_ref_node in find_by_path(parameters_node, [title_reference]):
parameter_names.append(title_ref_node.astext())
return parameter_names
def insert_callback(parameters_node):
# We need to know what params are here already
parameter_names = get_parameter_names(parameters_node)
if 'callback' not in parameter_names:
if '*args' in parameter_names:
args_pos = parameter_names.index('*args')
else:
args_pos = len(parameter_names)
if '**kwargs' in parameter_names:
kwargs_pos = parameter_names.index('**kwargs')
else:
kwargs_pos = len(parameter_names)
doc = (
" (optional): function taking (result, error), executed when"
" operation completes")
new_item = paragraph(
'', '',
literal('', 'callback'),
Text(doc))
if parameters_node.children and isinstance(parameters_node.children[0], list_item):
# Insert "callback" before *args and **kwargs
parameters_node.insert(min(args_pos, kwargs_pos), list_item('', new_item))
else:
parameters_node.append(new_item)
# Insert "callback" before *args and **kwargs
parameters_node.insert(min(args_pos, kwargs_pos), new_item)
docstring_warnings = []
@ -123,7 +62,7 @@ def maybe_warn_about_code_block(name, content_node):
def has_coro_annotation(signature_node):
try:
return 'coroutine' in signature_node[0][0]
return "coroutine" in signature_node[0][0]
except IndexError:
return False
@ -131,10 +70,7 @@ def has_coro_annotation(signature_node):
def process_motor_nodes(app, doctree):
# Search doctree for Motor's methods and attributes whose docstrings were
# copied from PyMongo, and fix them up for Motor:
# 1. Add a 'callback' param (sometimes optional, sometimes required) to
# all Motor Tornado methods. If the PyMongo method took no params, we
# create a parameter-list from scratch, otherwise we edit PyMongo's
# list.
# 1. Add a 'coroutine' annotation to the beginning of the declaration.
# 2. Remove all version annotations like "New in version 2.0" since
# PyMongo's version numbers are meaningless in Motor's docs.
# 3. Remove "seealso" directives that reference PyMongo's docs.
@ -143,66 +79,31 @@ def process_motor_nodes(app, doctree):
# 'autodoc-process-signature' event, because it's way easier to handle the
# parsed doctree before it's turned into HTML than it is to update the RST.
for objnode in doctree.traverse(desc):
if objnode['objtype'] in ('method', 'attribute'):
if objnode["objtype"] in ("method", "attribute"):
signature_node = find_by_path(objnode, [desc_signature])[0]
name = '.'.join([
signature_node['module'], signature_node['fullname']])
name = ".".join([signature_node["module"], signature_node["fullname"]])
assert name.startswith('motor.')
assert name.startswith("motor.")
obj_motor_info = motor_info.get(name)
if obj_motor_info:
desc_content_node = find_by_path(objnode, [desc_content])[0]
if (desc_content_node.line is None and
obj_motor_info['is_pymongo_docstring']):
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(
'coroutine ', 'coroutine ',
classes=['coro-annotation'])
"coroutine ", "coroutine ", classes=["coro-annotation"]
)
signature_node.insert(0, coro_annotation)
if not is_asyncio_api(name):
retval = ("If a callback is passed, returns None, else"
" returns a Future.")
callback_p = paragraph('', Text(retval))
# Find the parameter list.
parameters_nodes = find_by_path(
desc_content_node, [
field_list,
field,
field_body,
(bullet_list, paragraph)])
if parameters_nodes:
parameters_node = parameters_nodes[0]
else:
# PyMongo method has no parameters, create an empty
# params list
parameters_node = bullet_list()
parameters_field_list_node = field_list(
'',
field(
'',
field_name('', 'Parameters '),
field_body('', parameters_node)))
desc_content_node.append(parameters_field_list_node)
insert_callback(parameters_node)
if retval not in str(desc_content_node):
desc_content_node.append(callback_p)
if obj_motor_info['is_pymongo_docstring']:
if obj_motor_info["is_pymongo_docstring"]:
# Remove all "versionadded", "versionchanged" and
# "deprecated" directives from the docs we imported from
# PyMongo
version_nodes = find_by_path(
desc_content_node, [versionmodified])
version_nodes = find_by_path(desc_content_node, [versionmodified])
for version_node in version_nodes:
version_node.parent.remove(version_node)
@ -221,22 +122,19 @@ def get_motor_attr(motor_class, name, *defargs):
attribute. While we're at it, store some info about each attribute
in the global motor_info dict.
"""
attr = safe_getattr(motor_class, name)
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)
is_async_method = getattr(attr, 'is_async_method', False)
is_cursor_method = getattr(attr, 'is_motorcursor_chaining_method', False)
has_coroutine_annotation = getattr(attr, "coroutine_annotation", False)
is_async_method = getattr(attr, "is_async_method", False)
is_cursor_method = getattr(attr, "is_motorcursor_chaining_method", False)
if is_async_method or is_cursor_method:
pymongo_method = getattr(
motor_class.__delegate_class__, attr.pymongo_method_name)
pymongo_method = getattr(motor_class.__delegate_class__, attr.pymongo_method_name)
else:
pymongo_method = None
@ -244,68 +142,27 @@ def get_motor_attr(motor_class, name, *defargs):
is_pymongo_doc = pymongo_method and attr.__doc__ == pymongo_method.__doc__
motor_info[full_name] = motor_info[full_name_legacy] = {
'is_async_method': is_async_method or has_coroutine_annotation,
'is_pymongo_docstring': is_pymongo_doc,
'pymongo_method': pymongo_method,
"is_async_method": is_async_method or has_coroutine_annotation,
"is_pymongo_docstring": is_pymongo_doc,
"pymongo_method": pymongo_method,
}
return attr
def get_motor_argspec(name, method):
args, varargs, kwargs, defaults = inspect.getargspec(method)
# This part is copied from Sphinx's autodoc.py
if args and args[0] in ('cls', 'self'):
del args[0]
defaults = list(defaults) if defaults else []
if ('callback' not in chain(args or [], kwargs or []) and
not is_asyncio_api(name)):
# Add 'callback=None' argument
args.append('callback')
defaults.append(None)
return args, varargs, kwargs, defaults
# Adapted from MethodDocumenter.format_args
def format_motor_args(name, motor_method, pymongo_method):
if pymongo_method:
argspec = get_motor_argspec(name, pymongo_method)
else:
argspec = get_motor_argspec(name, motor_method)
formatted_argspec = inspect.formatargspec(*argspec)
# escape backslashes for reST
return formatted_argspec.replace('\\', '\\\\')
pymongo_ref_pat = re.compile(r':doc:`(.*?)`', re.MULTILINE)
pymongo_ref_pat = re.compile(r":doc:`(.*?)`", re.MULTILINE)
def _sub_pymongo_ref(match):
ref = match.group(1)
return ':doc:`%s`' % ref.lstrip('/')
return ":doc:`%s`" % ref.lstrip("/")
def process_motor_docstring(app, what, name, obj, options, lines):
if name in motor_info and motor_info[name].get('is_pymongo_docstring'):
joined = '\n'.join(lines)
subbed = pymongo_ref_pat.sub(_sub_pymongo_ref, joined, re.MULTILINE)
lines[:] = subbed.split('\n')
def process_motor_signature(
app, what, name, obj, options, signature, return_annotation):
info = motor_info.get(name)
if info:
# Real sig obscured by decorator, reconstruct it
pymongo_method = info['pymongo_method']
if info['is_async_method']:
args = format_motor_args(name, obj, pymongo_method)
return args, return_annotation
if name in motor_info and motor_info[name].get("is_pymongo_docstring"):
joined = "\n".join(lines)
subbed = pymongo_ref_pat.sub(_sub_pymongo_ref, joined)
lines[:] = subbed.split("\n")
def build_finished(app, exception):
@ -317,7 +174,7 @@ def build_finished(app, exception):
def setup(app):
app.add_autodoc_attrgetter(type(motor.core.AgnosticBase), get_motor_attr)
app.connect('autodoc-process-docstring', process_motor_docstring)
app.connect('autodoc-process-signature', process_motor_signature)
app.connect('doctree-read', process_motor_nodes)
app.connect('build-finished', build_finished)
app.connect("autodoc-process-docstring", process_motor_docstring)
app.connect("doctree-read", process_motor_nodes)
app.connect("build-finished", build_finished)
return {"parallel_write_safe": True, "parallel_read_safe": False}

View File

@ -1,37 +1,29 @@
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 2.6, 2.7, or 3.3 and later.
* PyMongo_ 3.4 and later.
* CPython 3.10 and later.
* PyMongo_ 4.9 and later.
Beginning with version 0.5, Motor can integrate with either Tornado or asyncio.
Motor can integrate with either Tornado or asyncio.
Requires the `futures`_ package from PyPI on Python 2.
The default authentication mechanism for MongoDB 3.0+ is SCRAM-SHA-1.
Install `backports.pbkdf2`_ for faster authentication with MongoDB 3.0+,
especially on Python older than 2.7.8, or on Python 3.3.
(Python 2.7.9 and later, or Python 3.4 and later, have builtin hash functions
nearly as fast as backports.pbkdf2.)
The default authentication mechanism for MongoDB is SCRAM-SHA-1.
Building the docs requires `sphinx`_.
In Python 2.6, unittest2_ is automatically installed by
``python setup.py test``.
.. _PyMongo: https://pypi.python.org/pypi/pymongo/
.. _futures: https://pypi.python.org/pypi/futures
.. _backports.pbkdf2: https://pypi.python.org/pypi/backports.pbkdf2/
.. _sphinx: http://sphinx.pocoo.org/
.. _unittest2: https://pypi.python.org/pypi/unittest2
.. _sphinx: https://www.sphinx-doc.org/
.. _compatibility-matrix:
Compatibility Matrix
--------------------
@ -39,63 +31,54 @@ Compatibility Matrix
Motor and PyMongo
`````````````````
Older versions of Motor depended on exact PyMongo versions. Version 0.7 requires
the latest PyMongo 2.9.x release beginning with 2.9.4, Version 1.0 works
with any PyMongo version beginning with 3.3.0, and Version 1.1 works with any
PyMongo version beginning with 3.4.0.
+-------------------+-----------------+
| Motor Version | PyMongo Version |
+===================+=================+
| 0.1 | 2.5.0 |
| 2.5 | 3.12+ |
+-------------------+-----------------+
| 0.2 | 2.7.0 |
| 3.0 | 4.1+ |
+-------------------+-----------------+
| 0.3 | 2.7.1 |
| 3.1 | 4.2+ |
+-------------------+-----------------+
| 0.4 | 2.8.0 |
| 3.2 | 4.4+ |
+-------------------+-----------------+
| 0.5 | 2.8.0 |
| 3.3 | 4.5+ |
+-------------------+-----------------+
| 0.6 | 2.8.0 |
| 3.4 | 4.5+ |
+-------------------+-----------------+
| 0.7 | 2.9.4+ |
| 3.5 | 4.5+ |
+-------------------+-----------------+
| 1.0 | 3.3+ |
| 3.6 | 4.9 |
+-------------------+-----------------+
| 1.1 | 3.4+ |
| 3.7 | 4.9+ |
+-------------------+-----------------+
Motor and MongoDB
`````````````````
All Motor versions are usable with all MongoDB versions as old as 2.2.
Where "N" appears there are some incompatibilities and
unsupported server features.
+---------------------------------------------------------+
| MongoDB Version |
+=====================+=====+=====+=====+=====+=====+=====+
| | 2.2 | 2.4 | 2.6 | 3.0 | 3.2 | 3.4 |
+---------------+-----+-----+-----+-----+-----+-----+-----+
| Motor Version | 0.1 | Y | Y |**N**|**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+
| | 0.2 | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+
| | 0.3 | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+
| | 0.4 | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+
| | 0.5 | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+
| | 0.6 | Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+
| | 0.7 | Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+
| | 1.0 | Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+-----+-----+-----+
| | 1.1 | 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.
@ -103,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://docs.mongodb.org/ecosystem/drivers/python/#mongodb-compatibility
.. _the PyMongo compatibility matrix: https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/compatibility/
Motor and Tornado
`````````````````
@ -111,76 +94,84 @@ Motor and Tornado
Where "N" appears in this matrix, the versions of Motor and Tornado are
known to be incompatible, or have not been tested together.
+---------------------------------+
| Tornado Version |
+=====================+=====+=====+
| | 3.x | 4.x |
+---------------+-----+-----+-----+
| Motor Version | 0.1 | Y |**N**|
+---------------+-----+-----+-----+
| | 0.2 | Y | Y |
+---------------+-----+-----+-----+
| | 0.3 | Y | Y |
+---------------+-----+-----+-----+
| | 0.4 | Y | Y |
+---------------+-----+-----+-----+
| | 0.5 | Y | Y |
+---------------+-----+-----+-----+
| | 0.6 | Y | Y |
+---------------+-----+-----+-----+
| | 0.7 | Y | Y |
+---------------+-----+-----+-----+
| | 1.0 | Y | Y |
+---------------+-----+-----+-----+
| | 1.1 | Y | Y |
+---------------+-----+-----+-----+
+---------------------------------------------+
| Tornado Version |
+=====================+=====+=====+=====+=====+
| Motor Version | 2.5 |**N**|**N**| Y | Y |
+---------------+-----+-----+-----+-----+-----+
| | 3.0 |**N**|**N**|**N**| Y |
+---------------+-----+-----+-----+-----+-----+
| | 3.1 |**N**|**N**|**N**| Y |
+---------------+-----+-----+-----+-----+-----+
| | 3.2 |**N**|**N**|**N**| Y |
+---------------+-----+-----+-----+-----+-----+
| | 3.3 |**N**|**N**|**N**| Y |
+---------------+-----+-----+-----+-----+-----+
| | 3.4 |**N**|**N**|**N**| Y |
+---------------+-----+-----+-----+-----+-----+
Motor and Python
````````````````
Until version 0.5, Motor required Tornado, and it supported the same version of
Python as its supported Tornado versions did.
Motor 2.5 deprecated support for Python 3.5.
Beginning in version 0.5, Motor integrates with asyncio or Tornado.
For asyncio support specifically, Motor requires Python 3.4+, or Python 3.3
with the `asyncio package from PyPI`_.
Motor 3.0 dropped support for Pythons older than 3.7.
+----------------------------------------------------------------+
| Python Version |
+=====================+=====+=====+=====+======+=====+=====+=====+
| | 2.5 | 2.6 | 2.7 | 3.3 | 3.4 | 3.5 | 3.6 |
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| Motor Version | 0.1 | Y | Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| | 0.2 |**N**| Y | Y | Y |**N**|**N**|**N**|
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| | 0.3 |**N**| Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| | 0.4 |**N**| Y | Y | Y | Y |**N**|**N**|
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| | 0.5 |**N**| Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| | 0.6 |**N**| Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| | 0.7 |**N**| Y | Y | Y | Y | Y |**N**|
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| | 1.0 |**N**| Y | Y | Y | Y | Y | Y |
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
| | 1.1 |**N**| Y | Y | Y | Y | Y | Y |
+---------------+-----+-----+-----+-----+------+-----+-----+-----+
Motor 3.1.1 added support for Python 3.11.
.. _asyncio package from PyPI: https://pypi.python.org/pypi/asyncio
Motor 3.3 added support for Python 3.12.
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
-------------
Motor does not support Windows:
* The author does not test Motor on Windows to ensure it is correct or fast.
* Tornado `is not officially supported on Windows
<http://www.tornadoweb.org/en/stable/index.html#installation>`_,
so Motor's Tornado integration on Windows is doubly-unsupported.
* Since asyncio *does* officially support Windows, Motor's asyncio integration
is more likely to work there, but it is untested.
Motor also does not support Jython.
Motor does not support Jython or IronPython.

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,22 +19,26 @@ Tutorial: Using Motor With :mod:`asyncio`
import pymongo
import motor.motor_asyncio
import asyncio
from asyncio import coroutine
db = motor.motor_asyncio.AsyncIOMotorClient().test_database
client = motor.motor_asyncio.AsyncIOMotorClient()
db = client.test_database
.. testsetup:: after-inserting-2000-docs
import pymongo
import motor.motor_asyncio
import asyncio
from asyncio import coroutine
db = motor.motor_asyncio.AsyncIOMotorClient().test_database
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.
@ -38,13 +49,13 @@ Tutorial Prerequisites
----------------------
You can learn about MongoDB with the `MongoDB Tutorial`_ before you learn Motor.
Using Python 3.4 or later, do::
Using Python 3.5 or later, do::
$ python3 -m pip install motor
This tutorial assumes that a MongoDB instance is running on the
default host and port. Assuming you have `downloaded and installed
<http://docs.mongodb.org/manual/installation/>`_ MongoDB, you
<https://mongodb.com/docs/manual/installation/>`_ MongoDB, you
can start it like so:
.. code-block:: bash
@ -53,7 +64,7 @@ can start it like so:
.. _pip: http://www.pip-installer.org/en/latest/installing.html
.. _MongoDB Tutorial: http://docs.mongodb.org/manual/tutorial/getting-started/
.. _MongoDB Tutorial: https://mongodb.com/docs/manual/tutorial/getting-started/
Object Hierarchy
----------------
@ -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,38 +100,38 @@ 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:
>>> client = motor.motor_asyncio.AsyncIOMotorClient('mongodb://host1,host2/?replicaSet=my-replicaset-name')
.. _connection URIs: http://docs.mongodb.org/manual/reference/connection-string/
.. _connection URIs: https://mongodb.com/docs/manual/reference/connection-string/
Getting a Database
------------------
A single instance of MongoDB can support multiple independent
`databases <http://docs.mongodb.org/manual/reference/glossary/#term-database>`_.
`databases <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_.
From an open client, you can get a reference to a particular database with
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.
Getting a Collection
--------------------
A `collection <http://docs.mongodb.org/manual/reference/glossary/#term-collection>`_
A `collection <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_
is a group of documents stored in MongoDB, and can be thought of as roughly
the equivalent of a table in a relational database. Getting a
collection in Motor works the same as getting a database:
@ -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,12 +153,13 @@ 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))
...
>>>
>>> loop = asyncio.get_event_loop()
>>> import asyncio
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_insert())
result ObjectId('...')
@ -156,31 +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)
Using native coroutines
-----------------------
Starting in Python 3.5, you can define a `native coroutine`_ with `async def`
instead of the ``coroutine`` decorator. Within a native coroutine, wait
for an async operation with `await` instead of `yield`:
Insert documents in large batches with :meth:`~AsyncIOMotorCollection.insert_many`:
.. doctest:: before-inserting-2000-docs
>>> async def do_insert():
... for i in range(2000):
... result = await db.test_collection.insert_one({'i': i})
... result = await db.test_collection.insert_many([{"i": i} for i in range(2000)])
... print("inserted %d docs" % (len(result.inserted_ids),))
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_insert())
inserted 2000 docs
Within a native coroutine, the syntax to use Motor with Tornado or asyncio
is often identical.
.. _native coroutine: https://www.python.org/dev/peps/pep-0492/
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
@ -189,10 +194,10 @@ 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 = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_find_one())
{'_id': ObjectId('...'), 'i': 0}
@ -216,11 +221,11 @@ 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)
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_find())
{'_id': ObjectId('...'), 'i': 0}
{'_id': ObjectId('...'), 'i': 1}
@ -240,10 +245,10 @@ 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 = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_find())
{'_id': ObjectId('...'), 'i': 0}
{'_id': ObjectId('...'), 'i': 1}
@ -253,13 +258,13 @@ 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': 5}})
... cursor = db.test_collection.find({"i": {"$lt": 4}})
... # Modify the query before iterating
... cursor.sort('i', -1).limit(2).skip(2)
... cursor.sort("i", -1).skip(1).limit(2)
... async for document in cursor:
... pprint.pprint(document)
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_find())
{'_id': ObjectId('...'), 'i': 2}
{'_id': ObjectId('...'), 'i': 1}
@ -267,55 +272,27 @@ 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://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches
Iteration in Python 3.3 and 3.4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Python versions without ``async for``, handle one document at a time with
:attr:`~motor.motor_asyncio.AsyncIOMotorCursor.fetch_next`
and :meth:`~motor.motor_asyncio.AsyncIOMotorCursor.next_object`:
.. doctest:: after-inserting-2000-docs
>>> @coroutine
... def do_find():
... cursor = db.test_collection.find({'i': {'$lt': 5}})
... while (yield from cursor.fetch_next):
... document = cursor.next_object()
... pprint.pprint(document)
...
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(do_find())
{'_id': ObjectId('...'), 'i': 0}
{'_id': ObjectId('...'), 'i': 1}
{'_id': ObjectId('...'), 'i': 2}
{'_id': ObjectId('...'), 'i': 3}
{'_id': ObjectId('...'), 'i': 4}
.. _`large batches`: https://www.mongodb.com/docs/manual/core/cursors/#cursor-batches
Counting Documents
------------------
Use :meth:`~motor.motor_asyncio.AsyncIOMotorCursor.count` to determine the number of documents in
a collection, or the number of documents that match a query:
Use :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.count_documents` to
determine the number of documents in a collection, or the number of documents
that match a query:
.. doctest:: after-inserting-2000-docs
>>> async def do_count():
... n = await db.test_collection.find().count()
... print('%s documents in collection' % n)
... n = await db.test_collection.find({'i': {'$gt': 1000}}).count()
... print('%s documents where i > 1000' % n)
... 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)
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_count())
2000 documents in collection
999 documents where i > 1000
:meth:`~motor.motor_asyncio.AsyncIOMotorCursor.count` uses the *count command* internally; we'll
cover commands_ below.
.. seealso:: `Count command <http://docs.mongodb.org/manual/reference/command/count/>`_
Updating Documents
------------------
@ -328,15 +305,15 @@ 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 = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_replace())
found document: {'_id': ObjectId('...'), 'i': 50}
replaced 1 document
@ -354,12 +331,12 @@ 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 = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_update())
updated 1 document
document is now {'_id': ObjectId('...'), 'i': 51, 'key': 'value'}
@ -377,6 +354,24 @@ update all of them with :meth:`update_many`::
Deleting Documents
------------------
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.delete_one` takes a query with the same syntax as
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find`.
:meth:`delete_one` immediately removes the first returned matching document.
.. doctest:: after-inserting-2000-docs
>>> 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({})))
...
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_delete_one())
2000 documents before calling delete_one()
1999 documents after
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.delete_many` takes a query with the same syntax as
:meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find`.
:meth:`delete_many` immediately removes all matching documents.
@ -385,40 +380,36 @@ Deleting Documents
>>> async def do_delete_many():
... coll = db.test_collection
... n = await coll.count()
... 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()))
... 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({})))
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_delete_many())
2000 documents before calling delete_many()
1999 documents before calling delete_many()
1000 documents after
.. mongodoc:: remove
Commands
--------
Besides the "CRUD" operations--insert, update, delete, and find--all other
operations on MongoDB are commands. Run them using
the :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command` method on :class:`~motor.motor_asyncio.AsyncIOMotorDatabase`:
All operations on MongoDB are implemented internally as commands. Run them using
the :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command` method on
:class:`~motor.motor_asyncio.AsyncIOMotorDatabase`::
.. doctest:: after-inserting-2000-docs
>>> from bson import SON
>>> async def use_count_command():
... response = await db.command(SON([("count", "test_collection")]))
... print('response: %s' % pprint.pformat(response))
>>> async def use_distinct_command():
... response = await db.command(SON([("distinct", "test_collection"), ("key", "i")]))
...
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(use_count_command())
response: {'n': 1000, 'ok': 1.0...}
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(use_distinct_command())
Since the order of command parameters matters, don't use a Python dict to pass
the command's parameters. Instead, make a habit of using :class:`bson.SON`,
from the ``bson`` module included with PyMongo::
await db.command(SON([("distinct", "test_collection"), ("key", "my_key"]))
from the ``bson`` module included with PyMongo.
Many commands have special helper methods, such as
:meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.create_collection` or
@ -470,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
@ -496,5 +485,4 @@ reference to Motor's complete feature set.
Learning to use the MongoDB driver is just the beginning, of course. For
in-depth instruction in MongoDB itself, see `The MongoDB Manual`_.
.. _The MongoDB Manual: http://docs.mongodb.org/manual/
.. _The MongoDB Manual: https://mongodb.com/docs/manual/

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.
@ -55,7 +64,7 @@ exception:
This tutorial also assumes that a MongoDB instance is running on the
default host and port. Assuming you have `downloaded and installed
<http://docs.mongodb.org/manual/installation/>`_ MongoDB, you
<https://mongodb.com/docs/manual/installation/>`_ MongoDB, you
can start it like so:
.. code-block:: bash
@ -64,7 +73,7 @@ can start it like so:
.. _pip: http://www.pip-installer.org/en/latest/installing.html
.. _MongoDB Tutorial: http://docs.mongodb.org/manual/tutorial/getting-started/
.. _MongoDB Tutorial: https://mongodb.com/docs/manual/tutorial/getting-started/
Object Hierarchy
----------------
@ -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,34 +106,34 @@ 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:
>>> client = motor.motor_tornado.MotorClient('mongodb://host1,host2/?replicaSet=my-replicaset-name')
.. _connection URIs: http://docs.mongodb.org/manual/reference/connection-string/
.. _connection URIs: https://mongodb.com/docs/manual/reference/connection-string/
Getting a Database
------------------
A single instance of MongoDB can support multiple independent
`databases <http://docs.mongodb.org/manual/reference/glossary/#term-database>`_.
`databases <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_.
From an open client, you can get a reference to a particular database with
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 accept a callback
or return a Future.
Creating a reference to a database does no I/O and does not require an
``await`` expression.
Tornado Application Startup Sequence
------------------------------------
@ -149,14 +159,37 @@ makes it available to request handlers::
def get(self):
db = self.settings['db']
.. 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.
.. 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
machine's CPUs. However, you must create your ``MotorClient`` after forking::
# Create the application before creating a MotorClient.
application = tornado.web.Application([
(r'/', MainHandler)
])
server = tornado.httpserver.HTTPServer(application)
server.bind(8888)
# Forks one process per CPU.
server.start(0)
# Now, in each child process, create a MotorClient.
application.settings['db'] = MotorClient().test_database
IOLoop.current().start()
For production-ready, multiple-CPU deployments of Tornado there are better
methods than ``HTTPServer.start()``. See Tornado's guide to
:doc:`tornado:guide/running`.
Getting a Collection
--------------------
A `collection <http://docs.mongodb.org/manual/reference/glossary/#term-collection>`_
A `collection <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_
is a group of documents stored in MongoDB, and can be thought of as roughly
the equivalent of a table in a relational database. Getting a
collection in Motor works the same as getting a database:
@ -164,73 +197,36 @@ 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 accept a callback or return a Future.
collection does no I/O and doesn't require an ``await`` expression.
Inserting a Document
--------------------
As in PyMongo, Motor represents MongoDB documents with Python dictionaries. To
store a document in MongoDB, call :meth:`~MotorCollection.insert_one` with a
document and a callback:
store a document in MongoDB, call :meth:`~MotorCollection.insert_one` in an
``await`` expression:
.. doctest:: before-inserting-2000-docs
>>> from tornado.ioloop import IOLoop
>>> def my_callback(result, error):
... print('result %s' % repr(result.inserted_id))
... IOLoop.current().stop()
>>> async def do_insert():
... document = {"key": "value"}
... result = await db.test_collection.insert_one(document)
... print("result %s" % repr(result.inserted_id))
...
>>> document = {'key': 'value'}
>>> db.test_collection.insert_one(document, callback=my_callback)
>>> IOLoop.current().start()
>>>
>>> IOLoop.current().run_sync(do_insert)
result ObjectId('...')
There are several differences to note between Motor and PyMongo. One is that,
unlike PyMongo's :meth:`~pymongo.collection.Collection.insert_one`, Motor's has no
return value. Another is that ``insert_one`` accepts an optional callback function.
The function must take two arguments and it must be passed to ``insert_one`` as a
keyword argument, like::
db.test_collection.insert_one(document, callback=some_function)
.. warning:: Passing the callback function using the ``callback=`` syntax is
required. (This requirement is a side-effect of the technique Motor uses to
wrap PyMongo.) If you pass the callback as a positional argument instead,
you may see an exception like ``TypeError: method takes exactly 1 argument (2
given)``, or ``TypeError: callable is required``, or some silent misbehavior.
:meth:`insert_one` is *asynchronous*. This means it returns immediately, and
the actual work of inserting the document into the collection is performed in
the background. When it completes, the callback is executed. If the insert
succeeded, the ``result`` parameter is a
:class:`~pymongo.results.InsertOneResult` with the new document's unique id and
the ``error`` parameter is ``None``. If there was an error, ``result`` is
``None`` and ``error`` is an ``Exception`` object. For example, we can trigger
a duplicate-key error by trying to insert two documents with the same unique
id:
.. mongodoc:: insert
.. doctest:: before-inserting-2000-docs
:hide:
>>> loop = IOLoop.current()
>>> def my_callback(result, error):
... print('result %s error %s' % (repr(result), repr(error)))
... IOLoop.current().stop()
...
>>> def insert_two_documents():
... db.test_collection.insert_one({'_id': 1}, callback=my_callback)
...
>>> IOLoop.current().add_callback(insert_two_documents)
>>> IOLoop.current().start()
result <pymongo.results.InsertOneResult ...> error None
>>> IOLoop.current().add_callback(insert_two_documents)
>>> IOLoop.current().start()
result None error DuplicateKeyError(...)
The first insert results in ``my_callback`` being called with result 1 and
error ``None``. The second insert triggers ``my_callback`` with result None and
a :class:`~pymongo.errors.DuplicateKeyError`.
>>> # Clean up from previous insert
>>> pymongo.MongoClient().test_database.test_collection.delete_many({})
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::
@ -243,62 +239,19 @@ not waiting for each insert to complete before beginning the next::
In PyMongo this would insert each document in turn using a single socket, but
Motor attempts to run all the :meth:`insert_one` operations at once. This requires
up to ``max_pool_size`` open sockets connected to MongoDB,
which taxes the client and server. To ensure instead that all inserts use a
single connection, wait for acknowledgment of each. This is a bit complex using
callbacks:
which taxes the client and server. To ensure instead that all inserts run in
sequence, use ``await``:
.. doctest:: before-inserting-2000-docs
>>> i = 0
>>> def do_insert(result, error):
... global i
... if error:
... raise error
... i += 1
... if i < 2000:
... db.test_collection.insert_one({'i': i}, callback=do_insert)
... else:
... IOLoop.current().stop()
...
>>> # Start
>>> db.test_collection.insert_one({'i': i}, callback=do_insert)
>>> IOLoop.current().start()
You can simplify this code with ``gen.coroutine``.
Using Motor with `gen.coroutine`
--------------------------------
The :mod:`tornado.gen` module lets you use generators to simplify asynchronous
code. There are two parts to coding with generators:
:func:`coroutine <tornado.gen.coroutine>` and
:class:`~tornado.concurrent.Future`.
First, decorate your generator function with ``@gen.coroutine``:
>>> @gen.coroutine
... def do_insert():
... pass
If you pass no callback to one of Motor's asynchronous methods, it returns a
``Future``. Yield the ``Future`` instance to wait for an operation to complete
and obtain its result:
.. doctest:: before-inserting-2000-docs
>>> @gen.coroutine
... def do_insert():
>>> async def do_insert():
... for i in range(2000):
... future = db.test_collection.insert_one({'i': i})
... result = yield future
... await db.test_collection.insert_one({"i": i})
...
>>> IOLoop.current().run_sync(do_insert)
In the code above, ``result`` is the ``_id`` of each inserted document.
.. seealso:: :doc:`examples/bulk`.
.. seealso:: :ref:`Detailed example of Motor and gen.coroutine <coroutine-example>`
.. mongodoc:: insert
.. doctest:: before-inserting-2000-docs
@ -306,27 +259,19 @@ In the code above, ``result`` is the ``_id`` of each inserted document.
>>> # Clean up from previous insert
>>> pymongo.MongoClient().test_database.test_collection.delete_many({})
<pymongo.results.DeleteResult ...>
DeleteResult({'n': 2000, 'ok': 1.0}, acknowledged=True)
Using native coroutines
-----------------------
Starting in Python 3.5, you can define a `native coroutine`_ with `async def`
instead of the `gen.coroutine` decorator. Within a native coroutine, wait
for an async operation with `await` instead of `yield`:
For better performance, insert documents in large batches with
:meth:`~MotorCollection.insert_many`:
.. doctest:: before-inserting-2000-docs
>>> async def do_insert():
... for i in range(2000):
... result = await db.test_collection.insert_one({'i': i})
... 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)
Within a native coroutine, the syntax to use Motor with Tornado or asyncio
is often identical.
.. _native coroutine: https://www.python.org/dev/peps/pep-0492/
inserted 2000 docs
Getting a Single Document With :meth:`~MotorCollection.find_one`
----------------------------------------------------------------
@ -336,9 +281,8 @@ less than 1:
.. doctest:: after-inserting-2000-docs
>>> @gen.coroutine
... def do_find_one():
... document = yield db.test_collection.find_one({'i': {'$lt': 1}})
>>> async def do_find_one():
... document = await db.test_collection.find_one({"i": {"$lt": 1}})
... pprint.pprint(document)
...
>>> IOLoop.current().run_sync(do_find_one)
@ -357,19 +301,18 @@ are sorted the same in your output as ours.)
Querying for More Than One Document
-----------------------------------
Use :meth:`~MotorCollection.find` to query for a set of documents.
:meth:`~MotorCollection.find` does no I/O and does not take a callback,
it merely creates a :class:`MotorCursor` instance. The query is actually
executed on the server when you call :meth:`~MotorCursor.to_list` or
:meth:`~MotorCursor.each`, or yield :attr:`~motor.motor_tornado.MotorCursor.fetch_next`.
:meth:`~MotorCollection.find` does no I/O and does not require an ``await``
expression. It merely creates an :class:`~MotorCursor` instance. The query is
actually executed on the server when you call :meth:`~MotorCursor.to_list`
or execute an ``async for`` loop.
To find all documents with "i" less than 5:
.. doctest:: after-inserting-2000-docs
>>> @gen.coroutine
... def do_find():
... cursor = db.test_collection.find({'i': {'$lt': 5}}).sort('i')
... for document in (yield cursor.to_list(length=100)):
>>> async def do_find():
... cursor = db.test_collection.find({"i": {"$lt": 5}}).sort("i")
... for document in await cursor.to_list(length=100):
... pprint.pprint(document)
...
>>> IOLoop.current().run_sync(do_find)
@ -379,93 +322,62 @@ To find all documents with "i" less than 5:
{'_id': ObjectId('...'), 'i': 3}
{'_id': ObjectId('...'), 'i': 4}
A ``length`` argument is required when you call to_list to prevent Motor from
buffering an unlimited number of documents.
A ``length`` argument is required when you call ``to_list`` to prevent Motor
from buffering an unlimited number of documents.
To get one document at a time with :attr:`~motor.motor_tornado.MotorCursor.fetch_next`
and :meth:`~MotorCursor.next_object`:
``async for``
~~~~~~~~~~~~~
.. doctest:: after-inserting-2000-docs
>>> @gen.coroutine
... def do_find():
... cursor = db.test_collection.find({'i': {'$lt': 5}})
... while (yield cursor.fetch_next):
... document = cursor.next_object()
... pprint.pprint(document)
...
>>> IOLoop.current().run_sync(do_find)
{'_id': ObjectId('...'), 'i': 0}
{'_id': ObjectId('...'), 'i': 1}
{'_id': ObjectId('...'), 'i': 2}
{'_id': ObjectId('...'), 'i': 3}
{'_id': ObjectId('...'), 'i': 4}
You can apply a sort, limit, or skip to a query before you begin iterating:
.. doctest:: after-inserting-2000-docs
>>> @gen.coroutine
... def do_find():
... c = db.test_collection
... cursor = c.find({'i': {'$lt': 5}})
... # Modify the query before iterating
... cursor.sort('i', -1).limit(2).skip(2)
... while (yield cursor.fetch_next):
... document = cursor.next_object()
... pprint.pprint(document)
...
>>> IOLoop.current().run_sync(do_find)
{'_id': ObjectId('...'), 'i': 2}
{'_id': ObjectId('...'), 'i': 1}
``fetch_next`` does not actually retrieve each document from the server
individually; it gets documents efficiently in `large batches`_.
.. _`large batches`: https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches
`async for`
-----------
In a native coroutine defined with `async def`, replace the while-loop with
`async for`:
You can handle one document at a time in an ``async for`` loop:
.. doctest:: after-inserting-2000-docs
>>> 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)
{'_id': ObjectId('...'), 'i': 0}
{'_id': ObjectId('...'), 'i': 1}
This version of the code is dramatically faster.
Counting Documents
------------------
Use :meth:`~MotorCursor.count` to determine the number of documents in
a collection, or the number of documents that match a query:
You can apply a sort, limit, or skip to a query before you begin iterating:
.. doctest:: after-inserting-2000-docs
>>> @gen.coroutine
... def do_count():
... n = yield db.test_collection.find().count()
... print('%s documents in collection' % n)
... n = yield db.test_collection.find({'i': {'$gt': 1000}}).count()
... print('%s documents where i > 1000' % n)
>>> async def do_find():
... cursor = db.test_collection.find({"i": {"$lt": 4}})
... # Modify the query before iterating
... cursor.sort("i", -1).skip(1).limit(2)
... async for document in cursor:
... pprint.pprint(document)
...
>>> IOLoop.current().run_sync(do_find)
{'_id': ObjectId('...'), 'i': 2}
{'_id': ObjectId('...'), 'i': 1}
The cursor does not actually retrieve each document from the server
individually; it gets documents efficiently in `large batches`_.
.. _`large batches`: https://www.mongodb.com/docs/manual/core/cursors/#cursor-batches
Counting Documents
------------------
Use :meth:`~MotorCollection.count_documents` to determine the number of
documents in a collection, or the number of documents that match a query:
.. doctest:: after-inserting-2000-docs
>>> 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)
...
>>> IOLoop.current().run_sync(do_count)
2000 documents in collection
999 documents where i > 1000
:meth:`~MotorCursor.count` uses the *count command* internally; we'll
cover commands_ below.
.. seealso:: `Count command <http://docs.mongodb.org/manual/reference/command/count/>`_
Updating Documents
------------------
@ -476,16 +388,15 @@ replacement document. The query follows the same syntax as for :meth:`find` or
.. doctest:: after-inserting-2000-docs
>>> @gen.coroutine
... def do_replace():
>>> async def do_replace():
... coll = db.test_collection
... old_document = yield coll.find_one({'i': 50})
... print('found document: %s' % pprint.pformat(old_document))
... _id = old_document['_id']
... result = yield coll.replace_one({'_id': _id}, {'key': 'value'})
... print('replaced %s document' % result.modified_count)
... new_document = yield 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}
@ -502,13 +413,12 @@ operator to set "key" to "value":
.. doctest:: after-inserting-2000-docs
>>> @gen.coroutine
... def do_update():
>>> async def do_update():
... coll = db.test_collection
... result = yield coll.update_one({'i': 51}, {'$set': {'key': 'value'}})
... print('updated %s document' % result.modified_count)
... new_document = yield 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
@ -519,7 +429,7 @@ operator to set "key" to "value":
:meth:`update_one` only affects the first document it finds, you can
update all of them with :meth:`update_many`::
yield coll.update_many({'i': {'$gt': 100}},
await coll.update_many({'i': {'$gt': 100}},
{'$set': {'key': 'value'}})
.. mongodoc:: update
@ -527,48 +437,59 @@ update all of them with :meth:`update_many`::
Removing Documents
------------------
:meth:`~MotorCollection.delete_one` takes a query with the same syntax as
:meth:`~MotorCollection.find`.
:meth:`delete_one` immediately removes the first returned matching document.
.. doctest:: after-inserting-2000-docs
>>> 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({})))
...
>>> IOLoop.current().run_sync(do_delete_one)
2000 documents before calling delete_one()
1999 documents after
:meth:`~MotorCollection.delete_many` takes a query with the same syntax as
:meth:`~MotorCollection.find`.
:meth:`delete_many` immediately removes all matching documents.
.. doctest:: after-inserting-2000-docs
>>> @gen.coroutine
... def do_delete_many():
>>> async def do_delete_many():
... coll = db.test_collection
... n = yield coll.count()
... print('%s documents before calling delete_many()' % n)
... result = yield db.test_collection.delete_many({'i': {'$gte': 1000}})
... print('%s documents after' % (yield coll.count()))
... 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({})))
...
>>> IOLoop.current().run_sync(do_delete_many)
2000 documents before calling delete_many()
1999 documents before calling delete_many()
1000 documents after
.. mongodoc:: remove
Commands
--------
Besides the "CRUD" operations--insert, update, delete, and find--all other
operations on MongoDB are commands. Run them using
the :meth:`~MotorDatabase.command` method on :class:`MotorDatabase`:
All operations on MongoDB are implemented internally as commands. Run them using
the :meth:`~motor.motor_tornado.MotorDatabase.command` method on
:class:`~motor.motor_tornado.MotorDatabase`::
.. doctest:: after-inserting-2000-docs
>>> from bson import SON
>>> @gen.coroutine
... def use_count_command():
... response = yield db.command(SON([("count", "test_collection")]))
... print('response: %s' % pprint.pformat(response))
>>> async def use_distinct_command():
... response = await db.command(SON([("distinct", "test_collection"), ("key", "i")]))
...
>>> IOLoop.current().run_sync(use_count_command)
response: {'n': 1000, 'ok': 1.0...}
>>> IOLoop.current().run_sync(use_distinct_command)
Since the order of command parameters matters, don't use a Python dict to pass
the command's parameters. Instead, make a habit of using :class:`bson.SON`,
from the ``bson`` module included with PyMongo::
yield db.command(SON([("distinct", "test_collection"), ("key", "my_key")]))
from the ``bson`` module included with PyMongo.
Many commands have special helper methods, such as
:meth:`~MotorDatabase.create_collection` or
@ -587,4 +508,4 @@ reference to Motor's complete feature set.
Learning to use the MongoDB driver is just the beginning, of course. For
in-depth instruction in MongoDB itself, see `The MongoDB Manual`_.
.. _The MongoDB Manual: http://docs.mongodb.org/manual/
.. _The MongoDB Manual: https://mongodb.com/docs/manual/

View File

@ -1,426 +0,0 @@
#!/usr/bin/env python
"""
Setuptools bootstrapping installer.
Maintained at https://github.com/pypa/setuptools/tree/bootstrap.
Run this script to install or upgrade setuptools.
"""
import os
import shutil
import sys
import tempfile
import zipfile
import optparse
import subprocess
import platform
import textwrap
import contextlib
import json
import codecs
from distutils import log
try:
from urllib.request import urlopen
from urllib.parse import urljoin
except ImportError:
from urllib2 import urlopen
from urlparse import urljoin
try:
from site import USER_SITE
except ImportError:
USER_SITE = None
LATEST = object()
DEFAULT_VERSION = LATEST
DEFAULT_URL = "https://pypi.io/packages/source/s/setuptools/"
DEFAULT_SAVE_DIR = os.curdir
def _python_cmd(*args):
"""
Execute a command.
Return True if the command succeeded.
"""
args = (sys.executable,) + args
return subprocess.call(args) == 0
def _install(archive_filename, install_args=()):
"""Install Setuptools."""
with archive_context(archive_filename):
# installing
log.warn('Installing Setuptools')
if not _python_cmd('setup.py', 'install', *install_args):
log.warn('Something went wrong during the installation.')
log.warn('See the error message above.')
# exitcode will be 2
return 2
def _build_egg(egg, archive_filename, to_dir):
"""Build Setuptools egg."""
with archive_context(archive_filename):
# building an egg
log.warn('Building a Setuptools egg in %s', to_dir)
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
# returning the result
log.warn(egg)
if not os.path.exists(egg):
raise IOError('Could not build the egg.')
class ContextualZipFile(zipfile.ZipFile):
"""Supplement ZipFile class to support context manager for Python 2.6."""
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
def __new__(cls, *args, **kwargs):
"""Construct a ZipFile or ContextualZipFile as appropriate."""
if hasattr(zipfile.ZipFile, '__exit__'):
return zipfile.ZipFile(*args, **kwargs)
return super(ContextualZipFile, cls).__new__(cls)
@contextlib.contextmanager
def archive_context(filename):
"""
Unzip filename to a temporary directory, set to the cwd.
The unzipped target is cleaned up after.
"""
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
with ContextualZipFile(filename) as archive:
archive.extractall()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
yield
finally:
os.chdir(old_wd)
shutil.rmtree(tmpdir)
def _do_download(version, download_base, to_dir, download_delay):
"""Download Setuptools."""
py_desig = 'py{sys.version_info[0]}.{sys.version_info[1]}'.format(sys=sys)
tp = 'setuptools-{version}-{py_desig}.egg'
egg = os.path.join(to_dir, tp.format(**locals()))
if not os.path.exists(egg):
archive = download_setuptools(version, download_base,
to_dir, download_delay)
_build_egg(egg, archive, to_dir)
sys.path.insert(0, egg)
# Remove previously-imported pkg_resources if present (see
# https://bitbucket.org/pypa/setuptools/pull-request/7/ for details).
if 'pkg_resources' in sys.modules:
_unload_pkg_resources()
import setuptools
setuptools.bootstrap_install_from = egg
def use_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=DEFAULT_SAVE_DIR, download_delay=15):
"""
Ensure that a setuptools version is installed.
Return None. Raise SystemExit if the requested version
or later cannot be installed.
"""
version = _resolve_version(version)
to_dir = os.path.abspath(to_dir)
# prior to importing, capture the module state for
# representative modules.
rep_modules = 'pkg_resources', 'setuptools'
imported = set(sys.modules).intersection(rep_modules)
try:
import pkg_resources
pkg_resources.require("setuptools>=" + version)
# a suitable version is already installed
return
except ImportError:
# pkg_resources not available; setuptools is not installed; download
pass
except pkg_resources.DistributionNotFound:
# no version of setuptools was found; allow download
pass
except pkg_resources.VersionConflict as VC_err:
if imported:
_conflict_bail(VC_err, version)
# otherwise, unload pkg_resources to allow the downloaded version to
# take precedence.
del pkg_resources
_unload_pkg_resources()
return _do_download(version, download_base, to_dir, download_delay)
def _conflict_bail(VC_err, version):
"""
Setuptools was imported prior to invocation, so it is
unsafe to unload it. Bail out.
"""
conflict_tmpl = textwrap.dedent("""
The required version of setuptools (>={version}) is not available,
and can't be installed while this script is running. Please
install a more recent version first, using
'easy_install -U setuptools'.
(Currently using {VC_err.args[0]!r})
""")
msg = conflict_tmpl.format(**locals())
sys.stderr.write(msg)
sys.exit(2)
def _unload_pkg_resources():
sys.meta_path = [
importer
for importer in sys.meta_path
if importer.__class__.__module__ != 'pkg_resources.extern'
]
del_modules = [
name for name in sys.modules
if name.startswith('pkg_resources')
]
for mod_name in del_modules:
del sys.modules[mod_name]
def _clean_check(cmd, target):
"""
Run the command to download target.
If the command fails, clean up before re-raising the error.
"""
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
if os.access(target, os.F_OK):
os.unlink(target)
raise
def download_file_powershell(url, target):
"""
Download the file at url to target using Powershell.
Powershell will validate trust.
Raise an exception if the command cannot complete.
"""
target = os.path.abspath(target)
ps_cmd = (
"[System.Net.WebRequest]::DefaultWebProxy.Credentials = "
"[System.Net.CredentialCache]::DefaultCredentials; "
'(new-object System.Net.WebClient).DownloadFile("%(url)s", "%(target)s")'
% locals()
)
cmd = [
'powershell',
'-Command',
ps_cmd,
]
_clean_check(cmd, target)
def has_powershell():
"""Determine if Powershell is available."""
if platform.system() != 'Windows':
return False
cmd = ['powershell', '-Command', 'echo test']
with open(os.path.devnull, 'wb') as devnull:
try:
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
except Exception:
return False
return True
download_file_powershell.viable = has_powershell
def download_file_curl(url, target):
cmd = ['curl', url, '--location', '--silent', '--output', target]
_clean_check(cmd, target)
def has_curl():
cmd = ['curl', '--version']
with open(os.path.devnull, 'wb') as devnull:
try:
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
except Exception:
return False
return True
download_file_curl.viable = has_curl
def download_file_wget(url, target):
cmd = ['wget', url, '--quiet', '--output-document', target]
_clean_check(cmd, target)
def has_wget():
cmd = ['wget', '--version']
with open(os.path.devnull, 'wb') as devnull:
try:
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
except Exception:
return False
return True
download_file_wget.viable = has_wget
def download_file_insecure(url, target):
"""Use Python to download the file, without connection authentication."""
src = urlopen(url)
try:
# Read all the data in one block.
data = src.read()
finally:
src.close()
# Write all the data in one block to avoid creating a partial file.
with open(target, "wb") as dst:
dst.write(data)
download_file_insecure.viable = lambda: True
def get_best_downloader():
downloaders = (
download_file_powershell,
download_file_curl,
download_file_wget,
download_file_insecure,
)
viable_downloaders = (dl for dl in downloaders if dl.viable())
return next(viable_downloaders, None)
def download_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=DEFAULT_SAVE_DIR, delay=15,
downloader_factory=get_best_downloader):
"""
Download setuptools from a specified location and return its filename.
`version` should be a valid setuptools version number that is available
as an sdist for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download
attempt.
``downloader_factory`` should be a function taking no arguments and
returning a function for downloading a URL to a target.
"""
version = _resolve_version(version)
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
zip_name = "setuptools-%s.zip" % version
url = download_base + zip_name
saveto = os.path.join(to_dir, zip_name)
if not os.path.exists(saveto): # Avoid repeated downloads
log.warn("Downloading %s", url)
downloader = downloader_factory()
downloader(url, saveto)
return os.path.realpath(saveto)
def _resolve_version(version):
"""
Resolve LATEST version
"""
if version is not LATEST:
return version
meta_url = urljoin(DEFAULT_URL, '/pypi/setuptools/json')
resp = urlopen(meta_url)
with contextlib.closing(resp):
try:
charset = resp.info().get_content_charset()
except Exception:
# Python 2 compat; assume UTF-8
charset = 'UTF-8'
reader = codecs.getreader(charset)
doc = json.load(reader(resp))
return str(doc['info']['version'])
def _build_install_args(options):
"""
Build the arguments to 'python setup.py install' on the setuptools package.
Returns list of command line arguments.
"""
return ['--user'] if options.user_install else []
def _parse_args():
"""Parse the command line for options."""
parser = optparse.OptionParser()
parser.add_option(
'--user', dest='user_install', action='store_true', default=False,
help='install in user site package')
parser.add_option(
'--download-base', dest='download_base', metavar="URL",
default=DEFAULT_URL,
help='alternative URL from where to download the setuptools package')
parser.add_option(
'--insecure', dest='downloader_factory', action='store_const',
const=lambda: download_file_insecure, default=get_best_downloader,
help='Use internal, non-validating downloader'
)
parser.add_option(
'--version', help="Specify which version to download",
default=DEFAULT_VERSION,
)
parser.add_option(
'--to-dir',
help="Directory to save (and re-use) package",
default=DEFAULT_SAVE_DIR,
)
options, args = parser.parse_args()
# positional arguments are ignored
return options
def _download_args(options):
"""Return args for download_setuptools function from cmdline args."""
return dict(
version=options.version,
download_base=options.download_base,
downloader_factory=options.downloader_factory,
to_dir=options.to_dir,
)
def main():
"""Install or upgrade setuptools and EasyInstall."""
options = _parse_args()
archive = download_setuptools(**_download_args(options))
return _install(archive, _build_install_args(options))
if __name__ == '__main__':
sys.exit(main())

View File

@ -1,4 +1,4 @@
# Copyright 2011-2015 MongoDB, Inc.
# Copyright 2011-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -13,66 +13,17 @@
# limitations under the License.
"""Motor, an asynchronous driver for MongoDB."""
from ._version import get_version_string, version, version_tuple # noqa: F401
from __future__ import unicode_literals, absolute_import
import pymongo
from motor.motor_py3_compat import text_type
version_tuple = (1, 2, 'dev0')
def get_version_string():
if isinstance(version_tuple[-1], text_type):
return '.'.join(map(str, version_tuple[:-1])) + version_tuple[-1]
return '.'.join(map(str, version_tuple))
version = get_version_string()
"""Current version of Motor."""
pymongo_required = 3, 4
if pymongo.version_tuple[:2] < pymongo_required:
major, minor = pymongo_required
msg = (
"Motor %s requires PyMongo %s.%s or later. "
"You have PyMongo %s. "
"Do python -m pip install \"pymongo>=%s.%s,<4\""
) % (version,
major, minor,
pymongo.version,
major, minor)
raise ImportError(msg)
# MotorClient runs getaddrinfo on a thread. If the hostname is unicode,
# getaddrinfo attempts to import encodings.idna. If this is done at
# module-import time, perhaps with "loop.run_sync(MotorClient.open)" at
# module scope, the import lock is already held by the main thread, leading to
# AutoReconnect. Avoid it by caching the idna encoder on the main thread now.
#
# Only required in Python 2 and Tornado 3.
#
# See also https://github.com/tornadoweb/tornado/pull/964
u'foo'.encode('idna')
try:
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 Motor 1.0. First get __all__.
from .motor_tornado import *
# Now some classes that aren't in __all__ but might be expected.
from .motor_tornado import (MotorCollection,
MotorDatabase,
MotorGridFS,
MotorGridFSBucket,
MotorGridIn,
MotorGridOut,
MotorBulkOperationBuilder)
# Make "from motor import *" the same as "from motor.motor_tornado import *"
from .motor_tornado import __all__
# at module root. This may change in the future.
from .motor_tornado import * # noqa: F403
from .motor_tornado import __all__ # noqa: F401

41
motor/_version.py Normal file
View File

@ -0,0 +1,41 @@
# Copyright 2022-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
"""Version-related data for motor."""
import re
from typing import Union
__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:
return __version__

View File

@ -14,18 +14,21 @@
"""Serve GridFS files with Motor and aiohttp.
Requires Python 3.5 or later and aiohttp 3.0 or later.
See the :doc:`/examples/aiohttp_gridfs_example`.
"""
import asyncio
import datetime
import mimetypes
import aiohttp.web
from aiohttp.web_reqrep import StreamResponse
import gridfs
from motor.motor_asyncio import (AsyncIOMotorDatabase,
AsyncIOMotorGridFSBucket)
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):
@ -105,17 +108,15 @@ def set_extra_headers(response, gridout):
- `gridout`: The :class:`~motor.motor_asyncio.AsyncIOMotorGridOut` we
will serve to the client
"""
pass
def _config_error(request):
try:
formatter = request.match_info.route.resource.get_info()['formatter']
msg = ('Bad AIOHTTPGridFS route "%s", requires a {filename} variable' %
formatter)
formatter = request.match_info.route.resource.get_info()["formatter"]
msg = 'Bad AIOHTTPGridFS route "%s", requires a {filename} variable' % formatter
except (KeyError, AttributeError):
# aiohttp API changed? Fall back to simpler error message.
msg = ('Bad AIOHTTPGridFS route for request: %s' % request)
msg = "Bad AIOHTTPGridFS route for request: %s" % request
raise aiohttp.web.HTTPInternalServerError(text=msg) from None
@ -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)
@ -152,18 +153,21 @@ class AIOHTTPGridFS:
- `get_cache_time`: Optional override for :func:`get_cache_time`
- `set_extra_headers`: Optional override for :func:`set_extra_headers`
.. _GridFS: https://docs.mongodb.com/manual/core/gridfs/
.. _GridFS: https://www.mongodb.com/docs/manual/core/gridfs/
"""
def __init__(self,
database,
root_collection='fs',
get_gridfs_file=get_gridfs_file,
get_cache_time=get_cache_time,
set_extra_headers=set_extra_headers):
def __init__(
self,
database,
root_collection="fs",
get_gridfs_file=get_gridfs_file,
get_cache_time=get_cache_time,
set_extra_headers=set_extra_headers,
):
if not isinstance(database, AsyncIOMotorDatabase):
raise TypeError("First argument to AIOHTTPGridFS must be "
"AsyncIOMotorDatabase, not %r" % database)
raise TypeError(
"First argument to AIOHTTPGridFS must be AsyncIOMotorDatabase, not %r" % database
)
self._database = database
self._bucket = AsyncIOMotorGridFSBucket(self._database, root_collection)
@ -171,27 +175,29 @@ class AIOHTTPGridFS:
self._get_cache_time = get_cache_time
self._set_extra_headers = set_extra_headers
@asyncio.coroutine
def __call__(self, request):
async def __call__(self, request):
"""Send filepath to client using request."""
try:
filename = request.match_info['filename']
filename = request.match_info["filename"]
except KeyError:
_config_error(request)
if request.method not in ('GET', 'HEAD'):
if request.method not in ("GET", "HEAD"):
raise aiohttp.web.HTTPMethodNotAllowed(
method=request.method, allowed_methods={'GET', 'HEAD'})
method=request.method, allowed_methods={"GET", "HEAD"}
)
try:
gridout = yield from self._get_gridfs_file(self._bucket,
filename,
request)
except gridfs.NoFile:
raise aiohttp.web.HTTPNotFound(text=request.path)
gridout = await self._get_gridfs_file(self._bucket, filename, request)
except gridfs.NoFile as e:
raise aiohttp.web.HTTPNotFound(text=request.path) from e
resp = StreamResponse()
self._set_standard_headers(request.path, resp, gridout)
resp = aiohttp.web.StreamResponse()
# Get the hash for the GridFS file.
checksum = _hash_gridout(gridout)
self._set_standard_headers(request.path, resp, gridout, checksum)
# Overridable method set_extra_headers.
self._set_extra_headers(resp, gridout)
@ -210,29 +216,23 @@ class AIOHTTPGridFS:
# Same for Etag
etag = request.headers.get("If-None-Match")
if etag is not None and etag.strip('"') == gridout.md5:
if etag is not None and etag.strip('"') == checksum:
resp.set_status(304)
return resp
resp.content_length = gridout.length
yield from resp.prepare(request)
if request.method == 'GET':
resp.set_tcp_cork(True)
try:
written = 0
while written < gridout.length:
# Reading chunk_size at a time minimizes buffering.
chunk = yield from gridout.read(gridout.chunk_size)
resp.write(chunk)
yield from resp.drain()
written += len(chunk)
finally:
resp.set_tcp_nodelay(True)
await resp.prepare(request)
if request.method == "GET":
written = 0
while written < gridout.length:
# Reading chunk_size at a time minimizes buffering.
chunk = await gridout.read(gridout.chunk_size)
await resp.write(chunk)
written += len(chunk)
return resp
def _set_standard_headers(self, path, resp, gridout):
def _set_standard_headers(self, path, resp, gridout, checksum):
resp.last_modified = gridout.upload_date
content_type = gridout.content_type
if content_type is None:
@ -241,18 +241,15 @@ class AIOHTTPGridFS:
if content_type:
resp.content_type = content_type
# MD5 is calculated on the MongoDB server when GridFS file is created.
resp.headers["Etag"] = '"%s"' % gridout.md5
resp.headers["Etag"] = '"%s"' % checksum
# Overridable method get_cache_time.
cache_time = self._get_cache_time(path,
gridout.upload_date,
gridout.content_type)
cache_time = self._get_cache_time(path, gridout.upload_date, gridout.content_type)
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)

File diff suppressed because it is too large Load Diff

889
motor/core.pyi Normal file
View File

@ -0,0 +1,889 @@
# Copyright 2023-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
from asyncio import Future
from collections.abc import Callable, Coroutine, Iterable, Mapping, MutableMapping, Sequence
from typing import (
Any,
Generic,
NoReturn,
Optional,
TypeVar,
Union,
overload,
)
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 ClientSession, SessionOptions, TransactionOptions
from pymongo.collection import Collection, ReturnDocument # noqa: F401
from pymongo.command_cursor import CommandCursor, RawBatchCommandCursor
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,
_CollationIn,
_DocumentType,
_DocumentTypeArg,
_Pipeline,
)
try:
from pymongo.operations import SearchIndexModel
except ImportError:
SearchIndexModel: typing_extensions.TypeAlias = Any # type:ignore[no-redef]
_WITH_TRANSACTION_RETRY_TIME_LIMIT: int
_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:
delegate: Any
def __eq__(self, other: object) -> bool: ...
def __init__(self, delegate: Any) -> None: ...
class AgnosticBaseProperties(AgnosticBase, Generic[_DocumentType]):
codec_options: CodecOptions[_DocumentType]
read_preference: _ServerMode
read_concern: ReadConcern
write_concern: WriteConcern
class AgnosticClient(AgnosticBaseProperties[_DocumentType]):
__motor_class_name__: str
__delegate_class__: type[pymongo.MongoClient[_DocumentType]]
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[_DocumentTypeArg]],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
) -> None: ...
def options(self) -> ClientOptions: ...
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,
) -> AgnosticDatabase[_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,
) -> 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
def is_mongos(self) -> bool: ...
def is_primary(self) -> bool: ...
async def list_databases(
self,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> 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]: ...
PORT: int
def primary(self) -> Optional[tuple[str, int]]: ...
read_concern: ReadConcern
def secondaries(self) -> set[tuple[str, int]]: ...
async def server_info(
self, session: Optional[AgnosticClientSession] = None
) -> dict[str, Any]: ...
def topology_description(self) -> TopologyDescription: ...
async def start_session(
self,
causal_consistency: Optional[bool] = None,
default_transaction_options: Optional[TransactionOptions] = None,
snapshot: Optional[bool] = False,
) -> AgnosticClientSession: ...
_io_loop: Optional[Any]
_framework: Any
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: ...
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[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,
) -> AgnosticChangeStream[_DocumentType]: ...
def __getattr__(self, name: str) -> AgnosticDatabase[_DocumentType]: ...
def __getitem__(self, name: str) -> AgnosticDatabase[_DocumentType]: ...
def wrap(self, obj: Any) -> Any: ...
class _MotorTransactionContext:
_session: AgnosticClientSession
def __init__(self, session: AgnosticClientSession): ...
async def __aenter__(self) -> _MotorTransactionContext: ...
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]
async def commit_transaction(self) -> None: ...
async def abort_transaction(self) -> None: ...
async def end_session(self) -> None: ...
def cluster_time(self) -> Optional[Mapping[str, Any]]: ...
def has_ended(self) -> bool: ...
def in_transaction(self) -> bool: ...
def options(self) -> SessionOptions: ...
def operation_time(self) -> Optional[Timestamp]: ...
def session_id(self) -> Mapping[str, Any]: ...
def advance_cluster_time(self, cluster_time: Mapping[str, Any]) -> None: ...
def advance_operation_time(self, operation_time: Timestamp) -> None: ...
def __init__(self, delegate: ClientSession, motor_client: AgnosticClient): ...
def get_io_loop(self) -> Any: ...
async def with_transaction(
self,
coro: Callable[..., Coroutine[Any, Any, Any]],
read_concern: Optional[ReadConcern] = None,
write_concern: Optional[WriteConcern] = None,
read_preference: Optional[_ServerMode] = None,
max_commit_time_ms: Optional[int] = None,
) -> _T: ...
def start_transaction(
self,
read_concern: Optional[ReadConcern] = None,
write_concern: Optional[WriteConcern] = None,
read_preference: Optional[_ServerMode] = None,
max_commit_time_ms: Optional[int] = None,
) -> _MotorTransactionContext: ...
@property
def client(self) -> AgnosticClient: ...
async def __aenter__(self) -> AgnosticClientSession: ...
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
def __enter__(self) -> None: ...
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]):
__motor_class_name__: str
__delegate_class__: type[Database[_DocumentType]]
def __hash__(self) -> int: ...
def __bool__(self) -> int: ...
async def cursor_command(
self,
command: Union[str, MutableMapping[str, Any]],
value: Any = 1,
read_preference: Optional[_ServerMode] = None,
codec_options: Optional[CodecOptions[_CodecDocumentType]] = None,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
max_await_time_ms: Optional[int] = None,
**kwargs: Any,
) -> 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]],
value: Any = 1,
check: bool = True,
allowable_errors: Optional[Sequence[Union[str, int]]] = None,
read_preference: Optional[_ServerMode] = None,
codec_options: Optional[CodecOptions[_CodecDocumentType]] = None,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> Union[dict[str, Any], _CodecDocumentType]: ...
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[AgnosticClientSession] = None,
check_exists: Optional[bool] = True,
**kwargs: Any,
) -> AgnosticCollection[_DocumentType]: ...
async def dereference(
self,
dbref: DBRef,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> Optional[_DocumentType]: ...
async def drop_collection(
self,
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]: ...
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[_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]: ...
async def list_collections(
self,
session: Optional[AgnosticClientSession] = None,
filter: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> AgnosticCommandCursor[MutableMapping[str, Any]]: ...
@property
def name(self) -> str: ...
async def validate_collection(
self,
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]: ...
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[_DocumentType]: ...
async def _async_aggregate(
self, pipeline: _Pipeline, session: Optional[AgnosticClientSession] = None, **kwargs: Any
) -> AgnosticCommandCursor[_DocumentType]: ...
def __init__(self, client: AgnosticClient[_DocumentType], name: str, **kwargs: Any) -> None: ...
def aggregate(
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
) -> AgnosticLatentCommandCursor[_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[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,
) -> AgnosticChangeStream[_DocumentType]: ...
@property
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[_DocumentType]):
__motor_class_name__: str
__delegate_class__: type[Collection[_DocumentType]]
def __hash__(self) -> int: ...
def __bool__(self) -> bool: ...
async def bulk_write(
self,
requests: Sequence[_WriteOp[_DocumentType]],
ordered: bool = True,
bypass_document_validation: bool = False,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
let: Optional[Mapping] = None,
) -> BulkWriteResult: ...
async def count_documents(
self,
filter: Mapping[str, Any],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> int: ...
async def create_index(
self,
keys: _IndexKeyHint,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> str: ...
async def create_indexes(
self,
indexes: Sequence[IndexModel],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> list[str]: ...
async def delete_many(
self,
filter: Mapping[str, Any],
collation: Optional[_CollationIn] = None,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
) -> DeleteResult: ...
async def delete_one(
self,
filter: Mapping[str, Any],
collation: Optional[_CollationIn] = None,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
) -> DeleteResult: ...
async def distinct(
self,
key: str,
filter: Optional[Mapping[str, Any]] = None,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> list[Any]: ...
async def drop(
self,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
encrypted_fields: Optional[Mapping[str, Any]] = None,
) -> None: ...
async def drop_index(
self,
index_or_name: _IndexKeyHint,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> None: ...
async def drop_indexes(
self,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> None: ...
async def estimated_document_count(
self, comment: Optional[Any] = None, **kwargs: Any
) -> int: ...
async def find_one(
self, filter: Optional[Any] = None, *args: Any, **kwargs: Any
) -> Optional[_DocumentType]: ...
async def find_one_and_delete(
self,
filter: Mapping[str, Any],
projection: Optional[Union[Mapping[str, Any], Iterable[str]]] = None,
sort: Optional[_IndexList] = None,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType: ...
async def find_one_and_replace(
self,
filter: Mapping[str, Any],
replacement: Mapping[str, Any],
projection: Optional[Union[Mapping[str, Any], Iterable[str]]] = None,
sort: Optional[_IndexList] = None,
upsert: bool = False,
return_document: bool = ...,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType: ...
async def find_one_and_update(
self,
filter: Mapping[str, Any],
update: Union[Mapping[str, Any], _Pipeline],
projection: Optional[Union[Mapping[str, Any], Iterable[str]]] = None,
sort: Optional[_IndexList] = None,
upsert: bool = False,
return_document: bool = ...,
array_filters: Optional[Sequence[Mapping[str, Any]]] = None,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType: ...
def full_name(self) -> str: ...
async def index_information(
self, session: Optional[AgnosticClientSession] = None, comment: Optional[Any] = None
) -> MutableMapping[str, Any]: ...
async def insert_many(
self,
documents: Iterable[Union[_DocumentType, RawBSONDocument]],
ordered: bool = True,
bypass_document_validation: bool = False,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
) -> InsertManyResult: ...
async def insert_one(
self,
document: Union[_DocumentType, RawBSONDocument],
bypass_document_validation: bool = False,
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
) -> MutableMapping[str, Any]: ...
async def rename(
self,
new_name: str,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> MutableMapping[str, Any]: ...
async def replace_one(
self,
filter: Mapping[str, Any],
replacement: Mapping[str, Any],
upsert: bool = False,
bypass_document_validation: bool = False,
collation: Optional[_CollationIn] = None,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
) -> UpdateResult: ...
async def update_many(
self,
filter: Mapping[str, Any],
update: Union[Mapping[str, Any], _Pipeline],
upsert: bool = False,
array_filters: Optional[Sequence[Mapping[str, Any]]] = None,
bypass_document_validation: Optional[bool] = None,
collation: Optional[_CollationIn] = None,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
) -> UpdateResult: ...
async def update_one(
self,
filter: Mapping[str, Any],
update: Union[Mapping[str, Any], _Pipeline],
upsert: bool = False,
bypass_document_validation: bool = False,
collation: Optional[_CollationIn] = None,
array_filters: Optional[Sequence[Mapping[str, Any]]] = None,
hint: Optional[_IndexKeyHint] = None,
session: Optional[AgnosticClientSession] = None,
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
) -> UpdateResult: ...
def with_options(
self,
codec_options: Optional[CodecOptions] = None,
read_preference: Optional[ReadPreference] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
) -> AgnosticCollection[_DocumentType]: ...
def list_search_indexes(
self,
name: Optional[str] = None,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> AgnosticLatentCommandCursor[Mapping[str, Any]]: ...
async def create_search_index(
self,
model: Union[Mapping[str, SearchIndexModel], Any],
session: Optional[AgnosticClientSession] = None,
comment: Any = None,
**kwargs: Any,
) -> str: ...
async def create_search_indexes(
self,
models: list[SearchIndexModel],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> list[str]: ...
async def drop_search_index(
self,
name: str,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> None: ...
async def update_search_index(
self,
name: str,
definition: Mapping[str, Any],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> None: ...
def __init__(
self,
database: Database[_DocumentType],
name: str,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional[ReadConcern] = None,
_delegate: Any = None,
**kwargs: Any,
) -> None: ...
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[_DocumentType]: ...
def find_raw_batches(
self, *args: Any, **kwargs: Any
) -> AgnosticRawBatchCursor[_DocumentType]: ...
def aggregate(
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
) -> AgnosticCommandCursor[_DocumentType]: ...
def aggregate_raw_batches(
self, pipeline: _Pipeline, **kwargs: Any
) -> AgnosticRawBatchCursor[_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[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,
) -> Any: ...
def list_indexes(
self, session: Optional[AgnosticClientSession] = None, **kwargs: Any
) -> AgnosticLatentCommandCursor[MutableMapping[str, Any]]: ...
def wrap(self, obj: Any) -> Any: ...
def get_io_loop(self) -> Any: ...
class AgnosticBaseCursor(AgnosticBase, Generic[_DocumentType]):
def __init__(
self,
cursor: Union[
Cursor[_DocumentType], CommandCursor[_DocumentType], _LatentCursor[_DocumentType]
],
collection: AgnosticCollection[_DocumentType],
) -> None: ...
def address(self) -> Optional[_Address]: ...
def cursor_id(self) -> Optional[int]: ...
def alive(self) -> bool: ...
def session(self) -> Optional[AgnosticClientSession]: ...
async def _async_close(self) -> None: ...
async def _refresh(self) -> int: ...
def __aiter__(self) -> Any: ...
async def next(self) -> _DocumentType: ...
__anext__ = next
async def __aenter__(self) -> 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) -> 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: 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[_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[_DocumentType]):
__motor_class_name__: str
__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[_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[_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[_DocumentType]):
__motor_class_name__: str
__delegate_class__: type[RawBatchCursor]
class AgnosticCommandCursor(AgnosticBaseCursor[_DocumentType]):
__motor_class_name__: str
__delegate_class__: type[CommandCursor]
def _query_flags(self) -> int: ...
def _data(self) -> Any: ...
def _killed(self) -> Any: ...
class AgnosticRawBatchCommandCursor(AgnosticCommandCursor[_DocumentType]):
__motor_class_name__: str
__delegate_class__: type[RawBatchCommandCursor]
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[_DocumentType]):
__motor_class_name__: str
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, Generic[_DocumentType]):
__motor_class_name__: str
__delegate_class__: type[ChangeStream]
async def _close(self) -> None: ...
@property
def resume_token(self) -> Optional[Mapping[str, Any]]: ...
def __init__(
self,
target: Union[
pymongo.MongoClient[_DocumentType], Database[_DocumentType], Collection[_DocumentType]
],
pipeline: Optional[_Pipeline],
full_document: Optional[str],
resume_after: Optional[Mapping[str, Any]],
max_await_time_ms: Optional[int],
batch_size: Optional[int],
collation: Optional[_CollationIn],
start_at_operation_time: Optional[Timestamp],
session: Optional[AgnosticClientSession],
start_after: Optional[Mapping[str, Any]],
comment: Optional[Any] = None,
full_document_before_change: Optional[str] = None,
show_expanded_events: Optional[bool] = None,
): ...
def _lazy_init(self) -> None: ...
def _try_next(self) -> Optional[_DocumentType]: ...
def alive(self) -> bool: ...
async def next(self) -> _DocumentType: ...
async def try_next(self) -> Optional[_DocumentType]: ...
async def close(self) -> None: ...
def __aiter__(self) -> AgnosticChangeStream[_DocumentType]: ...
__anext__ = next
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: object, exc_val: object, exc_tb: object) -> None: ...
class AgnosticClientEncryption(AgnosticBase, Generic[_DocumentType]):
__motor_class_name__: str
__delegate_class__: type[ClientEncryption]
def __init__(
self,
kms_providers: Mapping[str, Any],
key_vault_namespace: str,
key_vault_client: AgnosticClient[_DocumentTypeArg],
codec_options: CodecOptions,
io_loop: Optional[Any] = None,
kms_tls_options: Optional[Mapping[str, Any]] = None,
): ...
async def create_data_key(
self,
kms_provider: str,
master_key: Optional[Mapping[str, Any]] = None,
key_alt_names: Optional[Sequence[str]] = None,
key_material: Optional[bytes] = None,
) -> Binary: ...
async def encrypt(
self,
value: Any,
algorithm: str,
key_id: Optional[Binary] = None,
key_alt_name: Optional[str] = None,
query_type: Optional[str] = None,
contention_factor: Optional[int] = None,
range_opts: Optional[RangeOpts] = None,
) -> Binary: ...
async def decrypt(self, value: Binary) -> Any: ...
async def close(self) -> None: ...
async def rewrap_many_data_key(
self,
filter: Mapping[str, Any],
provider: Optional[str] = None,
master_key: Optional[Mapping[str, Any]] = None,
) -> RewrapManyDataKeyResult: ...
async def delete_key(self, id: Binary) -> DeleteResult: ...
async def get_key(self, id: Binary) -> Optional[RawBSONDocument]: ...
async def add_key_alt_name(self, id: Binary, key_alt_name: str) -> Any: ...
async def get_key_by_alt_name(self, key_alt_name: str) -> Optional[RawBSONDocument]: ...
async def remove_key_alt_name(
self, id: Binary, key_alt_name: str
) -> Optional[RawBSONDocument]: ...
async def encrypt_expression(
self,
expression: Mapping[str, Any],
algorithm: str,
key_id: Optional[Binary] = None,
key_alt_name: Optional[str] = None,
query_type: Optional[str] = None,
contention_factor: Optional[int] = None,
range_opts: Optional[RangeOpts] = None,
) -> RawBSONDocument: ...
@property
def io_loop(self) -> Any: ...
def get_io_loop(self) -> Any: ...
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: object, exc_val: object, exc_tb: object) -> None: ...
async def get_keys(self) -> AgnosticCursor[RawBSONDocument]: ...
async def create_encrypted_collection(
self,
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[_DocumentTypeArg], Mapping[str, Any]]: ...

File diff suppressed because it is too large Load Diff

View File

@ -16,26 +16,33 @@
See "Frameworks" in the Developer Guide.
"""
import asyncio
import asyncio.tasks
import os
import functools
import multiprocessing
import os
import warnings
from asyncio import get_event_loop # noqa: F401 - For framework interface.
from concurrent.futures import ThreadPoolExecutor
# mypy: ignore-errors
try:
from asyncio import ensure_future
import contextvars
except ImportError:
from asyncio import async as ensure_future
contextvars = None
CLASS_PREFIX = 'AsyncIO'
try:
from asyncio import coroutine
except ImportError:
def coroutine():
raise RuntimeError(
"The coroutine decorator was removed in Python 3.11. Use 'async def' instead"
)
def get_event_loop():
return asyncio.get_event_loop()
CLASS_PREFIX = "AsyncIO"
def is_event_loop(loop):
@ -44,66 +51,79 @@ def is_event_loop(loop):
def check_event_loop(loop):
if not is_event_loop(loop):
raise TypeError(
"io_loop must be instance of asyncio-compatible event loop,"
"not %r" % loop)
raise TypeError("io_loop must be instance of asyncio-compatible event loop, not %r" % loop)
def get_future(loop):
return asyncio.Future(loop=loop)
return loop.create_future()
if 'MOTOR_MAX_WORKERS' in os.environ:
max_workers = int(os.environ['MOTOR_MAX_WORKERS'])
if "MOTOR_MAX_WORKERS" in os.environ:
max_workers = int(os.environ["MOTOR_MAX_WORKERS"])
else:
max_workers = multiprocessing.cpu_count() * 5
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
def run_on_executor(loop, fn, self, *args, **kwargs):
# Ensures the wrapped future is resolved on the main thread, though the
# executor's future is resolved on a worker thread.
return asyncio.futures.wrap_future(
_EXECUTOR.submit(functools.partial(fn, self, *args, **kwargs)),
loop=loop)
def _reset_global_executor():
"""Re-initialize the global ThreadPoolExecutor"""
global _EXECUTOR # noqa: PLW0603
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
_DEFAULT = object()
if hasattr(os, "register_at_fork"):
# We need this to make sure that creating new clients in subprocesses doesn't deadlock.
os.register_at_fork(after_in_child=_reset_global_executor)
def future_or_callback(future, callback, loop, return_value=_DEFAULT):
def run_on_executor(loop, fn, *args, **kwargs):
if contextvars:
context = contextvars.copy_context()
fn = functools.partial(context.run, fn)
return loop.run_in_executor(_EXECUTOR, functools.partial(fn, *args, **kwargs))
# Adapted from tornado.gen.
def chain_future(a, b):
def copy(future):
assert future is a
if b.done():
return
if a.exception() is not None:
b.set_exception(a.exception())
else:
b.set_result(a.result())
a.add_done_callback(copy)
def chain_return_value(future, loop, return_value):
"""Compatible way to return a value in all Pythons.
PEP 479, raise StopIteration(value) from a coroutine won't work forever,
but "return value" doesn't work in Python 2. Instead, Motor methods that
return values either execute a callback with the value or resolve a Future
with it, and are implemented with callbacks rather than a coroutine
internally.
return values resolve a Future with it, and are implemented with callbacks
rather than a coroutine internally.
"""
if callback:
raise NotImplementedError("Motor with asyncio prohibits callbacks")
chained = loop.create_future()
if return_value is _DEFAULT:
return future
def copy(_future):
# Return early if the task was cancelled.
if chained.done():
return
if _future.exception() is not None:
chained.set_exception(_future.exception())
else:
chained.set_result(return_value)
chained = asyncio.Future(loop=loop)
def done_callback(_future):
try:
result = _future.result()
chained.set_result(result if return_value is _DEFAULT
else return_value)
except Exception as exc:
chained.set_exception(exc)
future.add_done_callback(functools.partial(loop.call_soon_threadsafe,
done_callback))
future.add_done_callback(functools.partial(loop.call_soon_threadsafe, copy))
return chained
def is_future(f):
return isinstance(f, asyncio.Future)
return asyncio.isfuture(f)
def call_soon(loop, callback, *args, **kwargs):
@ -114,11 +134,7 @@ def call_soon(loop, callback, *args, **kwargs):
def add_future(loop, future, callback, *args):
future.add_done_callback(
functools.partial(loop.call_soon_threadsafe, callback, *args))
coroutine = asyncio.coroutine
future.add_done_callback(functools.partial(loop.call_soon_threadsafe, callback, *args))
def pymongo_class_wrapper(f, pymongo_class):
@ -126,10 +142,10 @@ def pymongo_class_wrapper(f, pymongo_class):
See WrapAsync.
"""
@functools.wraps(f)
@asyncio.coroutine
def _wrapper(self, *args, **kwargs):
result = yield from f(self, *args, **kwargs)
async def _wrapper(self, *args, **kwargs):
result = await f(self, *args, **kwargs)
# Don't call isinstance(), not checking subclasses.
if result.__class__ == pymongo_class:
@ -142,5 +158,13 @@ def pymongo_class_wrapper(f, pymongo_class):
def yieldable(future):
# TODO: really explain.
warnings.warn(
"The yieldable function is deprecated and may be removed in a future major release",
DeprecationWarning,
stacklevel=2,
)
return next(iter(future))
def platform_info():
return "asyncio"

View File

@ -12,20 +12,29 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, unicode_literals
"""Tornado compatibility layer for Motor, an asynchronous MongoDB driver.
"""Tornado compatibility layer for MongoDB, an asynchronous MongoDB driver."""
See "Frameworks" in the Developer Guide.
"""
import functools
import os
import warnings
from concurrent.futures import ThreadPoolExecutor
import tornado.process
from tornado import concurrent, gen, ioloop
from tornado import concurrent, ioloop
from tornado import version as tornado_version
from tornado.gen import chain_future, coroutine # noqa: F401 - For framework interface.
from motor.motor_common import callback_type_error
try:
import contextvars
except ImportError:
contextvars = None
CLASS_PREFIX = ''
# mypy: ignore-errors
CLASS_PREFIX = ""
def get_event_loop():
@ -38,92 +47,61 @@ def is_event_loop(loop):
def check_event_loop(loop):
if not is_event_loop(loop):
raise TypeError(
"io_loop must be instance of IOLoop, not %r" % loop)
# Beginning in Tornado 4, TracebackFuture is a deprecated alias for Future.
# Future-proof Motor in case TracebackFuture is some day removed. Remove this
# Future-proofing once we drop support for Tornado 3.
try:
_TornadoFuture = concurrent.TracebackFuture
except AttributeError:
_TornadoFuture = concurrent.Future
raise TypeError("io_loop must be instance of IOLoop, not %r" % loop)
def get_future(loop):
return _TornadoFuture()
return concurrent.Future()
if 'MOTOR_MAX_WORKERS' in os.environ:
max_workers = int(os.environ['MOTOR_MAX_WORKERS'])
if "MOTOR_MAX_WORKERS" in os.environ:
max_workers = int(os.environ["MOTOR_MAX_WORKERS"])
else:
max_workers = tornado.process.cpu_count() * 5
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
def run_on_executor(loop, fn, self, *args, **kwargs):
# Need a Tornado Future for "await" expressions. exec_fut is resolved on a
# worker thread, loop.add_future ensures "future" is resolved on main.
future = _TornadoFuture()
exec_fut = _EXECUTOR.submit(fn, self, *args, **kwargs)
def copy(_):
if future.done():
return
if exec_fut.exception() is not None:
future.set_exception(exec_fut.exception())
else:
future.set_result(exec_fut.result())
# Ensure copy runs on main thread.
loop.add_future(exec_fut, copy)
return future
_DEFAULT = object()
def _reset_global_executor():
"""Re-initialize the global ThreadPoolExecutor"""
global _EXECUTOR # noqa: PLW0603
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
def future_or_callback(future, callback, io_loop, return_value=_DEFAULT):
if hasattr(os, "register_at_fork"):
# We need this to make sure that creating new clients in subprocesses doesn't deadlock.
os.register_at_fork(after_in_child=_reset_global_executor)
def run_on_executor(loop, fn, *args, **kwargs):
if contextvars:
context = contextvars.copy_context()
fn = functools.partial(context.run, fn)
return loop.run_in_executor(_EXECUTOR, functools.partial(fn, *args, **kwargs))
def chain_return_value(future, loop, return_value):
"""Compatible way to return a value in all Pythons.
PEP 479, raise StopIteration(value) from a coroutine won't work forever,
but "return value" doesn't work in Python 2. Instead, Motor methods that
return values either execute a callback with the value or resolve a Future
with it, and are implemented with callbacks rather than a coroutine
internally.
return values resolve a Future with it, and are implemented with callbacks
rather than a coroutine internally.
"""
if callback:
if not callable(callback):
raise callback_type_error
chained = concurrent.Future()
# Motor's callback convention is "callback(result, error)".
def done_callback(_future):
try:
result = _future.result()
callback(result if return_value is _DEFAULT else return_value,
None)
except Exception as exc:
callback(None, exc)
def copy(_future):
# Return early if the task was cancelled.
if chained.done():
return
if _future.exception() is not None:
chained.set_exception(_future.exception())
else:
chained.set_result(return_value)
io_loop.add_future(future, done_callback)
elif return_value is not _DEFAULT:
chained = _TornadoFuture()
def done_callback(_future):
try:
_future.result()
except Exception as exc:
chained.set_exception(exc)
else:
chained.set_result(return_value)
io_loop.add_future(future, done_callback)
return chained
else:
return future
future.add_done_callback(functools.partial(loop.add_callback, copy))
return chained
def is_future(f):
@ -141,53 +119,34 @@ def add_future(loop, future, callback, *args):
loop.add_future(future, functools.partial(callback, *args))
def coroutine(f):
"""A coroutine that accepts an optional callback.
Given a callback, the function returns None, and the callback is run
with (result, error). Without a callback the function returns a Future.
"""
coro = gen.coroutine(f)
@functools.wraps(f)
def wrapper(*args, **kwargs):
callback = kwargs.pop('callback', None)
if callback and not callable(callback):
raise callback_type_error
future = coro(*args, **kwargs)
if callback:
def _callback(_future):
try:
result = _future.result()
callback(result, None)
except Exception as e:
callback(None, e)
future.add_done_callback(_callback)
else:
return future
return wrapper
def pymongo_class_wrapper(f, pymongo_class):
"""Executes the coroutine f and wraps its result in a Motor class.
See WrapAsync.
"""
@functools.wraps(f)
@coroutine
def _wrapper(self, *args, **kwargs):
result = yield f(self, *args, **kwargs)
async def _wrapper(self, *args, **kwargs):
result = await f(self, *args, **kwargs)
# Don't call isinstance(), not checking subclasses.
if result.__class__ == pymongo_class:
# Delegate to the current object to wrap the result.
raise gen.Return(self.wrap(result))
return self.wrap(result)
else:
raise gen.Return(result)
return result
return _wrapper
def yieldable(future):
# TODO: really explain.
warnings.warn(
"The yieldable function is deprecated and may be removed in a future major release.",
DeprecationWarning,
stacklevel=2,
)
return future
def platform_info():
return f"Tornado {tornado_version}"

View File

@ -12,25 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import unicode_literals, absolute_import
"""Dynamic class-creation for Motor."""
import inspect
import functools
import inspect
from collections.abc import Callable
from typing import Any, TypeVar
from pymongo.cursor import Cursor
_class_cache: dict[Any, Any] = {}
from . import motor_py3_compat
_class_cache = {}
# mypy: ignore-errors
def asynchronize(framework, sync_method, doc=None):
"""Decorate `sync_method` so it accepts a callback or returns a Future.
def asynchronize(framework, sync_method: Callable, doc=None, wrap_class=None, unwrap_class=None):
"""Decorate `sync_method` so it returns a Future.
The method runs on a thread and calls the callback or resolves
the Future when the thread completes.
The method runs on a thread and resolves the Future when it completes.
:Parameters:
- `motor_class`: Motor class being created, e.g. MotorClient.
@ -38,62 +34,101 @@ def asynchronize(framework, sync_method, doc=None):
- `sync_method`: Unbound method of pymongo Collection, Database,
MongoClient, etc.
- `doc`: Optionally override sync_method's docstring
- `wrap_class`: Optional PyMongo class, wrap a returned object of
this PyMongo class in the equivalent Motor class
- `unwrap_class` Optional Motor class name, unwrap an argument with
this Motor class name and pass the wrapped PyMongo
object instead
"""
@functools.wraps(sync_method)
def method(self, *args, **kwargs):
loop = self.get_io_loop()
callback = kwargs.pop('callback', None)
future = framework.run_on_executor(loop,
sync_method,
self.delegate,
*args,
**kwargs)
if unwrap_class is not None:
# Don't call isinstance(), not checking subclasses.
unwrapped_args = [
obj.delegate
if obj.__class__.__name__.endswith((unwrap_class, "MotorClientSession"))
else obj
for obj in args
]
unwrapped_kwargs = {
key: (
obj.delegate
if obj.__class__.__name__.endswith((unwrap_class, "MotorClientSession"))
else obj
)
for key, obj in kwargs.items()
}
else:
# For speed, don't call unwrap_args_session/unwrap_kwargs_session.
unwrapped_args = [
obj.delegate if obj.__class__.__name__.endswith("MotorClientSession") else obj
for obj in args
]
unwrapped_kwargs = {
key: (
obj.delegate if obj.__class__.__name__.endswith("MotorClientSession") else obj
)
for key, obj in kwargs.items()
}
return framework.future_or_callback(future, callback, loop)
loop = self.get_io_loop()
return framework.run_on_executor(
loop, sync_method, self.delegate, *unwrapped_args, **unwrapped_kwargs
)
if wrap_class is not None:
method = framework.pymongo_class_wrapper(method, wrap_class)
method.is_wrap_method = True # For Synchro.
# This is for the benefit of motor_extensions.py, which needs this info to
# generate documentation with Sphinx.
method.is_async_method = True
name = sync_method.__name__
method.pymongo_method_name = name
if doc is not None:
method.__doc__ = doc
return method
def unwrap_args_session(args):
return (
obj.delegate if obj.__class__.__name__.endswith("MotorClientSession") else obj
for obj in args
)
def unwrap_kwargs_session(kwargs):
return {
key: (obj.delegate if obj.__class__.__name__.endswith("MotorClientSession") else obj)
for key, obj in kwargs.items()
}
_coro_token = object()
def motor_coroutine(f):
"""Used by Motor classes to mark functions as coroutines.
create_class_with_framework will decorate the function with a framework-
specific coroutine decorator, like asyncio.coroutine or Tornado's
gen.coroutine.
You cannot return a value from a motor_coroutine, the syntax differences
between Tornado on Python 2 and asyncio with Python 3.5 are impossible to
bridge.
"""
f._is_motor_coroutine = _coro_token
return f
def coroutine_annotation(f):
"""In docs, annotate a function that returns a Future with 'coroutine'.
Unlike @motor_coroutine, this doesn't affect behavior.
This doesn't affect behavior.
"""
# Like:
# @coroutine_annotation
# def method(self):
#
f.coroutine_annotation = True
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.
"""
def __init__(self, doc=None):
self.doc = doc
@ -103,53 +138,98 @@ class MotorAttributeFactory(object):
class Async(MotorAttributeFactory):
def __init__(self, attr_name, doc=None):
"""A descriptor that wraps a PyMongo method, such as insert or remove,
and returns an asynchronous version of the method, which accepts a
callback or returns a Future.
"""A descriptor that wraps a PyMongo method, such as insert_one,
and returns an asynchronous version of the method that returns a Future.
:Parameters:
- `attr_name`: The name of the attribute on the PyMongo class, if
different from attribute on the Motor class
"""
super(Async, self).__init__(doc)
super().__init__(doc)
self.attr_name = attr_name
self.wrap_class = None
self.unwrap_class = None
def create_attribute(self, cls, attr_name):
name = self.attr_name or attr_name
method = getattr(cls.__delegate_class__, name)
return asynchronize(framework=cls._framework,
sync_method=method,
doc=self.doc)
return asynchronize(
framework=cls._framework,
sync_method=method,
doc=self.doc,
wrap_class=self.wrap_class,
unwrap_class=self.unwrap_class,
)
def wrap(self, original_class):
return WrapAsync(self, original_class)
self.wrap_class = original_class
return self
def unwrap(self, class_name):
return Unwrap(self, class_name)
self.unwrap_class = class_name
return self
class WrapBase(MotorAttributeFactory):
def __init__(self, prop, doc=None):
super(WrapBase, self).__init__(doc)
self.property = prop
class Wrap(WrapBase):
def __init__(self, prop, original_class, doc=None):
"""Calls a synchronous method and wraps the PyMongo class instance it
returns in a Motor class instance.
:Parameters:
- `prop`: A DelegateMethod, the method to call before wrapping its
result in a Motor class.
- `original_class`: A PyMongo class to be wrapped.
class AsyncRead(Async):
def __init__(self, attr_name=None, doc=None):
"""A descriptor that wraps a PyMongo read method like find_one() that
returns a Future.
"""
super(Wrap, self).__init__(prop, doc=doc)
self.original_class = original_class
Async.__init__(self, attr_name=attr_name, doc=doc)
class AsyncWrite(Async):
def __init__(self, attr_name=None, doc=None):
"""A descriptor that wraps a PyMongo write method like update_one() that
accepts getLastError options and returns a Future.
"""
Async.__init__(self, attr_name=attr_name, doc=doc)
class AsyncCommand(Async):
def __init__(self, attr_name=None, doc=None):
"""A descriptor that wraps a PyMongo command like copy_database() that
returns a Future and does not accept getLastError options.
"""
Async.__init__(self, attr_name=attr_name, doc=doc)
class ReadOnlyProperty(MotorAttributeFactory):
"""Creates a readonly attribute on the wrapped PyMongo object."""
def create_attribute(self, cls, attr_name):
def fget(obj):
return getattr(obj.delegate, attr_name)
if self.doc:
doc = self.doc
else:
doc = getattr(cls.__delegate_class__, attr_name).__doc__
if doc:
return property(fget=fget, doc=doc)
else:
return property(fget=fget)
class DelegateMethod(ReadOnlyProperty):
"""A method on the wrapped PyMongo object that does no I/O and can be called
synchronously"""
def __init__(self, doc=None):
ReadOnlyProperty.__init__(self, doc)
self.wrap_class = None
def wrap(self, original_class):
self.wrap_class = original_class
return self
def create_attribute(self, cls, attr_name):
if self.wrap_class is None:
return ReadOnlyProperty.create_attribute(self, cls, attr_name)
method = getattr(cls.__delegate_class__, attr_name)
original_class = self.original_class
original_class = self.wrap_class
@functools.wraps(method)
def wrapper(self_, *args, **kwargs):
@ -169,134 +249,9 @@ class Wrap(WrapBase):
return wrapper
class WrapAsync(WrapBase):
def __init__(self, prop, original_class):
"""Like Async, but before it executes the callback or resolves the
Future, checks if result is a PyMongo class and wraps it in a Motor
class. E.g., Motor's map_reduce should pass a MotorCollection instead
of a PyMongo Collection to the Future. Uses the wrap() method on the
owner object to do the actual wrapping. E.g.,
Database.create_collection returns a Collection, so MotorDatabase has:
create_collection = AsyncCommand().wrap(Collection)
Once Database.create_collection is done, Motor calls
MotorDatabase.wrap() on its result, transforming the result from
Collection to MotorCollection, which is passed to the callback or
Future.
:Parameters:
- `prop`: An Async, the async method to call before wrapping its result
in a Motor class.
- `original_class`: A PyMongo class to be wrapped.
"""
super(WrapAsync, self).__init__(prop)
self.original_class = original_class
def create_attribute(self, cls, attr_name):
async_method = self.property.create_attribute(cls, attr_name)
original_class = self.original_class
wrapper = cls._framework.pymongo_class_wrapper(async_method,
original_class)
if self.doc:
wrapper.__doc__ = self.doc
return wrapper
class Unwrap(WrapBase):
def __init__(self, prop, motor_class_name, doc=None):
"""A descriptor that checks if arguments are Motor classes and unwraps
them. E.g., Motor's drop_database takes a MotorDatabase, unwraps it,
and passes a PyMongo Database instead.
:Parameters:
- `prop`: An Async or DelegateMethod, the method to call with
unwrapped arguments.
- `motor_class_name`: Like 'MotorDatabase' or 'MotorCollection'.
"""
super(Unwrap, self).__init__(prop, doc=doc)
assert isinstance(motor_class_name, motor_py3_compat.text_type)
self.motor_class_name = motor_class_name
def create_attribute(self, cls, attr_name):
f = self.property.create_attribute(cls, attr_name)
name = self.motor_class_name
@functools.wraps(f)
def _f(self, *args, **kwargs):
# Don't call isinstance(), not checking subclasses.
unwrapped_args = [
obj.delegate if obj.__class__.__name__.endswith(name) else obj
for obj in args]
unwrapped_kwargs = dict([
(key, obj.delegate if obj.__class__.__name__ == name else obj)
for key, obj in kwargs.items()])
return f(self, *unwrapped_args, **unwrapped_kwargs)
if self.doc:
_f.__doc__ = self.doc
_f.is_unwrap_method = True # For Synchro.
return _f
class AsyncRead(Async):
def __init__(self, attr_name=None, doc=None):
"""A descriptor that wraps a PyMongo read method like find_one() that
returns a Future.
"""
Async.__init__(self, attr_name=attr_name, doc=doc)
class AsyncWrite(Async):
def __init__(self, attr_name=None, doc=None):
"""A descriptor that wraps a PyMongo write method like update() that
accepts getLastError options and returns a Future.
"""
Async.__init__(self, attr_name=attr_name, doc=doc)
class AsyncCommand(Async):
def __init__(self, attr_name=None, doc=None):
"""A descriptor that wraps a PyMongo command like copy_database() that
returns a Future and does not accept getLastError options.
"""
Async.__init__(self, attr_name=attr_name, doc=doc)
class ReadOnlyProperty(MotorAttributeFactory):
"""Creates a readonly attribute on the wrapped PyMongo object."""
def create_attribute(self, cls, attr_name):
def fget(obj):
return getattr(obj.delegate, attr_name)
if self.doc:
doc = self.doc
else:
doc = getattr(cls.__delegate_class__, attr_name).__doc__
if doc:
return property(fget=fget, doc=doc)
else:
return property(fget=fget)
class DelegateMethod(ReadOnlyProperty):
"""A method on the wrapped PyMongo object that does no I/O and can be called
synchronously"""
def wrap(self, original_class):
return Wrap(self, original_class, doc=self.doc)
def unwrap(self, class_name):
return Unwrap(self, class_name, doc=self.doc)
class MotorCursorChainingMethod(MotorAttributeFactory):
def create_attribute(self, cls, attr_name):
cursor_method = getattr(Cursor, attr_name)
cursor_method = getattr(cls.__delegate_class__, attr_name)
@functools.wraps(cursor_method)
def return_clone(self, *args, **kwargs):
@ -312,18 +267,21 @@ 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)
if cached_class:
return cached_class
new_class = type(str(motor_class_name), cls.__bases__, cls.__dict__.copy())
new_class = type(str(motor_class_name), (cls,), {})
new_class.__module__ = module_name
new_class._framework = framework
assert hasattr(new_class, '__delegate_class__')
assert hasattr(new_class, "__delegate_class__")
# If we're constructing MotorClient from AgnosticClient, for example,
# the method resolution order is (AgnosticClient, AgnosticBase, object).
@ -336,11 +294,5 @@ def create_class_with_framework(cls, framework, module_name):
new_class_attr = attr.create_attribute(new_class, name)
setattr(new_class, name, new_class_attr)
elif getattr(attr, '_is_motor_coroutine', None) is _coro_token:
coro = framework.coroutine(attr)
del coro._is_motor_coroutine
coro.coroutine_annotation = True
setattr(new_class, name, coro)
_class_cache[cache_key] = new_class
return new_class

View File

@ -13,60 +13,64 @@
# limitations under the License.
"""Asyncio support for Motor, an asynchronous driver for MongoDB."""
__all__ = ['AsyncIOMotorClient']
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",
"AsyncIOMotorClientSession",
"AsyncIOMotorDatabase",
"AsyncIOMotorCollection",
"AsyncIOMotorCursor",
"AsyncIOMotorCommandCursor",
"AsyncIOMotorChangeStream",
"AsyncIOMotorGridFSBucket",
"AsyncIOMotorGridIn",
"AsyncIOMotorGridOut",
"AsyncIOMotorGridOutCursor",
"AsyncIOMotorClientEncryption",
]
def create_asyncio_class(cls):
return create_class_with_framework(cls, asyncio_framework, 'motor_asyncio')
def create_asyncio_class(cls: T) -> T:
return create_class_with_framework(cls, asyncio_framework, "motor.motor_asyncio")
AsyncIOMotorClient = create_asyncio_class(core.AgnosticClient)
AsyncIOMotorDatabase = create_asyncio_class(
core.AgnosticDatabase)
AsyncIOMotorClientSession = create_asyncio_class(core.AgnosticClientSession)
AsyncIOMotorCollection = create_asyncio_class(
core.AgnosticCollection)
AsyncIOMotorDatabase = create_asyncio_class(core.AgnosticDatabase)
AsyncIOMotorCursor = create_asyncio_class(
core.AgnosticCursor)
AsyncIOMotorCollection = create_asyncio_class(core.AgnosticCollection)
AsyncIOMotorCommandCursor = create_asyncio_class(
core.AgnosticCommandCursor)
AsyncIOMotorCursor = create_asyncio_class(core.AgnosticCursor)
AsyncIOMotorLatentCommandCursor = create_asyncio_class(
core.AgnosticLatentCommandCursor)
AsyncIOMotorCommandCursor = create_asyncio_class(core.AgnosticCommandCursor)
AsyncIOMotorBulkOperationBuilder = create_asyncio_class(
core.AgnosticBulkOperationBuilder)
AsyncIOMotorLatentCommandCursor = create_asyncio_class(core.AgnosticLatentCommandCursor)
AsyncIOMotorGridFS = create_asyncio_class(
motor_gridfs.AgnosticGridFS)
AsyncIOMotorChangeStream = create_asyncio_class(core.AgnosticChangeStream)
AsyncIOMotorGridFSBucket = create_asyncio_class(
motor_gridfs.AgnosticGridFSBucket)
AsyncIOMotorGridFSBucket = create_asyncio_class(motor_gridfs.AgnosticGridFSBucket)
AsyncIOMotorGridIn = create_asyncio_class(
motor_gridfs.AgnosticGridIn)
AsyncIOMotorGridIn = create_asyncio_class(motor_gridfs.AgnosticGridIn)
AsyncIOMotorGridOut = create_asyncio_class(
motor_gridfs.AgnosticGridOut)
AsyncIOMotorGridOut = create_asyncio_class(motor_gridfs.AgnosticGridOut)
AsyncIOMotorGridOutCursor = create_asyncio_class(
motor_gridfs.AgnosticGridOutCursor)
AsyncIOMotorGridOutCursor = create_asyncio_class(motor_gridfs.AgnosticGridOutCursor)
AsyncIOMotorClientEncryption = create_asyncio_class(core.AgnosticClientEncryption)

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

@ -12,8 +12,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import unicode_literals, absolute_import
"""Common code to support all async frameworks."""
callback_type_error = TypeError("callback must be a callable")

View File

@ -12,113 +12,56 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import unicode_literals, absolute_import
"""GridFS implementation for Motor, an asynchronous driver for MongoDB."""
import textwrap
import hashlib
import warnings
import gridfs
import pymongo
import pymongo.errors
from gridfs import grid_file
from gridfs import DEFAULT_CHUNK_SIZE, grid_file
from motor.core import (AgnosticBaseCursor,
AgnosticCollection,
AgnosticDatabase,
PY35,
PY352)
from motor.docstrings import *
from motor.metaprogramming import (AsyncCommand,
AsyncRead,
coroutine_annotation,
create_class_with_framework,
DelegateMethod,
motor_coroutine,
MotorCursorChainingMethod,
ReadOnlyProperty)
from motor import docstrings
from motor.core import AgnosticCollection, AgnosticCursor, AgnosticDatabase
from motor.metaprogramming import (
AsyncCommand,
AsyncRead,
DelegateMethod,
ReadOnlyProperty,
coroutine_annotation,
create_class_with_framework,
)
class AgnosticGridOutCursor(AgnosticBaseCursor):
__motor_class_name__ = 'MotorGridOutCursor'
class AgnosticGridOutCursor(AgnosticCursor):
__motor_class_name__ = "MotorGridOutCursor"
__delegate_class__ = gridfs.GridOutCursor
add_option = MotorCursorChainingMethod()
address = ReadOnlyProperty()
collation = ReadOnlyProperty()
comment = MotorCursorChainingMethod()
count = AsyncRead()
distinct = AsyncRead()
explain = AsyncRead()
hint = MotorCursorChainingMethod()
limit = MotorCursorChainingMethod()
max = MotorCursorChainingMethod()
max_await_time_ms = MotorCursorChainingMethod()
max_scan = MotorCursorChainingMethod()
max_time_ms = MotorCursorChainingMethod()
min = MotorCursorChainingMethod()
remove_option = MotorCursorChainingMethod()
skip = MotorCursorChainingMethod()
sort = MotorCursorChainingMethod(doc=cursor_sort_doc)
where = MotorCursorChainingMethod()
# PyMongo's GridOutCursor inherits __die from Cursor.
_Cursor__die = AsyncCommand()
def clone(self):
"""Get a clone of this cursor."""
return self.__class__(self.delegate.clone(), self.collection)
def next_object(self):
"""Get next GridOut object from cursor."""
grid_out = super(self.__class__, self).next_object()
"""**DEPRECATED** - Get next GridOut object from cursor."""
# Note: the super() call will raise a warning for the deprecation.
grid_out = super().next_object()
if grid_out:
grid_out_class = create_class_with_framework(
AgnosticGridOut, self._framework, self.__module__)
AgnosticGridOut, self._framework, self.__module__
)
return grid_out_class(self.collection, delegate=grid_out)
else:
# Exhausted.
return None
def rewind(self):
"""Rewind this cursor to its unevaluated state."""
self.delegate.rewind()
self.started = False
return self
def _empty(self):
return self.delegate._Cursor__empty
def _query_flags(self):
return self.delegate._Cursor__query_flags
def _data(self):
return self.delegate._Cursor__data
def _clear_cursor_id(self):
self.delegate._Cursor__id = 0
def _close_exhaust_cursor(self):
# Exhaust MotorGridOutCursors are prohibited.
pass
def _killed(self):
return self.delegate._Cursor__killed
@motor_coroutine
def _close(self):
yield self._framework.yieldable(self._Cursor__die())
class MotorGridOutProperty(ReadOnlyProperty):
"""Creates a readonly attribute on the wrapped PyMongo GridOut."""
def create_attribute(self, cls, attr_name):
def fget(obj):
if not obj.delegate._file:
raise pymongo.errors.InvalidOperation(
"You must call MotorGridOut.open() before accessing "
"the %s property" % attr_name)
"the %s property" % attr_name
)
return getattr(obj.delegate, attr_name)
@ -126,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
@ -138,109 +81,88 @@ class AgnosticGridOut(object):
instantiated directly, call :meth:`open`, :meth:`read`, or
:meth:`readline` before accessing its attributes.
"""
__motor_class_name__ = 'MotorGridOut'
__motor_class_name__ = "MotorGridOut"
__delegate_class__ = gridfs.GridOut
_ensure_file = AsyncCommand()
_id = MotorGridOutProperty()
aliases = MotorGridOutProperty()
chunk_size = MotorGridOutProperty()
close = MotorGridOutProperty()
_id = MotorGridOutProperty()
aliases = MotorGridOutProperty()
chunk_size = MotorGridOutProperty()
close = MotorGridOutProperty()
content_type = MotorGridOutProperty()
filename = MotorGridOutProperty()
length = MotorGridOutProperty()
md5 = MotorGridOutProperty()
metadata = MotorGridOutProperty()
name = MotorGridOutProperty()
read = AsyncRead()
readchunk = AsyncRead()
readline = AsyncRead()
seek = DelegateMethod()
tell = DelegateMethod()
upload_date = MotorGridOutProperty()
filename = MotorGridOutProperty()
length = MotorGridOutProperty()
metadata = MotorGridOutProperty()
name = MotorGridOutProperty()
_open = AsyncCommand(attr_name="open")
read = AsyncRead()
readable = DelegateMethod()
readchunk = AsyncRead()
readline = AsyncRead()
seek = DelegateMethod()
seekable = DelegateMethod()
tell = DelegateMethod()
upload_date = MotorGridOutProperty()
write = DelegateMethod()
def __init__(
self,
root_collection,
file_id=None,
file_document=None,
delegate=None,
self, root_collection, file_id=None, file_document=None, delegate=None, session=None
):
collection_class = create_class_with_framework(
AgnosticCollection, self._framework, self.__module__)
AgnosticCollection, self._framework, self.__module__
)
if not isinstance(root_collection, collection_class):
raise TypeError(
"First argument to MotorGridOut must be "
"MotorCollection, not %r" % root_collection)
"MotorCollection, not %r" % root_collection
)
if delegate:
self.delegate = delegate
else:
self.delegate = self.__delegate_class__(
root_collection.delegate,
file_id,
file_document)
root_collection.delegate, file_id, file_document, session=session
)
self.io_loop = root_collection.get_io_loop()
# python.org/dev/peps/pep-0492/#api-design-and-implementation-revisions
if PY352:
exec(textwrap.dedent("""
def __aiter__(self):
return self
def __aiter__(self):
return self
async def __anext__(self):
chunk = await self.readchunk()
if chunk:
return chunk
raise StopAsyncIteration()
"""), globals(), locals())
elif PY35:
# In Python 3.5.0 and 3.5.1, __aiter__ is a coroutine.
exec(textwrap.dedent("""
async def __aiter__(self):
return self
async def __anext__(self):
chunk = await self.readchunk()
if chunk:
return chunk
raise StopAsyncIteration()
"""), globals(), locals())
async def __anext__(self):
chunk = await self.readchunk()
if chunk:
return chunk
raise StopAsyncIteration()
def __getattr__(self, item):
if not self.delegate._file:
raise pymongo.errors.InvalidOperation(
"You must call MotorGridOut.open() before accessing "
"the %s property" % item)
"You must call MotorGridOut.open() before accessing the %s property" % item
)
return getattr(self.delegate, item)
@coroutine_annotation
def open(self, callback=None):
def open(self):
"""Retrieve this file's attributes from the server.
Takes an optional callback, or returns a Future.
Returns a Future.
:Parameters:
- `callback`: Optional function taking parameters (self, error)
.. versionchanged:: 2.0
No longer accepts a callback argument.
.. versionchanged:: 0.2
:class:`~motor.MotorGridOut` now opens itself on demand, calling
``open`` explicitly is rarely needed.
"""
return self._framework.future_or_callback(self._ensure_file(),
callback,
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
@motor_coroutine
def stream_to_handler(self, request_handler):
async def stream_to_handler(self, request_handler):
"""Write the contents of this file to a
:class:`tornado.web.RequestHandler`. This method calls
:meth:`~tornado.web.RequestHandler.flush` on
@ -254,16 +176,16 @@ class AgnosticGridOut(object):
@tornado.web.asynchronous
@gen.coroutine
def get(self, filename):
db = self.settings['db']
fs = yield motor.MotorGridFSBucket(db())
db = self.settings["db"]
fs = await motor.MotorGridFSBucket(db())
try:
gridout = yield fs.open_download_stream_by_name(filename)
gridout = await fs.open_download_stream_by_name(filename)
except gridfs.NoFile:
raise tornado.web.HTTPError(404)
self.set_header("Content-Type", gridout.content_type)
self.set_header("Content-Length", gridout.length)
yield gridout.stream_to_handler(self)
await gridout.stream_to_handler(self)
self.finish()
.. seealso:: Tornado `RequestHandler <http://tornadoweb.org/en/stable/web.html#request-handlers>`_
@ -271,9 +193,7 @@ class AgnosticGridOut(object):
written = 0
while written < self.length:
# Reading chunk_size at a time minimizes buffering.
f = self._framework.yieldable(self.read(self.chunk_size))
yield f
chunk = f.result()
chunk = await self.read(self.chunk_size)
# write() simply appends the output to a list; flush() sends it
# over the network and minimizes buffering in the handler.
@ -282,25 +202,31 @@ class AgnosticGridOut(object):
written += len(chunk)
class AgnosticGridIn(object):
__motor_class_name__ = 'MotorGridIn'
class AgnosticGridIn:
__motor_class_name__ = "MotorGridIn"
__delegate_class__ = gridfs.GridIn
__getattr__ = DelegateMethod()
abort = AsyncCommand()
closed = ReadOnlyProperty()
close = AsyncCommand()
write = AsyncCommand().unwrap('MotorGridOut')
writelines = AsyncCommand().unwrap('MotorGridOut')
_id = ReadOnlyProperty()
md5 = ReadOnlyProperty()
filename = ReadOnlyProperty()
name = ReadOnlyProperty()
__getattr__ = DelegateMethod()
_id = ReadOnlyProperty()
abort = AsyncCommand()
chunk_size = ReadOnlyProperty()
closed = ReadOnlyProperty()
close = AsyncCommand()
content_type = ReadOnlyProperty()
length = ReadOnlyProperty()
chunk_size = ReadOnlyProperty()
upload_date = ReadOnlyProperty()
set = AsyncCommand(attr_name='__setattr__', doc="""
filename = ReadOnlyProperty()
length = ReadOnlyProperty()
name = ReadOnlyProperty()
read = DelegateMethod()
readable = DelegateMethod()
seekable = DelegateMethod()
upload_date = ReadOnlyProperty()
write = AsyncCommand().unwrap("MotorGridOut")
writeable = DelegateMethod()
writelines = AsyncCommand().unwrap("MotorGridOut")
_exit = AsyncCommand("__exit__")
set = AsyncCommand(
attr_name="__setattr__",
doc="""
Set an arbitrary metadata attribute on the file. Stores value on the server
as a key-value pair within the file document once the file is closed. If
the file is already closed, calling :meth:`set` will immediately update the file
@ -313,22 +239,23 @@ Metadata set on the file appears as attributes on a
- `name`: Name of the attribute, will be stored as a key in the file
document on the server
- `value`: Value of the attribute
""")
""",
)
def __init__(self, root_collection, delegate=None, **kwargs):
def __init__(self, root_collection, delegate=None, session=None, **kwargs):
"""
Class to write data to GridFS. Application developers should not
generally need to instantiate this class - see
:meth:`~motor.MotorGridFSBucket.open_upload_stream`.
Any of the file level options specified in the `GridFS Spec
<http://dochub.mongodb.org/core/gridfs>`_ may be passed as
<http://dochub.mongodb.org/core/gridfs/>`_ may be passed as
keyword arguments. Any additional keyword arguments will be
set as additional fields on the file document. Valid keyword
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
@ -346,210 +273,83 @@ Metadata set on the file appears as attributes on a
:class:`bytes`.
:Parameters:
- `root_collection`: A :class:`~motor.MotorCollection`, the root
collection to write to
- `root_collection`: root collection to write to
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession` to use for all
commands
- `**kwargs` (optional): file level options (see above)
.. versionchanged:: 3.0
Removed support for the `disable_md5` parameter (to match the
GridIn class in PyMongo).
.. versionchanged:: 0.2
``open`` method removed, no longer needed.
"""
collection_class = create_class_with_framework(
AgnosticCollection, self._framework, self.__module__)
AgnosticCollection, self._framework, self.__module__
)
if not isinstance(root_collection, collection_class):
raise TypeError(
"First argument to MotorGridIn must be "
"MotorCollection, not %r" % root_collection)
"First argument to MotorGridIn must be MotorCollection, not %r" % root_collection
)
self.io_loop = root_collection.get_io_loop()
if delegate:
# Short cut.
self.delegate = delegate
else:
self.delegate = self.__delegate_class__(
root_collection.delegate,
**kwargs)
# Short cut.
self.delegate = delegate or self.__delegate_class__(
root_collection.delegate, session=session, **kwargs
)
if PY35:
# Support "async with fs.new_file() as f:"
exec(textwrap.dedent("""
async def __aenter__(self):
return self
# Support "async with bucket.open_upload_stream() as f:"
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.close()
"""), globals(), locals())
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self._exit(exc_type, exc_val, exc_tb)
def get_io_loop(self):
return self.io_loop
class _GFSBase(object):
__delegate_class__ = None
def __init__(self, database, collection="fs"):
db_class = create_class_with_framework(
AgnosticDatabase, self._framework, self.__module__)
if not isinstance(database, db_class):
raise TypeError(
"First argument to %s must be MotorDatabase, not %r" % (
self.__class__, database))
self.io_loop = database.get_io_loop()
self.collection = database[collection]
self.delegate = self.__delegate_class__(
database.delegate,
collection)
def get_io_loop(self):
return self.io_loop
def wrap(self, obj):
if obj.__class__ is grid_file.GridIn:
grid_in_class = create_class_with_framework(
AgnosticGridIn, self._framework, self.__module__)
return grid_in_class(
root_collection=self.collection,
delegate=obj)
elif obj.__class__ is grid_file.GridOut:
grid_out_class = create_class_with_framework(
AgnosticGridOut, self._framework, self.__module__)
return grid_out_class(
root_collection=self.collection,
delegate=obj)
elif obj.__class__ is gridfs.GridOutCursor:
grid_out_class = create_class_with_framework(
AgnosticGridOutCursor, self._framework, self.__module__)
return grid_out_class(
cursor=obj,
collection=self.collection)
class AgnosticGridFS(_GFSBase):
__motor_class_name__ = 'MotorGridFS'
__delegate_class__ = gridfs.GridFS
find_one = AsyncRead().wrap(grid_file.GridOut)
new_file = AsyncRead().wrap(grid_file.GridIn)
get = AsyncRead().wrap(grid_file.GridOut)
get_version = AsyncRead().wrap(grid_file.GridOut)
get_last_version = AsyncRead().wrap(grid_file.GridOut)
list = AsyncRead()
exists = AsyncRead(doc=exists_doc)
delete = AsyncCommand()
put = AsyncCommand()
def __init__(self, database, collection="fs"):
"""**DEPRECATED**: Use :class:`MotorGridFSBucket` or
:class:`AsyncIOMotorGridFSBucket`.
An instance of GridFS on top of a single Database.
:Parameters:
- `database`: a :class:`~motor.MotorDatabase`
- `collection` (optional): A string, name of root collection to use,
such as "fs" or "my_files"
.. mongodoc:: gridfs
.. versionchanged:: 0.2
``open`` method removed; no longer needed.
"""
super(self.__class__, self).__init__(database, collection)
def find(self, *args, **kwargs):
"""Query GridFS for files.
Returns a cursor that iterates across files matching
arbitrary queries on the files collection. Can be combined
with other modifiers for additional control. For example::
cursor = fs.find({"filename": "lisa.txt"}, no_cursor_timeout=True)
while (yield cursor.fetch_next):
grid_out = cursor.next_object()
data = yield grid_out.read()
This iterates through all versions of "lisa.txt" stored in GridFS.
Note that setting no_cursor_timeout may be important to prevent
the cursor from timing out during long multi-file processing work.
As another example, the call::
most_recent_three = fs.find().sort("uploadDate", -1).limit(3)
would return a cursor to the three most recently uploaded files
in GridFS.
:meth:`~motor.MotorGridFS.find` follows a similar
interface to :meth:`~motor.MotorCollection.find`
in :class:`~motor.MotorCollection`.
:Parameters:
- `filter` (optional): a SON object specifying elements which
must be present for a document to be included in the
result set
- `skip` (optional): the number of files to omit (from
the start of the result set) when returning the results
- `limit` (optional): the maximum number of results to
return
- `no_cursor_timeout` (optional): if False (the default), any
returned cursor is closed by the server after 10 minutes of
inactivity. If set to True, the returned cursor will never
time out on the server. Care should be taken to ensure that
cursors with no_cursor_timeout turned on are properly closed.
- `sort` (optional): a list of (key, direction) pairs
specifying the sort order for this query. See
:meth:`~pymongo.cursor.Cursor.sort` for details.
Raises :class:`TypeError` if any of the arguments are of
improper type. Returns an instance of
:class:`~gridfs.grid_file.GridOutCursor`
corresponding to this query.
.. versionchanged:: 1.0
Removed the read_preference, tag_sets, and
secondary_acceptable_latency_ms options.
.. versionadded:: 0.2
.. mongodoc:: find
"""
cursor = self.delegate.find(*args, **kwargs)
grid_out_cursor = create_class_with_framework(
AgnosticGridOutCursor, self._framework, self.__module__)
return grid_out_cursor(cursor, self.collection)
class AgnosticGridFSBucket(_GFSBase):
__motor_class_name__ = 'MotorGridFSBucket'
class AgnosticGridFSBucket:
__motor_class_name__ = "MotorGridFSBucket"
__delegate_class__ = gridfs.GridFSBucket
delete = AsyncCommand()
download_to_stream = AsyncCommand()
download_to_stream_by_name = AsyncCommand()
open_download_stream = AsyncCommand().wrap(gridfs.GridOut)
open_download_stream_by_name = AsyncCommand().wrap(gridfs.GridOut)
open_upload_stream = DelegateMethod().wrap(gridfs.GridIn)
open_upload_stream_with_id = DelegateMethod().wrap(gridfs.GridIn)
rename = AsyncCommand()
upload_from_stream = AsyncCommand()
upload_from_stream_with_id = AsyncCommand()
delete = AsyncCommand(doc=docstrings.gridfs_delete_doc)
download_to_stream = AsyncCommand(doc=docstrings.gridfs_download_to_stream_doc)
download_to_stream_by_name = AsyncCommand(doc=docstrings.gridfs_download_to_stream_by_name_doc)
open_download_stream = AsyncCommand(doc=docstrings.gridfs_open_download_stream_doc).wrap(
gridfs.GridOut
)
open_download_stream_by_name = AsyncCommand(
doc=docstrings.gridfs_open_download_stream_by_name_doc
).wrap(gridfs.GridOut)
open_upload_stream = DelegateMethod(doc=docstrings.gridfs_open_upload_stream_doc).wrap(
gridfs.GridIn
)
open_upload_stream_with_id = DelegateMethod(
doc=docstrings.gridfs_open_upload_stream_with_id_doc
).wrap(gridfs.GridIn)
rename = AsyncCommand(doc=docstrings.gridfs_rename_doc)
upload_from_stream = AsyncCommand(doc=docstrings.gridfs_upload_from_stream_doc)
upload_from_stream_with_id = AsyncCommand(doc=docstrings.gridfs_upload_from_stream_with_id_doc)
def __init__(self, database, collection="fs"):
def __init__(
self,
database,
bucket_name="fs",
chunk_size_bytes=DEFAULT_CHUNK_SIZE,
write_concern=None,
read_preference=None,
collection=None,
):
"""Create a handle to a GridFS bucket.
Raises :exc:`~pymongo.errors.ConfigurationError` if `write_concern`
is not acknowledged.
This class is a replacement for :class:`.MotorGridFS`; it conforms to the
`GridFS API Spec <https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.rst>`_
This class conforms to the `GridFS API Spec
<https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.rst>`_
for MongoDB drivers.
:Parameters:
@ -562,12 +362,73 @@ class AgnosticGridFSBucket(_GFSBase):
(the default) db.write_concern is used.
- `read_preference` (optional): The read preference to use. If
``None`` (the default) db.read_preference is used.
- `collection` (optional): Deprecated, an alias for `bucket_name`
that exists solely to provide backwards compatibility.
.. versionchanged:: 3.0
Removed support for the `disable_md5` parameter (to match the
GridFSBucket class in PyMongo).
.. versionchanged:: 2.1
Added support for the `bucket_name`, `chunk_size_bytes`,
`write_concern`, and `read_preference` parameters.
Deprecated the `collection` parameter which is now an alias to
`bucket_name` (to match the GridFSBucket class in PyMongo).
.. versionadded:: 1.0
.. mongodoc:: gridfs
"""
super(self.__class__, self).__init__(database, collection)
# Preserve backwards compatibility of "collection" parameter
if collection is not None:
warnings.warn(
'the "collection" parameter is deprecated, use "bucket_name" instead',
DeprecationWarning,
stacklevel=2,
)
bucket_name = collection
db_class = create_class_with_framework(AgnosticDatabase, self._framework, self.__module__)
if not isinstance(database, db_class):
raise TypeError(
f"First argument to {self.__class__} must be MotorDatabase, not {database!r}"
)
self.io_loop = database.get_io_loop()
self.collection = database.get_collection(
bucket_name, write_concern=write_concern, read_preference=read_preference
)
self.delegate = self.__delegate_class__(
database.delegate,
bucket_name,
chunk_size_bytes=chunk_size_bytes,
write_concern=write_concern,
read_preference=read_preference,
)
def get_io_loop(self):
return self.io_loop
def wrap(self, obj):
if obj.__class__ is grid_file.GridIn:
grid_in_class = create_class_with_framework(
AgnosticGridIn, self._framework, self.__module__
)
return grid_in_class(root_collection=self.collection, delegate=obj)
elif obj.__class__ is grid_file.GridOut:
grid_out_class = create_class_with_framework(
AgnosticGridOut, self._framework, self.__module__
)
return grid_out_class(root_collection=self.collection, delegate=obj)
elif obj.__class__ is gridfs.GridOutCursor:
grid_out_class = create_class_with_framework(
AgnosticGridOutCursor, self._framework, self.__module__
)
return grid_out_class(cursor=obj, collection=self.collection)
def find(self, *args, **kwargs):
"""Find and return the files collection documents that match ``filter``.
@ -579,9 +440,9 @@ class AgnosticGridFSBucket(_GFSBase):
For example::
cursor = bucket.find({"filename": "lisa.txt"}, no_cursor_timeout=True)
while (yield cursor.fetch_next):
while (await cursor.fetch_next):
grid_out = cursor.next_object()
data = yield grid_out.read()
data = await grid_out.read()
This iterates through all versions of "lisa.txt" stored in GridFS.
Note that setting no_cursor_timeout to True may be important to
@ -611,9 +472,33 @@ class AgnosticGridFSBucket(_GFSBase):
returning.
- `sort` (optional): The order by which to sort results. Defaults to
None.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`, created with
:meth:`~MotorClient.start_session`.
If a :class:`~pymongo.client_session.ClientSession` is passed to
:meth:`find`, all returned :class:`MotorGridOut` instances
are associated with that session.
.. versionchanged:: 1.2
Added session parameter.
"""
cursor = self.delegate.find(*args, **kwargs)
grid_out_cursor = create_class_with_framework(
AgnosticGridOutCursor, self._framework, self.__module__)
AgnosticGridOutCursor, self._framework, self.__module__
)
return grid_out_cursor(cursor, self.collection)
def _hash_gridout(gridout):
"""Compute the effective hash of a GridOut object for use with an Etag header.
Create a FIPS-compliant Etag HTTP header hash using sha256
We use the _id + length + upload_date as a proxy for
uniqueness to avoid reading the entire file.
"""
grid_hash = hashlib.sha256(str(gridout._id).encode("utf8"))
grid_hash.update(str(gridout.length).encode("utf8"))
grid_hash.update(str(gridout.upload_date).encode("utf8"))
return grid_hash.hexdigest()

182
motor/motor_gridfs.pyi Normal file
View File

@ -0,0 +1,182 @@
# Copyright 2023-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
import datetime
import os
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 # noqa: F401
from pymongo import WriteConcern
from pymongo.read_preferences import _ServerMode
from motor.core import (
AgnosticClientSession,
AgnosticCollection,
AgnosticCursor,
AgnosticDatabase,
)
_SEEK_SET = os.SEEK_SET
_SEEK_CUR = os.SEEK_CUR
_SEEK_END = os.SEEK_END
class AgnosticGridOutCursor(AgnosticCursor):
__motor_class_name__: str
__delegate_class__: type[GridOutCursor]
def next_object(self) -> AgnosticGridOutCursor: ...
class AgnosticGridOut:
__motor_class_name__: str
__delegate_class__: type[GridOut]
_id: Any
aliases: Optional[list[str]]
chunk_size: int
filename: Optional[str]
name: Optional[str]
content_type: Optional[str]
length: int
upload_date: datetime.datetime
metadata: Optional[Mapping[str, Any]]
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 = ...) -> int: ...
def seekable(self) -> bool: ...
def tell(self) -> int: ...
def write(self, data: Any) -> None: ...
def __init__(
self,
root_collection: AgnosticCollection,
file_id: Optional[int] = None,
file_document: Optional[Any] = None,
delegate: Any = None,
session: Optional[AgnosticClientSession] = None,
) -> None: ...
def __aiter__(self) -> AgnosticGridOut: ...
async def __anext__(self) -> bytes: ...
def __getattr__(self, item: str) -> Any: ...
def open(self) -> Any: ...
def get_io_loop(self) -> Any: ...
async def stream_to_handler(self, request_handler: Any) -> None: ...
class AgnosticGridIn:
__motor_class_name__: str
__delegate_class__: type[GridIn]
__getattr__: Any
_id: Any
filename: str
name: str
content_type: Optional[str]
length: int
chunk_size: int
upload_date: datetime.datetime
async def abort(self) -> None: ...
def closed(self) -> bool: ...
async def close(self) -> None: ...
def read(self, size: int = -1) -> NoReturn: ...
def readable(self) -> bool: ...
def seekable(self) -> bool: ...
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: 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,
) -> None: ...
async def __aenter__(self) -> AgnosticGridIn: ...
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
def get_io_loop(self) -> Any: ...
class AgnosticGridFSBucket:
__motor_class_name__: str
__delegate_class__: type[GridFSBucket]
async def delete(
self, file_id: Any, session: Optional[AgnosticClientSession] = None
) -> None: ...
async def download_to_stream(
self, file_id: Any, destination: Any, session: Optional[AgnosticClientSession] = None
) -> None: ...
async def download_to_stream_by_name(
self,
filename: str,
destination: Any,
revision: int = -1,
session: Optional[AgnosticClientSession] = None,
) -> None: ...
async def open_download_stream_by_name(
self, filename: str, revision: int = -1, session: Optional[AgnosticClientSession] = None
) -> AgnosticGridOut: ...
async def open_download_stream(
self, file_id: Any, session: Optional[AgnosticClientSession] = None
) -> AgnosticGridOut: ...
def open_upload_stream(
self,
filename: str,
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[AgnosticClientSession] = None,
) -> AgnosticGridIn: ...
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[AgnosticClientSession] = None,
) -> AgnosticGridIn: ...
async def rename(
self, file_id: Any, new_filename: str, session: Optional[AgnosticClientSession] = None
) -> None: ...
async def upload_from_stream(
self,
filename: str,
source: Any,
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[AgnosticClientSession] = None,
) -> ObjectId: ...
async def upload_from_stream_with_id(
self,
file_id: Any,
filename: str,
source: Any,
chunk_size_bytes: Optional[int] = None,
metadata: Optional[Mapping[str, Any]] = None,
session: Optional[AgnosticClientSession] = None,
) -> None: ...
def __init__(
self,
database: AgnosticDatabase,
bucket_name: str = "fs",
chunk_size_bytes: int = ...,
write_concern: Optional[WriteConcern] = None,
read_preference: Optional[_ServerMode] = None,
collection: Optional[str] = None,
) -> None: ...
def get_io_loop(self) -> Any: ...
def wrap(self, obj: Any) -> Any: ...
def find(self, *args: Any, **kwargs: Any) -> AgnosticGridOutCursor: ...
def _hash_gridout(gridout: AgnosticGridOut) -> str: ...

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