Compare commits
496 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cd98d9b94 | ||
|
|
4fe13c6f25 | ||
|
|
f45226d02f | ||
|
|
c869e5cd7d | ||
|
|
160dbce99f | ||
|
|
2433f62fd9 | ||
|
|
99b56b4633 | ||
|
|
bb744b7b5c | ||
|
|
94a26fa9da | ||
|
|
b3b102a89d | ||
|
|
9bae93d875 | ||
|
|
aab94545dd | ||
|
|
ccc861c5c7 | ||
|
|
87d1c9ed87 | ||
|
|
77bd8ba755 | ||
|
|
61c0e5ccf0 | ||
|
|
156f3b5f16 | ||
|
|
bafa539ed0 | ||
|
|
d0ed67cd80 | ||
|
|
7785176fdc | ||
|
|
536b2dd9d8 | ||
|
|
224da029d5 | ||
|
|
52234081fb | ||
|
|
b81fd4e181 | ||
|
|
820ba59552 | ||
|
|
144c460d8e | ||
|
|
d6c688b609 | ||
|
|
79fd03a68f | ||
|
|
33e4f112c7 | ||
|
|
12d9d7db05 | ||
|
|
5bcdd4ad6a | ||
|
|
5fc7c41fb5 | ||
|
|
bbada7dace | ||
|
|
4bb5e32e80 | ||
|
|
8412d20e28 | ||
|
|
4f1962faf2 | ||
|
|
edcae5440d | ||
|
|
71c3cf9dda | ||
|
|
1b2e255218 | ||
|
|
c734a3a3f6 | ||
|
|
e20ae5fe5b | ||
|
|
f6a70b8c3c | ||
|
|
83f735ad45 | ||
|
|
c291853a13 | ||
|
|
d5d0995776 | ||
|
|
eae1d25c5b | ||
|
|
a879ce528a | ||
|
|
6751d66ed3 | ||
|
|
0c8a698fa4 | ||
|
|
e036c6382d | ||
|
|
e2e967b610 | ||
|
|
9491e32850 | ||
|
|
251d2a088c | ||
|
|
381fdfbde0 | ||
|
|
9cf90a8898 | ||
|
|
0259b9f5a8 | ||
|
|
932bb7382e | ||
|
|
1433773db4 | ||
|
|
459760f4b4 | ||
|
|
b9547fa9c2 | ||
|
|
b4f887bf95 | ||
|
|
c1df3a2105 | ||
|
|
777d2aadf8 | ||
|
|
5cd704ea74 | ||
|
|
a98698531a | ||
|
|
ff9932ce6f | ||
|
|
cbef587966 | ||
|
|
66658d962e | ||
|
|
20015b458e | ||
|
|
cf7b4cfe53 | ||
|
|
e13888b08e | ||
|
|
5c77c38016 | ||
|
|
2a9225d0ce | ||
|
|
7050d54dc4 | ||
|
|
b652685be4 | ||
|
|
48f34cbe02 | ||
|
|
a08b1aff9c | ||
|
|
00f27f3294 | ||
|
|
99cc1b5155 | ||
|
|
633153954d | ||
|
|
972b7ffef9 | ||
|
|
72e68b7569 | ||
|
|
feb49a882c | ||
|
|
cc6dfabdde | ||
|
|
b1df705d6a | ||
|
|
9065e68df8 | ||
|
|
13ca542d5e | ||
|
|
5146708820 | ||
|
|
cd0316acd2 | ||
|
|
73a4cf0e60 | ||
|
|
fb67b5ff4a | ||
|
|
0967365505 | ||
|
|
fd55efee76 | ||
|
|
3ff6c13a53 | ||
|
|
0e429b94b0 | ||
|
|
be2cd387a2 | ||
|
|
9b2b4f07a3 | ||
|
|
d235a40f5a | ||
|
|
5ef6a15dc6 | ||
|
|
cac2145109 | ||
|
|
96297ac947 | ||
|
|
aa6876aa4d | ||
|
|
888b36a172 | ||
|
|
c2d9dc5954 | ||
|
|
0ace17713d | ||
|
|
fd3a60a4f4 | ||
|
|
c207caa105 | ||
|
|
6ee75e08f2 | ||
|
|
0248600e38 | ||
|
|
e93b9693d4 | ||
|
|
5cf54914f5 | ||
|
|
81a5b8ebdf | ||
|
|
96aeb4007a | ||
|
|
b0dda651bf | ||
|
|
19eeb29b98 | ||
|
|
72e8655033 | ||
|
|
0f1a6cdb79 | ||
|
|
7bcea044a9 | ||
|
|
f4832d97a6 | ||
|
|
2d3598796e | ||
|
|
1414d0fb02 | ||
|
|
9d00dd236e | ||
|
|
228e76af48 | ||
|
|
e8a6d6de3c | ||
|
|
8442c0a38e | ||
|
|
d902424109 | ||
|
|
cd6b2bb860 | ||
|
|
928213a715 | ||
|
|
820aa81ac2 | ||
|
|
c6d436b0c5 | ||
|
|
dc29694e14 | ||
|
|
43ea961870 | ||
|
|
bdf8a31f55 | ||
|
|
ba756ca62c | ||
|
|
e040f7d0c1 | ||
|
|
c5616e091a | ||
|
|
b578122363 | ||
|
|
dc8b329adf | ||
|
|
da50b55d24 | ||
|
|
2ada94b0e9 | ||
|
|
2f69643760 | ||
|
|
fc84d28fab | ||
|
|
3f8314db62 | ||
|
|
9a86b616dd | ||
|
|
597fb405dc | ||
|
|
bb1e4b9ba1 | ||
|
|
a1c57b7068 | ||
|
|
d58545a7a1 | ||
|
|
593b57f037 | ||
|
|
3836246b51 | ||
|
|
f0c9ff3e48 | ||
|
|
c644cdd9a6 | ||
|
|
546f7540f1 | ||
|
|
5e6453a266 | ||
|
|
cb050ef3d7 | ||
|
|
4513307899 | ||
|
|
8f71800c4a | ||
|
|
684279ed0a | ||
|
|
0ed06b65ac | ||
|
|
8a3712cfd1 | ||
|
|
47ff33f585 | ||
|
|
c391969776 | ||
|
|
c2985791cb | ||
|
|
5348e5af85 | ||
|
|
f905df8553 | ||
|
|
70136ad1f2 | ||
|
|
9fdb62a350 | ||
|
|
332360be4a | ||
|
|
424d03e15b | ||
|
|
fd55a40f4a | ||
|
|
f179495ca3 | ||
|
|
9d38d58189 | ||
|
|
ff05bfc6cd | ||
|
|
4f0ac92f34 | ||
|
|
6b7d0e930e | ||
|
|
2c0570f701 | ||
|
|
3534c33540 | ||
|
|
6a7c84de87 | ||
|
|
462d475972 | ||
|
|
440b3dc2c4 | ||
|
|
a16d48293d | ||
|
|
1557700377 | ||
|
|
fdc766be7c | ||
|
|
6462f1ecbb | ||
|
|
f9aed97221 | ||
|
|
16c476e618 | ||
|
|
da92e56829 | ||
|
|
935c543919 | ||
|
|
f4422ddaaf | ||
|
|
f77fa40ec5 | ||
|
|
c8ebda6726 | ||
|
|
7c2f259bac | ||
|
|
8d5a8bc85b | ||
|
|
8b2c17f2ce | ||
|
|
04a1d51c13 | ||
|
|
f0f0c09409 | ||
|
|
0f8dab12da | ||
|
|
a5610d3bc1 | ||
|
|
d25e4bc1c6 | ||
|
|
6994a2b53f | ||
|
|
4d2713c7b1 | ||
|
|
32bcfe713f | ||
|
|
fb8ea5822f | ||
|
|
c142ccebc6 | ||
|
|
b0d7970c4e | ||
|
|
a624899995 | ||
|
|
81f81f6ab0 | ||
|
|
99e65f85c5 | ||
|
|
6e8e2ee0e1 | ||
|
|
19d12b0f4e | ||
|
|
bed1a56467 | ||
|
|
555863da5d | ||
|
|
f74e740123 | ||
|
|
b9f36c2f7a | ||
|
|
52fc4267ba | ||
|
|
4c704fb756 | ||
|
|
47c84925ac | ||
|
|
39af29282c | ||
|
|
78e6a2240b | ||
|
|
a77db8f0bc | ||
|
|
a6b2a79e0c | ||
|
|
6a43c05865 | ||
|
|
69e1bae0ba | ||
|
|
92b4d51ecb | ||
|
|
19537bbafe | ||
|
|
2f75a4030c | ||
|
|
c564a02cc4 | ||
|
|
eb3ed1460c | ||
|
|
7822d938db | ||
|
|
ca0b27b207 | ||
|
|
0b0a3602c0 | ||
|
|
0c562e940b | ||
|
|
c16a62d074 | ||
|
|
f5ade74529 | ||
|
|
690c781066 | ||
|
|
588951e51e | ||
|
|
e2d5fa50ad | ||
|
|
57dd5773a3 | ||
|
|
276f93e0d0 | ||
|
|
64fbc8313b | ||
|
|
13b82efa1d | ||
|
|
5e07f66799 | ||
|
|
59a3e3229b | ||
|
|
1e62b868ea | ||
|
|
9c8d95e3d1 | ||
|
|
b7dc13fea6 | ||
|
|
055f5e05ab | ||
|
|
66fde7189d | ||
|
|
074d3c31be | ||
|
|
e4b0591538 | ||
|
|
79488f33fe | ||
|
|
928c92e0b7 | ||
|
|
401ebf2baf | ||
|
|
baa0bf27c7 | ||
|
|
bff55222dc | ||
|
|
6e29325037 | ||
|
|
d8d91a5b65 | ||
|
|
17e25323ae | ||
|
|
8ad5aac086 | ||
|
|
db1363b272 | ||
|
|
2a370562c7 | ||
|
|
3dec185017 | ||
|
|
0d82d21e00 | ||
|
|
94416e8d3a | ||
|
|
c81993e015 | ||
|
|
b0a79e1e30 | ||
|
|
d6ed164421 | ||
|
|
0f83e4c8c5 | ||
|
|
bdb970378a | ||
|
|
882f076ec5 | ||
|
|
30c14023ca | ||
|
|
a9dbf9a4fb | ||
|
|
0de9d25803 | ||
|
|
7956514873 | ||
|
|
96cd21b30c | ||
|
|
9a03afec12 | ||
|
|
afabd0079f | ||
|
|
8837ab095c | ||
|
|
512b3cc0a9 | ||
|
|
7f4203b5b7 | ||
|
|
59fe4116fb | ||
|
|
e953978172 | ||
|
|
5f0d9cff6f | ||
|
|
abbbde51e0 | ||
|
|
9bff41c276 | ||
|
|
c05d3eda44 | ||
|
|
1b5a21a202 | ||
|
|
8a8313da76 | ||
|
|
f97d911c3b | ||
|
|
0e729fb6f3 | ||
|
|
f444e21d8d | ||
|
|
b29ecd0335 | ||
|
|
2ee0d8dc9f | ||
|
|
7cf1f12038 | ||
|
|
e1ae65883f | ||
|
|
c3e51f8c17 | ||
|
|
6f7ced583c | ||
|
|
cd8c47aaff | ||
|
|
ed02ce5e75 | ||
|
|
ca6141cb6d | ||
|
|
e2d0e18c51 | ||
|
|
5053f219d6 | ||
|
|
ed7a0a2541 | ||
|
|
fad68810f9 | ||
|
|
84f8a051d0 | ||
|
|
70185d1361 | ||
|
|
d72b4f5847 | ||
|
|
5589020951 | ||
|
|
ae9595e9e0 | ||
|
|
938ff4ee8a | ||
|
|
11df507902 | ||
|
|
f89b3ca829 | ||
|
|
42b48d89a3 | ||
|
|
3ceb028056 | ||
|
|
114b88c07a | ||
|
|
be31a41106 | ||
|
|
25ac11a83b | ||
|
|
f47846ef5a | ||
|
|
c50625cb89 | ||
|
|
0b031c0b08 | ||
|
|
959c768260 | ||
|
|
35bdecfefe | ||
|
|
d7e5cb2955 | ||
|
|
aa7f55334e | ||
|
|
8208420d61 | ||
|
|
cad5560124 | ||
|
|
561e7e1c07 | ||
|
|
0825ed0a63 | ||
|
|
7c2978949f | ||
|
|
3d6ca6b492 | ||
|
|
4c7534c620 | ||
|
|
f493bd3772 | ||
|
|
dad11a5653 | ||
|
|
0690c83326 | ||
|
|
174b8227e5 | ||
|
|
393b6b2ded | ||
|
|
167e6a2497 | ||
|
|
47e7498e7c | ||
|
|
e797e6dcd7 | ||
|
|
bec8fb47e9 | ||
|
|
1a4fe95b5e | ||
|
|
0db1890128 | ||
|
|
ec8b33ffe7 | ||
|
|
58f7deb9b4 | ||
|
|
a3c4984c7b | ||
|
|
2c6d34ebe5 | ||
|
|
c4169d520d | ||
|
|
b32a050f68 | ||
|
|
f6e69a8bd9 | ||
|
|
898dc57b64 | ||
|
|
dd045250c8 | ||
|
|
b6bbe99766 | ||
|
|
163c56f6cc | ||
|
|
a300b5e0d5 | ||
|
|
1bc89db074 | ||
|
|
84646e0cc3 | ||
|
|
c78ab7c0c5 | ||
|
|
cf1d3d191e | ||
|
|
5b094e2640 | ||
|
|
35f2caaeaf | ||
|
|
dc6ae3ed3e | ||
|
|
46f849d945 | ||
|
|
6248419704 | ||
|
|
3d204bd02e | ||
|
|
05101e286f | ||
|
|
0d45707f7c | ||
|
|
8360cf4c5f | ||
|
|
a88316b8fd | ||
|
|
b01f810cca | ||
|
|
ff4cfdfff9 | ||
|
|
a815c55318 | ||
|
|
5c2da3887d | ||
|
|
343926df85 | ||
|
|
6891990e7a | ||
|
|
f58789472e | ||
|
|
da14373777 | ||
|
|
fab8b51a52 | ||
|
|
16529e47f1 | ||
|
|
4222501568 | ||
|
|
c428f737bb | ||
|
|
b38144e40f | ||
|
|
640914126b | ||
|
|
2fcb53de41 | ||
|
|
64adf52236 | ||
|
|
34372d1ace | ||
|
|
0b17a0482c | ||
|
|
4b1f141447 | ||
|
|
139f4f6340 | ||
|
|
6af2272072 | ||
|
|
67d8cbcbba | ||
|
|
b69b91bd51 | ||
|
|
c260535810 | ||
|
|
24120b9474 | ||
|
|
9d871fad4e | ||
|
|
b311c199ff | ||
|
|
ff51fc04c4 | ||
|
|
949f0b9b28 | ||
|
|
81c570d2e3 | ||
|
|
910f7c065d | ||
|
|
eb8e48f0d0 | ||
|
|
3d41e116f2 | ||
|
|
cef5cb9cf6 | ||
|
|
4d95efaadb | ||
|
|
ccf88768ee | ||
|
|
af59ce8f87 | ||
|
|
78a27f5def | ||
|
|
8a35d4d59f | ||
|
|
c3f579a818 | ||
|
|
9c8c5cf758 | ||
|
|
59b1ccb3b6 | ||
|
|
7a33bcc32c | ||
|
|
f722085e76 | ||
|
|
0d6b56b40d | ||
|
|
e18b6ff5db | ||
|
|
eb44d970e7 | ||
|
|
4cefdd43a7 | ||
|
|
58ac0d0c2b | ||
|
|
850539ab63 | ||
|
|
865783a747 | ||
|
|
d17de0a5d1 | ||
|
|
f2454bfaec | ||
|
|
2925db0d5d | ||
|
|
6d56f78c0a | ||
|
|
aad24aea46 | ||
|
|
077e6a448a | ||
|
|
fddb25215a | ||
|
|
ac6064f868 | ||
|
|
0dbcfbbda6 | ||
|
|
297b7f37f8 | ||
|
|
e54ac09626 | ||
|
|
84c607c37a | ||
|
|
e862c3b432 | ||
|
|
e6c367c8ef | ||
|
|
8610b9fc18 | ||
|
|
11b21db7de | ||
|
|
345798b72a | ||
|
|
08f882190c | ||
|
|
9a88d92dad | ||
|
|
21c36dd279 | ||
|
|
2614ae83ec | ||
|
|
0b564cb392 | ||
|
|
6af5bbaf21 | ||
|
|
d8be367d13 | ||
|
|
899c8f9b11 | ||
|
|
3021767038 | ||
|
|
79295cb92a | ||
|
|
2024c7c97c | ||
|
|
69b70a281b | ||
|
|
bee3d63d79 | ||
|
|
85dcfdce0f | ||
|
|
a56618e42a | ||
|
|
391f7a799d | ||
|
|
adb8d123ec | ||
|
|
45f31dc7b5 | ||
|
|
b1d972b969 | ||
|
|
6664abad00 | ||
|
|
76c328e546 | ||
|
|
1b3b69f9cc | ||
|
|
fa52a17cf0 | ||
|
|
c3a6e29a23 | ||
|
|
4f25c98124 | ||
|
|
80fb63767c | ||
|
|
2cbf0abb3b | ||
|
|
ad22c768ea | ||
|
|
3b30240ab9 | ||
|
|
3591cb4b8a | ||
|
|
55a3838e4a | ||
|
|
61754b8ba7 | ||
|
|
c4382ea1a4 | ||
|
|
bba885f338 | ||
|
|
7c58cbfa4e | ||
|
|
c1de57d96d | ||
|
|
606e0ba855 | ||
|
|
3fc852e822 | ||
|
|
714f88cfc7 | ||
|
|
c3d65ed502 | ||
|
|
a2c406f616 | ||
|
|
19778a7622 | ||
|
|
ebd2e24298 | ||
|
|
4202093554 | ||
|
|
26193a6108 | ||
|
|
ff9d25dfce | ||
|
|
709da4cca1 | ||
|
|
f3da17eab9 | ||
|
|
28c2a29454 | ||
|
|
173a78feb3 | ||
|
|
186dc7ffa0 | ||
|
|
0b56b0718d | ||
|
|
071d651306 | ||
|
|
c34b865251 | ||
|
|
8ebbb810bd | ||
|
|
d35ce7ce36 | ||
|
|
13bc0d548e | ||
|
|
90e816fffc | ||
|
|
d26085c805 | ||
|
|
3cc3de3ff3 |
@ -35,6 +35,7 @@ functions:
|
||||
params:
|
||||
working_dir: "src"
|
||||
script: |
|
||||
set -o xtrace
|
||||
# Get the current unique version of this checkout
|
||||
if [ "${is_patch}" = "true" ]; then
|
||||
CURRENT_VERSION=$(git describe)-patch-${version_id}
|
||||
@ -53,29 +54,26 @@ functions:
|
||||
|
||||
export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration"
|
||||
export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin"
|
||||
export UPLOAD_BUCKET="${project}"
|
||||
|
||||
cat <<EOT > expansion.yml
|
||||
CURRENT_VERSION: "$CURRENT_VERSION"
|
||||
DRIVERS_TOOLS: "$DRIVERS_TOOLS"
|
||||
MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME"
|
||||
MONGODB_BINARIES: "$MONGODB_BINARIES"
|
||||
UPLOAD_BUCKET: "$UPLOAD_BUCKET"
|
||||
PROJECT_DIRECTORY: "$PROJECT_DIRECTORY"
|
||||
PREPARE_SHELL: |
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
export DRIVERS_TOOLS="$DRIVERS_TOOLS"
|
||||
export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME"
|
||||
export MONGODB_BINARIES="$MONGODB_BINARIES"
|
||||
export UPLOAD_BUCKET="$UPLOAD_BUCKET"
|
||||
export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
|
||||
export TMPDIR="$MONGO_ORCHESTRATION_HOME/db"
|
||||
export PATH="$MONGODB_BINARIES:$PATH"
|
||||
export PROJECT="${project}"
|
||||
export ASYNC_TEST_TIMEOUT=30
|
||||
export PIP_QUIET=1
|
||||
EOT
|
||||
# See what we've done
|
||||
# See what we've done.
|
||||
cat expansion.yml
|
||||
|
||||
# Load the expansion file to make an evergreen variable with the current unique version
|
||||
@ -94,62 +92,10 @@ functions:
|
||||
# If this was a patch build, doing a fresh clone would not actually test the patch
|
||||
cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS
|
||||
else
|
||||
git clone git://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS
|
||||
git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS
|
||||
fi
|
||||
echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config
|
||||
|
||||
"upload release":
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
local_file: ${project}.tar.gz
|
||||
remote_file: ${UPLOAD_BUCKET}/${project}-${CURRENT_VERSION}.tar.gz
|
||||
bucket: mciuploads
|
||||
permissions: public-read
|
||||
content_type: ${content_type|application/x-gzip}
|
||||
|
||||
# Upload build artifacts that other tasks may depend on
|
||||
# Note this URL needs to be totally unique, while predictable for the next task
|
||||
# so it can automatically download the artifacts
|
||||
"upload build":
|
||||
# Compress and upload the entire build directory
|
||||
- command: archive.targz_pack
|
||||
params:
|
||||
# Example: mongo_c_driver_releng_9dfb7d741efbca16faa7859b9349d7a942273e43_16_11_08_19_29_52.tar.gz
|
||||
target: "${build_id}.tar.gz"
|
||||
source_dir: ${PROJECT_DIRECTORY}/
|
||||
include:
|
||||
- "./**"
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
local_file: ${build_id}.tar.gz
|
||||
# Example: /mciuploads/${UPLOAD_BUCKET}/gcc49/9dfb7d741efbca16faa7859b9349d7a942273e43/debug-compile-nosasl-nossl/mongo_c_driver_releng_9dfb7d741efbca16faa7859b9349d7a942273e43_16_11_08_19_29_52.tar.gz
|
||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${task_name}/${build_id}.tar.gz
|
||||
bucket: mciuploads
|
||||
permissions: public-read
|
||||
content_type: ${content_type|application/x-gzip}
|
||||
|
||||
"fetch build":
|
||||
- command: shell.exec
|
||||
params:
|
||||
continue_on_err: true
|
||||
script: "set -o xtrace && rm -rf ${PROJECT_DIRECTORY}"
|
||||
- command: s3.get
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${BUILD_NAME}/${build_id}.tar.gz
|
||||
bucket: mciuploads
|
||||
local_file: build.tar.gz
|
||||
- command: shell.exec
|
||||
params:
|
||||
continue_on_err: true
|
||||
# EVG-1105: Use s3.get extract_to: ./
|
||||
script: "set -o xtrace && cd .. && rm -rf ${PROJECT_DIRECTORY} && mkdir ${PROJECT_DIRECTORY}/ && tar xf build.tar.gz -C ${PROJECT_DIRECTORY}/"
|
||||
|
||||
"exec compile script" :
|
||||
- command: shell.exec
|
||||
type: test
|
||||
@ -168,74 +114,10 @@ functions:
|
||||
${PREPARE_SHELL}
|
||||
[ -f ${PROJECT_DIRECTORY}/${file} ] && sh ${PROJECT_DIRECTORY}/${file} || echo "${PROJECT_DIRECTORY}/${file} not available, skipping"
|
||||
|
||||
"upload docs" :
|
||||
- command: shell.exec
|
||||
params:
|
||||
silent: true
|
||||
script: |
|
||||
export AWS_ACCESS_KEY_ID=${aws_key}
|
||||
export AWS_SECRET_ACCESS_KEY=${aws_secret}
|
||||
aws s3 cp ${PROJECT_DIRECTORY}/doc/html s3://mciuploads/${UPLOAD_BUCKET}/docs/${CURRENT_VERSION} --recursive --acl public-read --region us-east-1
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
local_file: ${PROJECT_DIRECTORY}/doc/html/index.html
|
||||
remote_file: ${UPLOAD_BUCKET}/docs/${CURRENT_VERSION}/index.html
|
||||
bucket: mciuploads
|
||||
permissions: public-read
|
||||
content_type: text/html
|
||||
display_name: "Rendered docs"
|
||||
|
||||
"upload coverage" :
|
||||
- command: shell.exec
|
||||
params:
|
||||
silent: true
|
||||
script: |
|
||||
export AWS_ACCESS_KEY_ID=${aws_key}
|
||||
export AWS_SECRET_ACCESS_KEY=${aws_secret}
|
||||
aws s3 cp ${PROJECT_DIRECTORY}/coverage s3://mciuploads/${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/coverage/ --recursive --acl public-read --region us-east-1
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
local_file: ${PROJECT_DIRECTORY}/coverage/index.html
|
||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/coverage/index.html
|
||||
bucket: mciuploads
|
||||
permissions: public-read
|
||||
content_type: text/html
|
||||
display_name: "Coverage Report"
|
||||
|
||||
"upload scan artifacts" :
|
||||
- command: shell.exec
|
||||
type: test
|
||||
params:
|
||||
script: |
|
||||
cd
|
||||
if find ${PROJECT_DIRECTORY}/scan -name \*.html | grep -q html; then
|
||||
(cd ${PROJECT_DIRECTORY}/scan && find . -name index.html -exec echo "<li><a href='{}'>{}</a></li>" \;) >> scan.html
|
||||
else
|
||||
echo "No issues found" > scan.html
|
||||
fi
|
||||
- command: shell.exec
|
||||
params:
|
||||
silent: true
|
||||
script: |
|
||||
export AWS_ACCESS_KEY_ID=${aws_key}
|
||||
export AWS_SECRET_ACCESS_KEY=${aws_secret}
|
||||
aws s3 cp ${PROJECT_DIRECTORY}/scan s3://mciuploads/${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/scan/ --recursive --acl public-read --region us-east-1
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
local_file: ${PROJECT_DIRECTORY}/scan.html
|
||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/scan/index.html
|
||||
bucket: mciuploads
|
||||
permissions: public-read
|
||||
content_type: text/html
|
||||
display_name: "Scan Build Report"
|
||||
|
||||
"upload mo artifacts":
|
||||
- command: ec2.assume_role
|
||||
params:
|
||||
role_arn: ${assume_role_arn}
|
||||
- command: shell.exec
|
||||
params:
|
||||
script: |
|
||||
@ -243,76 +125,48 @@ functions:
|
||||
find $MONGO_ORCHESTRATION_HOME -name \*.log | xargs tar czf mongodb-logs.tar.gz
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||
local_file: mongodb-logs.tar.gz
|
||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz
|
||||
bucket: mciuploads
|
||||
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz
|
||||
bucket: ${aws_bucket}
|
||||
permissions: public-read
|
||||
content_type: ${content_type|application/x-gzip}
|
||||
display_name: "mongodb-logs.tar.gz"
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||
local_file: ${DRIVERS_TOOLS}/.evergreen/orchestration/server.log
|
||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-orchestration.log
|
||||
bucket: mciuploads
|
||||
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-orchestration.log
|
||||
bucket: ${aws_bucket}
|
||||
permissions: public-read
|
||||
content_type: ${content_type|text/plain}
|
||||
display_name: "orchestration.log"
|
||||
|
||||
"upload working dir":
|
||||
- command: archive.targz_pack
|
||||
params:
|
||||
target: "working-dir.tar.gz"
|
||||
source_dir: ${PROJECT_DIRECTORY}/
|
||||
include:
|
||||
- "./**"
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
local_file: working-dir.tar.gz
|
||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-working-dir.tar.gz
|
||||
bucket: mciuploads
|
||||
permissions: public-read
|
||||
content_type: ${content_type|application/x-gzip}
|
||||
display_name: "working-dir.tar.gz"
|
||||
- command: archive.targz_pack
|
||||
params:
|
||||
target: "drivers-dir.tar.gz"
|
||||
source_dir: ${DRIVERS_TOOLS}
|
||||
include:
|
||||
- "./**"
|
||||
exclude_files:
|
||||
# Windows cannot read the mongod *.lock files because they are locked.
|
||||
- "*.lock"
|
||||
- command: s3.put
|
||||
params:
|
||||
aws_key: ${aws_key}
|
||||
aws_secret: ${aws_secret}
|
||||
local_file: drivers-dir.tar.gz
|
||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-drivers-dir.tar.gz
|
||||
bucket: mciuploads
|
||||
permissions: public-read
|
||||
content_type: ${content_type|application/x-gzip}
|
||||
display_name: "drivers-dir.tar.gz"
|
||||
|
||||
"upload test results":
|
||||
- command: attach.results
|
||||
params:
|
||||
file_location: "${DRIVERS_TOOLS}/results.json"
|
||||
- command: attach.xunit_results
|
||||
params:
|
||||
file: "src/xunit-results/TEST-*.xml"
|
||||
files:
|
||||
- "src/xunit-results/TEST-*.xml"
|
||||
- "src/xunit-synchro-results"
|
||||
|
||||
"bootstrap mongo-orchestration":
|
||||
- command: shell.exec
|
||||
params:
|
||||
script: |
|
||||
${PREPARE_SHELL}
|
||||
MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} AUTH=${AUTH} SSL=${SSL} STORAGE_ENGINE=${STORAGE_ENGINE} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
|
||||
MONGODB_VERSION=${VERSION} \
|
||||
TOPOLOGY=${TOPOLOGY} \
|
||||
AUTH=${AUTH} \
|
||||
SSL=${SSL} \
|
||||
STORAGE_ENGINE=${STORAGE_ENGINE} \
|
||||
bash ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
|
||||
# run-orchestration generates expansion file with the MONGODB_URI for the cluster
|
||||
- command: expansions.update
|
||||
params:
|
||||
@ -323,26 +177,72 @@ functions:
|
||||
params:
|
||||
script: |
|
||||
${PREPARE_SHELL}
|
||||
sh ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh
|
||||
bash ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh
|
||||
|
||||
"run tox":
|
||||
- command: ec2.assume_role
|
||||
params:
|
||||
role_arn: ${aws_test_secrets_role}
|
||||
- command: subprocess.exec
|
||||
params:
|
||||
working_dir: "src"
|
||||
binary: bash
|
||||
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
|
||||
args:
|
||||
- ${DRIVERS_TOOLS}/.evergreen/csfle/setup-secrets.sh
|
||||
- command: subprocess.exec
|
||||
params:
|
||||
working_dir: "src"
|
||||
binary: bash
|
||||
background: true
|
||||
args:
|
||||
- ${DRIVERS_TOOLS}/.evergreen/csfle/start-servers.sh
|
||||
- command: subprocess.exec
|
||||
params:
|
||||
working_dir: "src"
|
||||
binary: bash
|
||||
args:
|
||||
- ${DRIVERS_TOOLS}/.evergreen/csfle/await-servers.sh
|
||||
- command: shell.exec
|
||||
type: test
|
||||
params:
|
||||
working_dir: "src"
|
||||
script: |
|
||||
${PREPARE_SHELL}
|
||||
PYTHON_BINARY=${PYTHON_BINARY} TOX_BINARY=${TOX_BINARY} TOX_ENV=${TOX_ENV} AUTH=${AUTH} SSL=${SSL} sh ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh
|
||||
LIBMONGOCRYPT_URL="${libmongocrypt_url}" \
|
||||
TEST_ENCRYPTION=1 \
|
||||
PYTHON_BINARY="${PYTHON_BINARY}" \
|
||||
TOX_ENV="${TOX_ENV}" \
|
||||
VIRTUALENV="${VIRTUALENV}" \
|
||||
CHECK_EXCLUDE_PATTERNS=1 \
|
||||
AUTH="${AUTH}" \
|
||||
SSL="${SSL}" \
|
||||
CERT_DIR="${DRIVERS_TOOLS}/.evergreen/x509gen" \
|
||||
bash ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh
|
||||
|
||||
"run enterprise auth tests":
|
||||
- command: ec2.assume_role
|
||||
params:
|
||||
role_arn: ${aws_test_secrets_role}
|
||||
- command: shell.exec
|
||||
type: test
|
||||
params:
|
||||
working_dir: "src"
|
||||
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
|
||||
script: |
|
||||
bash ${DRIVERS_TOOLS}/.evergreen/secrets_handling/setup-secrets.sh drivers/enterprise_auth
|
||||
- command: shell.exec
|
||||
type: test
|
||||
params:
|
||||
silent: true
|
||||
working_dir: "src"
|
||||
script: |
|
||||
# DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
|
||||
PYTHON_BINARY=${PYTHON_BINARY} TOX_ENV=${TOX_ENV} SASL_HOST=${sasl_host} SASL_PORT=${sasl_port} SASL_USER=${sasl_user} SASL_PASS=${sasl_pass} SASL_DB=${sasl_db} PRINCIPAL=${principal} GSSAPI_DB=${gssapi_db} KEYTAB_BASE64=${keytab_base64} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} sh ${PROJECT_DIRECTORY}/.evergreen/run-enterprise-auth-tests.sh
|
||||
set +x # disable xtrace
|
||||
CLIENT_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem \
|
||||
CA_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem \
|
||||
PYTHON_BINARY=${PYTHON_BINARY} \
|
||||
TOX_ENV=${TOX_ENV} \
|
||||
PROJECT_DIRECTORY=${PROJECT_DIRECTORY} \
|
||||
bash ${PROJECT_DIRECTORY}/.evergreen/run-enterprise-auth-tests.sh
|
||||
|
||||
"cleanup":
|
||||
- command: shell.exec
|
||||
@ -408,8 +308,6 @@ pre:
|
||||
- func: "install dependencies"
|
||||
|
||||
post:
|
||||
# Disabled, causing timeouts
|
||||
# - func: "upload working dir"
|
||||
- func: "upload mo artifacts"
|
||||
- func: "upload test results"
|
||||
- func: "stop mongo-orchestration"
|
||||
@ -446,140 +344,167 @@ tasks:
|
||||
genhtml --version || true
|
||||
valgrind --version || true
|
||||
|
||||
# Standard test tasks {{{
|
||||
# Test tasks {{{
|
||||
|
||||
- name: "test-2.6-standalone"
|
||||
tags: ["2.6", "standalone"]
|
||||
- name: "test-4.2-standalone"
|
||||
tags: ["4.2", "standalone"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "2.6"
|
||||
VERSION: "4.2"
|
||||
TOPOLOGY: "server"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-2.6-replica_set"
|
||||
tags: ["2.6", "replica_set"]
|
||||
- name: "test-4.2-replica_set"
|
||||
tags: ["4.2", "replica_set"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "2.6"
|
||||
VERSION: "4.2"
|
||||
TOPOLOGY: "replica_set"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-2.6-sharded_cluster"
|
||||
tags: ["2.6", "sharded_cluster"]
|
||||
- name: "test-4.2-sharded_cluster"
|
||||
tags: ["4.2", "sharded_cluster"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "2.6"
|
||||
VERSION: "4.2"
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.0-standalone"
|
||||
tags: ["3.0", "standalone"]
|
||||
- name: "test-4.4-standalone"
|
||||
tags: ["4.4", "standalone"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.0"
|
||||
VERSION: "4.4"
|
||||
TOPOLOGY: "server"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.0-replica_set"
|
||||
tags: ["3.0", "replica_set"]
|
||||
- name: "test-4.4-replica_set"
|
||||
tags: ["4.4", "replica_set"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.0"
|
||||
VERSION: "4.4"
|
||||
TOPOLOGY: "replica_set"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.0-sharded_cluster"
|
||||
tags: ["3.0", "sharded_cluster"]
|
||||
- name: "test-4.4-sharded_cluster"
|
||||
tags: ["4.4", "sharded_cluster"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.0"
|
||||
VERSION: "4.4"
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.2-standalone"
|
||||
tags: ["3.2", "standalone"]
|
||||
- name: "test-5.0-standalone"
|
||||
tags: ["5.0", "standalone"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.2"
|
||||
VERSION: "5.0"
|
||||
TOPOLOGY: "server"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.2-replica_set"
|
||||
tags: ["3.2", "replica_set"]
|
||||
- name: "test-5.0-replica_set"
|
||||
tags: ["5.0", "replica_set"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.2"
|
||||
VERSION: "5.0"
|
||||
TOPOLOGY: "replica_set"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.2-sharded_cluster"
|
||||
tags: ["3.2", "sharded_cluster"]
|
||||
- name: "test-5.0-sharded_cluster"
|
||||
tags: ["5.0", "sharded_cluster"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.2"
|
||||
VERSION: "5.0"
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.4-standalone"
|
||||
tags: ["3.4", "standalone"]
|
||||
- name: "test-6.0-standalone"
|
||||
tags: [ "6.0", "standalone" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.4"
|
||||
VERSION: "6.0"
|
||||
TOPOLOGY: "server"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.4-replica_set"
|
||||
tags: ["3.4", "replica_set"]
|
||||
- name: "test-6.0-replica_set"
|
||||
tags: [ "6.0", "replica_set" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.4"
|
||||
VERSION: "6.0"
|
||||
TOPOLOGY: "replica_set"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.4-sharded_cluster"
|
||||
tags: ["3.4", "sharded_cluster"]
|
||||
- name: "test-6.0-sharded_cluster"
|
||||
tags: [ "6.0", "sharded_cluster" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.4"
|
||||
VERSION: "6.0"
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.6-standalone"
|
||||
tags: ["3.6", "standalone"]
|
||||
- name: "test-7.0-standalone"
|
||||
tags: [ "7.0", "standalone" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.6"
|
||||
VERSION: "7.0"
|
||||
TOPOLOGY: "server"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.6-replica_set"
|
||||
tags: ["3.6", "replica_set"]
|
||||
- name: "test-7.0-replica_set"
|
||||
tags: [ "7.0", "replica_set" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.6"
|
||||
VERSION: "7.0"
|
||||
TOPOLOGY: "replica_set"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-3.6-sharded_cluster"
|
||||
tags: ["3.6", "sharded_cluster"]
|
||||
- name: "test-7.0-sharded_cluster"
|
||||
tags: [ "7.0", "sharded_cluster" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.6"
|
||||
VERSION: "7.0"
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-8.0-standalone"
|
||||
tags: [ "8.0", "standalone" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "8.0"
|
||||
TOPOLOGY: "server"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-8.0-replica_set"
|
||||
tags: [ "8.0", "replica_set" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "8.0"
|
||||
TOPOLOGY: "replica_set"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-8.0-sharded_cluster"
|
||||
tags: [ "8.0", "sharded_cluster" ]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "8.0"
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
- func: "run tox"
|
||||
|
||||
@ -610,8 +535,35 @@ tasks:
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-rapid-standalone"
|
||||
tags: ["rapid", "standalone"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "rapid"
|
||||
TOPOLOGY: "server"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-rapid-replica_set"
|
||||
tags: ["rapid", "replica_set"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "rapid"
|
||||
TOPOLOGY: "replica_set"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-rapid-sharded_cluster"
|
||||
tags: ["rapid", "sharded_cluster"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "rapid"
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
- func: "run tox"
|
||||
|
||||
- name: "test-enterprise-auth"
|
||||
tags: ["enterprise-auth"]
|
||||
tags: ["pr"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
@ -620,69 +572,26 @@ tasks:
|
||||
- func: "run enterprise auth tests"
|
||||
|
||||
- name: "docs"
|
||||
tags: ["pr"]
|
||||
commands:
|
||||
- func: "run tox"
|
||||
vars:
|
||||
TOX_ENV: py3-sphinx-docs
|
||||
TOX_ENV: docs
|
||||
|
||||
- name: "doctest"
|
||||
tags: ["pr"]
|
||||
commands:
|
||||
- func: "bootstrap mongo-orchestration"
|
||||
vars:
|
||||
VERSION: "3.6"
|
||||
VERSION: "5.0"
|
||||
TOPOLOGY: "server"
|
||||
- func: "run tox"
|
||||
vars:
|
||||
TOX_ENV: py3-sphinx-doctest
|
||||
TOX_ENV: doctest
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
axes:
|
||||
- id: versions
|
||||
display_name: MongoDB Version
|
||||
values:
|
||||
- id: "latest"
|
||||
display_name: "latest"
|
||||
variables:
|
||||
VERSION: "latest"
|
||||
- id: "3.6"
|
||||
display_name: "3.6"
|
||||
variables:
|
||||
VERSION: "3.6"
|
||||
- id: "3.4"
|
||||
display_name: "3.4"
|
||||
variables:
|
||||
VERSION: "3.4"
|
||||
- id: "3.2"
|
||||
display_name: "3.2"
|
||||
variables:
|
||||
VERSION: "3.2"
|
||||
- id: "3.0"
|
||||
display_name: "3.0"
|
||||
variables:
|
||||
VERSION: "3.0"
|
||||
- id: "2.6"
|
||||
display_name: "2.6"
|
||||
variables:
|
||||
VERSION: "2.6"
|
||||
|
||||
- id: topology
|
||||
display_name: Topology
|
||||
values:
|
||||
- id: standalone
|
||||
display_name: Standalone
|
||||
variables:
|
||||
TOPOLOGY: "server"
|
||||
- id: replicaset
|
||||
display_name: Replica Set
|
||||
variables:
|
||||
TOPOLOGY: "replica_set"
|
||||
- id: sharded-cluster
|
||||
display_name: Sharded Cluster
|
||||
variables:
|
||||
TOPOLOGY: "sharded_cluster"
|
||||
|
||||
- id: ssl
|
||||
display_name: SSL
|
||||
values:
|
||||
@ -698,161 +607,206 @@ axes:
|
||||
AUTH: "noauth"
|
||||
|
||||
- id: tox-env
|
||||
display_name: "Tox Env"
|
||||
display_name: "Tox Env RHEL8"
|
||||
values:
|
||||
# TODO: avoid repetition?
|
||||
- id: "tornado4-py27"
|
||||
- id: "test-pypy310"
|
||||
variables:
|
||||
TOX_ENV: "tornado4-py27"
|
||||
PYTHON_BINARY: "/opt/python/2.7/bin/python"
|
||||
- id: "tornado4-pypy"
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "/opt/python/pypy3.10/bin/python3"
|
||||
- id: "test-py310"
|
||||
variables:
|
||||
TOX_ENV: "tornado4-pypy"
|
||||
PYTHON_BINARY: "/opt/python/pypy/bin/pypy"
|
||||
- id: "tornado4-pypy3"
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
- id: "test-py311"
|
||||
variables:
|
||||
TOX_ENV: "tornado4-pypy3"
|
||||
PYTHON_BINARY: "/opt/python/pypy3.5/bin/pypy3"
|
||||
- id: "tornado4-py34"
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "/opt/python/3.11/bin/python3"
|
||||
- id: "test-py312"
|
||||
variables:
|
||||
TOX_ENV: "tornado4-py34"
|
||||
PYTHON_BINARY: "/opt/python/3.4/bin/python3"
|
||||
- id: "tornado4-py35"
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "/opt/python/3.12/bin/python3"
|
||||
- id: "test-py313"
|
||||
variables:
|
||||
TOX_ENV: "tornado4-py35"
|
||||
PYTHON_BINARY: "/opt/python/3.5/bin/python3"
|
||||
- id: "tornado4-py36"
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "/opt/python/3.13/bin/python3"
|
||||
- id: "test-py314"
|
||||
variables:
|
||||
TOX_ENV: "tornado4-py36"
|
||||
PYTHON_BINARY: "/opt/python/3.6/bin/python3"
|
||||
- id: "tornado_git-py27"
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "/opt/python/3.14/bin/python3"
|
||||
- id: "test-pymongo-4.9"
|
||||
variables:
|
||||
TOX_ENV: "tornado_git-py27"
|
||||
PYTHON_BINARY: "/opt/python/2.7/bin/python"
|
||||
- id: "tornado_git-py34"
|
||||
TOX_ENV: "test-pymongo-4.9"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
- id: "test-pymongo-4.10"
|
||||
variables:
|
||||
TOX_ENV: "tornado_git-py34"
|
||||
PYTHON_BINARY: "/opt/python/3.4/bin/python3"
|
||||
- id: "tornado_git-py35"
|
||||
TOX_ENV: "test-pymongo-4.10"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
- id: "test-pymongo-4.11"
|
||||
variables:
|
||||
TOX_ENV: "tornado_git-py35"
|
||||
PYTHON_BINARY: "/opt/python/3.5/bin/python3"
|
||||
- id: "tornado_git-py36"
|
||||
TOX_ENV: "test-pymongo-4.11"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
- id: "test-pymongo-latest"
|
||||
variables:
|
||||
TOX_ENV: "tornado_git-py36"
|
||||
PYTHON_BINARY: "/opt/python/3.6/bin/python3"
|
||||
- id: "asyncio-py34"
|
||||
TOX_ENV: "test-pymongo-latest"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
- id: "synchro-py310"
|
||||
variables:
|
||||
TOX_ENV: "asyncio-py34"
|
||||
PYTHON_BINARY: "/opt/python/3.4/bin/python3"
|
||||
- id: "asyncio-py35"
|
||||
TOX_ENV: "synchro"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
- id: "synchro-py313"
|
||||
variables:
|
||||
TOX_ENV: "asyncio-py35"
|
||||
PYTHON_BINARY: "/opt/python/3.5/bin/python3"
|
||||
- id: "asyncio-py36"
|
||||
variables:
|
||||
TOX_ENV: "asyncio-py36"
|
||||
PYTHON_BINARY: "/opt/python/3.6/bin/python3"
|
||||
- id: "py3-pymongo-master"
|
||||
variables:
|
||||
TOX_ENV: "py3-pymongo-master"
|
||||
PYTHON_BINARY: "/opt/python/3.6/bin/python3"
|
||||
- id: "synchro"
|
||||
variables:
|
||||
TOX_ENV: "synchro"
|
||||
# The synchro tests are configured for py27 in tox.ini.
|
||||
PYTHON_BINARY: "/opt/python/2.7/bin/python"
|
||||
TOX_ENV: "synchro"
|
||||
PYTHON_BINARY: "/opt/python/3.13/bin/python3"
|
||||
|
||||
- id: tox-env-rhel8
|
||||
display_name: "Tox Env RHEL8"
|
||||
values:
|
||||
- id: "test"
|
||||
variables:
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
|
||||
# Test Python 3.10 only on Mac.
|
||||
- id: tox-env-osx
|
||||
display_name: "Tox Env OSX"
|
||||
values:
|
||||
- id: "tornado4-py27"
|
||||
- id: "test"
|
||||
variables:
|
||||
TOX_ENV: "tornado4-py27"
|
||||
PYTHON_BINARY: "python2.7"
|
||||
- id: "tornado_git-py27"
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "/Library/Frameworks/Python.framework/Versions/3.10/bin/python3"
|
||||
|
||||
- id: tox-env-win
|
||||
display_name: "Tox Env Windows"
|
||||
values:
|
||||
- id: "test-py310"
|
||||
variables:
|
||||
TOX_ENV: "tornado_git-py27"
|
||||
PYTHON_BINARY: "python2.7"
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "c:/python/Python310/python.exe"
|
||||
- id: "test-py311"
|
||||
variables:
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "c:/python/Python311/python.exe"
|
||||
- id: "test-py312"
|
||||
variables:
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "c:/python/Python312/python.exe"
|
||||
- id: "test-py313"
|
||||
variables:
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "c:/python/Python313/python.exe"
|
||||
- id: "test-py314"
|
||||
variables:
|
||||
TOX_ENV: "test"
|
||||
PYTHON_BINARY: "c:/python/Python314/python.exe"
|
||||
|
||||
- id: os
|
||||
display_name: "Operating System"
|
||||
values:
|
||||
- id: "rhel"
|
||||
display_name: "RHEL 6.2"
|
||||
run_on: "rhel62-small"
|
||||
variables:
|
||||
TOX_BINARY: "/opt/python/3.6/bin/tox"
|
||||
- id: "osx"
|
||||
display_name: "OSX 10.10"
|
||||
run_on: "macos-1012"
|
||||
variables:
|
||||
TOX_BINARY: "tox"
|
||||
- id: "rhel8"
|
||||
display_name: "RHEL 8.x"
|
||||
run_on: "rhel84-small"
|
||||
- id: "win"
|
||||
display_name: "Windows"
|
||||
run_on: "windows-64-vsMulti-small"
|
||||
- id: "macos-1400"
|
||||
display_name: "macOS 14.00"
|
||||
run_on: "macos-1400"
|
||||
|
||||
buildvariants:
|
||||
|
||||
- matrix_name: "test-mongodb-versions"
|
||||
matrix_spec: {"os": "rhel", "tox-env": "*", ssl: "*"}
|
||||
# Main test matrix.
|
||||
- matrix_name: "main"
|
||||
display_name: "${os}-${tox-env}-${ssl}"
|
||||
matrix_spec:
|
||||
os: "rhel8"
|
||||
tox-env: "*"
|
||||
ssl: "*"
|
||||
exclude_spec:
|
||||
# TODO: synchro needs PyMongo master's updated SSL test certs,
|
||||
# which may require Motor test suite changes.
|
||||
- os: "*"
|
||||
tox-env: ["synchro"]
|
||||
ssl: "ssl"
|
||||
display_name: "${os} ${tox-env} ${ssl}"
|
||||
# TODO: synchro needs PyMongo's updated SSL test certs,
|
||||
# which may require Motor test suite changes.
|
||||
- os: "*"
|
||||
tox-env: ["synchro-py310", "synchro-py313"]
|
||||
ssl: "ssl"
|
||||
tasks:
|
||||
- ".rapid"
|
||||
- ".latest"
|
||||
- ".8.0"
|
||||
- ".7.0"
|
||||
- ".6.0"
|
||||
- ".5.0"
|
||||
- ".4.4"
|
||||
- ".4.2"
|
||||
- ".4.0"
|
||||
|
||||
- matrix_name: "test-rhel8"
|
||||
display_name: "${os}-${tox-env-rhel8}-${ssl}"
|
||||
matrix_spec:
|
||||
os: "rhel8"
|
||||
tox-env-rhel8: "*"
|
||||
ssl: "*"
|
||||
tasks:
|
||||
- ".rapid"
|
||||
- ".latest"
|
||||
- ".7.0"
|
||||
- ".6.0"
|
||||
- ".5.0"
|
||||
- ".4.4"
|
||||
|
||||
- matrix_name: "test-win"
|
||||
display_name: "${os}-${tox-env-win}-${ssl}"
|
||||
matrix_spec:
|
||||
os: "win"
|
||||
tox-env-win: "*"
|
||||
ssl: "*"
|
||||
tasks:
|
||||
- ".rapid"
|
||||
- ".latest"
|
||||
- ".8.0"
|
||||
- ".7.0"
|
||||
- ".6.0"
|
||||
- ".5.0"
|
||||
- ".4.4"
|
||||
- ".4.2"
|
||||
- ".4.0"
|
||||
- ".3.6"
|
||||
- ".3.4"
|
||||
- ".3.2"
|
||||
- ".3.0"
|
||||
- ".2.6"
|
||||
|
||||
- matrix_name: "test-macos"
|
||||
display_name: "${os}-${tox-env-osx}-${ssl}"
|
||||
matrix_spec:
|
||||
os: "macos-1400"
|
||||
tox-env-osx: "*"
|
||||
ssl: "*"
|
||||
tasks:
|
||||
- ".rapid"
|
||||
- ".latest"
|
||||
- ".8.0"
|
||||
- ".7.0"
|
||||
- ".6.0"
|
||||
|
||||
- matrix_name: "enterprise-auth"
|
||||
matrix_spec: {"tox-env": ["synchro"], ssl: "ssl"}
|
||||
display_name: "Enterprise Auth"
|
||||
display_name: "Enterprise Auth-${tox-env}"
|
||||
matrix_spec: {"tox-env": ["synchro-py310", "synchro-py313"], ssl: "ssl"}
|
||||
run_on:
|
||||
- rhel62-small
|
||||
- "rhel84-small"
|
||||
tasks:
|
||||
- name: "test-enterprise-auth"
|
||||
|
||||
- name: "docs"
|
||||
display_name: "Docs - Build"
|
||||
run_on:
|
||||
- rhel62-small
|
||||
- "rhel84-small"
|
||||
expansions:
|
||||
TOX_ENV: "py3-sphinx-docs"
|
||||
TOX_BINARY: "/opt/python/3.6/bin/tox"
|
||||
PYTHON_BINARY: "/opt/python/3.6/bin/python3"
|
||||
TOX_ENV: "docs"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
tasks:
|
||||
- name: "docs"
|
||||
|
||||
- name: "doctests"
|
||||
display_name: "Docs - Test"
|
||||
run_on:
|
||||
- rhel62-small
|
||||
- "rhel84-small"
|
||||
expansions:
|
||||
TOX_ENV: "py3-sphinx-doctest"
|
||||
TOX_BINARY: "/opt/python/3.6/bin/tox"
|
||||
PYTHON_BINARY: "/opt/python/3.6/bin/python3"
|
||||
TOX_ENV: "doctest"
|
||||
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
|
||||
tasks:
|
||||
- name: "doctest"
|
||||
|
||||
- matrix_name: "test-mongodb-versions-macos"
|
||||
matrix_spec: {"os": "osx", "tox-env-osx": "*", ssl: "*"}
|
||||
display_name: "${os} ${tox-env-osx} ${ssl}"
|
||||
tasks:
|
||||
- ".latest"
|
||||
- ".3.6"
|
||||
- ".3.4"
|
||||
- ".3.2"
|
||||
rules:
|
||||
# There are no builds of MongoDB <= 3.0 with SSL for macOS
|
||||
- if:
|
||||
os: "*"
|
||||
tox-env-osx: "*"
|
||||
ssl: "nossl"
|
||||
then:
|
||||
add_tasks:
|
||||
- ".3.0"
|
||||
- ".2.6"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
|
||||
# Don't trace to avoid secrets showing up in the logs
|
||||
set -o errexit
|
||||
set +x
|
||||
|
||||
echo "Running enterprise authentication tests"
|
||||
source ./secrets-export.sh
|
||||
|
||||
export DB_USER="bob"
|
||||
export DB_PASSWORD="pwd123"
|
||||
@ -23,7 +25,6 @@ export GSSAPI_PORT=${SASL_PORT}
|
||||
export GSSAPI_PRINCIPAL=${PRINCIPAL}
|
||||
|
||||
# Pass needed env variables to the test environment.
|
||||
export TOX_TESTENV_PASSENV=*
|
||||
export TOX_ENV="enterprise-synchro"
|
||||
|
||||
# --sitepackages allows use of pykerberos without a test dep.
|
||||
/opt/python/3.6/bin/python3 -m tox -e "$TOX_ENV" --sitepackages -- -x test.test_auth
|
||||
bash ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh
|
||||
|
||||
@ -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,9 +31,48 @@ if [ "$SSL" != "nossl" ]; then
|
||||
export CA_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem"
|
||||
fi
|
||||
|
||||
if [ "$TOX_ENV" = "synchro" ]; then
|
||||
SETUP_ARGS="-- --check-exclude-patterns"
|
||||
if [ -f secrets-export.sh ]; then
|
||||
source secrets-export.sh
|
||||
fi
|
||||
|
||||
# Run the tests, and store the results in Evergreen compatible XUnit XML
|
||||
${TOX_BINARY} -e ${TOX_ENV} ${SETUP_ARGS}
|
||||
# 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
2
.git-blame-ignore-revs
Normal file
@ -0,0 +1,2 @@
|
||||
# Initial pre-commit reformat
|
||||
1e62b868ea58afeb42b3d0346e33776561c16ab6
|
||||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
||||
* @mongodb/dbx-python
|
||||
16
.github/dependabot.yml
vendored
Normal file
16
.github/dependabot.yml
vendored
Normal 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
5
.github/reviewers.txt
vendored
Normal 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
76
.github/workflows/codeql.yml
vendored
Normal 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
43
.github/workflows/dist.yml
vendored
Normal 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
115
.github/workflows/release.yml
vendored
Normal 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
129
.github/workflows/test-python.yml
vendored
Normal 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
21
.github/workflows/zizmor.yml
vendored
Normal 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
7
.github/zizmor.yml
vendored
Normal 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
3
.gitignore
vendored
@ -12,3 +12,6 @@ setup.cfg
|
||||
doc/_build/
|
||||
.idea/
|
||||
xunit-results
|
||||
xunit-synchro-results
|
||||
.eggs
|
||||
toxenv
|
||||
|
||||
89
.pre-commit-config.yaml
Normal file
89
.pre-commit-config.yaml
Normal 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
23
.readthedocs.yaml
Normal 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"
|
||||
18
.travis.yml
18
.travis.yml
@ -1,18 +0,0 @@
|
||||
language: python
|
||||
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
|
||||
services: mongodb
|
||||
|
||||
install:
|
||||
- pip install tornado
|
||||
|
||||
script: "python setup.py test"
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
68
CONTRIBUTING.md
Normal file
68
CONTRIBUTING.md
Normal 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 :)
|
||||
@ -1,48 +0,0 @@
|
||||
Contributing to Motor
|
||||
=====================
|
||||
|
||||
Contributions are encouraged. Please read these guidelines before sending a
|
||||
pull request.
|
||||
|
||||
Bugfixes and New Features
|
||||
-------------------------
|
||||
|
||||
Before starting to write code, look for existing tickets or create one in `Jira
|
||||
<https://jira.mongodb.org/browse/MOTOR>`_ for your specific issue or feature
|
||||
request.
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
|
||||
Install a recent version of MongoDB and run it on the default port from a clean
|
||||
data directory. Pass "--setParameter enableTestCommands=1" to mongod to enable
|
||||
testing MotorCursor's ``max_time_ms`` method.
|
||||
|
||||
Control how the tests connect to MongoDB with these environment variables:
|
||||
|
||||
- ``DB_IP``: Defaults to "localhost", can be a domain name or IP
|
||||
- ``DB_PORT``: Defaults to 27017
|
||||
- ``DB_USER``, ``DB_PASSWORD``: To test with authentication, create an admin
|
||||
user and set these environment variables to the username and password
|
||||
- ``CERT_DIR``: Path with alternate client.pem and ca.pem for testing.
|
||||
Otherwise the suite uses those in test/certificates/.
|
||||
|
||||
Install `tox`_ and run it from the command line in the repository directory.
|
||||
You will need a variety of Python interpreters installed. For a minimal test,
|
||||
ensure you have Python 2.7 and 3.5, and run::
|
||||
|
||||
> tox -e tornado4-py27,tornado4-py36
|
||||
|
||||
The doctests pass with Python 3.6 and a MongoDB 3.6 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 :)
|
||||
1
LICENSE
1
LICENSE
@ -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.
|
||||
|
||||
|
||||
@ -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
209
README.md
Normal file
@ -0,0 +1,209 @@
|
||||
# Motor
|
||||
|
||||
[](https://pypi.org/project/motor)
|
||||
[](https://pypi.org/project/motor)
|
||||
[](https://pepy.tech/project/motor)
|
||||
[](http://motor.readthedocs.io/en/stable/?badge=stable)
|
||||
|
||||

|
||||
|
||||
> [!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.
|
||||
117
README.rst
117
README.rst
@ -1,117 +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.7 or later.
|
||||
* `futures`_ on Python 2.7.
|
||||
* `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.
|
||||
|
||||
.. _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/
|
||||
71
RELEASE.md
Normal file
71
RELEASE.md
Normal 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."
|
||||
@ -1 +0,0 @@
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
:class:`~motor.motor_asyncio.AsyncIOMotorChangeStream`
|
||||
======================================================
|
||||
|
||||
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
|
||||
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
|
||||
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
|
||||
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
|
||||
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
|
||||
|
||||
.. currentmodule:: motor.motor_asyncio
|
||||
|
||||
.. autoclass:: AsyncIOMotorChangeStream
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
13
doc/api-asyncio/asyncio_motor_client_encryption.rst
Normal file
13
doc/api-asyncio/asyncio_motor_client_encryption.rst
Normal 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:
|
||||
11
doc/api-asyncio/asyncio_motor_client_session.rst
Normal file
11
doc/api-asyncio/asyncio_motor_client_session.rst
Normal 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:
|
||||
@ -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
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
:class:`~motor.motor_asyncio.AsyncIOMotorCursor`
|
||||
================================================
|
||||
|
||||
.. currentmodule:: motor.motor_asyncio
|
||||
|
||||
.. autoclass:: AsyncIOMotorCursor
|
||||
:members:
|
||||
@ -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
|
||||
|
||||
23
doc/api-asyncio/cursors.rst
Normal file
23
doc/api-asyncio/cursors.rst
Normal 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:
|
||||
@ -1,13 +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
|
||||
|
||||
@ -15,4 +23,3 @@ Motor asyncio API
|
||||
|
||||
This page describes using Motor with asyncio. For Tornado integration, see
|
||||
:doc:`../api-tornado/index`.
|
||||
|
||||
|
||||
23
doc/api-tornado/cursors.rst
Normal file
23
doc/api-tornado/cursors.rst
Normal 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:
|
||||
@ -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:
|
||||
|
||||
@ -1,13 +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
|
||||
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
:class:`~motor.motor_tornado.MotorChangeStream`
|
||||
===============================================
|
||||
|
||||
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
|
||||
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
|
||||
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
|
||||
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
|
||||
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
|
||||
|
||||
.. currentmodule:: motor.motor_tornado
|
||||
|
||||
.. autoclass:: MotorChangeStream
|
||||
|
||||
@ -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
|
||||
|
||||
13
doc/api-tornado/motor_client_encryption.rst
Normal file
13
doc/api-tornado/motor_client_encryption.rst
Normal 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:
|
||||
13
doc/api-tornado/motor_client_session.rst
Normal file
13
doc/api-tornado/motor_client_session.rst
Normal 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:
|
||||
@ -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
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
:class:`~motor.motor_tornado.MotorCursor`
|
||||
=========================================
|
||||
|
||||
.. currentmodule:: motor.motor_tornado
|
||||
|
||||
.. autoclass:: MotorCursor
|
||||
:members:
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -3,6 +3,645 @@ 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
|
||||
-----------
|
||||
|
||||
@ -70,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.
|
||||
@ -101,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. ``{}``).
|
||||
|
||||
@ -124,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::
|
||||
@ -144,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`.
|
||||
|
||||
@ -175,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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -225,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:
|
||||
|
||||
@ -257,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.
|
||||
@ -294,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')
|
||||
|
||||
@ -335,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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -451,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.
|
||||
|
||||
@ -490,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.
|
||||
@ -518,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():
|
||||
@ -541,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.
|
||||
@ -579,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`
|
||||
@ -590,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`
|
||||
@ -598,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`
|
||||
@ -627,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.
|
||||
|
||||
@ -663,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
|
||||
@ -675,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
|
||||
-----------
|
||||
|
||||
@ -767,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:
|
||||
@ -953,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
|
||||
~~~~~~~~~~~~
|
||||
@ -972,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
|
||||
~~~~~~~~
|
||||
|
||||
156
doc/conf.py
156
doc/conf.py
@ -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-present 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
|
||||
@ -87,20 +103,20 @@ import pymongo
|
||||
from pymongo.mongo_client import MongoClient
|
||||
|
||||
sync_client = MongoClient()
|
||||
ismaster = sync_client.admin.command('isMaster')
|
||||
hello = sync_client.admin.command('hello')
|
||||
server_info = sync_client.server_info()
|
||||
|
||||
if 'setName' in ismaster:
|
||||
if 'setName' in hello:
|
||||
raise Exception(
|
||||
"Run doctests with standalone MongoDB 3.6 server, not a replica set")
|
||||
"Run doctests with standalone MongoDB 5.0 server, not a replica set")
|
||||
|
||||
if ismaster.get('msg') == 'isdbgrid':
|
||||
if hello.get('msg') == 'isdbgrid':
|
||||
raise Exception(
|
||||
"Run doctests with standalone MongoDB 3.6 server, not mongos")
|
||||
"Run doctests with standalone MongoDB 5.0 server, not mongos")
|
||||
|
||||
if server_info['versionArray'][:2] != [3, 6]:
|
||||
if server_info['versionArray'][:2] != [5, 0]:
|
||||
raise Exception(
|
||||
"Run doctests with standalone MongoDB 3.6 server, not %s" % (
|
||||
"Run doctests with standalone MongoDB 5.0 server, not %s" % (
|
||||
server_info['version'], ))
|
||||
|
||||
sync_client.drop_database("doctest_test")
|
||||
@ -114,102 +130,106 @@ from motor import MotorClient
|
||||
|
||||
html_copy_source = False
|
||||
|
||||
# Theme gratefully vendored from CPython source.
|
||||
html_theme = "pydoctheme"
|
||||
html_theme_path = ["."]
|
||||
html_theme_options = {'collapsiblesidebar': True}
|
||||
html_static_path = ['static']
|
||||
try:
|
||||
import furo # noqa: F401
|
||||
|
||||
html_sidebars = {
|
||||
'index': ['globaltoc.html', 'searchbox.html'],
|
||||
}
|
||||
html_theme = "furo"
|
||||
except ImportError:
|
||||
# Theme gratefully vendored from CPython source.
|
||||
html_theme = "pydoctheme"
|
||||
html_theme_path = ["."]
|
||||
html_theme_options = {"collapsiblesidebar": True}
|
||||
html_static_path = ["static"]
|
||||
|
||||
html_sidebars = {
|
||||
"index": ["globaltoc.html", "searchbox.html"],
|
||||
}
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#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),
|
||||
}
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
Configuration
|
||||
=============
|
||||
|
||||
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
|
||||
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
|
||||
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
|
||||
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
|
||||
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
|
||||
|
||||
|
||||
TLS Protocol Version
|
||||
''''''''''''''''''''
|
||||
|
||||
@ -28,14 +35,14 @@ and executing the following command::
|
||||
|
||||
You should see "TLS 1.X" where X is >= 1.
|
||||
|
||||
You can read more about TLS versions and their security implications here:
|
||||
You can read more about TLS versions and their security implications in this `cheat sheet`_.
|
||||
|
||||
`<https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols>`_
|
||||
|
||||
.. _python.org: https://www.python.org/downloads/
|
||||
.. _homebrew: https://brew.sh/
|
||||
.. _macports: https://www.macports.org/
|
||||
.. _requests: https://pypi.python.org/pypi/requests
|
||||
.. _cheat sheet: https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html#only-support-strong-protocols
|
||||
|
||||
Thread Pool Size
|
||||
''''''''''''''''
|
||||
@ -46,3 +53,17 @@ system; to override the default set the environment variable ``MOTOR_MAX_WORKERS
|
||||
|
||||
Some additional threads are used for monitoring servers and background tasks, so the total
|
||||
count of threads in your process will be greater.
|
||||
|
||||
Network Compression
|
||||
'''''''''''''''''''
|
||||
|
||||
Like PyMongo, Motor supports network compression where network traffic between
|
||||
the client and MongoDB server are compressed.
|
||||
Keyword arguments to the Motor clients match those in MongoClient, documented
|
||||
`here <https://pymongo.readthedocs.io/en/stable/examples/network_compression.html>`_.
|
||||
By default no compression is used. If you wish to use wire compression,
|
||||
you will have to install one of the optional dependencies.
|
||||
Snappy requires `python-snappy <https://pypi.org/project/python-snappy>`_
|
||||
and zstandard requires `zstandard <https://pypi.org/project/zstandard>`_::
|
||||
|
||||
$ python3 -m pip install "motor[snappy, zstd]"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 supports the asyncio module in the standard library of Python 3.4 and
|
||||
Motor supports the asyncio module in the standard library of Python 3.5.3 and
|
||||
later.
|
||||
Motor also works with Tornado 4.5 and later along with all the Python versions
|
||||
Motor also works with Tornado 5.0 and later along with all the Python versions
|
||||
it supports.
|
||||
|
||||
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``.
|
||||
|
||||
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.
|
||||
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,4 +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.
|
||||
PyMongo's test suite and run it with Synchro.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
tornado
|
||||
aiohttp
|
||||
@ -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 --
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
"""Serve pre-compressed static content from GridFS with aiohttp.
|
||||
|
||||
Requires Python 3.4 or later and aiohttp 2.0 or later.
|
||||
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:
|
||||
|
||||
@ -14,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()
|
||||
|
||||
@ -22,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)
|
||||
|
||||
@ -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`.
|
||||
|
||||
|
||||
@ -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/
|
||||
|
||||
101
doc/examples/auto_csfle_example.py
Normal file
101
doc/examples/auto_csfle_example.py
Normal 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())
|
||||
@ -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,
|
||||
|
||||
@ -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
131
doc/examples/encryption.rst
Normal 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
|
||||
@ -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())
|
||||
68
doc/examples/explicit_encryption_example.py
Normal file
68
doc/examples/explicit_encryption_example.py
Normal 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())
|
||||
@ -1,16 +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`.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
|
||||
100
doc/examples/server_fle_enforcement_example.py
Normal file
100
doc/examples/server_fle_enforcement_example.py
Normal 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())
|
||||
@ -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.sleep(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
87
doc/examples/timeouts.rst
Normal 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.
|
||||
@ -4,16 +4,16 @@ import sys
|
||||
from base64 import urlsafe_b64encode
|
||||
from pprint import pformat
|
||||
|
||||
from motor.motor_tornado import MotorClient
|
||||
from bson import json_util # Installed with PyMongo.
|
||||
|
||||
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")
|
||||
@ -22,17 +22,13 @@ define("ns", default="test.test", help="database and collection name")
|
||||
|
||||
class Application(tornado.web.Application):
|
||||
def __init__(self):
|
||||
handlers = [
|
||||
(r"/", MainHandler),
|
||||
(r"/socket", ChangesHandler)]
|
||||
handlers = [(r"/", MainHandler), (r"/socket", ChangesHandler)]
|
||||
|
||||
templates = os.path.join(os.path.dirname(__file__),
|
||||
"tornado_change_stream_templates")
|
||||
templates = os.path.join(os.path.dirname(__file__), "tornado_change_stream_templates")
|
||||
|
||||
super(Application, self).__init__(handlers,
|
||||
template_path=templates,
|
||||
template_whitespace="all",
|
||||
debug=options.debug)
|
||||
super().__init__(
|
||||
handlers, template_path=templates, template_whitespace="all", debug=options.debug
|
||||
)
|
||||
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
@ -55,7 +51,7 @@ class ChangesHandler(tornado.websocket.WebSocketHandler):
|
||||
def update_cache(cls, change):
|
||||
cls.cache.append(change)
|
||||
if len(cls.cache) > cls.cache_size:
|
||||
cls.cache = cls.cache[-cls.cache_size:]
|
||||
cls.cache = cls.cache[-cls.cache_size :]
|
||||
|
||||
@classmethod
|
||||
def send_change(cls, change):
|
||||
@ -63,22 +59,23 @@ class ChangesHandler(tornado.websocket.WebSocketHandler):
|
||||
for waiter in cls.waiters:
|
||||
try:
|
||||
waiter.write_message(change_json)
|
||||
except Exception:
|
||||
logging.error("Error sending message", exc_info=True)
|
||||
except Exception as exc:
|
||||
logging.exception(exc)
|
||||
|
||||
@classmethod
|
||||
def on_change(cls, change):
|
||||
logging.info("got change of type '%s'", change.get('operationType'))
|
||||
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.
|
||||
html_id = urlsafe_b64encode(change['_id']['_data']).decode().rstrip('=')
|
||||
change.pop('_id')
|
||||
change['html'] = '<div id="change-%s"><pre>%s</pre></div>' % (
|
||||
html_id,
|
||||
tornado.escape.xhtml_escape(pformat(change)))
|
||||
|
||||
change['html_id'] = html_id
|
||||
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)
|
||||
|
||||
@ -96,26 +93,24 @@ async def watch(collection):
|
||||
|
||||
def main():
|
||||
tornado.options.parse_command_line()
|
||||
if '.' not in options.ns:
|
||||
sys.stderr.write('Invalid ns "%s", must contain a "."' % (options.ns,))
|
||||
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)
|
||||
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.
|
||||
loop.add_callback(watch, collection)
|
||||
try:
|
||||
loop.start()
|
||||
loop.run_sync(lambda: watch(collection))
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
if change_stream is not None:
|
||||
change_stream.close()
|
||||
if change_stream:
|
||||
loop.run_sync(change_stream.close)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -3,10 +3,17 @@
|
||||
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.
|
||||
each change notification on a web page using web sockets.
|
||||
|
||||
Instructions
|
||||
------------
|
||||
@ -18,7 +25,7 @@ http://localhost:8888
|
||||
Open a ``mongo`` shell in the terminal and perform some operations on the
|
||||
"test" collection in the "test" database:
|
||||
|
||||
.. code-block:: none
|
||||
.. code-block:: text
|
||||
|
||||
> use test
|
||||
switched to db test
|
||||
@ -29,7 +36,7 @@ Open a ``mongo`` shell in the terminal and perform some operations on the
|
||||
The application receives each change notification and displays it as JSON on
|
||||
the web page:
|
||||
|
||||
.. code-block:: none
|
||||
.. code-block:: text
|
||||
|
||||
Changes
|
||||
|
||||
|
||||
393
doc/examples/type_hints.rst
Normal file
393
doc/examples/type_hints.rst
Normal 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
|
||||
@ -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
|
||||
====================
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -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
215
doc/migrate-to-motor-2.rst
Normal 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
469
doc/migrate-to-motor-3.rst
Normal 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.
|
||||
@ -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,8 +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
|
||||
|
||||
|
||||
class mongodoc(nodes.Admonition, nodes.Element):
|
||||
@ -28,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):
|
||||
@ -36,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,7 +55,7 @@ class MongodocDirective(Directive):
|
||||
|
||||
def run(self):
|
||||
node = mongodoc()
|
||||
title = 'See general MongoDB documentation'
|
||||
title = "The MongoDB documentation on"
|
||||
node += nodes.title(title, title)
|
||||
self.state.nested_parse(self.content, self.content_offset, node)
|
||||
return [node]
|
||||
@ -75,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
|
||||
@ -86,14 +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)
|
||||
return {'parallel_write_safe': True, 'parallel_read_safe': True}
|
||||
|
||||
@ -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,56 +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)
|
||||
|
||||
|
||||
docstring_warnings = []
|
||||
|
||||
|
||||
@ -122,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
|
||||
|
||||
@ -130,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.
|
||||
@ -142,67 +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)
|
||||
and obj_motor_info['coroutine_has_callback']):
|
||||
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,25 +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)
|
||||
coroutine_has_callback = has_coroutine_annotation or is_async_method
|
||||
if has_coroutine_annotation:
|
||||
coroutine_has_callback = getattr(attr, 'coroutine_has_callback', True)
|
||||
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
|
||||
|
||||
@ -247,76 +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,
|
||||
'coroutine_has_callback': coroutine_has_callback,
|
||||
'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 []
|
||||
add_callback = True
|
||||
if 'callback' in chain(args or [], kwargs or []):
|
||||
add_callback = False
|
||||
elif is_asyncio_api(name):
|
||||
add_callback = False
|
||||
elif (getattr(method, 'coroutine_annotation', False)
|
||||
and not getattr(method, 'coroutine_has_callback', True)):
|
||||
add_callback = False
|
||||
|
||||
if add_callback:
|
||||
# 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)
|
||||
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 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
|
||||
lines[:] = subbed.split("\n")
|
||||
|
||||
|
||||
def build_finished(app, exception):
|
||||
@ -328,8 +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)
|
||||
return {'parallel_write_safe': True, 'parallel_read_safe': False}
|
||||
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}
|
||||
|
||||
@ -1,32 +1,27 @@
|
||||
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.7, or 3.4 and later.
|
||||
* PyMongo_ 3.6 and later.
|
||||
* CPython 3.10 and later.
|
||||
* PyMongo_ 4.9 and later.
|
||||
|
||||
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.
|
||||
|
||||
(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`_.
|
||||
|
||||
.. _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/
|
||||
|
||||
.. _sphinx: https://www.sphinx-doc.org/
|
||||
|
||||
.. _compatibility-matrix:
|
||||
|
||||
@ -36,67 +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+ |
|
||||
+-------------------+-----------------+
|
||||
| 1.2 | 3.6+ |
|
||||
| 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 |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 1.2 |**N**|**N**| 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.
|
||||
@ -104,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
|
||||
`````````````````
|
||||
@ -112,83 +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 |
|
||||
+---------------+-----+-----+-----+
|
||||
| | 1.2 |**N**| 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.
|
||||
Motor 3.0 dropped support for Pythons older than 3.7.
|
||||
|
||||
Beginning in version 0.5, supports the "async for" syntax with cursors in
|
||||
Python 3.5 and later. Motor 1.2 dropped support for the short-lived version of
|
||||
the "async for" protocol implemented in Python 3.5.0 and 3.5.1. Motor continues
|
||||
to work with "async for" loops in Python 3.5.2 and later.
|
||||
Motor 3.1.1 added support for Python 3.11.
|
||||
|
||||
+-------------------------------------------------------------------------+
|
||||
| Python Version |
|
||||
+=====================+=====+=====+=====+=====+=====+=======+=======+=====+
|
||||
| | 2.5 | 2.6 | 2.7 | 3.3 | 3.4 | 3.5.0 | 3.5.2 | 3.6 |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| Motor Version | 0.1 | Y | Y | Y | Y |**N**|**N** |**N** |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 0.2 |**N**| Y | Y | Y |**N**|**N** |**N** |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 0.3 |**N**| Y | Y | Y | Y |**N** |**N** |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 0.4 |**N**| Y | Y | Y | Y |**N** |**N** |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 0.5 |**N**| Y | Y | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 0.6 |**N**| Y | Y | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 0.7 |**N**| Y | Y | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 1.0 |**N**| Y | Y | Y | Y | Y | Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 1.1 |**N**| Y | Y | Y | Y | Y | Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
| | 1.2 |**N**|**N**| Y |**N**| Y |**N** | Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+
|
||||
Motor 3.3 added support for Python 3.12.
|
||||
|
||||
.. _asyncio package from PyPI: https://pypi.python.org/pypi/asyncio
|
||||
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.
|
||||
|
||||
@ -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.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/
|
||||
|
||||
@ -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,10 +159,10 @@ makes it available to request handlers::
|
||||
def get(self):
|
||||
db = self.settings['db']
|
||||
|
||||
It is a common mistake to create a new client object for every
|
||||
request; **this comes at a dire performance cost**. Create the client
|
||||
when your application starts and reuse that one client for the lifetime
|
||||
of the process, as shown in these examples.
|
||||
.. warning:: It is a common mistake to create a new client object for every
|
||||
request; **this comes at a dire performance cost**. Create the client
|
||||
when your application starts and reuse that one client for the lifetime
|
||||
of the process, as shown in these examples.
|
||||
|
||||
The Tornado :class:`~tornado.httpserver.HTTPServer` class's :meth:`start`
|
||||
method is a simple way to fork multiple web servers and use all of your
|
||||
@ -179,7 +189,7 @@ methods than ``HTTPServer.start()``. See Tornado's guide to
|
||||
|
||||
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:
|
||||
@ -187,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::
|
||||
@ -266,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
|
||||
@ -329,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`
|
||||
----------------------------------------------------------------
|
||||
@ -359,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)
|
||||
@ -380,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)
|
||||
@ -402,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
|
||||
------------------
|
||||
|
||||
@ -499,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}
|
||||
@ -525,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
|
||||
@ -542,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
|
||||
@ -550,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
|
||||
@ -610,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/
|
||||
|
||||
426
ez_setup.py
426
ez_setup.py
@ -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())
|
||||
@ -13,54 +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, 3, 'dev0')
|
||||
|
||||
|
||||
def get_version_string():
|
||||
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)
|
||||
|
||||
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
41
motor/_version.py
Normal 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__
|
||||
@ -14,19 +14,21 @@
|
||||
|
||||
"""Serve GridFS files with Motor and aiohttp.
|
||||
|
||||
Requires Python 3.4 or later and aiohttp 2.0 or later.
|
||||
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
|
||||
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):
|
||||
@ -106,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
|
||||
|
||||
@ -135,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)
|
||||
@ -153,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)
|
||||
@ -172,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 = aiohttp.web.StreamResponse()
|
||||
self._set_standard_headers(request.path, resp, gridout)
|
||||
|
||||
# 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)
|
||||
@ -211,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:
|
||||
@ -242,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)
|
||||
|
||||
2377
motor/core.py
2377
motor/core.py
File diff suppressed because it is too large
Load Diff
889
motor/core.pyi
Normal file
889
motor/core.pyi
Normal 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
@ -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,96 +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 _reset_global_executor():
|
||||
"""Re-initialize the global ThreadPoolExecutor"""
|
||||
global _EXECUTOR # noqa: PLW0603
|
||||
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
|
||||
|
||||
|
||||
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):
|
||||
# Adapted from asyncio's wrap_future and _chain_future. Ensure the wrapped
|
||||
# future is resolved on the main thread when the executor's future is
|
||||
# resolved on a worker thread. asyncio's wrap_future does the same, but
|
||||
# throws an error if the loop is stopped. We want to avoid errors if a
|
||||
# background task completes after the loop stops, e.g. ChangeStream.next()
|
||||
# returns while the program is shutting down.
|
||||
def _set_state():
|
||||
if dest.cancelled():
|
||||
return
|
||||
if contextvars:
|
||||
context = contextvars.copy_context()
|
||||
fn = functools.partial(context.run, fn)
|
||||
|
||||
if source.cancelled():
|
||||
dest.cancel()
|
||||
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:
|
||||
exception = source.exception()
|
||||
if exception is not None:
|
||||
dest.set_exception(exception)
|
||||
else:
|
||||
result = source.result()
|
||||
dest.set_result(result)
|
||||
b.set_result(a.result())
|
||||
|
||||
def _call_check_cancel(_):
|
||||
if dest.cancelled():
|
||||
source.cancel()
|
||||
|
||||
def _call_set_state(_):
|
||||
if loop.is_closed():
|
||||
return
|
||||
|
||||
loop.call_soon_threadsafe(_set_state)
|
||||
|
||||
source = _EXECUTOR.submit(functools.partial(fn, *args, **kwargs))
|
||||
dest = asyncio.Future(loop=loop)
|
||||
dest.add_done_callback(_call_check_cancel)
|
||||
source.add_done_callback(_call_set_state)
|
||||
return dest
|
||||
a.add_done_callback(copy)
|
||||
|
||||
|
||||
_DEFAULT = object()
|
||||
|
||||
|
||||
def future_or_callback(future, callback, loop, return_value=_DEFAULT):
|
||||
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):
|
||||
@ -144,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):
|
||||
@ -156,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:
|
||||
@ -172,4 +158,13 @@ def pymongo_class_wrapper(f, pymongo_class):
|
||||
|
||||
|
||||
def yieldable(future):
|
||||
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"
|
||||
|
||||
@ -12,8 +12,6 @@
|
||||
# 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.
|
||||
|
||||
See "Frameworks" in the Developer Guide.
|
||||
@ -21,14 +19,22 @@ 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():
|
||||
@ -41,84 +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)
|
||||
raise TypeError("io_loop must be instance of IOLoop, not %r" % loop)
|
||||
|
||||
|
||||
def get_future(loop):
|
||||
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 _reset_global_executor():
|
||||
"""Re-initialize the global ThreadPoolExecutor"""
|
||||
global _EXECUTOR # noqa: PLW0603
|
||||
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
|
||||
|
||||
|
||||
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):
|
||||
# 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 = concurrent.Future()
|
||||
exec_fut = _EXECUTOR.submit(fn, *args, **kwargs)
|
||||
if contextvars:
|
||||
context = contextvars.copy_context()
|
||||
fn = functools.partial(context.run, fn)
|
||||
|
||||
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
|
||||
return loop.run_in_executor(_EXECUTOR, functools.partial(fn, *args, **kwargs))
|
||||
|
||||
|
||||
_DEFAULT = object()
|
||||
|
||||
|
||||
def future_or_callback(future, callback, io_loop, return_value=_DEFAULT):
|
||||
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 = concurrent.Future()
|
||||
|
||||
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):
|
||||
@ -136,52 +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):
|
||||
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}"
|
||||
|
||||
@ -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,80 +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(callback):
|
||||
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.
|
||||
"""
|
||||
if isinstance(callback, bool):
|
||||
# Like:
|
||||
# @coroutine_annotation(callback=False)
|
||||
# def method(self):
|
||||
#
|
||||
def decorator(f):
|
||||
f.coroutine_annotation = True
|
||||
f.coroutine_has_callback = callback
|
||||
return f
|
||||
|
||||
return decorator
|
||||
|
||||
# Like:
|
||||
# @coroutine_annotation
|
||||
# def method(self):
|
||||
#
|
||||
f = callback
|
||||
f.coroutine_annotation = True
|
||||
f.coroutine_has_callback = 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
|
||||
|
||||
@ -121,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):
|
||||
@ -187,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):
|
||||
@ -330,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).
|
||||
@ -354,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
|
||||
|
||||
@ -13,64 +13,64 @@
|
||||
# limitations under the License.
|
||||
|
||||
"""Asyncio support for Motor, an asynchronous driver for MongoDB."""
|
||||
|
||||
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']
|
||||
__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)
|
||||
|
||||
|
||||
AsyncIOMotorChangeStream = create_asyncio_class(
|
||||
core.AgnosticChangeStream)
|
||||
AsyncIOMotorLatentCommandCursor = create_asyncio_class(core.AgnosticLatentCommandCursor)
|
||||
|
||||
|
||||
AsyncIOMotorBulkOperationBuilder = create_asyncio_class(
|
||||
core.AgnosticBulkOperationBuilder)
|
||||
AsyncIOMotorChangeStream = create_asyncio_class(core.AgnosticChangeStream)
|
||||
|
||||
|
||||
AsyncIOMotorGridFS = create_asyncio_class(
|
||||
motor_gridfs.AgnosticGridFS)
|
||||
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
264
motor/motor_asyncio.pyi
Normal 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: ...
|
||||
@ -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")
|
||||
|
||||
@ -12,112 +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)
|
||||
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)
|
||||
|
||||
@ -125,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
|
||||
@ -137,96 +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 PY35:
|
||||
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())
|
||||
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
|
||||
@ -240,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>`_
|
||||
@ -257,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.
|
||||
@ -268,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
|
||||
@ -299,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
|
||||
@ -332,220 +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.
|
||||
- `session` (optional): a
|
||||
:class:`~pymongo.client_session.ClientSession`, created with
|
||||
:meth:`~MotorClient.start_session`.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
.. 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:
|
||||
@ -558,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``.
|
||||
@ -575,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
|
||||
@ -620,6 +485,20 @@ class AgnosticGridFSBucket(_GFSBase):
|
||||
"""
|
||||
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()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user