Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cbd45f581 | ||
|
|
88119c849c | ||
|
|
057b1afa06 | ||
|
|
1f6a3d692c | ||
|
|
aac3bc527f | ||
|
|
fd4eceddae | ||
|
|
5ded4425fb |
File diff suppressed because it is too large
Load Diff
@ -2,10 +2,8 @@
|
||||
|
||||
# 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"
|
||||
@ -25,6 +23,7 @@ export GSSAPI_PORT=${SASL_PORT}
|
||||
export GSSAPI_PRINCIPAL=${PRINCIPAL}
|
||||
|
||||
# Pass needed env variables to the test environment.
|
||||
export TOX_ENV="enterprise-synchro"
|
||||
export TOX_TESTENV_PASSENV=*
|
||||
|
||||
bash ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh
|
||||
# --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
|
||||
|
||||
@ -1,26 +1,19 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
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. "synchro", required.
|
||||
# PYTHON_BINARY Path to python, required.
|
||||
# TOX_ENV Tox environment name, e.g. "tornado4-py36"
|
||||
# TOX_BINARY Path to tox executable
|
||||
# INSTALL_TOX Whether to install tox in a virtualenv
|
||||
# PYTHON_BINARY Path to python
|
||||
# VIRTUALENV Path to virtualenv script
|
||||
|
||||
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"
|
||||
@ -31,48 +24,24 @@ if [ "$SSL" != "nossl" ]; then
|
||||
export CA_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem"
|
||||
fi
|
||||
|
||||
if [ -f secrets-export.sh ]; then
|
||||
source secrets-export.sh
|
||||
if [ "$TOX_ENV" = "synchro37" ]; then
|
||||
SETUP_ARGS="-- --check-exclude-patterns"
|
||||
fi
|
||||
|
||||
# Usage:
|
||||
# createvirtualenv /path/to/python /output/path/for/venv
|
||||
# * param1: Python binary to use for the virtualenv
|
||||
# * param2: Path to the virtualenv to create
|
||||
createvirtualenv () {
|
||||
PYTHON=$1
|
||||
VENVPATH=$2
|
||||
if $PYTHON -m virtualenv --version; then
|
||||
VIRTUALENV="$PYTHON -m virtualenv"
|
||||
elif $PYTHON -m venv -h > /dev/null; then
|
||||
# System virtualenv might not be compatible with the python3 on our path
|
||||
VIRTUALENV="$PYTHON -m venv"
|
||||
if [ "${INSTALL_TOX}" = "true" ]; then
|
||||
$VIRTUALENV motorenv
|
||||
set +o xtrace
|
||||
if [ -f motorenv/bin/activate ]; then
|
||||
source motorenv/bin/activate
|
||||
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
|
||||
# Windows.
|
||||
ls -l motorenv
|
||||
source motorenv/Scripts/activate
|
||||
fi
|
||||
set -o xtrace
|
||||
pip install tox>=3.18
|
||||
TOX_BINARY=tox
|
||||
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}"
|
||||
# Run the tests, and store the results in Evergreen compatible XUnit XML
|
||||
${TOX_BINARY} -e ${TOX_ENV} ${SETUP_ARGS} "$@"
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
# Initial pre-commit reformat
|
||||
1e62b868ea58afeb42b3d0346e33776561c16ab6
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
||||
* @mongodb/dbx-python
|
||||
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@ -1,16 +0,0 @@
|
||||
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
5
.github/reviewers.txt
vendored
@ -1,5 +0,0 @@
|
||||
# List of reviewers for auto-assignment of reviews.
|
||||
caseyclements
|
||||
blink1073
|
||||
Jibola
|
||||
NoahStapp
|
||||
76
.github/workflows/codeql.yml
vendored
76
.github/workflows/codeql.yml
vendored
@ -1,76 +0,0 @@
|
||||
# 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
43
.github/workflows/dist.yml
vendored
@ -1,43 +0,0 @@
|
||||
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
115
.github/workflows/release.yml
vendored
@ -1,115 +0,0 @@
|
||||
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
129
.github/workflows/test-python.yml
vendored
@ -1,129 +0,0 @@
|
||||
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
21
.github/workflows/zizmor.yml
vendored
@ -1,21 +0,0 @@
|
||||
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
7
.github/zizmor.yml
vendored
@ -1,7 +0,0 @@
|
||||
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,6 +12,3 @@ setup.cfg
|
||||
doc/_build/
|
||||
.idea/
|
||||
xunit-results
|
||||
xunit-synchro-results
|
||||
.eggs
|
||||
toxenv
|
||||
|
||||
@ -1,89 +1,5 @@
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v3.4.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"]
|
||||
- id: debug-statements
|
||||
|
||||
@ -11,13 +11,9 @@ sphinx:
|
||||
|
||||
# Set the version of Python and requirements required to build the docs.
|
||||
python:
|
||||
version: 3.7
|
||||
install:
|
||||
# Install motor itself.
|
||||
- method: pip
|
||||
path: .
|
||||
- requirements: requirements/docs.txt
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
- requirements: doc/docs-requirements.txt
|
||||
|
||||
@ -1,68 +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](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 :)
|
||||
48
CONTRIBUTING.rst
Normal file
48
CONTRIBUTING.rst
Normal file
@ -0,0 +1,48 @@
|
||||
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 3.9, and run::
|
||||
|
||||
> tox -e asyncio-py39
|
||||
|
||||
The doctests pass with Python 3.7 and a MongoDB 5.0 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,3 +199,4 @@
|
||||
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.
|
||||
|
||||
|
||||
8
MANIFEST.in
Normal file
8
MANIFEST.in
Normal file
@ -0,0 +1,8 @@
|
||||
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
209
README.md
@ -1,209 +0,0 @@
|
||||
# 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.
|
||||
179
README.rst
Normal file
179
README.rst
Normal file
@ -0,0 +1,179 @@
|
||||
=====
|
||||
Motor
|
||||
=====
|
||||
|
||||
.. image:: https://raw.github.com/mongodb/motor/master/doc/_static/motor.png
|
||||
|
||||
:Info: Motor is a full-featured, non-blocking MongoDB_ driver for Python
|
||||
Tornado_ and asyncio_ applications.
|
||||
:Documentation: Available at `motor.readthedocs.io <https://motor.readthedocs.io/en/stable/>`_
|
||||
:Author: A\. Jesse Jiryu Davis
|
||||
|
||||
About
|
||||
=====
|
||||
|
||||
Motor presents a coroutine-based API for non-blocking access
|
||||
to MongoDB. The source is `on GitHub <https://github.com/mongodb/motor>`_
|
||||
and the docs are on ReadTheDocs_.
|
||||
|
||||
"We use Motor in high throughput environments, processing tens of thousands
|
||||
of requests per second. It allows us to take full advantage of modern
|
||||
hardware, ensuring we utilise the entire capacity of our purchased CPUs.
|
||||
This helps us be more efficient with computing power, compute spend and
|
||||
minimises the environmental impact of our infrastructure as a result."
|
||||
|
||||
--*David Mytton, Server Density*
|
||||
|
||||
"We develop easy-to-use sensors and sensor systems with open source
|
||||
software to ensure every innovator, from school child to laboratory
|
||||
researcher, has the same opportunity to create. We integrate Motor into our
|
||||
software to guarantee massively scalable sensor systems for everyone."
|
||||
|
||||
--*Ryan Smith, inXus Interactive*
|
||||
|
||||
Support / Feedback
|
||||
==================
|
||||
|
||||
For issues with, questions about, or feedback for PyMongo, please look into
|
||||
our `support channels <https://support.mongodb.com/welcome>`_. Please
|
||||
do not email any of the Motor developers directly with issues or
|
||||
questions - you're more likely to get an answer on the `MongoDB Community
|
||||
Forums <https://developer.mongodb.com/community/forums/tag/motor-driver>`_.
|
||||
|
||||
Bugs / Feature Requests
|
||||
=======================
|
||||
|
||||
Think you've found a bug? Want to see a new feature in Motor? Please open a
|
||||
case in our issue management tool, JIRA:
|
||||
|
||||
- `Create an account and login <https://jira.mongodb.org>`_.
|
||||
- Navigate to `the MOTOR project <https://jira.mongodb.org/browse/MOTOR>`_.
|
||||
- Click **Create Issue** - Please provide as much information as possible about the issue type and how to reproduce it.
|
||||
|
||||
Bug reports in JIRA for all driver projects (i.e. MOTOR, CSHARP, JAVA) and the
|
||||
Core Server (i.e. SERVER) project are **public**.
|
||||
|
||||
How To Ask For Help
|
||||
-------------------
|
||||
|
||||
Please include all of the following information when opening an issue:
|
||||
|
||||
- Detailed steps to reproduce the problem, including full traceback, if possible.
|
||||
- The exact python version used, with patch level::
|
||||
|
||||
$ python -c "import sys; print(sys.version)"
|
||||
|
||||
- The exact version of Motor used, with patch level::
|
||||
|
||||
$ python -c "import motor; print(motor.version)"
|
||||
|
||||
- The exact version of PyMongo used, with patch level::
|
||||
|
||||
$ python -c "import pymongo; print(pymongo.version); print(pymongo.has_c())"
|
||||
|
||||
- The exact Tornado version, if you are using Tornado::
|
||||
|
||||
$ python -c "import tornado; print(tornado.version)"
|
||||
|
||||
- The operating system and version (e.g. RedHat Enterprise Linux 6.4, OSX 10.9.5, ...)
|
||||
|
||||
Security Vulnerabilities
|
||||
------------------------
|
||||
|
||||
If you've identified a security vulnerability in a driver or any other
|
||||
MongoDB project, please report it according to the `instructions here
|
||||
<http://docs.mongodb.org/manual/tutorial/create-a-vulnerability-report>`_.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Motor can be installed with `pip <http://pypi.python.org/pypi/pip>`_::
|
||||
|
||||
$ pip install motor
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
Motor works in all the environments officially supported by Tornado or by
|
||||
asyncio. It requires:
|
||||
|
||||
* Unix (including macOS) or Windows.
|
||||
* PyMongo_ >=3.12,<4
|
||||
* Python 3.5+
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
Motor supports same optional dependencies as PyMongo. Required dependencies can be installed
|
||||
along with Motor.
|
||||
|
||||
GSSAPI authentication requires ``gssapi`` extra dependency. The correct
|
||||
dependency can be installed automatically along with Motor::
|
||||
|
||||
$ pip install "motor[gssapi]"
|
||||
|
||||
similarly,
|
||||
|
||||
MONGODB-AWS authentication requires ``aws`` extra dependency::
|
||||
|
||||
$ pip install "motor[aws]"
|
||||
|
||||
Support for mongodb+srv:// URIs requires ``srv`` extra dependency::
|
||||
|
||||
$ pip install "motor[srv]"
|
||||
|
||||
OCSP requires ``ocsp`` extra dependency::
|
||||
|
||||
$ pip install "motor[ocsp]"
|
||||
|
||||
Wire protocol compression with snappy requires ``snappy`` extra dependency::
|
||||
|
||||
$ pip install "motor[snappy]"
|
||||
|
||||
Wire protocol compression with zstandard requires ``zstd`` extra dependency::
|
||||
|
||||
$ pip install "motor[zstd]"
|
||||
|
||||
Client-Side Field Level Encryption requires ``encryption`` extra dependency::
|
||||
|
||||
$ pip install "motor[encryption]"
|
||||
|
||||
You can install all dependencies automatically with the following
|
||||
command::
|
||||
|
||||
$ pip install "motor[gssapi,aws,ocsp,snappy,srv,zstd,encryption]"
|
||||
|
||||
See `requirements <https://motor.readthedocs.io/en/stable/requirements.html>`_
|
||||
for details about compatibility.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
See the `examples on ReadTheDocs <https://motor.readthedocs.io/en/stable/examples/index.html>`_.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
Motor's documentation is on ReadTheDocs_.
|
||||
|
||||
Build the documentation with Python 3.5. Install sphinx_, Tornado_, and aiohttp_,
|
||||
and do ``cd doc; make 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
|
||||
|
||||
.. _aiohttp: https://github.com/aio-libs/aiohttp
|
||||
|
||||
.. _ReadTheDocs: https://motor.readthedocs.io/en/stable/
|
||||
|
||||
.. _sphinx: http://sphinx.pocoo.org/
|
||||
71
RELEASE.md
71
RELEASE.md
@ -1,71 +0,0 @@
|
||||
# Motor Releases
|
||||
|
||||
## Versioning
|
||||
|
||||
Motor's version numbers follow [semantic
|
||||
versioning](http://semver.org/): each version number is structured
|
||||
"major.minor.patch". Patch releases fix bugs, minor releases add
|
||||
features (and may fix bugs), and major releases include API changes that
|
||||
break backwards compatibility (and may add features and fix bugs).
|
||||
|
||||
In between releases we add .devN to the version number to denote the
|
||||
version under development. So if we just released 2.3.0, then the
|
||||
current dev version might be 2.3.1.dev0 or 2.4.0.dev0. When we make the
|
||||
next release we replace all instances of 2.x.x.devN in the docs with the
|
||||
new version number.
|
||||
|
||||
<https://www.python.org/dev/peps/pep-0440/>
|
||||
|
||||
## Release Process
|
||||
|
||||
Motor ships a [pure Python
|
||||
wheel](https://packaging.python.org/guides/distributing-packages-using-setuptools/#pure-python-wheels)
|
||||
and a [source
|
||||
distribution](https://packaging.python.org/guides/distributing-packages-using-setuptools/#source-distributions).
|
||||
|
||||
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."
|
||||
68
RELEASE.rst
Normal file
68
RELEASE.rst
Normal file
@ -0,0 +1,68 @@
|
||||
==============
|
||||
Motor Releases
|
||||
==============
|
||||
|
||||
Versioning
|
||||
----------
|
||||
|
||||
Motor's version numbers follow `semantic versioning <http://semver.org/>`_:
|
||||
each version number is structured "major.minor.patch". Patch releases fix
|
||||
bugs, minor releases add features (and may fix bugs), and major releases
|
||||
include API changes that break backwards compatibility (and may add features
|
||||
and fix bugs).
|
||||
|
||||
In between releases we add .devN to the version number to denote the version
|
||||
under development. So if we just released 2.3.0, then the current dev
|
||||
version might be 2.3.1.dev0 or 2.4.0.dev0. When we make the next release we
|
||||
replace all instances of 2.x.x.devN in the docs with the new version number.
|
||||
|
||||
https://www.python.org/dev/peps/pep-0440/
|
||||
|
||||
Release Process
|
||||
---------------
|
||||
|
||||
Motor ships a `pure Python wheel <https://packaging.python.org/guides/distributing-packages-using-setuptools/#pure-python-wheels>`_
|
||||
and a `source distribution <https://packaging.python.org/guides/distributing-packages-using-setuptools/#source-distributions>`_.
|
||||
|
||||
#. Motor is tested on Evergreen. Ensure that the latest commit is passing CI as
|
||||
expected: https://evergreen.mongodb.com/waterfall/motor.
|
||||
|
||||
#. Check JIRA to ensure all the tickets in this version have been completed.
|
||||
|
||||
#. Add release notes to `doc/changelog.rst`. Generally just summarize/clarify
|
||||
the git log, but you might add some more long form notes for big changes.
|
||||
|
||||
#. Search and replace the `devN` version number w/ the new version number (see
|
||||
note above in `Versioning`_). Make sure version number is updated in
|
||||
`setup.py` and `motor/__init__.py`. Commit the change and tag the release.
|
||||
Immediately bump the version number to `dev0` in a new commit::
|
||||
|
||||
$ # Bump to release version number
|
||||
$ git commit -a -m "BUMP <release version number>"
|
||||
$ git tag -a "<release version number>" -m "BUMP <release version number>"
|
||||
$ # Bump to dev version number
|
||||
$ git commit -a -m "BUMP <dev version number>"
|
||||
$ git push
|
||||
$ git push --tags
|
||||
|
||||
#. Build the release packages by running the `release.sh`
|
||||
script on macOS::
|
||||
|
||||
$ git clone git@github.com:mongodb/motor.git
|
||||
$ cd motor
|
||||
$ git checkout "<release version number>"
|
||||
$ ./release.sh
|
||||
|
||||
This will create the following distributions::
|
||||
|
||||
$ ls dist
|
||||
motor-<version>.tar.gz
|
||||
motor-<version>-py3-none-any.whl
|
||||
|
||||
#. Upload all the release packages to PyPI with twine::
|
||||
|
||||
$ python3 -m twine upload dist/*
|
||||
|
||||
#. Trigger a build of the docs on https://readthedocs.org/.
|
||||
|
||||
#. Announce!
|
||||
@ -0,0 +1 @@
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
: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,12 +1,6 @@
|
||||
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>`_.
|
||||
@ -14,8 +8,366 @@ 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>`.
|
||||
|
||||
.. autoclass:: AsyncIOMotorGridFSBucket
|
||||
:members:
|
||||
|
||||
.. 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 = fs.open_upload_stream(
|
||||
"test_file", 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.open_upload_stream(
|
||||
"test_file", metadata={"contentType": "text/plain"}) 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 = fs.open_upload_stream_with_id(
|
||||
ObjectId(), "test_file",
|
||||
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!",
|
||||
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!",
|
||||
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:: AsyncIOMotorGridIn
|
||||
:members:
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
: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,12 +1,6 @@
|
||||
: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:
|
||||
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
: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
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
: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,16 +1,11 @@
|
||||
: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
|
||||
|
||||
@ -23,3 +18,110 @@
|
||||
|
||||
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,12 +1,6 @@
|
||||
: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
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
: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
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
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
|
||||
@ -23,3 +17,4 @@ Motor asyncio API
|
||||
|
||||
This page describes using Motor with asyncio. For Tornado integration, see
|
||||
:doc:`../api-tornado/index`.
|
||||
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
: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
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
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>`_.
|
||||
@ -16,8 +10,380 @@ Store blobs of data in `GridFS <http://dochub.mongodb.org/core/gridfs>`_.
|
||||
|
||||
.. seealso:: :doc:`web`
|
||||
|
||||
.. autoclass:: MotorGridFSBucket
|
||||
:members:
|
||||
.. 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))
|
||||
|
||||
Delete a file's metadata and data chunks from a GridFS bucket::
|
||||
|
||||
async def delete():
|
||||
my_db = MotorClient().test
|
||||
fs = MotorGridFSBucket(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.
|
||||
|
||||
Returns a Future.
|
||||
|
||||
.. 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 = MotorClient().test
|
||||
fs = MotorGridFSBucket(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`.
|
||||
|
||||
Returns a Future.
|
||||
|
||||
.. 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 = MotorClient().test
|
||||
fs = MotorGridFSBucket(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:`~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.
|
||||
|
||||
Returns a Future.
|
||||
|
||||
.. 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 = MotorClient().test
|
||||
fs = MotorGridFSBucket(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 Future that resolves to a :class:`MotorGridOut`.
|
||||
|
||||
.. 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 = MotorClient().test
|
||||
fs = MotorGridFSBucket(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 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::
|
||||
|
||||
async def upload():
|
||||
my_db = MotorClient().test
|
||||
fs = MotorGridFSBucket(my_db)
|
||||
grid_in = 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:`MotorGridIn`.
|
||||
|
||||
Raises :exc:`~gridfs.errors.NoFile` if no such version of
|
||||
that file exists.
|
||||
Raises :exc:`~ValueError` if `filename` is not a string.
|
||||
|
||||
Using the "async with" statement calls :meth:`~MotorGridIn.close`
|
||||
automatically::
|
||||
|
||||
async def upload():
|
||||
my_db = MotorClient().test
|
||||
fs = MotorGridFSBucket(my_db)
|
||||
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')
|
||||
|
||||
# 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::
|
||||
|
||||
async def upload():
|
||||
my_db = MotorClient().test
|
||||
fs = MotorGridFSBucket(my_db)
|
||||
grid_in = 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:`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))
|
||||
|
||||
Renames the stored file with the specified file_id.
|
||||
|
||||
For example::
|
||||
|
||||
|
||||
async def rename():
|
||||
my_db = MotorClient().test
|
||||
fs = MotorGridFSBucket(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.
|
||||
|
||||
Returns a Future.
|
||||
|
||||
.. 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 = MotorClient().test
|
||||
fs = MotorGridFSBucket(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:`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.
|
||||
|
||||
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))
|
||||
|
||||
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 = MotorClient().test
|
||||
fs = MotorGridFSBucket(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:`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.
|
||||
|
||||
Returns a Future.
|
||||
|
||||
.. autoclass:: MotorGridIn
|
||||
:members:
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
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
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
: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,12 +1,6 @@
|
||||
: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
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
: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:
|
||||
:members:
|
||||
@ -1,12 +1,6 @@
|
||||
: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
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
: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
|
||||
|
||||
@ -23,3 +18,114 @@
|
||||
|
||||
The :class:`MotorDatabase` that this
|
||||
:class:`MotorCollection` 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
|
||||
|
||||
Returns a Future.
|
||||
|
||||
.. 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)
|
||||
|
||||
Returns a Future.
|
||||
|
||||
.. _map reduce command: http://docs.mongodb.org/manual/reference/command/mapReduce/
|
||||
|
||||
.. mongodoc:: mapreduce
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
: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,12 +1,6 @@
|
||||
: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,296 +3,6 @@ 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
|
||||
-----------
|
||||
|
||||
@ -316,7 +26,7 @@ later.
|
||||
New features:
|
||||
|
||||
- Added support for MongoDB 5.0.
|
||||
- Support for MongoDB Stable API, see :class:`~pymongo.server_api.ServerApi`.
|
||||
- Support for MongoDB Versioned 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
|
||||
@ -479,7 +189,7 @@ Deprecations:
|
||||
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/>`_
|
||||
`fsync command <https://docs.mongodb.com/manual/reference/command/fsync/>`_
|
||||
directly with :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command`
|
||||
instead.
|
||||
|
||||
@ -579,10 +289,10 @@ 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/
|
||||
.. _createUser: https://docs.mongodb.com/manual/reference/command/createUser/
|
||||
.. _usersInfo: https://docs.mongodb.com/manual/reference/command/usersInfo/
|
||||
.. _updateUser: https://docs.mongodb.com/manual/reference/command/updateUser/
|
||||
.. _dropUser: https://docs.mongodb.com/manual/reference/command/createUser/
|
||||
|
||||
The deprecated GridFS classes ``MotorGridFS`` and ``AsyncIOMotorGridFS`` are
|
||||
deleted in favor of :class:`~motor.motor_tornado.MotorGridFSBucket` and
|
||||
@ -709,9 +419,9 @@ Highlights include:
|
||||
|
||||
- Complete support for MongoDB 3.4:
|
||||
|
||||
- Unicode aware string comparison using collations.
|
||||
- Unicode aware string comparison using collations. See :ref:`PyMongo's examples for collation <collation-on-operation>`.
|
||||
- :class:`MotorCursor` and :class:`MotorGridOutCursor` have a new attribute :meth:`~MotorCursor.collation`.
|
||||
- Support for the new :class:`~pymongo.decimal128.Decimal128` BSON type.
|
||||
- Support for the new :class:`~bson.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.
|
||||
@ -740,9 +450,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:`~pymongo.json_util.JSONOptions`.
|
||||
- Allow :class:`~pymongo.code.Code` objects to have a scope of ``None``,
|
||||
- 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``,
|
||||
signifying no scope. Also allow encoding Code objects with an empty scope
|
||||
(i.e. ``{}``).
|
||||
|
||||
@ -765,7 +475,7 @@ Motor 1.0
|
||||
Motor now depends on PyMongo 3.3 and later. The move from PyMongo 2 to 3 brings
|
||||
a large number of API changes, read the `the PyMongo 3 changelog`_ carefully.
|
||||
|
||||
.. _the PyMongo 3 changelog: https://pymongo.readthedocs.io/en/4.0/changelog.html#changes-in-version-3-0
|
||||
.. _the PyMongo 3 changelog: http://api.mongodb.com/python/current/changelog.html#changes-in-version-3-0
|
||||
|
||||
:class:`MotorReplicaSetClient` is removed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -812,8 +522,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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -862,9 +572,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:`~pymongo.codec_options.CodecOptions` instead)
|
||||
:class:`~bson.codec_options.CodecOptions` instead)
|
||||
- compile_re (BSON regular expressions are always decoded to
|
||||
:class:`~pymongo.regex.Regex`)
|
||||
:class:`~bson.regex.Regex`)
|
||||
|
||||
The following find/find_one options are deprecated:
|
||||
|
||||
@ -1089,20 +799,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.
|
||||
|
||||
@ -1128,7 +838,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.
|
||||
@ -1156,18 +866,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():
|
||||
@ -1179,7 +889,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.
|
||||
@ -1217,7 +927,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`
|
||||
@ -1228,7 +938,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`
|
||||
@ -1236,12 +946,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`
|
||||
@ -1265,13 +975,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
|
||||
<https://mongodb.com/docs/v3.0/reference/method/cursor.limit/>`_.
|
||||
<http://docs.mongodb.org/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 message like "hostname 'X'
|
||||
to raise a :exc:`~pymongo.errors.ConnectionFailure` with a useful messsage like "hostname 'X'
|
||||
doesn't match 'Y'". The message is now empty and Tornado logs a warning
|
||||
instead.
|
||||
|
||||
@ -1301,7 +1011,8 @@ 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.
|
||||
and `features
|
||||
<http://api.mongodb.org/python/current/changelog.html#changes-in-version-2-8>`_.
|
||||
|
||||
Fixes `a connection-pool timeout when waitQueueMultipleMS is set
|
||||
<https://jira.mongodb.org/browse/MOTOR-62>`_ and `two bugs in replica set
|
||||
@ -1312,9 +1023,13 @@ 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
|
||||
-----------
|
||||
|
||||
@ -1400,7 +1115,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.motor_tornado.MotorClient(host, port)
|
||||
client = motor.MotorClient(host, port)
|
||||
try:
|
||||
loop.run_sync(client.open)
|
||||
except pymongo.errors.ConnectionFailure:
|
||||
@ -1586,7 +1301,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: https://mongodb.com/docs/manual/reference/method/db.fsyncLock/
|
||||
.. _fsyncLock: http://docs.mongodb.org/manual/reference/method/db.fsyncLock/
|
||||
|
||||
New Features
|
||||
~~~~~~~~~~~~
|
||||
@ -1605,7 +1320,7 @@ PyMongo 2.6 and 2.7:
|
||||
with :meth:`~MotorCursor.max_time_ms`.
|
||||
* A new :meth:`MotorGridFS.find` method for querying GridFS.
|
||||
|
||||
.. _authentication examples: https://pymongo.readthedocs.io/en/stable/examples/authentication.html
|
||||
.. _authentication examples: http://api.mongodb.org/python/current/examples/authentication.html
|
||||
|
||||
Bugfixes
|
||||
~~~~~~~~
|
||||
|
||||
138
doc/conf.py
138
doc/conf.py
@ -1,44 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Motor documentation build configuration file
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
import os
|
||||
import sys
|
||||
from importlib.metadata import metadata
|
||||
|
||||
sys.path[0:0] = [os.path.abspath("..")]
|
||||
import sys, os
|
||||
sys.path[0:0] = [os.path.abspath('..')]
|
||||
|
||||
import motor # noqa: E402
|
||||
from pymongo import version as pymongo_version
|
||||
import motor
|
||||
|
||||
# -- 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 root toctree document.
|
||||
root_doc = "index"
|
||||
root_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = "Motor"
|
||||
copyright = "2016-present MongoDB, Inc."
|
||||
project = u'Motor'
|
||||
copyright = u'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
|
||||
@ -54,42 +47,33 @@ 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 = []
|
||||
|
||||
# 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
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# -- 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
|
||||
@ -130,106 +114,104 @@ from motor import MotorClient
|
||||
|
||||
html_copy_source = False
|
||||
|
||||
try:
|
||||
import furo # noqa: F401
|
||||
# Theme gratefully vendored from CPython source.
|
||||
html_theme = "pydoctheme"
|
||||
html_theme_path = ["."]
|
||||
html_theme_options = {'collapsiblesidebar': True}
|
||||
html_static_path = ['static']
|
||||
|
||||
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"],
|
||||
}
|
||||
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", "Motor Documentation", "A. Jesse Jiryu Davis", "manual"),
|
||||
('index', 'Motor.tex', u'Motor Documentation',
|
||||
u'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_options = {
|
||||
"inherited-members": True,
|
||||
"member-order": "groupwise",
|
||||
'inherited-members': True,
|
||||
'member-order': 'groupwise',
|
||||
}
|
||||
|
||||
pymongo_version = metadata("pymongo")["version"]
|
||||
pymongo_inventory = ("https://pymongo.readthedocs.io/en/%s/" % pymongo_version, None)
|
||||
pymongo_inventory = ('https://pymongo.readthedocs.io/en/%s/' % pymongo_version,
|
||||
None)
|
||||
|
||||
intersphinx_mapping = {
|
||||
"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),
|
||||
'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),
|
||||
}
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
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
|
||||
''''''''''''''''''''
|
||||
|
||||
@ -35,14 +28,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 in this `cheat sheet`_.
|
||||
You can read more about TLS versions and their security implications here:
|
||||
|
||||
`<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
|
||||
''''''''''''''''
|
||||
@ -53,17 +46,3 @@ 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]"
|
||||
|
||||
@ -15,7 +15,3 @@ The following is a list of people who have contributed to
|
||||
- Bulat Khasanov
|
||||
- William Zhou
|
||||
- Tushar Singh
|
||||
- Steven Silvester
|
||||
- Julius Park
|
||||
- Doeke Buursma
|
||||
- Scott Luu
|
||||
|
||||
@ -1,28 +1,29 @@
|
||||
"""Gratefully adapted from aiohttp, provides coroutine support to autodoc."""
|
||||
from sphinx import addnodes
|
||||
|
||||
from sphinx.domains.python import PyFunction, PyMethod
|
||||
from sphinx import addnodes
|
||||
|
||||
|
||||
class PyCoroutineMixin:
|
||||
class PyCoroutineMixin(object):
|
||||
def handle_signature(self, sig, signode):
|
||||
ret = super().handle_signature(sig, signode)
|
||||
signode.insert(0, addnodes.desc_annotation("coroutine ", "coroutine "))
|
||||
signode.insert(0, addnodes.desc_annotation('coroutine ', 'coroutine '))
|
||||
return ret
|
||||
|
||||
|
||||
class PyCoroutineFunction(PyCoroutineMixin, PyFunction):
|
||||
def run(self):
|
||||
self.name = "py:function"
|
||||
self.name = 'py:function'
|
||||
return PyFunction.run(self)
|
||||
|
||||
|
||||
class PyCoroutineMethod(PyCoroutineMixin, PyMethod):
|
||||
def run(self):
|
||||
self.name = "py:method"
|
||||
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,13 +2,6 @@
|
||||
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
|
||||
@ -52,7 +45,7 @@ See the ``frameworks/tornado`` and ``frameworks/asyncio`` modules.
|
||||
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.
|
||||
will be removed in Motor 3.0.
|
||||
|
||||
A framework-specific class, like ``MotorClient`` for Tornado or
|
||||
``AsyncIOMotorClient`` for asyncio, is created by the
|
||||
@ -112,5 +105,5 @@ synchronous and Motor is async; how can Motor pass PyMongo's tests?
|
||||
Synchro is a hacky little module that re-synchronizes all Motor methods using
|
||||
the Tornado IOLoop's ``run_sync`` method. ``synchrotest.py`` overrides the Python
|
||||
interpreter's import machinery to allow Synchro to masquerade as PyMongo, and
|
||||
runs PyMongo's test suite against it. Use ``tox -e synchro`` to check out
|
||||
runs PyMongo's test suite against it. Use ``tox -e synchro37`` to check out
|
||||
PyMongo's test suite and run it with Synchro.
|
||||
|
||||
@ -4,12 +4,6 @@
|
||||
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.
|
||||
|
||||
@ -49,8 +43,8 @@ GridFS
|
||||
|
||||
- File-like
|
||||
|
||||
PyMongo's :class:`~pymongo.grid_file.GridIn` and
|
||||
:class:`~pymongo.grid_file.GridOut` strive to act like Python's built-in
|
||||
PyMongo's :class:`~gridfs.grid_file.GridIn` and
|
||||
:class:`~gridfs.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
|
||||
@ -59,7 +53,7 @@ GridFS
|
||||
- Setting properties
|
||||
|
||||
In PyMongo, you can set arbitrary attributes on
|
||||
a :class:`~pymongo.grid_file.GridIn` and they're stored as metadata on
|
||||
a :class:`~gridfs.grid_file.GridIn` and they're stored as metadata on
|
||||
the server, even after the ``GridIn`` is closed::
|
||||
|
||||
fs = gridfs.GridFSBucket(db)
|
||||
@ -96,9 +90,9 @@ system_js
|
||||
|
||||
PyMongo supports Javascript procedures stored in MongoDB with syntax like:
|
||||
|
||||
.. code-block:: pycon
|
||||
.. code-block:: python
|
||||
|
||||
>>> 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
|
||||
|
||||
@ -122,7 +116,7 @@ In Motor, however, no exception is raised. The query simply has no results:
|
||||
async def f():
|
||||
cursor = db.collection.find()[100]
|
||||
|
||||
# Iterates zero or one time.
|
||||
# Iterates zero or one times.
|
||||
async for doc in cursor:
|
||||
print(doc)
|
||||
|
||||
@ -139,10 +133,17 @@ 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:
|
||||
@ -150,4 +151,7 @@ only the typical style is allowed:
|
||||
.. code-block:: python
|
||||
|
||||
async def f():
|
||||
await db.create_collection("collection1", capped=True, size=1000)
|
||||
await db.create_collection(
|
||||
'collection1',
|
||||
capped=True,
|
||||
size=1000)
|
||||
|
||||
5
doc/docs-requirements.txt
Normal file
5
doc/docs-requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
tornado
|
||||
aiohttp
|
||||
Sphinx~=4.1
|
||||
sphinx_rtd_theme~=0.5
|
||||
readthedocs-sphinx-search~=0.1
|
||||
@ -1,59 +1,48 @@
|
||||
# These comments let tutorial-asyncio.rst include this code in sections.
|
||||
# -- setup-start --
|
||||
from aiohttp import web
|
||||
import asyncio
|
||||
|
||||
from aiohttp import web
|
||||
from motor.motor_asyncio import AsyncIOMotorClient
|
||||
|
||||
|
||||
async def setup_db():
|
||||
db = AsyncIOMotorClient().test
|
||||
await db.pages.drop()
|
||||
html = "<html><body>{}</body></html>"
|
||||
await db.pages.insert_one({"_id": "page-one", "body": html.format("Hello!")})
|
||||
html = '<html><body>{}</body></html>'
|
||||
await db.pages.insert_one({'_id': 'page-one',
|
||||
'body': html.format('Hello!')})
|
||||
|
||||
await 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 --
|
||||
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 = await db.pages.find_one(page_name)
|
||||
|
||||
if not document:
|
||||
return web.HTTPNotFound(text=f"No page named {page_name!r}")
|
||||
|
||||
return web.Response(body=document["body"].encode(), content_type="text/html")
|
||||
|
||||
return web.HTTPNotFound(text='No page named {!r}'.format(page_name))
|
||||
|
||||
return web.Response(body=document['body'].encode(),
|
||||
content_type='text/html')
|
||||
# -- handler-end --
|
||||
|
||||
|
||||
# -- main-start --
|
||||
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()
|
||||
db = asyncio.run(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)
|
||||
# -- main-end --
|
||||
|
||||
@ -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 = AsyncIOMotorGridFSBucket(client.my_database)
|
||||
tmp.seek(0)
|
||||
await gfs.upload_from_stream(
|
||||
filename="my_file", source=tmp, metadata={"contentType": "text", "compressed": True}
|
||||
)
|
||||
|
||||
await gfs.upload_from_stream(filename='my_file',
|
||||
source=tmp,
|
||||
metadata={'contentType': 'text',
|
||||
'compressed': True})
|
||||
|
||||
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,13 +1,6 @@
|
||||
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,13 +3,6 @@
|
||||
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.
|
||||
|
||||
@ -18,7 +11,7 @@ replica sets or sharded clusters, ``--keyFile``. Create an admin user and
|
||||
optionally normal users or read-only users.
|
||||
|
||||
.. seealso:: `MongoDB Authentication
|
||||
<https://mongodb.com/docs/manual/tutorial/control-access-to-mongodb-with-authentication/>`_
|
||||
<http://docs.mongodb.org/manual/tutorial/control-access-to-mongodb-with-authentication/>`_
|
||||
|
||||
To create an authenticated connection use a `MongoDB connection URI`_::
|
||||
|
||||
@ -27,4 +20,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.
|
||||
|
||||
.. _MongoDB connection URI: https://mongodb.com/docs/manual/reference/connection-string/
|
||||
.. _MongoDB connection URI: http://docs.mongodb.org/manual/reference/connection-string/
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import asyncio
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
from bson import json_util
|
||||
from bson.codec_options import CodecOptions
|
||||
from bson import json_util
|
||||
|
||||
from motor.motor_asyncio import (AsyncIOMotorClient,
|
||||
AsyncIOMotorClientEncryption)
|
||||
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):
|
||||
async def create_json_schema_file(kms_providers, key_vault_namespace,
|
||||
key_vault_client):
|
||||
client_encryption = AsyncIOMotorClientEncryption(
|
||||
kms_providers,
|
||||
key_vault_namespace,
|
||||
@ -19,31 +21,31 @@ async def create_json_schema_file(kms_providers, key_vault_namespace, key_vault_
|
||||
# on MotorClient, Database, or Collection. We will not be calling
|
||||
# encrypt() or decrypt() in this example so we can use any
|
||||
# CodecOptions.
|
||||
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"]
|
||||
)
|
||||
'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,
|
||||
"algorithm":
|
||||
Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic
|
||||
}
|
||||
}
|
||||
},
|
||||
"bsonType": "object",
|
||||
"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)
|
||||
json_schema_string = json_util.dumps(
|
||||
schema, json_options=json_util.CANONICAL_JSON_OPTIONS)
|
||||
|
||||
with open("jsonSchema.json", "w") as file:
|
||||
with open('jsonSchema.json', 'w') as file:
|
||||
file.write(json_schema_string)
|
||||
|
||||
|
||||
@ -68,20 +70,21 @@ async def main():
|
||||
# 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}}
|
||||
)
|
||||
"keyAltNames",
|
||||
unique=True,
|
||||
partialFilterExpression={"keyAltNames": {"$exists": True}})
|
||||
|
||||
await create_json_schema_file(kms_providers, key_vault_namespace, key_vault_client)
|
||||
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:
|
||||
with open('jsonSchema.json', 'r') 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
|
||||
)
|
||||
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)
|
||||
@ -91,10 +94,10 @@ async def main():
|
||||
|
||||
await coll.insert_one({"encryptedField": "123456789"})
|
||||
decrypted_doc = await coll.find_one()
|
||||
print(f"Decrypted document: {decrypted_doc}")
|
||||
print('Decrypted document: %s' % (decrypted_doc,))
|
||||
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name]
|
||||
encrypted_doc = await unencrypted_coll.find_one()
|
||||
print(f"Encrypted document: {encrypted_doc}")
|
||||
print('Encrypted document: %s' % (encrypted_doc,))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -5,13 +5,6 @@
|
||||
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()
|
||||
@ -38,10 +31,9 @@ bulk insert operations.
|
||||
.. doctest::
|
||||
|
||||
>>> async def f():
|
||||
... await db.test.insert_many(({"i": i} for i in range(10000)))
|
||||
... await db.test.insert_many(({'i': i} for i in range(10000)))
|
||||
... count = await db.test.count_documents({})
|
||||
... print("Final count: %d" % count)
|
||||
...
|
||||
>>>
|
||||
>>> IOLoop.current().run_sync(f)
|
||||
Final count: 10000
|
||||
@ -69,17 +61,14 @@ of operations performed.
|
||||
>>> from pprint import pprint
|
||||
>>> from pymongo import InsertOne, DeleteMany, ReplaceOne, UpdateOne
|
||||
>>> async def f():
|
||||
... result = await db.test.bulk_write(
|
||||
... [
|
||||
... DeleteMany({}), # Remove all documents from the previous example.
|
||||
... InsertOne({"_id": 1}),
|
||||
... InsertOne({"_id": 2}),
|
||||
... InsertOne({"_id": 3}),
|
||||
... UpdateOne({"_id": 1}, {"$set": {"foo": "bar"}}),
|
||||
... UpdateOne({"_id": 4}, {"$inc": {"j": 1}}, upsert=True),
|
||||
... ReplaceOne({"j": 1}, {"j": 2}),
|
||||
... ]
|
||||
... )
|
||||
... result = await db.test.bulk_write([
|
||||
... DeleteMany({}), # Remove all documents from the previous example.
|
||||
... InsertOne({'_id': 1}),
|
||||
... InsertOne({'_id': 2}),
|
||||
... InsertOne({'_id': 3}),
|
||||
... UpdateOne({'_id': 1}, {'$set': {'foo': 'bar'}}),
|
||||
... UpdateOne({'_id': 4}, {'$inc': {'j': 1}}, upsert=True),
|
||||
... ReplaceOne({'j': 1}, {'j': 2})])
|
||||
... pprint(result.bulk_api_result)
|
||||
...
|
||||
>>> IOLoop.current().run_sync(f)
|
||||
@ -106,10 +95,9 @@ the failure.
|
||||
>>> from pymongo.errors import BulkWriteError
|
||||
>>> async def f():
|
||||
... requests = [
|
||||
... ReplaceOne({"j": 2}, {"i": 5}),
|
||||
... InsertOne({"_id": 4}), # Violates the unique key constraint on _id.
|
||||
... DeleteOne({"i": 5}),
|
||||
... ]
|
||||
... ReplaceOne({'j': 2}, {'i': 5}),
|
||||
... InsertOne({'_id': 4}), # Violates the unique key constraint on _id.
|
||||
... DeleteOne({'i': 5})]
|
||||
... try:
|
||||
... await db.test.bulk_write(requests)
|
||||
... except BulkWriteError as bwe:
|
||||
@ -148,11 +136,10 @@ and fourth operations succeed.
|
||||
|
||||
>>> async def f():
|
||||
... requests = [
|
||||
... InsertOne({"_id": 1}),
|
||||
... DeleteOne({"_id": 2}),
|
||||
... InsertOne({"_id": 3}),
|
||||
... ReplaceOne({"_id": 4}, {"i": 1}),
|
||||
... ]
|
||||
... InsertOne({'_id': 1}),
|
||||
... DeleteOne({'_id': 2}),
|
||||
... InsertOne({'_id': 3}),
|
||||
... ReplaceOne({'_id': 4}, {'i': 1})]
|
||||
... try:
|
||||
... await db.test.bulk_write(requests, ordered=False)
|
||||
... except BulkWriteError as bwe:
|
||||
@ -194,9 +181,10 @@ after all operations are attempted, regardless of execution order.
|
||||
|
||||
>>> from pymongo import WriteConcern
|
||||
>>> async def f():
|
||||
... coll = db.get_collection("test", write_concern=WriteConcern(w=4, wtimeout=1))
|
||||
... coll = db.get_collection(
|
||||
... 'test', write_concern=WriteConcern(w=4, wtimeout=1))
|
||||
... try:
|
||||
... await coll.bulk_write([InsertOne({"a": i}) for i in range(4)])
|
||||
... await coll.bulk_write([InsertOne({'a': i}) for i in range(4)])
|
||||
... except BulkWriteError as bwe:
|
||||
... pprint(bwe.details)
|
||||
...
|
||||
|
||||
@ -3,13 +3,6 @@
|
||||
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
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from motor.motor_asyncio import (AsyncIOMotorClient,
|
||||
AsyncIOMotorClientEncryption)
|
||||
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
|
||||
@ -22,8 +22,7 @@ async def main():
|
||||
# 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
|
||||
)
|
||||
kms_providers, key_vault_namespace, bypass_auto_encryption=True)
|
||||
|
||||
client = AsyncIOMotorClient(auto_encryption_opts=auto_encryption_opts)
|
||||
coll = client.test.coll
|
||||
@ -35,8 +34,9 @@ async def main():
|
||||
# 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}}
|
||||
)
|
||||
"keyAltNames",
|
||||
unique=True,
|
||||
partialFilterExpression={"keyAltNames": {"$exists": True}})
|
||||
|
||||
client_encryption = AsyncIOMotorClientEncryption(
|
||||
kms_providers,
|
||||
@ -47,26 +47,23 @@ async def main():
|
||||
# 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,
|
||||
)
|
||||
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"]
|
||||
)
|
||||
data_key_id = 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",
|
||||
)
|
||||
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}")
|
||||
print('Decrypted document: %s' % (doc,))
|
||||
unencrypted_coll = AsyncIOMotorClient().test.coll
|
||||
print(f"Encrypted document: {await unencrypted_coll.find_one()}")
|
||||
print('Encrypted document: %s' % (await unencrypted_coll.find_one(),))
|
||||
|
||||
# Cleanup resources.
|
||||
await client_encryption.close()
|
||||
|
||||
@ -1,67 +1,67 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from motor.motor_asyncio import (AsyncIOMotorClient,
|
||||
AsyncIOMotorClientEncryption)
|
||||
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}}
|
||||
# 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 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()
|
||||
# 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}}
|
||||
)
|
||||
# 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,
|
||||
)
|
||||
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"]
|
||||
)
|
||||
# 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 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('Encrypted document: %s' % (doc,))
|
||||
|
||||
# Explicitly decrypt the field:
|
||||
doc["encryptedField"] = await client_encryption.decrypt(doc["encryptedField"])
|
||||
print(f"Decrypted document: {doc}")
|
||||
# Explicitly decrypt the field:
|
||||
doc["encryptedField"] = await client_encryption.decrypt(doc["encryptedField"])
|
||||
print('Decrypted document: %s' % (doc,))
|
||||
|
||||
# Cleanup resources.
|
||||
await client_encryption.close()
|
||||
# Cleanup resources.
|
||||
await client_encryption.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
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::
|
||||
@ -19,7 +12,5 @@ Motor Examples
|
||||
authentication
|
||||
aiohttp_gridfs_example
|
||||
encryption
|
||||
timeouts
|
||||
type_hints
|
||||
|
||||
See also :ref:`example-web-application-aiohttp`.
|
||||
|
||||
@ -3,13 +3,6 @@
|
||||
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.
|
||||
|
||||
@ -91,6 +84,6 @@ See also:
|
||||
* The `Topology Monitoring`_ Spec
|
||||
* The `monitoring_example.py`_ example file in the Motor repository
|
||||
|
||||
.. _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
|
||||
.. _Command Monitoring: https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst
|
||||
.. _Topology Monitoring: https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring-monitoring.rst
|
||||
.. _monitoring_example.py: https://github.com/mongodb/motor/blob/master/doc/examples/monitoring_example.py
|
||||
|
||||
@ -26,29 +26,21 @@ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
|
||||
|
||||
class CommandLogger(monitoring.CommandListener):
|
||||
def started(self, event):
|
||||
logging.info(
|
||||
f"Command {event.command_name} with request id "
|
||||
f"{event.request_id} started on server "
|
||||
f"{event.connection_id}"
|
||||
)
|
||||
logging.info("Command {0.command_name} with request id "
|
||||
"{0.request_id} started on server "
|
||||
"{0.connection_id}".format(event))
|
||||
|
||||
def succeeded(self, 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"
|
||||
)
|
||||
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))
|
||||
|
||||
def failed(self, 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"
|
||||
)
|
||||
|
||||
|
||||
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))
|
||||
# command logger end
|
||||
|
||||
# command logger register start
|
||||
@ -57,14 +49,13 @@ 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_one({"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)
|
||||
@ -73,70 +64,68 @@ 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(f"Server {event.server_address} added to topology {event.topology_id}")
|
||||
logging.info("Server {0.server_address} added to topology "
|
||||
"{0.topology_id}".format(event))
|
||||
|
||||
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(
|
||||
f"Server {event.server_address} changed type from "
|
||||
f"{event.previous_description.server_type_name} to "
|
||||
f"{event.new_description.server_type_name}"
|
||||
)
|
||||
"Server {0.server_address} changed type from "
|
||||
"{0.previous_description.server_type_name} to "
|
||||
"{0.new_description.server_type_name}".format(event))
|
||||
|
||||
def closed(self, event):
|
||||
logging.warning(f"Server {event.server_address} removed from topology {event.topology_id}")
|
||||
logging.warning("Server {0.server_address} removed from topology "
|
||||
"{0.topology_id}".format(event))
|
||||
|
||||
|
||||
monitoring.register(ServerLogger())
|
||||
# server logger end
|
||||
|
||||
|
||||
# topology logger start
|
||||
class TopologyLogger(monitoring.TopologyListener):
|
||||
def opened(self, event):
|
||||
logging.info(f"Topology with id {event.topology_id} opened")
|
||||
logging.info("Topology with id {0.topology_id} "
|
||||
"opened".format(event))
|
||||
|
||||
def description_changed(self, event):
|
||||
logging.info(f"Topology description updated for topology id {event.topology_id}")
|
||||
logging.info("Topology description updated for "
|
||||
"topology id {0.topology_id}".format(event))
|
||||
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(
|
||||
f"Topology {event.topology_id} changed type from "
|
||||
f"{event.previous_description.topology_type_name} to "
|
||||
f"{event.new_description.topology_type_name}"
|
||||
)
|
||||
"Topology {0.topology_id} changed type from "
|
||||
"{0.previous_description.topology_type_name} to "
|
||||
"{0.new_description.topology_type_name}".format(event))
|
||||
|
||||
def closed(self, event):
|
||||
logging.info(f"Topology with id {event.topology_id} closed")
|
||||
logging.info("Topology with id {0.topology_id} "
|
||||
"closed".format(event))
|
||||
|
||||
|
||||
monitoring.register(TopologyLogger())
|
||||
# topology logger end
|
||||
|
||||
|
||||
# heartbeat logger start
|
||||
class HeartbeatLogger(monitoring.ServerHeartbeatListener):
|
||||
def started(self, event):
|
||||
logging.info(f"Heartbeat sent to server {event.connection_id}")
|
||||
logging.info("Heartbeat sent to server "
|
||||
"{0.connection_id}".format(event))
|
||||
|
||||
def succeeded(self, event):
|
||||
logging.info(
|
||||
f"Heartbeat to server {event.connection_id} "
|
||||
"succeeded with reply "
|
||||
f"{event.reply.document}"
|
||||
)
|
||||
logging.info("Heartbeat to server {0.connection_id} "
|
||||
"succeeded with reply "
|
||||
"{0.reply.document}".format(event))
|
||||
|
||||
def failed(self, event):
|
||||
logging.warning(
|
||||
f"Heartbeat to server {event.connection_id} failed with error {event.reply}"
|
||||
)
|
||||
logging.warning("Heartbeat to server {0.connection_id} "
|
||||
"failed with error {0.reply}".format(event))
|
||||
|
||||
|
||||
monitoring.register(HeartbeatLogger())
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import asyncio
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
from bson.binary import STANDARD
|
||||
from bson.codec_options import CodecOptions
|
||||
from bson.binary import STANDARD
|
||||
|
||||
from motor.motor_asyncio import (AsyncIOMotorClient,
|
||||
AsyncIOMotorClientEncryption)
|
||||
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
|
||||
@ -32,8 +33,9 @@ async def main():
|
||||
# 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}}
|
||||
)
|
||||
"keyAltNames",
|
||||
unique=True,
|
||||
partialFilterExpression={"keyAltNames": {"$exists": True}})
|
||||
|
||||
client_encryption = AsyncIOMotorClientEncryption(
|
||||
kms_providers,
|
||||
@ -44,27 +46,27 @@ async def main():
|
||||
# on MotorClient, Database, or Collection. We will not be calling
|
||||
# encrypt() or decrypt() in this example so we can use any
|
||||
# CodecOptions.
|
||||
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"]
|
||||
)
|
||||
'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,
|
||||
"algorithm":
|
||||
Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic
|
||||
}
|
||||
}
|
||||
},
|
||||
"bsonType": "object",
|
||||
"bsonType": "object"
|
||||
}
|
||||
|
||||
auto_encryption_opts = AutoEncryptionOpts(kms_providers, key_vault_namespace)
|
||||
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]
|
||||
@ -80,20 +82,19 @@ async def main():
|
||||
# JSON Schema.
|
||||
codec_options=CodecOptions(uuid_representation=STANDARD),
|
||||
write_concern=WriteConcern(w="majority"),
|
||||
validator={"$jsonSchema": json_schema},
|
||||
)
|
||||
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}")
|
||||
print('Decrypted document: %s' % (decrypted_doc,))
|
||||
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name]
|
||||
encrypted_doc = await unencrypted_coll.find_one()
|
||||
print(f"Encrypted document: {encrypted_doc}")
|
||||
print('Encrypted document: %s' % (encrypted_doc,))
|
||||
try:
|
||||
await unencrypted_coll.insert_one({"encryptedField": "123456789"})
|
||||
except OperationFailure as exc:
|
||||
print(f"Unencrypted insert failed: {exc.details}")
|
||||
print('Unencrypted insert failed: %s' % (exc.details,))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -3,13 +3,6 @@
|
||||
Motor Tailable Cursor Example
|
||||
=============================
|
||||
|
||||
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
|
||||
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
|
||||
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
|
||||
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
|
||||
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
|
||||
|
||||
|
||||
By default, MongoDB will automatically close a cursor when the client has
|
||||
exhausted all results in the cursor. However, for capped collections you may
|
||||
use a tailable cursor that remains open after the client exhausts the results
|
||||
@ -23,12 +16,11 @@ of a replica set member:
|
||||
from asyncio import sleep
|
||||
from pymongo.cursor import CursorType
|
||||
|
||||
|
||||
async def tail_oplog_example():
|
||||
oplog = client.local.oplog.rs
|
||||
first = await oplog.find().sort("$natural", pymongo.ASCENDING).limit(-1).next()
|
||||
first = await oplog.find().sort('$natural', pymongo.ASCENDING).limit(-1).next()
|
||||
print(first)
|
||||
ts = first["ts"]
|
||||
ts = first['ts']
|
||||
|
||||
while True:
|
||||
# For a regular capped collection CursorType.TAILABLE_AWAIT is the
|
||||
@ -38,14 +30,12 @@ of a replica set member:
|
||||
# can only be used when querying the oplog. Starting in MongoDB 4.4
|
||||
# this option is ignored by the server as queries against the oplog
|
||||
# are optimized automatically by the MongoDB query engine.
|
||||
cursor = oplog.find(
|
||||
{"ts": {"$gt": ts}},
|
||||
cursor_type=CursorType.TAILABLE_AWAIT,
|
||||
oplog_replay=True,
|
||||
)
|
||||
cursor = oplog.find({'ts': {'$gt': ts}},
|
||||
cursor_type=CursorType.TAILABLE_AWAIT,
|
||||
oplog_replay=True)
|
||||
while cursor.alive:
|
||||
async for doc in cursor:
|
||||
ts = doc["ts"]
|
||||
ts = doc['ts']
|
||||
print(doc)
|
||||
# We end up here if the find() returned no documents or if the
|
||||
# tailable cursor timed out (no new documents were added to the
|
||||
|
||||
@ -1,87 +0,0 @@
|
||||
|
||||
.. _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,13 +22,17 @@ 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().__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):
|
||||
@ -51,7 +55,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):
|
||||
@ -59,23 +63,22 @@ class ChangesHandler(tornado.websocket.WebSocketHandler):
|
||||
for waiter in cls.waiters:
|
||||
try:
|
||||
waiter.write_message(change_json)
|
||||
except Exception as exc:
|
||||
logging.exception(exc)
|
||||
except Exception:
|
||||
logging.error("Error sending message", exc_info=True)
|
||||
|
||||
@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.
|
||||
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
|
||||
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
|
||||
ChangesHandler.send_change(change)
|
||||
ChangesHandler.update_cache(change)
|
||||
|
||||
@ -93,24 +96,26 @@ async def watch(collection):
|
||||
|
||||
def main():
|
||||
tornado.options.parse_command_line()
|
||||
if "." not in options.ns:
|
||||
sys.stderr.write(f'Invalid ns "{options.ns}", must contain a "."')
|
||||
if '.' not in options.ns:
|
||||
sys.stderr.write('Invalid ns "%s", must contain a "."' % (options.ns,))
|
||||
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.run_sync(lambda: watch(collection))
|
||||
loop.start()
|
||||
except KeyboardInterrupt:
|
||||
if change_stream:
|
||||
loop.run_sync(change_stream.close)
|
||||
pass
|
||||
finally:
|
||||
if change_stream is not None:
|
||||
change_stream.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -3,17 +3,10 @@
|
||||
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
|
||||
------------
|
||||
@ -25,7 +18,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:: text
|
||||
.. code-block:: none
|
||||
|
||||
> use test
|
||||
switched to db test
|
||||
@ -36,7 +29,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:: text
|
||||
.. code-block:: none
|
||||
|
||||
Changes
|
||||
|
||||
|
||||
@ -1,393 +0,0 @@
|
||||
|
||||
.. _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,13 +4,6 @@ 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
|
||||
@ -27,8 +20,8 @@ Featureful
|
||||
Motor wraps almost all of PyMongo's API and makes it non-blocking. For the few
|
||||
PyMongo features not implemented in Motor, see :doc:`differences`.
|
||||
|
||||
Convenient With ``tornado.gen``
|
||||
===============================
|
||||
Convenient With `tornado.gen`
|
||||
=============================
|
||||
The :mod:`tornado.gen` module lets you use coroutines to simplify asynchronous
|
||||
code. Motor methods return Futures that are convenient to use with coroutines.
|
||||
|
||||
|
||||
@ -4,12 +4,6 @@ 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
|
||||
-----
|
||||
|
||||
@ -60,7 +54,7 @@ project.
|
||||
|
||||
Feature Requests / Feedback
|
||||
---------------------------
|
||||
Use our `feedback engine <https://feedback.mongodb.com/?category=7548141816650747033>`_
|
||||
Use our `feedback engine <https://feedback.mongodb.com/forums/924286-drivers>`_
|
||||
to send us feature requests and general feedback about PyMongo.
|
||||
|
||||
Contributing
|
||||
@ -91,7 +85,6 @@ Contents
|
||||
examples/index
|
||||
changelog
|
||||
migrate-to-motor-2
|
||||
migrate-to-motor-3
|
||||
developer-guide
|
||||
contributors
|
||||
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
|
||||
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
|
||||
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
|
||||
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
|
||||
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
|
||||
|
||||
|
||||
Install Motor from PyPI_ with pip_::
|
||||
|
||||
$ python3 -m pip install motor
|
||||
@ -26,8 +19,8 @@ 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+
|
||||
* PyMongo_ >=3.12,<4
|
||||
* Python 3.5+
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
@ -41,7 +34,7 @@ dependency can be installed automatically along with Motor::
|
||||
|
||||
similarly,
|
||||
|
||||
`MONGODB-AWS <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/security/authentication/aws-iam/#std-label-pymongo-mongodb-aws>`_
|
||||
`MONGODB-AWS <https://pymongo.readthedocs.io/en/stable/examples/authentication.html#mongodb-aws>`_
|
||||
authentication requires ``aws`` extra dependency::
|
||||
|
||||
$ pip install "motor[aws]"
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
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
|
||||
@ -125,7 +118,6 @@ used callbacks:
|
||||
else:
|
||||
print(result)
|
||||
|
||||
|
||||
collection.find_one({}, callback=callback)
|
||||
|
||||
Callbacks have been largely superseded by a Futures API intended for use with
|
||||
@ -142,7 +134,6 @@ a parameter:
|
||||
except Exception as exc:
|
||||
print(exc)
|
||||
|
||||
|
||||
future = collection.find_one({})
|
||||
future.add_done_callback(callback)
|
||||
|
||||
@ -190,7 +181,7 @@ Or:
|
||||
.. code-block:: python3
|
||||
|
||||
with client.start_session() as session:
|
||||
doc = client.db.collection.find_one({}, session=session)
|
||||
doc = client.db.collection.find_one({}, session=session)
|
||||
|
||||
To support multi-document transactions, in Motor 2.0
|
||||
:meth:`MotorClient.start_session` is a coroutine, not a regular method. It must
|
||||
@ -212,4 +203,4 @@ Or:
|
||||
.. code-block:: python3
|
||||
|
||||
async with client.start_session() as session:
|
||||
doc = await client.db.collection.find_one({}, session=session)
|
||||
doc = await client.db.collection.find_one({}, session=session)
|
||||
|
||||
@ -1,469 +0,0 @@
|
||||
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.
|
||||
@ -36,17 +36,20 @@ 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(rst.Directive):
|
||||
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
@ -55,7 +58,7 @@ class MongodocDirective(rst.Directive):
|
||||
|
||||
def run(self):
|
||||
node = mongodoc()
|
||||
title = "The MongoDB documentation on"
|
||||
title = 'The MongoDB documentation on'
|
||||
node += nodes.title(title, title)
|
||||
self.state.nested_parse(self.content, self.content_offset, node)
|
||||
return [node]
|
||||
@ -83,13 +86,12 @@ 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)
|
||||
|
||||
@ -18,7 +18,11 @@ import re
|
||||
|
||||
from docutils.nodes import doctest_block, literal_block
|
||||
from sphinx import addnodes
|
||||
from sphinx.addnodes import desc, desc_content, desc_signature, seealso, versionmodified
|
||||
from sphinx.addnodes import (desc,
|
||||
desc_content,
|
||||
desc_signature,
|
||||
seealso,
|
||||
versionmodified)
|
||||
from sphinx.util.inspect import safe_getattr
|
||||
|
||||
import motor
|
||||
@ -32,7 +36,7 @@ def has_node_of_type(root, klass):
|
||||
if isinstance(root, klass):
|
||||
return True
|
||||
|
||||
for child in root.children: # noqa: SIM110
|
||||
for child in root.children:
|
||||
if has_node_of_type(child, klass):
|
||||
return True
|
||||
|
||||
@ -62,7 +66,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
|
||||
|
||||
@ -79,31 +83,34 @@ 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"]: # noqa: SIM102
|
||||
if obj_motor_info['is_async_method']:
|
||||
# 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 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)
|
||||
@ -125,16 +132,19 @@ def get_motor_attr(motor_class, name, *defargs):
|
||||
attr = safe_getattr(motor_class, name, *defargs)
|
||||
|
||||
# Store some info for process_motor_nodes()
|
||||
full_name = f"{motor_class.__module__}.{motor_class.__name__}.{name}"
|
||||
full_name = '%s.%s.%s' % (
|
||||
motor_class.__module__, motor_class.__name__, name)
|
||||
|
||||
full_name_legacy = f"motor.{motor_class.__module__}.{motor_class.__name__}.{name}"
|
||||
full_name_legacy = 'motor.%s.%s.%s' % (
|
||||
motor_class.__module__, motor_class.__name__, name)
|
||||
|
||||
# These sub-attributes are set in motor.asynchronize()
|
||||
has_coroutine_annotation = getattr(attr, "coroutine_annotation", False)
|
||||
is_async_method = getattr(attr, "is_async_method", False)
|
||||
is_cursor_method = getattr(attr, "is_motorcursor_chaining_method", False)
|
||||
has_coroutine_annotation = getattr(attr, 'coroutine_annotation', False)
|
||||
is_async_method = getattr(attr, 'is_async_method', False)
|
||||
is_cursor_method = getattr(attr, 'is_motorcursor_chaining_method', False)
|
||||
if is_async_method or is_cursor_method:
|
||||
pymongo_method = getattr(motor_class.__delegate_class__, attr.pymongo_method_name)
|
||||
pymongo_method = getattr(
|
||||
motor_class.__delegate_class__, attr.pymongo_method_name)
|
||||
else:
|
||||
pymongo_method = None
|
||||
|
||||
@ -142,27 +152,27 @@ def get_motor_attr(motor_class, name, *defargs):
|
||||
is_pymongo_doc = pymongo_method and attr.__doc__ == pymongo_method.__doc__
|
||||
|
||||
motor_info[full_name] = motor_info[full_name_legacy] = {
|
||||
"is_async_method": is_async_method or has_coroutine_annotation,
|
||||
"is_pymongo_docstring": is_pymongo_doc,
|
||||
"pymongo_method": pymongo_method,
|
||||
'is_async_method': is_async_method or has_coroutine_annotation,
|
||||
'is_pymongo_docstring': is_pymongo_doc,
|
||||
'pymongo_method': pymongo_method,
|
||||
}
|
||||
|
||||
return attr
|
||||
|
||||
|
||||
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")
|
||||
lines[:] = subbed.split('\n')
|
||||
|
||||
|
||||
def build_finished(app, exception):
|
||||
@ -174,7 +184,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("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,27 +1,20 @@
|
||||
Requirements
|
||||
============
|
||||
|
||||
.. warning:: As of May 14th, 2025, Motor is deprecated in favor of the GA release of the PyMongo Async API.
|
||||
No new features will be added to Motor, and only bug fixes will be provided until it reaches end of life on May 14th, 2026.
|
||||
After that, only critical bug fixes will be made until final support ends on May 14th, 2027.
|
||||
We strongly recommend migrating to the PyMongo Async API while Motor is still supported.
|
||||
For help transitioning, see the `Migrate to PyMongo Async guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.
|
||||
|
||||
|
||||
The current version of Motor requires:
|
||||
|
||||
* CPython 3.10 and later.
|
||||
* PyMongo_ 4.9 and later.
|
||||
* CPython 3.5 and later.
|
||||
* PyMongo_ 3.12 and later.
|
||||
|
||||
Motor can integrate with either Tornado or asyncio.
|
||||
|
||||
The default authentication mechanism for MongoDB is SCRAM-SHA-1.
|
||||
The default authentication mechanism for MongoDB 3.0+ is SCRAM-SHA-1.
|
||||
|
||||
Building the docs requires `sphinx`_.
|
||||
|
||||
.. _PyMongo: https://pypi.python.org/pypi/pymongo/
|
||||
|
||||
.. _sphinx: https://www.sphinx-doc.org/
|
||||
.. _sphinx: http://sphinx.pocoo.org/
|
||||
|
||||
.. _compatibility-matrix:
|
||||
|
||||
@ -34,51 +27,55 @@ Motor and PyMongo
|
||||
+-------------------+-----------------+
|
||||
| Motor Version | PyMongo Version |
|
||||
+===================+=================+
|
||||
| 1.0 | 3.3+ |
|
||||
+-------------------+-----------------+
|
||||
| 1.1 | 3.4+ |
|
||||
+-------------------+-----------------+
|
||||
| 1.2 | 3.6+ |
|
||||
+-------------------+-----------------+
|
||||
| 1.3 | 3.6+ |
|
||||
+-------------------+-----------------+
|
||||
| 2.0 | 3.7+ |
|
||||
+-------------------+-----------------+
|
||||
| 2.1 | 3.10+ |
|
||||
+-------------------+-----------------+
|
||||
| 2.2 | 3.11+ |
|
||||
+-------------------+-----------------+
|
||||
| 2.3 | 3.11+ |
|
||||
+-------------------+-----------------+
|
||||
| 2.4 | 3.11+ |
|
||||
+-------------------+-----------------+
|
||||
| 2.5 | 3.12+ |
|
||||
+-------------------+-----------------+
|
||||
| 3.0 | 4.1+ |
|
||||
+-------------------+-----------------+
|
||||
| 3.1 | 4.2+ |
|
||||
+-------------------+-----------------+
|
||||
| 3.2 | 4.4+ |
|
||||
+-------------------+-----------------+
|
||||
| 3.3 | 4.5+ |
|
||||
+-------------------+-----------------+
|
||||
| 3.4 | 4.5+ |
|
||||
+-------------------+-----------------+
|
||||
| 3.5 | 4.5+ |
|
||||
+-------------------+-----------------+
|
||||
| 3.6 | 4.9 |
|
||||
+-------------------+-----------------+
|
||||
| 3.7 | 4.9+ |
|
||||
+-------------------+-----------------+
|
||||
|
||||
Motor and MongoDB
|
||||
`````````````````
|
||||
|
||||
+---------------------------------------------------------------------+
|
||||
| MongoDB Version |
|
||||
+=====================+=====+=====+=====+=====+=====+=====+=====+=====+
|
||||
| | 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 |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
+---------------------------------------------------------------------------------------+
|
||||
| MongoDB Version |
|
||||
+=====================+=====+=====+=====+=====+=====+=====+=====+=====+=====+=====+=====+
|
||||
| | 2.2 | 2.4 | 2.6 | 3.0 | 3.2 | 3.4 | 3.6 | 4.0 | 4.2 | 4.4 | 5.0 |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| Motor Version | 1.0 | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 1.1 | Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 1.2 |**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 1.3 |**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.0 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.1 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.2 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.3 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.4 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.5 |**N**|**N**|**N**| Y | 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.
|
||||
@ -86,7 +83,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://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/compatibility/
|
||||
.. _the PyMongo compatibility matrix: https://docs.mongodb.com/drivers/pymongo#mongodb-compatibility
|
||||
|
||||
Motor and Tornado
|
||||
`````````````````
|
||||
@ -97,79 +94,61 @@ known to be incompatible, or have not been tested together.
|
||||
+---------------------------------------------+
|
||||
| Tornado Version |
|
||||
+=====================+=====+=====+=====+=====+
|
||||
| Motor Version | 2.5 |**N**|**N**| Y | Y |
|
||||
| | 3.x | 4.x | 5.x | 6.x |
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
| | 3.0 |**N**|**N**|**N**| Y |
|
||||
| Motor Version | 1.0 | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
| | 3.1 |**N**|**N**|**N**| Y |
|
||||
| | 1.1 | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
| | 3.2 |**N**|**N**|**N**| Y |
|
||||
| | 1.2 |**N**| Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
| | 3.3 |**N**|**N**|**N**| Y |
|
||||
| | 1.3 |**N**| Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
| | 3.4 |**N**|**N**|**N**| Y |
|
||||
| | 2.0 |**N**| Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
| | 2.1 |**N**| Y | Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
| | 2.2 |**N**|**N**| Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
| | 2.3 |**N**|**N**| Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+
|
||||
|
||||
Motor and Python
|
||||
````````````````
|
||||
|
||||
Motor 2.5 deprecated support for Python 3.5.
|
||||
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.0 dropped support for Pythons older than 3.7.
|
||||
Motor 1.2.5 and 1.3.1 add compatibility with Python 3.7, but at the cost of
|
||||
dropping Python 3.4.3 and older. Motor 2.2 dropped support for Pythons older
|
||||
than 3.5.2. Motor 2.5 deprecates support for Python 3.5.
|
||||
|
||||
Motor 3.1.1 added support for Python 3.11.
|
||||
|
||||
Motor 3.3 added support for Python 3.12.
|
||||
|
||||
Motor 3.5 dropped support for Python 3.7 and added support for Python 3.13.
|
||||
|
||||
Motor 3.7 dropped support for Python 3.8.
|
||||
|
||||
Motor 3.8 dropped support for Python 3.9 and added support for Python 3.14.
|
||||
|
||||
+---------------------------------------------------------------------------+
|
||||
| Python Version |
|
||||
+=====================+=====+=====+=====+=====+=====+=====+=====+=====+=====+
|
||||
| | 3.6 | 3.7 | 3.8 | 3.9 | 3.10| 3.11| 3.12| 3.13| 3.14|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| Motor Version | 1.0 | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 1.1 | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 1.2 | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 1.3 | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.0 | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.1 | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.2 | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.3 | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.4 | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 2.5 | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.0 |**N**| Y | Y | Y | Y |**N**|**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.1 |**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.2 |**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.3 |**N**| Y | Y | Y | Y | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.4 |**N**| Y | Y | Y | Y | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.5 |**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.6 |**N**|**N**| Y | Y | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.7 |**N**|**N**|**N**| Y | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| | 3.8 |**N**|**N**|**N**|**N**| Y | Y | Y | Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
+-------------------------------------------------------------------------------------------+
|
||||
| Python Version |
|
||||
+=====================+=====+=====+=====+=====+=====+=======+=======+=====+=====+=====+=====+
|
||||
| | 2.5 | 2.6 | 2.7 | 3.3 | 3.4 | 3.5.0 | 3.5.2 | 3.6 | 3.7 | 3.8 | 3.9 |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| Motor Version | 1.0 |**N**| Y | Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 1.1 |**N**| Y | Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 1.2 |**N**|**N**| Y |**N**| Y |**N** | Y | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 1.3 |**N**|**N**| Y |**N**| Y |**N** | Y | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 2.0 |**N**|**N**| Y |**N**| Y |**N** | Y | Y | Y |**N**|**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 2.1 |**N**|**N**| Y |**N**| Y |**N** | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 2.2 |**N**|**N**|**N**|**N**|**N**|**N** | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 2.3 |**N**|**N**|**N**|**N**|**N**|**N** | Y | Y | Y | Y |**N**|
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 2.4 |**N**|**N**|**N**|**N**|**N**|**N** | Y | Y | Y | Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
| | 2.5 |**N**|**N**|**N**|**N**|**N**|**N** | Y | Y | Y | Y | Y |
|
||||
+---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+-----+
|
||||
|
||||
Not Supported
|
||||
-------------
|
||||
|
||||
@ -3,13 +3,6 @@
|
||||
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.
|
||||
@ -19,7 +12,6 @@ Tutorial: Using Motor With :mod:`asyncio`
|
||||
import pymongo
|
||||
import motor.motor_asyncio
|
||||
import asyncio
|
||||
|
||||
client = motor.motor_asyncio.AsyncIOMotorClient()
|
||||
db = client.test_database
|
||||
|
||||
@ -28,17 +20,14 @@ Tutorial: Using Motor With :mod:`asyncio`
|
||||
import pymongo
|
||||
import motor.motor_asyncio
|
||||
import asyncio
|
||||
|
||||
client = motor.motor_asyncio.AsyncIOMotorClient()
|
||||
db = client.test_database
|
||||
pymongo.MongoClient().test_database.test_collection.insert_many(
|
||||
[{"i": i} for i in range(2000)]
|
||||
)
|
||||
[{'i': i} for i in range(2000)])
|
||||
|
||||
.. testcleanup:: *
|
||||
|
||||
import pymongo
|
||||
|
||||
pymongo.MongoClient().test_database.test_collection.delete_many({})
|
||||
|
||||
A guide to using MongoDB and asyncio with Motor.
|
||||
@ -49,13 +38,13 @@ Tutorial Prerequisites
|
||||
----------------------
|
||||
You can learn about MongoDB with the `MongoDB Tutorial`_ before you learn Motor.
|
||||
|
||||
Using Python 3.5 or later, do::
|
||||
Using Python 3.4 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
|
||||
<https://mongodb.com/docs/manual/installation/>`_ MongoDB, you
|
||||
<http://docs.mongodb.org/manual/installation/>`_ MongoDB, you
|
||||
can start it like so:
|
||||
|
||||
.. code-block:: bash
|
||||
@ -64,7 +53,7 @@ can start it like so:
|
||||
|
||||
.. _pip: http://www.pip-installer.org/en/latest/installing.html
|
||||
|
||||
.. _MongoDB Tutorial: https://mongodb.com/docs/manual/tutorial/getting-started/
|
||||
.. _MongoDB Tutorial: http://docs.mongodb.org/manual/tutorial/getting-started/
|
||||
|
||||
Object Hierarchy
|
||||
----------------
|
||||
@ -85,9 +74,7 @@ Motor, like PyMongo, represents data with a 4-level object hierarchy:
|
||||
|
||||
Creating a Client
|
||||
-----------------
|
||||
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
|
||||
You typically create a single instance of :class:`~motor.motor_asyncio.AsyncIOMotorClient` at the time your
|
||||
application starts up.
|
||||
|
||||
.. doctest:: before-inserting-2000-docs
|
||||
@ -100,38 +87,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: https://mongodb.com/docs/manual/reference/connection-string/
|
||||
.. _connection URIs: http://docs.mongodb.org/manual/reference/connection-string/
|
||||
|
||||
Getting a Database
|
||||
------------------
|
||||
A single instance of MongoDB can support multiple independent
|
||||
`databases <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_.
|
||||
`databases <http://docs.mongodb.org/manual/reference/glossary/#term-database>`_.
|
||||
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 <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_
|
||||
A `collection <http://docs.mongodb.org/manual/reference/glossary/#term-collection>`_
|
||||
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:
|
||||
@ -139,7 +126,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.
|
||||
@ -153,9 +140,9 @@ store a document in MongoDB, call :meth:`~AsyncIOMotorCollection.insert_one` in
|
||||
.. doctest:: before-inserting-2000-docs
|
||||
|
||||
>>> async def do_insert():
|
||||
... document = {"key": "value"}
|
||||
... document = {'key': 'value'}
|
||||
... result = await db.test_collection.insert_one(document)
|
||||
... print("result %s" % repr(result.inserted_id))
|
||||
... print('result %s' % repr(result.inserted_id))
|
||||
...
|
||||
>>>
|
||||
>>> import asyncio
|
||||
@ -170,22 +157,23 @@ store a document in MongoDB, call :meth:`~AsyncIOMotorCollection.insert_one` in
|
||||
|
||||
>>> # Clean up from previous insert
|
||||
>>> pymongo.MongoClient().test_database.test_collection.delete_many({})
|
||||
DeleteResult({'n': 1, 'ok': 1.0}, acknowledged=True)
|
||||
<pymongo.results.DeleteResult ...>
|
||||
|
||||
Insert documents in large batches with :meth:`~AsyncIOMotorCollection.insert_many`:
|
||||
|
||||
.. doctest:: before-inserting-2000-docs
|
||||
|
||||
>>> async def do_insert():
|
||||
... result = await db.test_collection.insert_many([{"i": i} for i in range(2000)])
|
||||
... print("inserted %d docs" % (len(result.inserted_ids),))
|
||||
... result = await db.test_collection.insert_many(
|
||||
... [{'i': i} for i in range(2000)])
|
||||
... print('inserted %d docs' % (len(result.inserted_ids),))
|
||||
...
|
||||
>>> loop = client.get_io_loop()
|
||||
>>> loop.run_until_complete(do_insert())
|
||||
inserted 2000 docs
|
||||
|
||||
Getting a Single Document With ``find_one``
|
||||
-------------------------------------------
|
||||
Getting a Single Document With `find_one`
|
||||
-----------------------------------------
|
||||
|
||||
Use :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_one` to get the first document that
|
||||
matches a query. For example, to get a document where the value for key "i" is
|
||||
@ -194,7 +182,7 @@ less than 1:
|
||||
.. doctest:: after-inserting-2000-docs
|
||||
|
||||
>>> async def do_find_one():
|
||||
... document = await db.test_collection.find_one({"i": {"$lt": 1}})
|
||||
... document = await db.test_collection.find_one({'i': {'$lt': 1}})
|
||||
... pprint.pprint(document)
|
||||
...
|
||||
>>> loop = client.get_io_loop()
|
||||
@ -221,7 +209,7 @@ To find all documents with "i" less than 5:
|
||||
.. doctest:: after-inserting-2000-docs
|
||||
|
||||
>>> async def do_find():
|
||||
... cursor = db.test_collection.find({"i": {"$lt": 5}}).sort("i")
|
||||
... cursor = db.test_collection.find({'i': {'$lt': 5}}).sort('i')
|
||||
... for document in await cursor.to_list(length=100):
|
||||
... pprint.pprint(document)
|
||||
...
|
||||
@ -245,7 +233,7 @@ You can handle one document at a time in an ``async for`` loop:
|
||||
|
||||
>>> async def do_find():
|
||||
... c = db.test_collection
|
||||
... async for document in c.find({"i": {"$lt": 2}}):
|
||||
... async for document in c.find({'i': {'$lt': 2}}):
|
||||
... pprint.pprint(document)
|
||||
...
|
||||
>>> loop = client.get_io_loop()
|
||||
@ -258,9 +246,9 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
|
||||
.. doctest:: after-inserting-2000-docs
|
||||
|
||||
>>> async def do_find():
|
||||
... cursor = db.test_collection.find({"i": {"$lt": 4}})
|
||||
... cursor = db.test_collection.find({'i': {'$lt': 4}})
|
||||
... # Modify the query before iterating
|
||||
... cursor.sort("i", -1).skip(1).limit(2)
|
||||
... cursor.sort('i', -1).skip(1).limit(2)
|
||||
... async for document in cursor:
|
||||
... pprint.pprint(document)
|
||||
...
|
||||
@ -272,7 +260,7 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
|
||||
The cursor does not actually retrieve each document from the server
|
||||
individually; it gets documents efficiently in `large batches`_.
|
||||
|
||||
.. _`large batches`: https://www.mongodb.com/docs/manual/core/cursors/#cursor-batches
|
||||
.. _`large batches`: https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches
|
||||
|
||||
Counting Documents
|
||||
------------------
|
||||
@ -284,9 +272,9 @@ that match a query:
|
||||
|
||||
>>> async def do_count():
|
||||
... n = await db.test_collection.count_documents({})
|
||||
... print("%s documents in collection" % n)
|
||||
... n = await db.test_collection.count_documents({"i": {"$gt": 1000}})
|
||||
... print("%s documents where i > 1000" % n)
|
||||
... print('%s documents in collection' % n)
|
||||
... n = await db.test_collection.count_documents({'i': {'$gt': 1000}})
|
||||
... print('%s documents where i > 1000' % n)
|
||||
...
|
||||
>>> loop = client.get_io_loop()
|
||||
>>> loop.run_until_complete(do_count())
|
||||
@ -305,13 +293,13 @@ replacement document. The query follows the same syntax as for :meth:`find` or
|
||||
|
||||
>>> async def do_replace():
|
||||
... coll = db.test_collection
|
||||
... old_document = await coll.find_one({"i": 50})
|
||||
... print("found document: %s" % pprint.pformat(old_document))
|
||||
... _id = old_document["_id"]
|
||||
... result = await coll.replace_one({"_id": _id}, {"key": "value"})
|
||||
... print("replaced %s document" % result.modified_count)
|
||||
... new_document = await coll.find_one({"_id": _id})
|
||||
... print("document is now %s" % pprint.pformat(new_document))
|
||||
... old_document = await coll.find_one({'i': 50})
|
||||
... print('found document: %s' % pprint.pformat(old_document))
|
||||
... _id = old_document['_id']
|
||||
... result = await coll.replace_one({'_id': _id}, {'key': 'value'})
|
||||
... print('replaced %s document' % result.modified_count)
|
||||
... new_document = await coll.find_one({'_id': _id})
|
||||
... print('document is now %s' % pprint.pformat(new_document))
|
||||
...
|
||||
>>> loop = client.get_io_loop()
|
||||
>>> loop.run_until_complete(do_replace())
|
||||
@ -331,10 +319,10 @@ operator to set "key" to "value":
|
||||
|
||||
>>> async def do_update():
|
||||
... coll = db.test_collection
|
||||
... result = await coll.update_one({"i": 51}, {"$set": {"key": "value"}})
|
||||
... print("updated %s document" % result.modified_count)
|
||||
... new_document = await coll.find_one({"i": 51})
|
||||
... print("document is now %s" % pprint.pformat(new_document))
|
||||
... result = await coll.update_one({'i': 51}, {'$set': {'key': 'value'}})
|
||||
... print('updated %s document' % result.modified_count)
|
||||
... new_document = await coll.find_one({'i': 51})
|
||||
... print('document is now %s' % pprint.pformat(new_document))
|
||||
...
|
||||
>>> loop = client.get_io_loop()
|
||||
>>> loop.run_until_complete(do_update())
|
||||
@ -354,24 +342,6 @@ 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.
|
||||
@ -381,13 +351,13 @@ Deleting Documents
|
||||
>>> async def do_delete_many():
|
||||
... coll = db.test_collection
|
||||
... n = await coll.count_documents({})
|
||||
... print("%s documents before calling delete_many()" % n)
|
||||
... result = await db.test_collection.delete_many({"i": {"$gte": 1000}})
|
||||
... print("%s documents after" % (await coll.count_documents({})))
|
||||
... print('%s documents before calling delete_many()' % n)
|
||||
... result = await db.test_collection.delete_many({'i': {'$gte': 1000}})
|
||||
... print('%s documents after' % (await coll.count_documents({})))
|
||||
...
|
||||
>>> loop = client.get_io_loop()
|
||||
>>> loop.run_until_complete(do_delete_many())
|
||||
1999 documents before calling delete_many()
|
||||
2000 documents before calling delete_many()
|
||||
1000 documents after
|
||||
|
||||
.. mongodoc:: remove
|
||||
@ -402,7 +372,8 @@ the :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command` method on
|
||||
|
||||
>>> from bson import SON
|
||||
>>> async def use_distinct_command():
|
||||
... response = await db.command(SON([("distinct", "test_collection"), ("key", "i")]))
|
||||
... response = await db.command(SON([("distinct", "test_collection"),
|
||||
... ("key", "i")]))
|
||||
...
|
||||
>>> loop = client.get_io_loop()
|
||||
>>> loop.run_until_complete(use_distinct_command())
|
||||
@ -461,9 +432,11 @@ to an :class:`aiohttp.web.Application`:
|
||||
:start-after: main-start
|
||||
:end-before: main-end
|
||||
|
||||
.. 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.
|
||||
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.
|
||||
|
||||
Visit ``localhost:8080/pages/page-one`` and the server responds "Hello!".
|
||||
At ``localhost:8080/pages/page-two`` it responds "Goodbye." At other URLs it
|
||||
@ -485,4 +458,5 @@ 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: https://mongodb.com/docs/manual/
|
||||
.. _The MongoDB Manual: http://docs.mongodb.org/manual/
|
||||
|
||||
|
||||
@ -3,13 +3,6 @@
|
||||
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.
|
||||
@ -21,7 +14,6 @@ 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
|
||||
@ -31,16 +23,15 @@ 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.
|
||||
@ -64,7 +55,7 @@ exception:
|
||||
|
||||
This tutorial also assumes that a MongoDB instance is running on the
|
||||
default host and port. Assuming you have `downloaded and installed
|
||||
<https://mongodb.com/docs/manual/installation/>`_ MongoDB, you
|
||||
<http://docs.mongodb.org/manual/installation/>`_ MongoDB, you
|
||||
can start it like so:
|
||||
|
||||
.. code-block:: bash
|
||||
@ -73,7 +64,7 @@ can start it like so:
|
||||
|
||||
.. _pip: http://www.pip-installer.org/en/latest/installing.html
|
||||
|
||||
.. _MongoDB Tutorial: https://mongodb.com/docs/manual/tutorial/getting-started/
|
||||
.. _MongoDB Tutorial: http://docs.mongodb.org/manual/tutorial/getting-started/
|
||||
|
||||
Object Hierarchy
|
||||
----------------
|
||||
@ -93,9 +84,8 @@ Motor, like PyMongo, represents data with a 4-level object hierarchy:
|
||||
|
||||
Creating a Client
|
||||
-----------------
|
||||
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.
|
||||
You typically create a single instance of :class:`MotorClient` at the time your
|
||||
application starts up.
|
||||
|
||||
.. doctest:: before-inserting-2000-docs
|
||||
|
||||
@ -106,31 +96,31 @@ 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: https://mongodb.com/docs/manual/reference/connection-string/
|
||||
.. _connection URIs: http://docs.mongodb.org/manual/reference/connection-string/
|
||||
|
||||
Getting a Database
|
||||
------------------
|
||||
A single instance of MongoDB can support multiple independent
|
||||
`databases <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_.
|
||||
`databases <http://docs.mongodb.org/manual/reference/glossary/#term-database>`_.
|
||||
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.
|
||||
@ -159,10 +149,10 @@ makes it available to request handlers::
|
||||
def get(self):
|
||||
db = self.settings['db']
|
||||
|
||||
.. warning:: It is a common mistake to create a new client object for every
|
||||
request; **this comes at a dire performance cost**. Create the client
|
||||
when your application starts and reuse that one client for the lifetime
|
||||
of the process, as shown in these examples.
|
||||
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
|
||||
@ -189,7 +179,7 @@ methods than ``HTTPServer.start()``. See Tornado's guide to
|
||||
|
||||
Getting a Collection
|
||||
--------------------
|
||||
A `collection <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_
|
||||
A `collection <http://docs.mongodb.org/manual/reference/glossary/#term-collection>`_
|
||||
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:
|
||||
@ -197,7 +187,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.
|
||||
@ -211,9 +201,9 @@ store a document in MongoDB, call :meth:`~MotorCollection.insert_one` in an
|
||||
.. doctest:: before-inserting-2000-docs
|
||||
|
||||
>>> async def do_insert():
|
||||
... document = {"key": "value"}
|
||||
... document = {'key': 'value'}
|
||||
... result = await db.test_collection.insert_one(document)
|
||||
... print("result %s" % repr(result.inserted_id))
|
||||
... print('result %s' % repr(result.inserted_id))
|
||||
...
|
||||
>>>
|
||||
>>> IOLoop.current().run_sync(do_insert)
|
||||
@ -226,7 +216,7 @@ store a document in MongoDB, call :meth:`~MotorCollection.insert_one` in an
|
||||
|
||||
>>> # Clean up from previous insert
|
||||
>>> pymongo.MongoClient().test_database.test_collection.delete_many({})
|
||||
DeleteResult({'n': 1, 'ok': 1.0}, acknowledged=True)
|
||||
<pymongo.results.DeleteResult ...>
|
||||
|
||||
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::
|
||||
@ -246,7 +236,7 @@ sequence, use ``await``:
|
||||
|
||||
>>> async def do_insert():
|
||||
... for i in range(2000):
|
||||
... await db.test_collection.insert_one({"i": i})
|
||||
... await db.test_collection.insert_one({'i': i})
|
||||
...
|
||||
>>> IOLoop.current().run_sync(do_insert)
|
||||
|
||||
@ -259,7 +249,7 @@ sequence, use ``await``:
|
||||
|
||||
>>> # Clean up from previous insert
|
||||
>>> pymongo.MongoClient().test_database.test_collection.delete_many({})
|
||||
DeleteResult({'n': 2000, 'ok': 1.0}, acknowledged=True)
|
||||
<pymongo.results.DeleteResult ...>
|
||||
|
||||
For better performance, insert documents in large batches with
|
||||
:meth:`~MotorCollection.insert_many`:
|
||||
@ -267,8 +257,9 @@ For better performance, insert documents in large batches with
|
||||
.. doctest:: before-inserting-2000-docs
|
||||
|
||||
>>> async def do_insert():
|
||||
... result = await db.test_collection.insert_many([{"i": i} for i in range(2000)])
|
||||
... print("inserted %d docs" % (len(result.inserted_ids),))
|
||||
... result = await db.test_collection.insert_many(
|
||||
... [{'i': i} for i in range(2000)])
|
||||
... print('inserted %d docs' % (len(result.inserted_ids),))
|
||||
...
|
||||
>>> IOLoop.current().run_sync(do_insert)
|
||||
inserted 2000 docs
|
||||
@ -282,7 +273,7 @@ less than 1:
|
||||
.. doctest:: after-inserting-2000-docs
|
||||
|
||||
>>> async def do_find_one():
|
||||
... document = await db.test_collection.find_one({"i": {"$lt": 1}})
|
||||
... document = await db.test_collection.find_one({'i': {'$lt': 1}})
|
||||
... pprint.pprint(document)
|
||||
...
|
||||
>>> IOLoop.current().run_sync(do_find_one)
|
||||
@ -311,7 +302,7 @@ To find all documents with "i" less than 5:
|
||||
.. doctest:: after-inserting-2000-docs
|
||||
|
||||
>>> async def do_find():
|
||||
... cursor = db.test_collection.find({"i": {"$lt": 5}}).sort("i")
|
||||
... cursor = db.test_collection.find({'i': {'$lt': 5}}).sort('i')
|
||||
... for document in await cursor.to_list(length=100):
|
||||
... pprint.pprint(document)
|
||||
...
|
||||
@ -334,7 +325,7 @@ You can handle one document at a time in an ``async for`` loop:
|
||||
|
||||
>>> async def do_find():
|
||||
... c = db.test_collection
|
||||
... async for document in c.find({"i": {"$lt": 2}}):
|
||||
... async for document in c.find({'i': {'$lt': 2}}):
|
||||
... pprint.pprint(document)
|
||||
...
|
||||
>>> IOLoop.current().run_sync(do_find)
|
||||
@ -346,9 +337,9 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
|
||||
.. doctest:: after-inserting-2000-docs
|
||||
|
||||
>>> async def do_find():
|
||||
... cursor = db.test_collection.find({"i": {"$lt": 4}})
|
||||
... cursor = db.test_collection.find({'i': {'$lt': 4}})
|
||||
... # Modify the query before iterating
|
||||
... cursor.sort("i", -1).skip(1).limit(2)
|
||||
... cursor.sort('i', -1).skip(1).limit(2)
|
||||
... async for document in cursor:
|
||||
... pprint.pprint(document)
|
||||
...
|
||||
@ -359,7 +350,7 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
|
||||
The cursor does not actually retrieve each document from the server
|
||||
individually; it gets documents efficiently in `large batches`_.
|
||||
|
||||
.. _`large batches`: https://www.mongodb.com/docs/manual/core/cursors/#cursor-batches
|
||||
.. _`large batches`: https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches
|
||||
|
||||
Counting Documents
|
||||
------------------
|
||||
@ -370,9 +361,9 @@ documents in a collection, or the number of documents that match a query:
|
||||
|
||||
>>> async def do_count():
|
||||
... n = await db.test_collection.count_documents({})
|
||||
... print("%s documents in collection" % n)
|
||||
... n = await db.test_collection.count_documents({"i": {"$gt": 1000}})
|
||||
... print("%s documents where i > 1000" % n)
|
||||
... print('%s documents in collection' % n)
|
||||
... n = await db.test_collection.count_documents({'i': {'$gt': 1000}})
|
||||
... print('%s documents where i > 1000' % n)
|
||||
...
|
||||
>>> IOLoop.current().run_sync(do_count)
|
||||
2000 documents in collection
|
||||
@ -390,13 +381,13 @@ replacement document. The query follows the same syntax as for :meth:`find` or
|
||||
|
||||
>>> async def do_replace():
|
||||
... coll = db.test_collection
|
||||
... old_document = await coll.find_one({"i": 50})
|
||||
... print("found document: %s" % pprint.pformat(old_document))
|
||||
... _id = old_document["_id"]
|
||||
... result = await coll.replace_one({"_id": _id}, {"key": "value"})
|
||||
... print("replaced %s document" % result.modified_count)
|
||||
... new_document = await coll.find_one({"_id": _id})
|
||||
... print("document is now %s" % pprint.pformat(new_document))
|
||||
... old_document = await coll.find_one({'i': 50})
|
||||
... print('found document: %s' % pprint.pformat(old_document))
|
||||
... _id = old_document['_id']
|
||||
... result = await coll.replace_one({'_id': _id}, {'key': 'value'})
|
||||
... print('replaced %s document' % result.modified_count)
|
||||
... new_document = await coll.find_one({'_id': _id})
|
||||
... print('document is now %s' % pprint.pformat(new_document))
|
||||
...
|
||||
>>> IOLoop.current().run_sync(do_replace)
|
||||
found document: {'_id': ObjectId('...'), 'i': 50}
|
||||
@ -415,10 +406,10 @@ operator to set "key" to "value":
|
||||
|
||||
>>> async def do_update():
|
||||
... coll = db.test_collection
|
||||
... result = await coll.update_one({"i": 51}, {"$set": {"key": "value"}})
|
||||
... print("updated %s document" % result.modified_count)
|
||||
... new_document = await coll.find_one({"i": 51})
|
||||
... print("document is now %s" % pprint.pformat(new_document))
|
||||
... result = await coll.update_one({'i': 51}, {'$set': {'key': 'value'}})
|
||||
... print('updated %s document' % result.modified_count)
|
||||
... new_document = await coll.find_one({'i': 51})
|
||||
... print('document is now %s' % pprint.pformat(new_document))
|
||||
...
|
||||
>>> IOLoop.current().run_sync(do_update)
|
||||
updated 1 document
|
||||
@ -437,23 +428,6 @@ 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.
|
||||
@ -463,12 +437,12 @@ Removing Documents
|
||||
>>> async def do_delete_many():
|
||||
... coll = db.test_collection
|
||||
... n = await coll.count_documents({})
|
||||
... print("%s documents before calling delete_many()" % n)
|
||||
... result = await db.test_collection.delete_many({"i": {"$gte": 1000}})
|
||||
... print("%s documents after" % (await coll.count_documents({})))
|
||||
... print('%s documents before calling delete_many()' % n)
|
||||
... result = await db.test_collection.delete_many({'i': {'$gte': 1000}})
|
||||
... print('%s documents after' % (await coll.count_documents({})))
|
||||
...
|
||||
>>> IOLoop.current().run_sync(do_delete_many)
|
||||
1999 documents before calling delete_many()
|
||||
2000 documents before calling delete_many()
|
||||
1000 documents after
|
||||
|
||||
.. mongodoc:: remove
|
||||
@ -483,7 +457,8 @@ the :meth:`~motor.motor_tornado.MotorDatabase.command` method on
|
||||
|
||||
>>> from bson import SON
|
||||
>>> async def use_distinct_command():
|
||||
... response = await db.command(SON([("distinct", "test_collection"), ("key", "i")]))
|
||||
... response = await db.command(SON([("distinct", "test_collection"),
|
||||
... ("key", "i")]))
|
||||
...
|
||||
>>> IOLoop.current().run_sync(use_distinct_command)
|
||||
|
||||
@ -508,4 +483,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: https://mongodb.com/docs/manual/
|
||||
.. _The MongoDB Manual: http://docs.mongodb.org/manual/
|
||||
|
||||
426
ez_setup.py
Normal file
426
ez_setup.py
Normal file
@ -0,0 +1,426 @@
|
||||
#!/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().__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,17 +13,33 @@
|
||||
# limitations under the License.
|
||||
|
||||
"""Motor, an asynchronous driver for MongoDB."""
|
||||
from ._version import get_version_string, version, version_tuple # noqa: F401
|
||||
|
||||
version_tuple = (2, 5, 2, 'dev0')
|
||||
|
||||
|
||||
def get_version_string():
|
||||
return '.'.join(map(str, version_tuple))
|
||||
|
||||
|
||||
version = get_version_string()
|
||||
"""Current version of Motor."""
|
||||
|
||||
|
||||
try:
|
||||
import tornado
|
||||
except ImportError:
|
||||
tornado = None # type:ignore[assignment]
|
||||
tornado = None
|
||||
else:
|
||||
# For backwards compatibility with Motor 0.4, export Motor's Tornado classes
|
||||
# at module root. This may change in the future.
|
||||
from .motor_tornado import * # noqa: F403
|
||||
from .motor_tornado import __all__ # noqa: F401
|
||||
# at module root. This may change in the future. 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,
|
||||
MotorGridFSBucket,
|
||||
MotorGridIn,
|
||||
MotorGridOut)
|
||||
|
||||
# Make "from motor import *" the same as "from motor.motor_tornado import *"
|
||||
from .motor_tornado import __all__
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
# 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__
|
||||
@ -24,11 +24,8 @@ import mimetypes
|
||||
|
||||
import aiohttp.web
|
||||
import gridfs
|
||||
|
||||
from motor.motor_asyncio import AsyncIOMotorDatabase, AsyncIOMotorGridFSBucket
|
||||
from motor.motor_gridfs import _hash_gridout
|
||||
|
||||
# mypy: disable-error-code="no-untyped-def,no-untyped-call"
|
||||
from motor.motor_asyncio import (AsyncIOMotorDatabase,
|
||||
AsyncIOMotorGridFSBucket)
|
||||
|
||||
|
||||
def get_gridfs_file(bucket, filename, request):
|
||||
@ -108,15 +105,17 @@ 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 +134,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,21 +152,18 @@ class AIOHTTPGridFS:
|
||||
- `get_cache_time`: Optional override for :func:`get_cache_time`
|
||||
- `set_extra_headers`: Optional override for :func:`set_extra_headers`
|
||||
|
||||
.. _GridFS: https://www.mongodb.com/docs/manual/core/gridfs/
|
||||
.. _GridFS: https://docs.mongodb.com/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)
|
||||
@ -178,26 +174,23 @@ class AIOHTTPGridFS:
|
||||
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 = await self._get_gridfs_file(self._bucket, filename, request)
|
||||
except gridfs.NoFile as e:
|
||||
raise aiohttp.web.HTTPNotFound(text=request.path) from e
|
||||
gridout = await self._get_gridfs_file(self._bucket,
|
||||
filename,
|
||||
request)
|
||||
except gridfs.NoFile:
|
||||
raise aiohttp.web.HTTPNotFound(text=request.path)
|
||||
|
||||
resp = aiohttp.web.StreamResponse()
|
||||
|
||||
# Get the hash for the GridFS file.
|
||||
checksum = _hash_gridout(gridout)
|
||||
|
||||
self._set_standard_headers(request.path, resp, gridout, checksum)
|
||||
self._set_standard_headers(request.path, resp, gridout)
|
||||
|
||||
# Overridable method set_extra_headers.
|
||||
self._set_extra_headers(resp, gridout)
|
||||
@ -216,14 +209,14 @@ class AIOHTTPGridFS:
|
||||
|
||||
# Same for Etag
|
||||
etag = request.headers.get("If-None-Match")
|
||||
if etag is not None and etag.strip('"') == checksum:
|
||||
if etag is not None and etag.strip('"') == gridout.md5:
|
||||
resp.set_status(304)
|
||||
return resp
|
||||
|
||||
resp.content_length = gridout.length
|
||||
await resp.prepare(request)
|
||||
|
||||
if request.method == "GET":
|
||||
if request.method == 'GET':
|
||||
written = 0
|
||||
while written < gridout.length:
|
||||
# Reading chunk_size at a time minimizes buffering.
|
||||
@ -232,7 +225,7 @@ class AIOHTTPGridFS:
|
||||
written += len(chunk)
|
||||
return resp
|
||||
|
||||
def _set_standard_headers(self, path, resp, gridout, checksum):
|
||||
def _set_standard_headers(self, path, resp, gridout):
|
||||
resp.last_modified = gridout.upload_date
|
||||
content_type = gridout.content_type
|
||||
if content_type is None:
|
||||
@ -241,15 +234,18 @@ class AIOHTTPGridFS:
|
||||
if content_type:
|
||||
resp.content_type = content_type
|
||||
|
||||
resp.headers["Etag"] = '"%s"' % checksum
|
||||
# MD5 is calculated on the MongoDB server when GridFS file is created.
|
||||
resp.headers["Etag"] = '"%s"' % gridout.md5
|
||||
|
||||
# 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.now(datetime.timezone.utc).replace(tzinfo=None)
|
||||
+ datetime.timedelta(seconds=cache_time)
|
||||
datetime.datetime.utcnow() +
|
||||
datetime.timedelta(seconds=cache_time)
|
||||
).strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
resp.headers["Cache-Control"] = "max-age=" + str(cache_time)
|
||||
|
||||
1263
motor/core.py
1263
motor/core.py
File diff suppressed because it is too large
Load Diff
889
motor/core.pyi
889
motor/core.pyi
@ -1,889 +0,0 @@
|
||||
# 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,33 +16,34 @@
|
||||
|
||||
See "Frameworks" in the Developer Guide.
|
||||
"""
|
||||
|
||||
|
||||
import asyncio
|
||||
import asyncio.tasks
|
||||
import functools
|
||||
import multiprocessing
|
||||
import os
|
||||
import warnings
|
||||
from asyncio import get_event_loop # noqa: F401 - For framework interface.
|
||||
|
||||
from asyncio import coroutine # For framework interface.
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
# mypy: ignore-errors
|
||||
|
||||
try:
|
||||
import contextvars
|
||||
except ImportError:
|
||||
contextvars = None
|
||||
|
||||
try:
|
||||
from asyncio import coroutine
|
||||
except ImportError:
|
||||
|
||||
def coroutine():
|
||||
raise RuntimeError(
|
||||
"The coroutine decorator was removed in Python 3.11. Use 'async def' instead"
|
||||
)
|
||||
CLASS_PREFIX = 'AsyncIO'
|
||||
|
||||
|
||||
CLASS_PREFIX = "AsyncIO"
|
||||
def get_event_loop():
|
||||
try:
|
||||
return asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
# Workaround for bugs.python.org/issue39529.
|
||||
return asyncio.get_event_loop_policy().get_event_loop()
|
||||
|
||||
|
||||
def is_event_loop(loop):
|
||||
@ -51,38 +52,30 @@ 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 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):
|
||||
if contextvars:
|
||||
context = contextvars.copy_context()
|
||||
fn = functools.partial(context.run, fn)
|
||||
|
||||
return loop.run_in_executor(_EXECUTOR, functools.partial(fn, *args, **kwargs))
|
||||
return loop.run_in_executor(
|
||||
_EXECUTOR, functools.partial(fn, *args, **kwargs))
|
||||
|
||||
|
||||
# Adapted from tornado.gen.
|
||||
@ -118,7 +111,8 @@ def chain_return_value(future, loop, return_value):
|
||||
else:
|
||||
chained.set_result(return_value)
|
||||
|
||||
future.add_done_callback(functools.partial(loop.call_soon_threadsafe, copy))
|
||||
future.add_done_callback(
|
||||
functools.partial(loop.call_soon_threadsafe, copy))
|
||||
return chained
|
||||
|
||||
|
||||
@ -134,7 +128,8 @@ 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))
|
||||
future.add_done_callback(
|
||||
functools.partial(loop.call_soon_threadsafe, callback, *args))
|
||||
|
||||
|
||||
def pymongo_class_wrapper(f, pymongo_class):
|
||||
@ -142,7 +137,6 @@ def pymongo_class_wrapper(f, pymongo_class):
|
||||
|
||||
See WrapAsync.
|
||||
"""
|
||||
|
||||
@functools.wraps(f)
|
||||
async def _wrapper(self, *args, **kwargs):
|
||||
result = await f(self, *args, **kwargs)
|
||||
@ -159,12 +153,10 @@ 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,
|
||||
)
|
||||
"The yieldable function is deprecated and will be removed in "
|
||||
"Motor 3.0", DeprecationWarning, stacklevel=2)
|
||||
return next(iter(future))
|
||||
|
||||
|
||||
def platform_info():
|
||||
return "asyncio"
|
||||
return 'asyncio'
|
||||
|
||||
@ -19,22 +19,21 @@ See "Frameworks" in the Developer Guide.
|
||||
|
||||
import functools
|
||||
import os
|
||||
import warnings
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import tornado.process
|
||||
from tornado import concurrent, ioloop
|
||||
from tornado import version as tornado_version
|
||||
from tornado.gen import chain_future, coroutine # noqa: F401 - For framework interface.
|
||||
import warnings
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from tornado import concurrent, gen, ioloop, version as tornado_version
|
||||
from tornado.gen import chain_future, coroutine # For framework interface.
|
||||
|
||||
|
||||
try:
|
||||
import contextvars
|
||||
except ImportError:
|
||||
contextvars = None
|
||||
|
||||
# mypy: ignore-errors
|
||||
|
||||
CLASS_PREFIX = ""
|
||||
CLASS_PREFIX = ''
|
||||
|
||||
|
||||
def get_event_loop():
|
||||
@ -47,38 +46,29 @@ 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):
|
||||
if contextvars:
|
||||
context = contextvars.copy_context()
|
||||
fn = functools.partial(context.run, fn)
|
||||
|
||||
return loop.run_in_executor(_EXECUTOR, functools.partial(fn, *args, **kwargs))
|
||||
return loop.run_in_executor(
|
||||
_EXECUTOR, functools.partial(fn, *args, **kwargs))
|
||||
|
||||
|
||||
def chain_return_value(future, loop, return_value):
|
||||
@ -124,7 +114,6 @@ def pymongo_class_wrapper(f, pymongo_class):
|
||||
|
||||
See WrapAsync.
|
||||
"""
|
||||
|
||||
@functools.wraps(f)
|
||||
async def _wrapper(self, *args, **kwargs):
|
||||
result = await f(self, *args, **kwargs)
|
||||
@ -141,12 +130,10 @@ 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,
|
||||
)
|
||||
"The yieldable function is deprecated and will be removed in "
|
||||
"Motor 3.0", DeprecationWarning, stacklevel=2)
|
||||
return future
|
||||
|
||||
|
||||
def platform_info():
|
||||
return f"Tornado {tornado_version}"
|
||||
return 'Tornado %s' % (tornado_version,)
|
||||
|
||||
@ -13,17 +13,17 @@
|
||||
# limitations under the License.
|
||||
|
||||
"""Dynamic class-creation for Motor."""
|
||||
import functools
|
||||
|
||||
import inspect
|
||||
from collections.abc import Callable
|
||||
from typing import Any, TypeVar
|
||||
import functools
|
||||
|
||||
_class_cache: dict[Any, Any] = {}
|
||||
from pymongo.cursor import Cursor
|
||||
|
||||
# mypy: ignore-errors
|
||||
_class_cache = {}
|
||||
|
||||
|
||||
def asynchronize(framework, sync_method: Callable, doc=None, wrap_class=None, unwrap_class=None):
|
||||
def asynchronize(
|
||||
framework, sync_method, doc=None, wrap_class=None, unwrap_class=None):
|
||||
"""Decorate `sync_method` so it returns a Future.
|
||||
|
||||
The method runs on a thread and resolves the Future when it completes.
|
||||
@ -40,42 +40,41 @@ def asynchronize(framework, sync_method: Callable, doc=None, wrap_class=None, un
|
||||
this Motor class name and pass the wrapped PyMongo
|
||||
object instead
|
||||
"""
|
||||
|
||||
@functools.wraps(sync_method)
|
||||
def method(self, *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"))
|
||||
if obj.__class__.__name__.endswith(
|
||||
(unwrap_class, 'MotorClientSession'))
|
||||
else obj
|
||||
for obj in args
|
||||
]
|
||||
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()
|
||||
}
|
||||
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
|
||||
]
|
||||
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()
|
||||
}
|
||||
key: (obj.delegate
|
||||
if obj.__class__.__name__.endswith('MotorClientSession')
|
||||
else obj)
|
||||
for key, obj in kwargs.items()}
|
||||
|
||||
loop = self.get_io_loop()
|
||||
return framework.run_on_executor(
|
||||
loop, sync_method, self.delegate, *unwrapped_args, **unwrapped_kwargs
|
||||
)
|
||||
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)
|
||||
@ -95,16 +94,18 @@ def asynchronize(framework, sync_method: Callable, doc=None, wrap_class=None, un
|
||||
|
||||
def unwrap_args_session(args):
|
||||
return (
|
||||
obj.delegate if obj.__class__.__name__.endswith("MotorClientSession") else obj
|
||||
for obj in args
|
||||
)
|
||||
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()
|
||||
}
|
||||
key: (obj.delegate
|
||||
if obj.__class__.__name__.endswith('MotorClientSession')
|
||||
else obj)
|
||||
for key, obj in kwargs.items()}
|
||||
|
||||
|
||||
_coro_token = object()
|
||||
@ -123,12 +124,11 @@ def coroutine_annotation(f):
|
||||
return f
|
||||
|
||||
|
||||
class MotorAttributeFactory:
|
||||
class MotorAttributeFactory(object):
|
||||
"""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
|
||||
|
||||
@ -153,13 +153,11 @@ class Async(MotorAttributeFactory):
|
||||
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,
|
||||
wrap_class=self.wrap_class,
|
||||
unwrap_class=self.unwrap_class,
|
||||
)
|
||||
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):
|
||||
self.wrap_class = original_class
|
||||
@ -180,7 +178,7 @@ class AsyncRead(Async):
|
||||
|
||||
class AsyncWrite(Async):
|
||||
def __init__(self, attr_name=None, doc=None):
|
||||
"""A descriptor that wraps a PyMongo write method like update_one() that
|
||||
"""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)
|
||||
@ -196,7 +194,6 @@ class AsyncCommand(Async):
|
||||
|
||||
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)
|
||||
@ -267,10 +264,7 @@ class MotorCursorChainingMethod(MotorAttributeFactory):
|
||||
return return_clone
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def create_class_with_framework(cls: T, framework: Any, module_name: str) -> T:
|
||||
def create_class_with_framework(cls, framework, module_name):
|
||||
motor_class_name = framework.CLASS_PREFIX + cls.__motor_class_name__
|
||||
cache_key = (cls, motor_class_name, framework)
|
||||
cached_class = _class_cache.get(cache_key)
|
||||
@ -281,7 +275,7 @@ def create_class_with_framework(cls: T, framework: Any, module_name: str) -> T:
|
||||
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).
|
||||
|
||||
@ -13,28 +13,17 @@
|
||||
# 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 T, create_class_with_framework
|
||||
from .metaprogramming import create_class_with_framework
|
||||
|
||||
__all__ = [
|
||||
"AsyncIOMotorClient",
|
||||
"AsyncIOMotorClientSession",
|
||||
"AsyncIOMotorDatabase",
|
||||
"AsyncIOMotorCollection",
|
||||
"AsyncIOMotorCursor",
|
||||
"AsyncIOMotorCommandCursor",
|
||||
"AsyncIOMotorChangeStream",
|
||||
"AsyncIOMotorGridFSBucket",
|
||||
"AsyncIOMotorGridIn",
|
||||
"AsyncIOMotorGridOut",
|
||||
"AsyncIOMotorGridOutCursor",
|
||||
"AsyncIOMotorClientEncryption",
|
||||
]
|
||||
__all__ = ['AsyncIOMotorClient','AsyncIOMotorClientEncryption']
|
||||
|
||||
|
||||
def create_asyncio_class(cls: T) -> T:
|
||||
return create_class_with_framework(cls, asyncio_framework, "motor.motor_asyncio")
|
||||
def create_asyncio_class(cls):
|
||||
return create_class_with_framework(cls, asyncio_framework,
|
||||
'motor.motor_asyncio')
|
||||
|
||||
|
||||
AsyncIOMotorClient = create_asyncio_class(core.AgnosticClient)
|
||||
@ -43,34 +32,45 @@ AsyncIOMotorClient = create_asyncio_class(core.AgnosticClient)
|
||||
AsyncIOMotorClientSession = create_asyncio_class(core.AgnosticClientSession)
|
||||
|
||||
|
||||
AsyncIOMotorDatabase = create_asyncio_class(core.AgnosticDatabase)
|
||||
AsyncIOMotorDatabase = create_asyncio_class(
|
||||
core.AgnosticDatabase)
|
||||
|
||||
|
||||
AsyncIOMotorCollection = create_asyncio_class(core.AgnosticCollection)
|
||||
AsyncIOMotorCollection = create_asyncio_class(
|
||||
core.AgnosticCollection)
|
||||
|
||||
|
||||
AsyncIOMotorCursor = create_asyncio_class(core.AgnosticCursor)
|
||||
AsyncIOMotorCursor = create_asyncio_class(
|
||||
core.AgnosticCursor)
|
||||
|
||||
|
||||
AsyncIOMotorCommandCursor = create_asyncio_class(core.AgnosticCommandCursor)
|
||||
AsyncIOMotorCommandCursor = create_asyncio_class(
|
||||
core.AgnosticCommandCursor)
|
||||
|
||||
|
||||
AsyncIOMotorLatentCommandCursor = create_asyncio_class(core.AgnosticLatentCommandCursor)
|
||||
AsyncIOMotorLatentCommandCursor = create_asyncio_class(
|
||||
core.AgnosticLatentCommandCursor)
|
||||
|
||||
|
||||
AsyncIOMotorChangeStream = create_asyncio_class(core.AgnosticChangeStream)
|
||||
AsyncIOMotorChangeStream = create_asyncio_class(
|
||||
core.AgnosticChangeStream)
|
||||
|
||||
|
||||
AsyncIOMotorGridFSBucket = create_asyncio_class(motor_gridfs.AgnosticGridFSBucket)
|
||||
AsyncIOMotorGridFSBucket = create_asyncio_class(
|
||||
motor_gridfs.AgnosticGridFSBucket)
|
||||
|
||||
|
||||
AsyncIOMotorGridIn = create_asyncio_class(motor_gridfs.AgnosticGridIn)
|
||||
AsyncIOMotorGridIn = create_asyncio_class(
|
||||
motor_gridfs.AgnosticGridIn)
|
||||
|
||||
|
||||
AsyncIOMotorGridOut = create_asyncio_class(motor_gridfs.AgnosticGridOut)
|
||||
AsyncIOMotorGridOut = create_asyncio_class(
|
||||
motor_gridfs.AgnosticGridOut)
|
||||
|
||||
|
||||
AsyncIOMotorGridOutCursor = create_asyncio_class(motor_gridfs.AgnosticGridOutCursor)
|
||||
AsyncIOMotorGridOutCursor = create_asyncio_class(
|
||||
motor_gridfs.AgnosticGridOutCursor)
|
||||
|
||||
|
||||
AsyncIOMotorClientEncryption = create_asyncio_class(core.AgnosticClientEncryption)
|
||||
AsyncIOMotorClientEncryption = create_asyncio_class(
|
||||
core.AgnosticClientEncryption)
|
||||
|
||||
@ -1,264 +0,0 @@
|
||||
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: ...
|
||||
@ -13,4 +13,5 @@
|
||||
# limitations under the License.
|
||||
|
||||
"""Common code to support all async frameworks."""
|
||||
|
||||
callback_type_error = TypeError("callback must be a callable")
|
||||
|
||||
@ -13,38 +13,41 @@
|
||||
# limitations under the License.
|
||||
|
||||
"""GridFS implementation for Motor, an asynchronous driver for MongoDB."""
|
||||
import hashlib
|
||||
|
||||
import warnings
|
||||
|
||||
import gridfs
|
||||
import pymongo
|
||||
import pymongo.errors
|
||||
from gridfs import DEFAULT_CHUNK_SIZE, grid_file
|
||||
from gridfs import (DEFAULT_CHUNK_SIZE,
|
||||
grid_file)
|
||||
|
||||
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,
|
||||
)
|
||||
from motor.core import (AgnosticCursor,
|
||||
AgnosticCollection,
|
||||
AgnosticDatabase)
|
||||
from motor.docstrings import *
|
||||
from motor.metaprogramming import (AsyncCommand,
|
||||
AsyncRead,
|
||||
coroutine_annotation,
|
||||
create_class_with_framework,
|
||||
DelegateMethod,
|
||||
MotorCursorChainingMethod,
|
||||
ReadOnlyProperty)
|
||||
|
||||
|
||||
class AgnosticGridOutCursor(AgnosticCursor):
|
||||
__motor_class_name__ = "MotorGridOutCursor"
|
||||
__motor_class_name__ = 'MotorGridOutCursor'
|
||||
__delegate_class__ = gridfs.GridOutCursor
|
||||
|
||||
# PyMongo's GridOutCursor inherits __die from Cursor.
|
||||
_Cursor__die = AsyncCommand()
|
||||
|
||||
def next_object(self):
|
||||
"""**DEPRECATED** - Get next GridOut object from cursor."""
|
||||
# Note: the super() call will raise a warning for the deprecation.
|
||||
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:
|
||||
@ -54,14 +57,12 @@ class AgnosticGridOutCursor(AgnosticCursor):
|
||||
|
||||
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)
|
||||
|
||||
@ -69,11 +70,11 @@ class MotorGridOutProperty(ReadOnlyProperty):
|
||||
return property(fget=fget, doc=doc)
|
||||
|
||||
|
||||
class AgnosticGridOut:
|
||||
class AgnosticGridOut(object):
|
||||
"""Class to read data out of GridFS.
|
||||
|
||||
MotorGridOut supports the same attributes as PyMongo's
|
||||
:class:`~pymongo.grid_file.GridOut`, such as ``_id``, ``content_type``,
|
||||
:class:`~gridfs.grid_file.GridOut`, such as ``_id``, ``content_type``,
|
||||
etc.
|
||||
|
||||
You don't need to instantiate this class directly - use the
|
||||
@ -81,49 +82,48 @@ class AgnosticGridOut:
|
||||
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
|
||||
|
||||
_id = MotorGridOutProperty()
|
||||
aliases = MotorGridOutProperty()
|
||||
chunk_size = MotorGridOutProperty()
|
||||
close = MotorGridOutProperty()
|
||||
_ensure_file = AsyncCommand()
|
||||
_id = MotorGridOutProperty()
|
||||
aliases = MotorGridOutProperty()
|
||||
chunk_size = MotorGridOutProperty()
|
||||
close = MotorGridOutProperty()
|
||||
content_type = 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()
|
||||
filename = MotorGridOutProperty()
|
||||
length = MotorGridOutProperty()
|
||||
md5 = MotorGridOutProperty()
|
||||
metadata = MotorGridOutProperty()
|
||||
name = MotorGridOutProperty()
|
||||
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, session=None
|
||||
):
|
||||
def __init__(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, session=session
|
||||
)
|
||||
root_collection.delegate,
|
||||
file_id,
|
||||
file_document,
|
||||
session=session)
|
||||
|
||||
self.io_loop = root_collection.get_io_loop()
|
||||
|
||||
@ -139,8 +139,8 @@ class AgnosticGridOut:
|
||||
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)
|
||||
|
||||
@ -157,7 +157,9 @@ class AgnosticGridOut:
|
||||
:class:`~motor.MotorGridOut` now opens itself on demand, calling
|
||||
``open`` explicitly is rarely needed.
|
||||
"""
|
||||
return self._framework.chain_return_value(self._open(), self.get_io_loop(), self)
|
||||
return self._framework.chain_return_value(self._ensure_file(),
|
||||
self.get_io_loop(),
|
||||
self)
|
||||
|
||||
def get_io_loop(self):
|
||||
return self.io_loop
|
||||
@ -176,7 +178,7 @@ class AgnosticGridOut:
|
||||
@tornado.web.asynchronous
|
||||
@gen.coroutine
|
||||
def get(self, filename):
|
||||
db = self.settings["db"]
|
||||
db = self.settings['db']
|
||||
fs = await motor.MotorGridFSBucket(db())
|
||||
try:
|
||||
gridout = await fs.open_download_stream_by_name(filename)
|
||||
@ -202,31 +204,29 @@ class AgnosticGridOut:
|
||||
written += len(chunk)
|
||||
|
||||
|
||||
class AgnosticGridIn:
|
||||
__motor_class_name__ = "MotorGridIn"
|
||||
class AgnosticGridIn(object):
|
||||
__motor_class_name__ = 'MotorGridIn'
|
||||
__delegate_class__ = gridfs.GridIn
|
||||
|
||||
__getattr__ = DelegateMethod()
|
||||
_id = ReadOnlyProperty()
|
||||
abort = AsyncCommand()
|
||||
chunk_size = ReadOnlyProperty()
|
||||
closed = ReadOnlyProperty()
|
||||
close = AsyncCommand()
|
||||
__getattr__ = DelegateMethod()
|
||||
_id = ReadOnlyProperty()
|
||||
abort = AsyncCommand()
|
||||
chunk_size = ReadOnlyProperty()
|
||||
closed = ReadOnlyProperty()
|
||||
close = AsyncCommand()
|
||||
content_type = ReadOnlyProperty()
|
||||
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="""
|
||||
filename = ReadOnlyProperty()
|
||||
length = ReadOnlyProperty()
|
||||
md5 = ReadOnlyProperty()
|
||||
name = ReadOnlyProperty()
|
||||
read = DelegateMethod()
|
||||
readable = DelegateMethod()
|
||||
seekable = DelegateMethod()
|
||||
upload_date = ReadOnlyProperty()
|
||||
write = AsyncCommand().unwrap('MotorGridOut')
|
||||
writeable = DelegateMethod()
|
||||
writelines = AsyncCommand().unwrap('MotorGridOut')
|
||||
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
|
||||
@ -239,23 +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, session=None, **kwargs):
|
||||
def __init__(self, root_collection, delegate=None, session=None,
|
||||
disable_md5=False, **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:`~pymongo.objectid.ObjectId`) - this ``"_id"`` must
|
||||
:class:`~bson.objectid.ObjectId`) - this ``"_id"`` must
|
||||
not have already been used for another file
|
||||
|
||||
- ``"filename"``: human name for the file
|
||||
@ -277,72 +277,63 @@ Metadata set on the file appears as attributes on a
|
||||
- `session` (optional): a
|
||||
:class:`~pymongo.client_session.ClientSession` to use for all
|
||||
commands
|
||||
- `disable_md5` (optional): When True, an MD5 checksum will not be
|
||||
computed for the uploaded file. Useful in environments where
|
||||
MD5 cannot be used for regulatory or other reasons. Defaults to
|
||||
False.
|
||||
- `**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()
|
||||
# Short cut.
|
||||
self.delegate = delegate or self.__delegate_class__(
|
||||
root_collection.delegate, session=session, **kwargs
|
||||
)
|
||||
if delegate:
|
||||
# Short cut.
|
||||
self.delegate = delegate
|
||||
else:
|
||||
self.delegate = self.__delegate_class__(
|
||||
root_collection.delegate,
|
||||
session=session,
|
||||
disable_md5=disable_md5,
|
||||
**kwargs)
|
||||
|
||||
# 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._exit(exc_type, exc_val, exc_tb)
|
||||
await self.close()
|
||||
|
||||
def get_io_loop(self):
|
||||
return self.io_loop
|
||||
|
||||
|
||||
class AgnosticGridFSBucket:
|
||||
__motor_class_name__ = "MotorGridFSBucket"
|
||||
class AgnosticGridFSBucket(object):
|
||||
__motor_class_name__ = 'MotorGridFSBucket'
|
||||
__delegate_class__ = gridfs.GridFSBucket
|
||||
|
||||
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)
|
||||
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()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
database,
|
||||
bucket_name="fs",
|
||||
chunk_size_bytes=DEFAULT_CHUNK_SIZE,
|
||||
write_concern=None,
|
||||
read_preference=None,
|
||||
collection=None,
|
||||
):
|
||||
def __init__(self, database, bucket_name="fs", disable_md5=False,
|
||||
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`
|
||||
@ -362,12 +353,12 @@ class AgnosticGridFSBucket:
|
||||
(the default) db.write_concern is used.
|
||||
- `read_preference` (optional): The read preference to use. If
|
||||
``None`` (the default) db.read_preference is used.
|
||||
- `disable_md5` (optional): When True, MD5 checksums will not be
|
||||
computed for uploaded files. Useful in environments where MD5
|
||||
cannot be used for regulatory or other reasons. Defaults to False.
|
||||
- `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.
|
||||
@ -379,31 +370,31 @@ class AgnosticGridFSBucket:
|
||||
"""
|
||||
# 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,
|
||||
)
|
||||
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__)
|
||||
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}"
|
||||
)
|
||||
"First argument to %s must be MotorDatabase, not %r" % (
|
||||
self.__class__, database))
|
||||
|
||||
self.io_loop = database.get_io_loop()
|
||||
self.collection = database.get_collection(
|
||||
bucket_name, write_concern=write_concern, read_preference=read_preference
|
||||
)
|
||||
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,
|
||||
)
|
||||
disable_md5=disable_md5)
|
||||
|
||||
def get_io_loop(self):
|
||||
return self.io_loop
|
||||
@ -411,24 +402,27 @@ class AgnosticGridFSBucket:
|
||||
def wrap(self, obj):
|
||||
if obj.__class__ is grid_file.GridIn:
|
||||
grid_in_class = create_class_with_framework(
|
||||
AgnosticGridIn, self._framework, self.__module__
|
||||
)
|
||||
AgnosticGridIn, self._framework, self.__module__)
|
||||
|
||||
return grid_in_class(root_collection=self.collection, delegate=obj)
|
||||
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__
|
||||
)
|
||||
AgnosticGridOut, self._framework, self.__module__)
|
||||
|
||||
return grid_out_class(root_collection=self.collection, delegate=obj)
|
||||
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__
|
||||
)
|
||||
AgnosticGridOutCursor, self._framework, self.__module__)
|
||||
|
||||
return grid_out_class(cursor=obj, collection=self.collection)
|
||||
return grid_out_class(
|
||||
cursor=obj,
|
||||
collection=self.collection)
|
||||
|
||||
def find(self, *args, **kwargs):
|
||||
"""Find and return the files collection documents that match ``filter``.
|
||||
@ -485,20 +479,6 @@ class AgnosticGridFSBucket:
|
||||
"""
|
||||
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()
|
||||
|
||||
@ -1,182 +0,0 @@
|
||||
# Copyright 2023-present MongoDB, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import os
|
||||
from collections.abc import Iterable, Mapping
|
||||
from typing import Any, NoReturn, Optional
|
||||
|
||||
from bson import ObjectId
|
||||
from gridfs import DEFAULT_CHUNK_SIZE, GridFSBucket, GridIn, GridOut, GridOutCursor # noqa: F401
|
||||
from pymongo import WriteConcern
|
||||
from pymongo.read_preferences import _ServerMode
|
||||
|
||||
from motor.core import (
|
||||
AgnosticClientSession,
|
||||
AgnosticCollection,
|
||||
AgnosticCursor,
|
||||
AgnosticDatabase,
|
||||
)
|
||||
|
||||
_SEEK_SET = os.SEEK_SET
|
||||
_SEEK_CUR = os.SEEK_CUR
|
||||
_SEEK_END = os.SEEK_END
|
||||
|
||||
class AgnosticGridOutCursor(AgnosticCursor):
|
||||
__motor_class_name__: str
|
||||
__delegate_class__: type[GridOutCursor]
|
||||
def next_object(self) -> AgnosticGridOutCursor: ...
|
||||
|
||||
class AgnosticGridOut:
|
||||
__motor_class_name__: str
|
||||
__delegate_class__: type[GridOut]
|
||||
_id: Any
|
||||
aliases: Optional[list[str]]
|
||||
chunk_size: int
|
||||
filename: Optional[str]
|
||||
name: Optional[str]
|
||||
content_type: Optional[str]
|
||||
length: int
|
||||
upload_date: datetime.datetime
|
||||
metadata: Optional[Mapping[str, Any]]
|
||||
async def _open(self) -> None: ...
|
||||
def close(self) -> None: ...
|
||||
async def read(self, size: int = -1) -> NoReturn: ...
|
||||
def readable(self) -> bool: ...
|
||||
async def readchunk(self) -> bytes: ...
|
||||
async def readline(self, size: int = -1) -> bytes: ...
|
||||
def seek(self, pos: int, whence: int = ...) -> int: ...
|
||||
def seekable(self) -> bool: ...
|
||||
def tell(self) -> int: ...
|
||||
def write(self, data: Any) -> None: ...
|
||||
def __init__(
|
||||
self,
|
||||
root_collection: AgnosticCollection,
|
||||
file_id: Optional[int] = None,
|
||||
file_document: Optional[Any] = None,
|
||||
delegate: Any = None,
|
||||
session: Optional[AgnosticClientSession] = None,
|
||||
) -> None: ...
|
||||
def __aiter__(self) -> AgnosticGridOut: ...
|
||||
async def __anext__(self) -> bytes: ...
|
||||
def __getattr__(self, item: str) -> Any: ...
|
||||
def open(self) -> Any: ...
|
||||
def get_io_loop(self) -> Any: ...
|
||||
async def stream_to_handler(self, request_handler: Any) -> None: ...
|
||||
|
||||
class AgnosticGridIn:
|
||||
__motor_class_name__: str
|
||||
__delegate_class__: type[GridIn]
|
||||
__getattr__: Any
|
||||
_id: Any
|
||||
filename: str
|
||||
name: str
|
||||
content_type: Optional[str]
|
||||
length: int
|
||||
chunk_size: int
|
||||
upload_date: datetime.datetime
|
||||
|
||||
async def abort(self) -> None: ...
|
||||
def closed(self) -> bool: ...
|
||||
async def close(self) -> None: ...
|
||||
def read(self, size: int = -1) -> NoReturn: ...
|
||||
def readable(self) -> bool: ...
|
||||
def seekable(self) -> bool: ...
|
||||
async def write(self, data: Any) -> None: ...
|
||||
def writeable(self) -> bool: ...
|
||||
async def writelines(self, sequence: Iterable[Any]) -> None: ...
|
||||
async def _exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> Any: ...
|
||||
async def set(self, name: str, value: Any) -> None: ...
|
||||
def __init__(
|
||||
self,
|
||||
root_collection: AgnosticCollection,
|
||||
delegate: Any = None,
|
||||
session: Optional[AgnosticClientSession] = None,
|
||||
**kwargs: Any,
|
||||
) -> None: ...
|
||||
async def __aenter__(self) -> AgnosticGridIn: ...
|
||||
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
|
||||
def get_io_loop(self) -> Any: ...
|
||||
|
||||
class AgnosticGridFSBucket:
|
||||
__motor_class_name__: str
|
||||
__delegate_class__: type[GridFSBucket]
|
||||
async def delete(
|
||||
self, file_id: Any, session: Optional[AgnosticClientSession] = None
|
||||
) -> None: ...
|
||||
async def download_to_stream(
|
||||
self, file_id: Any, destination: Any, session: Optional[AgnosticClientSession] = None
|
||||
) -> None: ...
|
||||
async def download_to_stream_by_name(
|
||||
self,
|
||||
filename: str,
|
||||
destination: Any,
|
||||
revision: int = -1,
|
||||
session: Optional[AgnosticClientSession] = None,
|
||||
) -> None: ...
|
||||
async def open_download_stream_by_name(
|
||||
self, filename: str, revision: int = -1, session: Optional[AgnosticClientSession] = None
|
||||
) -> AgnosticGridOut: ...
|
||||
async def open_download_stream(
|
||||
self, file_id: Any, session: Optional[AgnosticClientSession] = None
|
||||
) -> AgnosticGridOut: ...
|
||||
def open_upload_stream(
|
||||
self,
|
||||
filename: str,
|
||||
chunk_size_bytes: Optional[int] = None,
|
||||
metadata: Optional[Mapping[str, Any]] = None,
|
||||
session: Optional[AgnosticClientSession] = None,
|
||||
) -> AgnosticGridIn: ...
|
||||
def open_upload_stream_with_id(
|
||||
self,
|
||||
file_id: Any,
|
||||
filename: str,
|
||||
chunk_size_bytes: Optional[int] = None,
|
||||
metadata: Optional[Mapping[str, Any]] = None,
|
||||
session: Optional[AgnosticClientSession] = None,
|
||||
) -> AgnosticGridIn: ...
|
||||
async def rename(
|
||||
self, file_id: Any, new_filename: str, session: Optional[AgnosticClientSession] = None
|
||||
) -> None: ...
|
||||
async def upload_from_stream(
|
||||
self,
|
||||
filename: str,
|
||||
source: Any,
|
||||
chunk_size_bytes: Optional[int] = None,
|
||||
metadata: Optional[Mapping[str, Any]] = None,
|
||||
session: Optional[AgnosticClientSession] = None,
|
||||
) -> ObjectId: ...
|
||||
async def upload_from_stream_with_id(
|
||||
self,
|
||||
file_id: Any,
|
||||
filename: str,
|
||||
source: Any,
|
||||
chunk_size_bytes: Optional[int] = None,
|
||||
metadata: Optional[Mapping[str, Any]] = None,
|
||||
session: Optional[AgnosticClientSession] = None,
|
||||
) -> None: ...
|
||||
def __init__(
|
||||
self,
|
||||
database: AgnosticDatabase,
|
||||
bucket_name: str = "fs",
|
||||
chunk_size_bytes: int = ...,
|
||||
write_concern: Optional[WriteConcern] = None,
|
||||
read_preference: Optional[_ServerMode] = None,
|
||||
collection: Optional[str] = None,
|
||||
) -> None: ...
|
||||
def get_io_loop(self) -> Any: ...
|
||||
def wrap(self, obj: Any) -> Any: ...
|
||||
def find(self, *args: Any, **kwargs: Any) -> AgnosticGridOutCursor: ...
|
||||
|
||||
def _hash_gridout(gridout: AgnosticGridOut) -> str: ...
|
||||
@ -16,26 +16,14 @@
|
||||
|
||||
from . import core, motor_gridfs
|
||||
from .frameworks import tornado as tornado_framework
|
||||
from .metaprogramming import T, create_class_with_framework
|
||||
from .metaprogramming import create_class_with_framework
|
||||
|
||||
__all__ = [
|
||||
"MotorClient",
|
||||
"MotorClientSession",
|
||||
"MotorDatabase",
|
||||
"MotorCollection",
|
||||
"MotorCursor",
|
||||
"MotorCommandCursor",
|
||||
"MotorChangeStream",
|
||||
"MotorGridFSBucket",
|
||||
"MotorGridIn",
|
||||
"MotorGridOut",
|
||||
"MotorGridOutCursor",
|
||||
"MotorClientEncryption",
|
||||
]
|
||||
__all__ = ['MotorClient', 'MotorClientEncryption']
|
||||
|
||||
|
||||
def create_motor_class(cls: T) -> T:
|
||||
return create_class_with_framework(cls, tornado_framework, "motor.motor_tornado")
|
||||
def create_motor_class(cls):
|
||||
return create_class_with_framework(cls, tornado_framework,
|
||||
'motor.motor_tornado')
|
||||
|
||||
|
||||
MotorClient = create_motor_class(core.AgnosticClient)
|
||||
|
||||
@ -1,259 +0,0 @@
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from bson import Code, CodecOptions, Timestamp
|
||||
from bson.raw_bson import RawBSONDocument
|
||||
from pymongo.client_session import TransactionOptions
|
||||
from pymongo.cursor_shared import _Hint, _Sort
|
||||
from pymongo.read_concern import ReadConcern
|
||||
from pymongo.read_preferences import ReadPreference, _ServerMode
|
||||
from pymongo.typings import _CollationIn, _DocumentType, _DocumentTypeArg, _Pipeline
|
||||
from pymongo.write_concern import WriteConcern
|
||||
|
||||
from motor import core, motor_gridfs
|
||||
|
||||
__all__: list[str] = [
|
||||
"MotorClient",
|
||||
"MotorClientSession",
|
||||
"MotorDatabase",
|
||||
"MotorCollection",
|
||||
"MotorCursor",
|
||||
"MotorCommandCursor",
|
||||
"MotorChangeStream",
|
||||
"MotorGridFSBucket",
|
||||
"MotorGridIn",
|
||||
"MotorGridOut",
|
||||
"MotorGridOutCursor",
|
||||
"MotorClientEncryption",
|
||||
]
|
||||
|
||||
class MotorClient(core.AgnosticClient[_DocumentType]):
|
||||
def get_database(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
|
||||
read_preference: Optional[_ServerMode] = None,
|
||||
write_concern: Optional[WriteConcern] = None,
|
||||
read_concern: Optional[ReadConcern] = None,
|
||||
) -> MotorDatabase[_DocumentType]: ...
|
||||
def get_default_database(
|
||||
self,
|
||||
default: Optional[str] = None,
|
||||
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
|
||||
read_preference: Optional[_ServerMode] = None,
|
||||
write_concern: Optional[WriteConcern] = None,
|
||||
read_concern: Optional[ReadConcern] = None,
|
||||
) -> MotorDatabase[_DocumentType]: ...
|
||||
async def list_databases(
|
||||
self,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
comment: Optional[Any] = None,
|
||||
**kwargs: Any,
|
||||
) -> MotorCommandCursor[dict[str, Any]]: ...
|
||||
async def start_session(
|
||||
self,
|
||||
causal_consistency: Optional[bool] = None,
|
||||
default_transaction_options: Optional[TransactionOptions] = None,
|
||||
snapshot: Optional[bool] = False,
|
||||
) -> MotorClientSession: ...
|
||||
def watch(
|
||||
self,
|
||||
pipeline: Optional[_Pipeline] = None,
|
||||
full_document: Optional[str] = None,
|
||||
resume_after: Optional[Mapping[str, Any]] = None,
|
||||
max_await_time_ms: Optional[int] = None,
|
||||
batch_size: Optional[int] = None,
|
||||
collation: Optional[_CollationIn] = None,
|
||||
start_at_operation_time: Optional[Timestamp] = None,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
start_after: Optional[Mapping[str, Any]] = None,
|
||||
comment: Optional[str] = None,
|
||||
full_document_before_change: Optional[str] = None,
|
||||
show_expanded_events: Optional[bool] = None,
|
||||
) -> MotorChangeStream[_DocumentType]: ...
|
||||
def __getattr__(self, name: str) -> MotorDatabase[_DocumentType]: ...
|
||||
def __getitem__(self, name: str) -> MotorDatabase[_DocumentType]: ...
|
||||
|
||||
class MotorClientSession(core.AgnosticClientSession):
|
||||
@property
|
||||
def client(self) -> MotorClient: ...
|
||||
async def __aenter__(self) -> MotorClientSession: ...
|
||||
|
||||
class MotorDatabase(core.AgnosticDatabase[_DocumentType]):
|
||||
async def cursor_command(
|
||||
self,
|
||||
command: Union[str, MutableMapping[str, Any]],
|
||||
value: Any = 1,
|
||||
read_preference: Optional[_ServerMode] = None,
|
||||
codec_options: Optional[CodecOptions[core._CodecDocumentType]] = None,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
comment: Optional[Any] = None,
|
||||
max_await_time_ms: Optional[int] = None,
|
||||
**kwargs: Any,
|
||||
) -> MotorCommandCursor[_DocumentType]: ...
|
||||
async def create_collection(
|
||||
self,
|
||||
name: str,
|
||||
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
|
||||
read_preference: Optional[_ServerMode] = None,
|
||||
write_concern: Optional[WriteConcern] = None,
|
||||
read_concern: Optional[ReadConcern] = None,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
check_exists: Optional[bool] = True,
|
||||
**kwargs: Any,
|
||||
) -> MotorCollection[_DocumentType]: ...
|
||||
def get_collection(
|
||||
self,
|
||||
name: str,
|
||||
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
|
||||
read_preference: Optional[_ServerMode] = None,
|
||||
write_concern: Optional[WriteConcern] = None,
|
||||
read_concern: Optional[ReadConcern] = None,
|
||||
) -> MotorCollection[_DocumentType]: ...
|
||||
async def list_collections(
|
||||
self,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
filter: Optional[Mapping[str, Any]] = None,
|
||||
comment: Optional[Any] = None,
|
||||
**kwargs: Any,
|
||||
) -> MotorCommandCursor[MutableMapping[str, Any]]: ...
|
||||
def with_options(
|
||||
self,
|
||||
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
|
||||
read_preference: Optional[_ServerMode] = None,
|
||||
write_concern: Optional[WriteConcern] = None,
|
||||
read_concern: Optional[ReadConcern] = None,
|
||||
) -> MotorDatabase[_DocumentType]: ...
|
||||
def aggregate(
|
||||
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
|
||||
) -> MotorLatentCommandCursor[_DocumentType]: ...
|
||||
def watch(
|
||||
self,
|
||||
pipeline: Optional[_Pipeline] = None,
|
||||
full_document: Optional[str] = None,
|
||||
resume_after: Optional[Mapping[str, Any]] = None,
|
||||
max_await_time_ms: Optional[int] = None,
|
||||
batch_size: Optional[int] = None,
|
||||
collation: Optional[_CollationIn] = None,
|
||||
start_at_operation_time: Optional[Timestamp] = None,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
start_after: Optional[Mapping[str, Any]] = None,
|
||||
comment: Optional[Any] = None,
|
||||
full_document_before_change: Optional[str] = None,
|
||||
show_expanded_events: Optional[bool] = None,
|
||||
) -> MotorChangeStream[_DocumentType]: ...
|
||||
@property
|
||||
def client(self) -> MotorClient[_DocumentType]: ...
|
||||
def __getattr__(self, name: str) -> MotorCollection[_DocumentType]: ...
|
||||
def __getitem__(self, name: str) -> MotorCollection[_DocumentType]: ...
|
||||
|
||||
class MotorCollection(core.AgnosticCollection[_DocumentType]):
|
||||
def with_options(
|
||||
self,
|
||||
codec_options: Optional[CodecOptions] = None,
|
||||
read_preference: Optional[ReadPreference] = None,
|
||||
write_concern: Optional[WriteConcern] = None,
|
||||
read_concern: Optional[ReadConcern] = None,
|
||||
) -> MotorCollection[_DocumentType]: ...
|
||||
def list_search_indexes(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
comment: Optional[Any] = None,
|
||||
**kwargs: Any,
|
||||
) -> MotorLatentCommandCursor[Mapping[str, Any]]: ...
|
||||
def __getattr__(self, name: str) -> MotorCollection[_DocumentType]: ...
|
||||
def __getitem__(self, name: str) -> MotorCollection[_DocumentType]: ...
|
||||
def find(self, *args: Any, **kwargs: Any) -> MotorCursor[_DocumentType]: ...
|
||||
def find_raw_batches(self, *args: Any, **kwargs: Any) -> MotorRawBatchCursor[_DocumentType]: ...
|
||||
def aggregate(
|
||||
self, pipeline: _Pipeline, *args: Any, **kwargs: Any
|
||||
) -> MotorCommandCursor[_DocumentType]: ...
|
||||
def aggregate_raw_batches(
|
||||
self, pipeline: _Pipeline, **kwargs: Any
|
||||
) -> MotorRawBatchCursor[_DocumentType]: ...
|
||||
def list_indexes(
|
||||
self, session: Optional[core.AgnosticClientSession] = None, **kwargs: Any
|
||||
) -> MotorLatentCommandCursor[MutableMapping[str, Any]]: ...
|
||||
|
||||
class MotorLatentCommandCursor(core.AgnosticLatentCommandCursor[_DocumentType]): ...
|
||||
|
||||
class MotorCursor(core.AgnosticCursor[_DocumentType]):
|
||||
def collation(self, collation: Optional[_CollationIn]) -> MotorCursor[_DocumentType]: ...
|
||||
def add_option(self, mask: int) -> MotorCursor[_DocumentType]: ...
|
||||
def remove_option(self, mask: int) -> MotorCursor[_DocumentType]: ...
|
||||
def limit(self, limit: int) -> MotorCursor[_DocumentType]: ...
|
||||
def skip(self, skip: int) -> MotorCursor[_DocumentType]: ...
|
||||
def max_scan(self, max_scan: Optional[int]) -> MotorCursor[_DocumentType]: ...
|
||||
def sort(
|
||||
self, key_or_list: _Hint, direction: Optional[Union[int, str]] = None
|
||||
) -> MotorCursor[_DocumentType]: ...
|
||||
def hint(self, index: Optional[_Hint]) -> MotorCursor[_DocumentType]: ...
|
||||
def where(self, code: Union[str, Code]) -> MotorCursor[_DocumentType]: ...
|
||||
def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> MotorCursor[_DocumentType]: ...
|
||||
def max_time_ms(self, max_time_ms: Optional[int]) -> MotorCursor[_DocumentType]: ...
|
||||
def min(self, spec: _Sort) -> MotorCursor[_DocumentType]: ...
|
||||
def max(self, spec: _Sort) -> MotorCursor[_DocumentType]: ...
|
||||
def comment(self, comment: Any) -> MotorCursor[_DocumentType]: ...
|
||||
def allow_disk_use(self, allow_disk_use: bool) -> MotorCursor[_DocumentType]: ...
|
||||
def rewind(self) -> MotorCursor[_DocumentType]: ...
|
||||
def clone(self) -> MotorCursor[_DocumentType]: ...
|
||||
def __copy__(self) -> MotorCursor[_DocumentType]: ...
|
||||
def __deepcopy__(self, memo: Any) -> MotorCursor[_DocumentType]: ...
|
||||
|
||||
class MotorRawBatchCursor(core.AgnosticRawBatchCursor[_DocumentType]): ...
|
||||
class MotorCommandCursor(core.AgnosticCommandCursor[_DocumentType]): ...
|
||||
class MotorRawBatchCommandCursor(core.AgnosticRawBatchCommandCursor[_DocumentType]): ...
|
||||
|
||||
class MotorChangeStream(core.AgnosticChangeStream[_DocumentType]):
|
||||
def __aiter__(self) -> MotorChangeStream[_DocumentType]: ...
|
||||
async def __aenter__(self) -> MotorChangeStream[_DocumentType]: ...
|
||||
|
||||
class MotorClientEncryption(core.AgnosticClientEncryption[_DocumentType]):
|
||||
async def __aenter__(self) -> MotorClientEncryption[_DocumentType]: ...
|
||||
async def get_keys(self) -> MotorCursor[RawBSONDocument]: ...
|
||||
async def create_encrypted_collection(
|
||||
self,
|
||||
database: core.AgnosticDatabase[_DocumentTypeArg],
|
||||
name: str,
|
||||
encrypted_fields: Mapping[str, Any],
|
||||
kms_provider: Optional[str] = None,
|
||||
master_key: Optional[Mapping[str, Any]] = None,
|
||||
**kwargs: Any,
|
||||
) -> tuple[MotorCollection[_DocumentTypeArg], Mapping[str, Any]]: ...
|
||||
|
||||
class MotorGridOutCursor(motor_gridfs.AgnosticGridOutCursor):
|
||||
def next_object(self) -> MotorGridOutCursor: ...
|
||||
|
||||
class MotorGridOut(motor_gridfs.AgnosticGridOut):
|
||||
def __aiter__(self) -> MotorGridOut: ...
|
||||
|
||||
class MotorGridIn(motor_gridfs.AgnosticGridIn):
|
||||
async def __aenter__(self) -> MotorGridIn: ...
|
||||
|
||||
class MotorGridFSBucket(motor_gridfs.AgnosticGridFSBucket):
|
||||
async def open_download_stream_by_name(
|
||||
self,
|
||||
filename: str,
|
||||
revision: int = -1,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
) -> MotorGridOut: ...
|
||||
async def open_download_stream(
|
||||
self, file_id: Any, session: Optional[core.AgnosticClientSession] = None
|
||||
) -> MotorGridOut: ...
|
||||
def open_upload_stream(
|
||||
self,
|
||||
filename: str,
|
||||
chunk_size_bytes: Optional[int] = None,
|
||||
metadata: Optional[Mapping[str, Any]] = None,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
) -> MotorGridIn: ...
|
||||
def open_upload_stream_with_id(
|
||||
self,
|
||||
file_id: Any,
|
||||
filename: str,
|
||||
chunk_size_bytes: Optional[int] = None,
|
||||
metadata: Optional[Mapping[str, Any]] = None,
|
||||
session: Optional[core.AgnosticClientSession] = None,
|
||||
) -> MotorGridIn: ...
|
||||
def find(self, *args: Any, **kwargs: Any) -> MotorGridOutCursor: ...
|
||||
@ -1,2 +0,0 @@
|
||||
# PEP-561 Support File.
|
||||
# "Package maintainers who wish to support type checking of their code MUST add a marker file named py.typed to their package supporting typing".
|
||||
48
motor/web.py
48
motor/web.py
@ -13,26 +13,26 @@
|
||||
# limitations under the License.
|
||||
|
||||
"""Utilities for using Motor with Tornado web applications."""
|
||||
|
||||
import datetime
|
||||
import email.utils
|
||||
import mimetypes
|
||||
import time
|
||||
|
||||
import gridfs
|
||||
import tornado.web
|
||||
from tornado import gen
|
||||
|
||||
import gridfs
|
||||
import motor
|
||||
from motor.motor_gridfs import _hash_gridout
|
||||
|
||||
# mypy: disable-error-code="no-untyped-def,no-untyped-call"
|
||||
|
||||
# TODO: this class is not a drop-in replacement for StaticFileHandler.
|
||||
# StaticFileHandler provides class method make_static_url, which appends
|
||||
# an checksum of the static file's contents. Templates thus can do
|
||||
# an MD5 of the static file's contents. Templates thus can do
|
||||
# {{ static_url('image.png') }} and get "/static/image.png?v=1234abcdef",
|
||||
# which is cached forever. Problem is, it calculates the checksum synchronously.
|
||||
# which is cached forever. Problem is, it calculates the MD5 synchronously.
|
||||
# Two options: keep a synchronous GridFS available to get each grid file's
|
||||
# checksum synchronously for every static_url call, or find some other idiom.
|
||||
# MD5 synchronously for every static_url call, or find some other idiom.
|
||||
|
||||
|
||||
class GridFSHandler(tornado.web.RequestHandler):
|
||||
@ -42,19 +42,16 @@ class GridFSHandler(tornado.web.RequestHandler):
|
||||
.. code-block:: python
|
||||
|
||||
db = motor.MotorClient().my_database
|
||||
application = web.Application(
|
||||
[
|
||||
(r"/static/(.*)", web.GridFSHandler, {"database": db}),
|
||||
]
|
||||
)
|
||||
application = web.Application([
|
||||
(r"/static/(.*)", web.GridFSHandler, {"database": db}),
|
||||
])
|
||||
|
||||
By default, requests' If-Modified-Since headers are honored, but no
|
||||
specific cache-control timeout is sent to clients. Thus each request for
|
||||
a GridFS file requires a quick check of the file's ``uploadDate`` in
|
||||
MongoDB. Override :meth:`get_cache_time` in a subclass to customize this.
|
||||
"""
|
||||
|
||||
def initialize(self, database, root_collection="fs"):
|
||||
def initialize(self, database, root_collection='fs'):
|
||||
self.database = database
|
||||
self.root_collection = root_collection
|
||||
|
||||
@ -100,16 +97,14 @@ class GridFSHandler(tornado.web.RequestHandler):
|
||||
try:
|
||||
gridout = await self.get_gridfs_file(fs, path, self.request)
|
||||
except gridfs.NoFile:
|
||||
raise tornado.web.HTTPError(404) from None
|
||||
raise tornado.web.HTTPError(404)
|
||||
|
||||
# If-Modified-Since header is only good to the second.
|
||||
modified = gridout.upload_date.replace(microsecond=0)
|
||||
self.set_header("Last-Modified", modified)
|
||||
|
||||
# Get the hash for the GridFS file.
|
||||
checksum = _hash_gridout(gridout)
|
||||
|
||||
self.set_header("Etag", '"%s"' % checksum)
|
||||
# MD5 is calculated on the MongoDB server when GridFS file is created
|
||||
self.set_header("Etag", '"%s"' % gridout.md5)
|
||||
|
||||
mime_type = gridout.content_type
|
||||
|
||||
@ -124,11 +119,8 @@ class GridFSHandler(tornado.web.RequestHandler):
|
||||
cache_time = self.get_cache_time(path, modified, mime_type)
|
||||
|
||||
if cache_time > 0:
|
||||
self.set_header(
|
||||
"Expires",
|
||||
datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
|
||||
+ datetime.timedelta(seconds=cache_time),
|
||||
)
|
||||
self.set_header("Expires", datetime.datetime.utcnow() +
|
||||
datetime.timedelta(seconds=cache_time))
|
||||
self.set_header("Cache-Control", "max-age=" + str(cache_time))
|
||||
else:
|
||||
self.set_header("Cache-Control", "public")
|
||||
@ -140,13 +132,12 @@ class GridFSHandler(tornado.web.RequestHandler):
|
||||
ims_value = self.request.headers.get("If-Modified-Since")
|
||||
if ims_value is not None:
|
||||
date_tuple = email.utils.parsedate(ims_value)
|
||||
assert date_tuple is not None
|
||||
|
||||
# If our MotorClient is tz-aware, assume the naive ims_value is in
|
||||
# its time zone.
|
||||
if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple)).replace(
|
||||
tzinfo=modified.tzinfo
|
||||
)
|
||||
if_since = datetime.datetime.fromtimestamp(
|
||||
time.mktime(date_tuple)
|
||||
).replace(tzinfo=modified.tzinfo)
|
||||
|
||||
if if_since >= modified:
|
||||
self.set_status(304)
|
||||
@ -154,7 +145,7 @@ class GridFSHandler(tornado.web.RequestHandler):
|
||||
|
||||
# Same for Etag
|
||||
etag = self.request.headers.get("If-None-Match")
|
||||
if etag is not None and etag.strip('"') == checksum:
|
||||
if etag is not None and etag.strip('"') == gridout.md5:
|
||||
self.set_status(304)
|
||||
return
|
||||
|
||||
@ -181,3 +172,4 @@ class GridFSHandler(tornado.web.RequestHandler):
|
||||
|
||||
def set_extra_headers(self, path, gridout):
|
||||
"""For subclass to add extra headers to the response"""
|
||||
pass
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user