pax_global_header00006660000000000000000000000064137332241610014514gustar00rootroot0000000000000052 comment=c3e51f8c17151c150eed2570ca30024333af79ef motor-2.3.0/000077500000000000000000000000001373322416100126565ustar00rootroot00000000000000motor-2.3.0/.evergreen/000077500000000000000000000000001373322416100147165ustar00rootroot00000000000000motor-2.3.0/.evergreen/config.yml000066400000000000000000000743421373322416100167200ustar00rootroot00000000000000######################################## # Evergreen Template for MongoDB Drivers ######################################## # When a task that used to pass starts to fail # Go through all versions that may have been skipped to detect # when the task started failing stepback: true # Mark a failure as a system/bootstrap failure (purple box) rather then a task # failure by default. # Actual testing tasks are marked with `type: test` command_type: system # Protect ourself against rogue test case, or curl gone wild, that runs forever # Good rule of thumb: the averageish length a task takes, times 5 # That roughly accounts for variable system performance for various buildvariants exec_timeout_secs: 1800 # 6 minutes is the longest we'll ever run # What to do when evergreen hits the timeout (`post:` tasks are run automatically) timeout: - command: shell.exec params: script: | ls -la functions: "fetch source": # Executes git clone and applies the submitted patch, if any - command: git.get_project params: directory: "src" # Make an evergreen exapanstion file with dynamic values - command: shell.exec params: working_dir: "src" script: | # Get the current unique version of this checkout if [ "${is_patch}" = "true" ]; then CURRENT_VERSION=$(git describe)-patch-${version_id} else CURRENT_VERSION=latest fi export DRIVERS_TOOLS="$(pwd)/../drivers-tools" export PROJECT_DIRECTORY="$(pwd)" # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS) export PROJECT_DIRECTORY=$(cygpath -m $PROJECT_DIRECTORY) fi export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration" export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" export UPLOAD_BUCKET="${project}" cat < expansion.yml CURRENT_VERSION: "$CURRENT_VERSION" DRIVERS_TOOLS: "$DRIVERS_TOOLS" MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME" MONGODB_BINARIES: "$MONGODB_BINARIES" UPLOAD_BUCKET: "$UPLOAD_BUCKET" PROJECT_DIRECTORY: "$PROJECT_DIRECTORY" PREPARE_SHELL: | set -o errexit set -o xtrace export DRIVERS_TOOLS="$DRIVERS_TOOLS" export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME" export MONGODB_BINARIES="$MONGODB_BINARIES" export UPLOAD_BUCKET="$UPLOAD_BUCKET" export PROJECT_DIRECTORY="$PROJECT_DIRECTORY" export TMPDIR="$MONGO_ORCHESTRATION_HOME/db" export PATH="$MONGODB_BINARIES:$PATH" export PROJECT="${project}" export ASYNC_TEST_TIMEOUT=30 EOT # See what we've done cat expansion.yml # Load the expansion file to make an evergreen variable with the current unique version - command: expansions.update params: file: src/expansion.yml "prepare resources": - command: shell.exec params: working_dir: "src" script: | ${PREPARE_SHELL} rm -rf $DRIVERS_TOOLS if [ "${project}" = "drivers-tools" ]; then # If this was a patch build, doing a fresh clone would not actually test the patch cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS else git clone git://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS fi echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config "upload release": - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: ${project}.tar.gz remote_file: ${UPLOAD_BUCKET}/${project}-${CURRENT_VERSION}.tar.gz bucket: mciuploads permissions: public-read content_type: ${content_type|application/x-gzip} # Upload build artifacts that other tasks may depend on # Note this URL needs to be totally unique, while predictable for the next task # so it can automatically download the artifacts "upload build": # Compress and upload the entire build directory - command: archive.targz_pack params: # Example: mongo_c_driver_releng_9dfb7d741efbca16faa7859b9349d7a942273e43_16_11_08_19_29_52.tar.gz target: "${build_id}.tar.gz" source_dir: ${PROJECT_DIRECTORY}/ include: - "./**" - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: ${build_id}.tar.gz # Example: /mciuploads/${UPLOAD_BUCKET}/gcc49/9dfb7d741efbca16faa7859b9349d7a942273e43/debug-compile-nosasl-nossl/mongo_c_driver_releng_9dfb7d741efbca16faa7859b9349d7a942273e43_16_11_08_19_29_52.tar.gz remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${task_name}/${build_id}.tar.gz bucket: mciuploads permissions: public-read content_type: ${content_type|application/x-gzip} "fetch build": - command: shell.exec params: continue_on_err: true script: "set -o xtrace && rm -rf ${PROJECT_DIRECTORY}" - command: s3.get params: aws_key: ${aws_key} aws_secret: ${aws_secret} remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${BUILD_NAME}/${build_id}.tar.gz bucket: mciuploads local_file: build.tar.gz - command: shell.exec params: continue_on_err: true # EVG-1105: Use s3.get extract_to: ./ script: "set -o xtrace && cd .. && rm -rf ${PROJECT_DIRECTORY} && mkdir ${PROJECT_DIRECTORY}/ && tar xf build.tar.gz -C ${PROJECT_DIRECTORY}/" "exec compile script" : - command: shell.exec type: test params: working_dir: "src" script: | ${PREPARE_SHELL} [ -f ${PROJECT_DIRECTORY}/${file} ] && BUILDTOOL="${buildtool}" sh ${PROJECT_DIRECTORY}/${file} || echo "${PROJECT_DIRECTORY}/${file} not available, skipping" "exec script" : - command: shell.exec type: test params: working_dir: "src" script: | ${PREPARE_SHELL} [ -f ${PROJECT_DIRECTORY}/${file} ] && sh ${PROJECT_DIRECTORY}/${file} || echo "${PROJECT_DIRECTORY}/${file} not available, skipping" "upload docs" : - command: shell.exec params: silent: true script: | export AWS_ACCESS_KEY_ID=${aws_key} export AWS_SECRET_ACCESS_KEY=${aws_secret} aws s3 cp ${PROJECT_DIRECTORY}/doc/html s3://mciuploads/${UPLOAD_BUCKET}/docs/${CURRENT_VERSION} --recursive --acl public-read --region us-east-1 - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: ${PROJECT_DIRECTORY}/doc/html/index.html remote_file: ${UPLOAD_BUCKET}/docs/${CURRENT_VERSION}/index.html bucket: mciuploads permissions: public-read content_type: text/html display_name: "Rendered docs" "upload coverage" : - command: shell.exec params: silent: true script: | export AWS_ACCESS_KEY_ID=${aws_key} export AWS_SECRET_ACCESS_KEY=${aws_secret} aws s3 cp ${PROJECT_DIRECTORY}/coverage s3://mciuploads/${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/coverage/ --recursive --acl public-read --region us-east-1 - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: ${PROJECT_DIRECTORY}/coverage/index.html remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/coverage/index.html bucket: mciuploads permissions: public-read content_type: text/html display_name: "Coverage Report" "upload scan artifacts" : - command: shell.exec type: test params: script: | cd if find ${PROJECT_DIRECTORY}/scan -name \*.html | grep -q html; then (cd ${PROJECT_DIRECTORY}/scan && find . -name index.html -exec echo "
  • {}
  • " \;) >> scan.html else echo "No issues found" > scan.html fi - command: shell.exec params: silent: true script: | export AWS_ACCESS_KEY_ID=${aws_key} export AWS_SECRET_ACCESS_KEY=${aws_secret} aws s3 cp ${PROJECT_DIRECTORY}/scan s3://mciuploads/${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/scan/ --recursive --acl public-read --region us-east-1 - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: ${PROJECT_DIRECTORY}/scan.html remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/scan/index.html bucket: mciuploads permissions: public-read content_type: text/html display_name: "Scan Build Report" "upload mo artifacts": - command: shell.exec params: script: | ${PREPARE_SHELL} find $MONGO_ORCHESTRATION_HOME -name \*.log | xargs tar czf mongodb-logs.tar.gz - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: mongodb-logs.tar.gz remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz bucket: mciuploads permissions: public-read content_type: ${content_type|application/x-gzip} display_name: "mongodb-logs.tar.gz" - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: ${DRIVERS_TOOLS}/.evergreen/orchestration/server.log remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-orchestration.log bucket: mciuploads permissions: public-read content_type: ${content_type|text/plain} display_name: "orchestration.log" "upload working dir": - command: archive.targz_pack params: target: "working-dir.tar.gz" source_dir: ${PROJECT_DIRECTORY}/ include: - "./**" - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: working-dir.tar.gz remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-working-dir.tar.gz bucket: mciuploads permissions: public-read content_type: ${content_type|application/x-gzip} display_name: "working-dir.tar.gz" - command: archive.targz_pack params: target: "drivers-dir.tar.gz" source_dir: ${DRIVERS_TOOLS} include: - "./**" exclude_files: # Windows cannot read the mongod *.lock files because they are locked. - "*.lock" - command: s3.put params: aws_key: ${aws_key} aws_secret: ${aws_secret} local_file: drivers-dir.tar.gz remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-drivers-dir.tar.gz bucket: mciuploads permissions: public-read content_type: ${content_type|application/x-gzip} display_name: "drivers-dir.tar.gz" "upload test results": - command: attach.results params: file_location: "${DRIVERS_TOOLS}/results.json" - command: attach.xunit_results params: files: - "src/xunit-results/TEST-*.xml" - "src/xunit-synchro-results" "bootstrap mongo-orchestration": - command: shell.exec params: script: | ${PREPARE_SHELL} MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} AUTH=${AUTH} SSL=${SSL} STORAGE_ENGINE=${STORAGE_ENGINE} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh # run-orchestration generates expansion file with the MONGODB_URI for the cluster - command: expansions.update params: file: mo-expansion.yml "stop mongo-orchestration": - command: shell.exec params: script: | ${PREPARE_SHELL} sh ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh "run tox": - command: shell.exec type: test params: working_dir: "src" script: | ${PREPARE_SHELL} PYTHON_BINARY="${PYTHON_BINARY}" \ TOX_BINARY="${TOX_BINARY}" \ TOX_ENV="${TOX_ENV}" \ INSTALL_TOX="${INSTALL_TOX}" \ VIRTUALENV="${VIRTUALENV}" \ AUTH="${AUTH}" \ SSL="${SSL}" \ CERT_DIR="${DRIVERS_TOOLS}/.evergreen/x509gen" \ sh ${PROJECT_DIRECTORY}/.evergreen/run-tox.sh "run enterprise auth tests": - command: shell.exec type: test params: silent: true working_dir: "src" script: | # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) CLIENT_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem \ CA_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem \ PYTHON_BINARY=${PYTHON_BINARY} \ TOX_ENV=${TOX_ENV} \ SASL_HOST=${sasl_host} \ SASL_PORT=${sasl_port} \ SASL_USER=${sasl_user} \ SASL_PASS=${sasl_pass} \ SASL_DB=${sasl_db} \ PRINCIPAL=${principal} \ GSSAPI_DB=${gssapi_db} \ KEYTAB_BASE64=${keytab_base64} \ PROJECT_DIRECTORY=${PROJECT_DIRECTORY} \ sh ${PROJECT_DIRECTORY}/.evergreen/run-enterprise-auth-tests.sh "cleanup": - command: shell.exec params: script: | ${PREPARE_SHELL} rm -rf $DRIVERS_TOOLS || true "fix absolute paths": - command: shell.exec params: script: | ${PREPARE_SHELL} for filename in $(find ${DRIVERS_TOOLS} -name \*.json); do perl -p -i -e "s|ABSOLUTE_PATH_REPLACEMENT_TOKEN|${DRIVERS_TOOLS}|g" $filename done "windows fix": - command: shell.exec params: script: | ${PREPARE_SHELL} for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do cat $i | tr -d '\r' > $i.new mv $i.new $i done # Copy client certificate because symlinks do not work on Windows. cp ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem ${MONGO_ORCHESTRATION_HOME}/lib/client.pem "make files executable": - command: shell.exec params: script: | ${PREPARE_SHELL} for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do chmod +x $i done "init test-results": - command: shell.exec params: script: | ${PREPARE_SHELL} echo '{"results": [{ "status": "FAIL", "test_file": "Build", "log_raw": "No test-results.json found was created" } ]}' > ${PROJECT_DIRECTORY}/test-results.json "install dependencies": - command: shell.exec params: working_dir: "src" script: | ${PREPARE_SHELL} file="${PROJECT_DIRECTORY}/.evergreen/install-dependencies.sh" # Don't use ${file} syntax here because evergreen treats it as an empty expansion. [ -f "$file" ] && sh $file || echo "$file not available, skipping" pre: - func: "fetch source" - func: "prepare resources" - func: "windows fix" - func: "fix absolute paths" - func: "init test-results" - func: "make files executable" - func: "install dependencies" post: # Disabled, causing timeouts # - func: "upload working dir" - func: "upload mo artifacts" - func: "upload test results" - func: "stop mongo-orchestration" - func: "cleanup" tasks: # Wildcard task. Do you need to find out what tools are available and where? # Throw it here, and execute this task on all buildvariants - name: getdata commands: - command: shell.exec type: test params: script: | set -o xtrace . ${DRIVERS_TOOLS}/.evergreen/download-mongodb.sh || true get_distro || true echo $DISTRO echo $MARCH echo $OS uname -a || true ls /etc/*release* || true cc --version || true gcc --version || true clang --version || true gcov --version || true lcov --version || true llvm-cov --version || true echo $PATH ls -la /usr/local/Cellar/llvm/*/bin/ || true ls -la /usr/local/Cellar/ || true scan-build --version || true genhtml --version || true valgrind --version || true # Test tasks {{{ - name: "test-3.0-standalone" tags: ["3.0", "standalone"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.0" TOPOLOGY: "server" - func: "run tox" - name: "test-3.0-replica_set" tags: ["3.0", "replica_set"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.0" TOPOLOGY: "replica_set" - func: "run tox" - name: "test-3.0-sharded_cluster" tags: ["3.0", "sharded_cluster"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.0" TOPOLOGY: "sharded_cluster" - func: "run tox" - name: "test-3.2-standalone" tags: ["3.2", "standalone"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.2" TOPOLOGY: "server" - func: "run tox" - name: "test-3.2-replica_set" tags: ["3.2", "replica_set"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.2" TOPOLOGY: "replica_set" - func: "run tox" - name: "test-3.2-sharded_cluster" tags: ["3.2", "sharded_cluster"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.2" TOPOLOGY: "sharded_cluster" - func: "run tox" - name: "test-3.4-standalone" tags: ["3.4", "standalone"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.4" TOPOLOGY: "server" - func: "run tox" - name: "test-3.4-replica_set" tags: ["3.4", "replica_set"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.4" TOPOLOGY: "replica_set" - func: "run tox" - name: "test-3.4-sharded_cluster" tags: ["3.4", "sharded_cluster"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.4" TOPOLOGY: "sharded_cluster" - func: "run tox" - name: "test-3.6-standalone" tags: ["3.6", "standalone"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.6" TOPOLOGY: "server" - func: "run tox" - name: "test-3.6-replica_set" tags: ["3.6", "replica_set"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.6" TOPOLOGY: "replica_set" - func: "run tox" - name: "test-3.6-sharded_cluster" tags: ["3.6", "sharded_cluster"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "3.6" TOPOLOGY: "sharded_cluster" - func: "run tox" - name: "test-4.0-standalone" tags: ["4.0", "standalone"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.0" TOPOLOGY: "server" - func: "run tox" - name: "test-4.0-replica_set" tags: ["4.0", "replica_set"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.0" TOPOLOGY: "replica_set" - func: "run tox" - name: "test-4.0-sharded_cluster" tags: ["4.0", "sharded_cluster"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.0" TOPOLOGY: "sharded_cluster" - func: "run tox" - name: "test-4.2-standalone" tags: ["4.2", "standalone"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.2" TOPOLOGY: "server" - func: "run tox" - name: "test-4.2-replica_set" tags: ["4.2", "replica_set"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.2" TOPOLOGY: "replica_set" - func: "run tox" - name: "test-4.2-sharded_cluster" tags: ["4.2", "sharded_cluster"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.2" TOPOLOGY: "sharded_cluster" - func: "run tox" - name: "test-4.4-standalone" tags: ["4.4", "standalone"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.4" TOPOLOGY: "server" - func: "run tox" - name: "test-4.4-replica_set" tags: ["4.4", "replica_set"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.4" TOPOLOGY: "replica_set" - func: "run tox" - name: "test-4.4-sharded_cluster" tags: ["4.4", "sharded_cluster"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.4" TOPOLOGY: "sharded_cluster" - func: "run tox" - name: "test-latest-standalone" tags: ["latest", "standalone"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "latest" TOPOLOGY: "server" - func: "run tox" - name: "test-latest-replica_set" tags: ["latest", "replica_set"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "latest" TOPOLOGY: "replica_set" - func: "run tox" - name: "test-latest-sharded_cluster" tags: ["latest", "sharded_cluster"] commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "latest" TOPOLOGY: "sharded_cluster" - func: "run tox" - name: "test-enterprise-auth" commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "latest" TOPOLOGY: "server" - func: "run enterprise auth tests" - name: "docs" commands: - func: "run tox" vars: TOX_ENV: py3-sphinx-docs - name: "doctest" commands: - func: "bootstrap mongo-orchestration" vars: VERSION: "4.0" TOPOLOGY: "server" - func: "run tox" vars: TOX_ENV: py3-sphinx-doctest # }}} axes: - id: ssl display_name: SSL values: - id: ssl display_name: SSL variables: SSL: "ssl" AUTH: "auth" - id: nossl display_name: NoSSL variables: SSL: "nossl" AUTH: "noauth" - id: tox-env display_name: "Tox Env" values: - id: "tornado5-pypy35" variables: TOX_ENV: "tornado5-pypy35" PYTHON_BINARY: "/opt/python/pypy3.5/bin/pypy3" - id: "tornado5-pypy36" variables: TOX_ENV: "tornado5-pypy36" PYTHON_BINARY: "/opt/python/pypy3.6/bin/pypy3" - id: "tornado5-py35" variables: TOX_ENV: "tornado5-py35" PYTHON_BINARY: "/opt/python/3.5/bin/python3" - id: "tornado5-py36" variables: TOX_ENV: "tornado5-py36" PYTHON_BINARY: "/opt/python/3.6/bin/python3" - id: "tornado5-py37" variables: TOX_ENV: "tornado5-py37" PYTHON_BINARY: "/opt/python/3.7/bin/python3" - id: "tornado6-pypy35" variables: TOX_ENV: "tornado6-pypy35" PYTHON_BINARY: "/opt/python/pypy3.5/bin/pypy3" - id: "tornado6-pypy36" variables: TOX_ENV: "tornado6-pypy36" PYTHON_BINARY: "/opt/python/pypy3.6/bin/pypy3" - id: "tornado6-py35" variables: TOX_ENV: "tornado6-py35" PYTHON_BINARY: "/opt/python/3.5/bin/python3" - id: "tornado6-py36" variables: TOX_ENV: "tornado6-py36" PYTHON_BINARY: "/opt/python/3.6/bin/python3" - id: "tornado6-py37" variables: TOX_ENV: "tornado6-py37" PYTHON_BINARY: "/opt/python/3.7/bin/python3" - id: "tornado6-py38" variables: TOX_ENV: "tornado6-py38" PYTHON_BINARY: "/opt/python/3.8/bin/python3" - id: "tornado_git-py35" variables: TOX_ENV: "tornado_git-py35" PYTHON_BINARY: "/opt/python/3.5/bin/python3" - id: "tornado_git-py36" variables: TOX_ENV: "tornado_git-py36" PYTHON_BINARY: "/opt/python/3.6/bin/python3" - id: "tornado_git-py37" variables: TOX_ENV: "tornado_git-py37" PYTHON_BINARY: "/opt/python/3.7/bin/python3" - id: "asyncio-pypy35" variables: TOX_ENV: "asyncio-pypy35" PYTHON_BINARY: "/opt/python/pypy3.5/bin/pypy3" - id: "asyncio-pypy36" variables: TOX_ENV: "asyncio-pypy36" PYTHON_BINARY: "/opt/python/pypy3.6/bin/pypy3" - id: "asyncio-py35" variables: TOX_ENV: "asyncio-py35" PYTHON_BINARY: "/opt/python/3.5/bin/python3" - id: "asyncio-py36" variables: TOX_ENV: "asyncio-py36" PYTHON_BINARY: "/opt/python/3.6/bin/python3" - id: "asyncio-py37" variables: TOX_ENV: "asyncio-py37" PYTHON_BINARY: "/opt/python/3.7/bin/python3" - id: "asyncio-py38" variables: TOX_ENV: "asyncio-py38" PYTHON_BINARY: "/opt/python/3.8/bin/python3" - id: "py3-pymongo-master" variables: TOX_ENV: "py3-pymongo-master" # Use 3.6 for now until 3.7 is on all Evergreen distros. PYTHON_BINARY: "/opt/python/3.6/bin/python3" - id: "synchro37" variables: TOX_ENV: "synchro37" PYTHON_BINARY: "/opt/python/3.7/bin/python3" # Test Python only up to 3.6 on Mac, until our toolchain is updated. - id: tox-env-osx display_name: "Tox Env OSX" values: - id: "tornado5-py36" variables: TOX_ENV: "tornado5-py36" PYTHON_BINARY: "/opt/mongodbtoolchain/v2/bin/python3.6" - id: "tornado_git-py36" variables: TOX_ENV: "tornado_git-py36" PYTHON_BINARY: "/opt/mongodbtoolchain/v2/bin/python3.6" - id: tox-env-win display_name: "Tox Env Windows" values: - id: "asyncio-py36" variables: TOX_ENV: "asyncio-py36" PYTHON_BINARY: "c:\\python\\Python36\\python.exe" - id: "asyncio-py37" variables: TOX_ENV: "asyncio-py37" PYTHON_BINARY: "c:\\python\\Python37\\python.exe" - id: "tornado6-py36" variables: TOX_ENV: "tornado6-py36" PYTHON_BINARY: "c:\\python\\Python36\\python.exe" - id: "tornado6-py37" variables: TOX_ENV: "tornado6-py37" PYTHON_BINARY: "c:\\python\\Python37\\python.exe" - id: os display_name: "Operating System" values: - id: "rhel" display_name: "RHEL 6.2" run_on: "rhel62-small" variables: TOX_BINARY: "/opt/python/3.6/bin/tox" - id: "ubuntu" display_name: "Ubuntu 16.04" run_on: "ubuntu1604-test" variables: TOX_BINARY: "/opt/python/3.6/bin/tox" - id: "win" display_name: "Windows" run_on: "windows-64-vsMulti-small" variables: INSTALL_TOX: true VIRTUALENV: "/cygdrive/c/python/Python36/python.exe -m virtualenv" - id: "macos-1014" display_name: "macOS 10.14" run_on: "macos-1014" variables: INSTALL_TOX: true VIRTUALENV: "/opt/mongodbtoolchain/v2/bin/python3.6 -m virtualenv" buildvariants: # Test MongoDB 3.0 and Python only up to 3.6 on RHEL. - matrix_name: "test-mongodb-3.0-rhel" display_name: "${os}-${tox-env}-${ssl}" matrix_spec: os: "rhel" tox-env: ["tornado5-py36", "py3-pymongo-master"] ssl: "*" tasks: - ".3.0" # Main test matrix. - matrix_name: "main" display_name: "${os}-${tox-env}-${ssl}" matrix_spec: os: "ubuntu" tox-env: "*" ssl: "*" exclude_spec: # TODO: synchro needs PyMongo master's updated SSL test certs, # which may require Motor test suite changes. - os: "*" tox-env: ["synchro37"] ssl: "ssl" tasks: - ".latest" - ".4.4" - ".4.2" - ".4.0" - ".3.6" - ".3.4" - ".3.2" - matrix_name: "test-win" display_name: "${os}-${tox-env-win}-${ssl}" matrix_spec: os: "win" tox-env-win: "*" ssl: "*" tasks: - ".latest" - ".4.4" - ".4.2" - ".4.0" - ".3.6" - ".3.4" - ".3.2" - matrix_name: "test-macos" display_name: "${os}-${tox-env-osx}-${ssl}" matrix_spec: os: "macos-1014" tox-env-osx: "*" ssl: "*" tasks: - ".latest" - matrix_name: "enterprise-auth" display_name: "Enterprise Auth-${tox-env}" matrix_spec: {"tox-env": ["synchro37"], ssl: "ssl"} run_on: - ubuntu1604-test tasks: - name: "test-enterprise-auth" - name: "docs" display_name: "Docs - Build" run_on: - ubuntu1604-test expansions: TOX_ENV: "py3-sphinx-docs" TOX_BINARY: "/opt/python/3.6/bin/tox" PYTHON_BINARY: "/opt/python/3.6/bin/python3" tasks: - name: "docs" - name: "doctests" display_name: "Docs - Test" run_on: - ubuntu1604-test expansions: TOX_ENV: "py3-sphinx-doctest" TOX_BINARY: "/opt/python/3.6/bin/tox" PYTHON_BINARY: "/opt/python/3.6/bin/python3" tasks: - name: "doctest" motor-2.3.0/.evergreen/install-dependencies.sh000066400000000000000000000005601373322416100213450ustar00rootroot00000000000000#!/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 if [ -w /etc/hosts ]; then SUDO="" else SUDO="sudo" fi # Add 'server' and 'hostname_not_in_cert' as a hostnames echo "127.0.0.1 server" | $SUDO tee -a /etc/hosts echo "127.0.0.1 hostname_not_in_cert" | $SUDO tee -a /etc/hosts motor-2.3.0/.evergreen/run-enterprise-auth-tests.sh000066400000000000000000000015621373322416100223370ustar00rootroot00000000000000#!/bin/bash # Don't trace to avoid secrets showing up in the logs set -o errexit echo "Running enterprise authentication tests" export DB_USER="bob" export DB_PASSWORD="pwd123" # BUILD-3830 touch ${PROJECT_DIRECTORY}/.evergreen/krb5.conf.empty export KRB5_CONFIG=${PROJECT_DIRECTORY}/.evergreen/krb5.conf.empty echo "Writing keytab" echo ${KEYTAB_BASE64} | base64 -d > ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab echo "Running kinit" kinit -k -t ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab -p ${PRINCIPAL} echo "Setting GSSAPI variables" export GSSAPI_HOST=${SASL_HOST} export GSSAPI_PORT=${SASL_PORT} export GSSAPI_PRINCIPAL=${PRINCIPAL} # Pass needed env variables to the test environment. export TOX_TESTENV_PASSENV=* # --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 motor-2.3.0/.evergreen/run-tox.sh000077500000000000000000000026711373322416100166770ustar00rootroot00000000000000#!/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. "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 [ "$AUTH" != "noauth" ]; then export DB_USER="bob" export DB_PASSWORD="pwd123" fi if [ "$SSL" != "nossl" ]; then export CLIENT_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/client.pem" export CA_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem" fi if [ "$TOX_ENV" = "synchro37" ]; then SETUP_ARGS="-- --check-exclude-patterns" fi if [ "${INSTALL_TOX}" = "true" ]; then $VIRTUALENV motorenv set +o xtrace if [ -f motorenv/bin/activate ]; then source motorenv/bin/activate else # Windows. ls -l motorenv source motorenv/Scripts/activate fi set -o xtrace pip install tox TOX_BINARY=tox fi # Run the tests, and store the results in Evergreen compatible XUnit XML ${TOX_BINARY} -e ${TOX_ENV} ${SETUP_ARGS} "$@" motor-2.3.0/.gitignore000066400000000000000000000001531373322416100146450ustar00rootroot00000000000000*~ *#* .DS* *.pyc *.pyd build/ dist/ motor.egg-info/ setup.cfg *.egg .tox doc/_build/ .idea/ xunit-results motor-2.3.0/.travis.yml000066400000000000000000000002671373322416100147740ustar00rootroot00000000000000language: python python: - "3.5" - "3.6" - "3.7" - "3.8" services: mongodb install: - pip install tornado script: "python setup.py test" branches: only: - master motor-2.3.0/CONTRIBUTING.rst000066400000000000000000000031421373322416100153170ustar00rootroot00000000000000Contributing 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 `_ for your specific issue or feature request. Running Tests ------------- Install a recent version of MongoDB and run it on the default port from a clean data directory. Pass "--setParameter enableTestCommands=1" to mongod to enable testing MotorCursor's ``max_time_ms`` method. Control how the tests connect to MongoDB with these environment variables: - ``DB_IP``: Defaults to "localhost", can be a domain name or IP - ``DB_PORT``: Defaults to 27017 - ``DB_USER``, ``DB_PASSWORD``: To test with authentication, create an admin user and set these environment variables to the username and password - ``CERT_DIR``: Path with alternate client.pem and ca.pem for testing. Otherwise the suite uses those in test/certificates/. Install `tox`_ and run it from the command line in the repository directory. You will need a variety of Python interpreters installed. For a minimal test, ensure you have Python 2.7 and 3.5, and run:: > tox -e tornado4-py27,tornado4-py36 The doctests pass with Python 3.6 and a MongoDB 3.6 instance running on port 27017: > tox -e py3-sphinx-doctest .. _tox: https://testrun.org/tox/ General Guidelines ------------------ - Avoid backward breaking changes if at all possible. - Write inline documentation for new classes and methods. - Add yourself to doc/contributors.rst :) motor-2.3.0/LICENSE000066400000000000000000000261361373322416100136730ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. motor-2.3.0/MANIFEST.in000066400000000000000000000003121373322416100144100ustar00rootroot00000000000000include 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 motor-2.3.0/README.rst000066400000000000000000000104611373322416100143470ustar00rootroot00000000000000===== 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 `_ :Author: A\. Jesse Jiryu Davis About ===== Motor presents a coroutine-based API for non-blocking access to MongoDB. The source is `on GitHub `_ 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 `_. 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 `_. 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 `_. - Navigate to `the MOTOR project `_. - 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 `_. Installation ============ Motor can be installed with `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.11,<4 * Python 3.5+ See `requirements `_ for details about compatibility. Examples ======== See the `examples on ReadTheDocs `_. 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/ motor-2.3.0/RELEASE.rst000066400000000000000000000047251373322416100145000ustar00rootroot00000000000000============== Motor Releases ============== Versioning ---------- Motor's version numbers follow `semantic versioning `_: 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 `_ and a `source distribution `_. #. 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 " $ git tag -a "" -m "BUMP " $ # Bump to dev version number $ git commit -a -m "BUMP " $ 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.sh This will create the following distributions:: $ ls dist motor-.tar.gz motor--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! motor-2.3.0/doc/000077500000000000000000000000001373322416100134235ustar00rootroot00000000000000motor-2.3.0/doc/Makefile000066400000000000000000000060641373322416100150710ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Motor.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Motor.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." motor-2.3.0/doc/__init__.py000066400000000000000000000000011373322416100155230ustar00rootroot00000000000000 motor-2.3.0/doc/_static/000077500000000000000000000000001373322416100150515ustar00rootroot00000000000000motor-2.3.0/doc/_static/motor.png000066400000000000000000001576221373322416100167340ustar00rootroot00000000000000‰PNG  IHDR·aÆþtEXtSoftwareAdobe ImageReadyqÉe<ñiTXtXML:com.adobe.xmp ¡a‘ PLTE‡¥¶·Þõ˜Ëçù0.Þ‰,ÔÒÒ)KZ­j'ÿþjî,,º«Fûébhz‡þ­Pø–(Âìÿ1Zkÿõ_•„5jY#ÿÿn+ÞÒY‰§¸#"³±±¯ÖìéÚZÈ-*±ÃõN6ÿ÷‘ØÉT415¿çþ7EJ2ª)%Ç·K{k+ABIÃx)ZI»Î]itŸ;ì!(”´Èäãã‘Åàúi=~•¤ÿù«­C«¿ ÿ—K:cvޝÁ4! ÿ÷…ÿûj@gzÿûÆS\d•¶Éÿž)¼ãû77= —¶ËøïgÏêúPSZûüþ»âøWblFIP‰$ ‘¯Â³9*wœcr|ÿÙ^øñja8J|”òã_'=HJOW%KZÿÌ[nŽ«¼õõõ±Ûó!0:çôýªËßRt‹˜… ³’³Å'/ÿ§2¬Ð傜¬’³Çš]"ÿ›(ÂÝp…’ÿýâÿõt‹„…lcd*O_ÿùiðèg|’¡‹©ºÿòfûÊL5Rbü‚E£ÁÔöúü—˜rˆ•Þ2.½Õü›-xx~š¬ÿÿò† ±Çðÿ¶Ûïzž/*-Üïü'BOÿömÿïe'M]ÏÀPÁåù3JW +#'üõlóÊ[™³Ä‰µÍ=]llO>DmƒŠ«¼s•§(FU‰x1…šªœÏë3]oüõg÷—.„ž­ñ60"úólõ#*RJJî(*…wEkŠ›™¨÷ølaYZ==B¦¡£€§¼/DPtll‚˜¦ö,,«ÀÃÀÀ?,þ™'‰ž­úúm…¢´¨ÇÛÿ¾Pÿ—%é2/‡œ¬¡Öòÿ10w™®3Wi‹QãÞev †³È‹ °ê&Œ¹Ñïðm¢Êàž¯ÿõh8`r ÿõeÿ÷h‚œ¯ƒ ±„ž±„ž¯ž²ÿ÷hŒ£³ÿõhƒ ¯ííí„®Ãÿ¡*”ŽŽ¿¶WiOV_ìøý¨·ö‘&ðððÿõe”¬»ÿ÷eÑD-Hw¹à÷Ž­¿ÿôgÿÿÿÿôh„¼/ù\Ô=ºÞ¿ò>Õß™«ÞÝ;½íŠË®ºµô“/+%«.»‚ÌþTÝ÷œáS¥êÃòªoÉz.õîÐÜ,¼mê=mª¿Í{ìâ?Ä;Pš„ºžhˆúQy„Pu™V/NÑ7ëê×ÒçH!FðZ.¸T¼[/CôƒQ~¢ß•~ê2¡ÜzZ¹õ²ÛfÏžM½÷ DŸ]#rða\ïœèû'ò1ïNͧÔ_Ñ?@¼—¥ª!>š¼§Þ ðñtÕw'þ’è·Ò¢oFõI¦è,ˆï"T‹†¸žªsñõ‰Ñ)S'/ñƒú§€»úÑÙŸ^qÕ&È­_u™n€ç Ι«•|rs†¨znÞg#Þ‚ú¥Í€8®ÔHtmoˆz#bÞÑ~u ‰˜sE>õÆj[çju7§Ñ³ÆòtPW9Bá ð‰ {§í‚~ÕÙ³o»lBüà2w6…ŽšÏ¤ž$>ÀúDÔ<c°Á)õ>1ѽŽ9­Þ£ó„J]háqðŽ –ÏÓ8èä=˜GêyGלi#cØôãSVj·÷ ÊvRøœˆV]¸\óúˆ|:ûŠ«ÆÈ­WÌþÔ~¾ös§(‘k96c-õÓ¶šb³¤µŒ‚' ¬•¥QÖ½`U5 Ú+é÷&êü-UuÈ\¨Ëè™YgMë–çõÕ'41€ n’¢S«r¬îìÙ—ý`\Fg›h¬ˆ4ÏÔ…¶ Ù"×8z»PøÑ¨‹4 è](*`•àÏ¥»ÈØ)SOß38x>‚Ž¿<Ç ïÄŠFF”ºø'Ñá¤òz!Ñ&Ó•ïàQxÊù;íÖ’¹l¶ñW¶¬‘²ÀÃeÎ)¡ö!‡Î…ž‡‚X"Š<Œ‹nC?Sj¾…ªPËØ}ªßšx6“‚ØÜ O¸ ž¦õ© k‚§†q™hNÇáÊ,{OÄ—æQ©[óB¹Ù?¼ª„@¾:›Ú 6œÈAPŠOŽú̆*8äÆÝ²ô„*3 Íž%u uLÿ­ ü(ÕAá—N”57¾Ë5¾Ê2€^@O‘™$ ª' b?©xæ&ÕòBuu*½‡y•O RÆ…ªMü.›ZÍ ! G$® 6ð„ÈØÀ“ çìŒ#÷¢5cȰïD}“Ðx¡ŽýLÂéý‘Q§'b<ï €˜"¦ý·Q$dk©å׈‹ … ŠAŠÎ|;½ÊÈe³)°€ 9@NÄ“0’ƒ¶Ôê5ïxÏRqBñÍy.€Àü§ö&ש…oïݶ9+ĤÑÞó…É+U(û­H W͆îÉ…¯/ï›ÀÅíÐÙ‚ {qóÔô# æC`gµŽ€ …G™ ÝE•‚\ÅE†Ö«ÙPbÔÿ40Æ €Ê (H,rw@CÞEd”‚Ü@ª´¸ãÙ·ýäVò©KQ!ÈÄ&:Q% \àbÓ壃– Fhðû¼z™¼3в<  rl´(Nß´‰´ºèäP3¼LA’«*m(Â1£g„¡ŽéÄD×# (2)^FÝÙ_¹b¶É¡à®ˆ }‚‰X@)PûsTµ_p]xf¨U ‹NÇM½ÌûŒ(Fv LÃr.géW†¹ÏD00)÷Š )‡UùmY$r"Ç’Uv™}•ÈU³A5ŠzQ9EåÛPÃR-Š,¨‰Xl3ϵ•A;l4Š~ÆÈF¹ºˆKMq™/hÕaÚC\[ÁÑCk쥿ÚÄ벘• $>e3ªò)ùÈn›M-+ ª. ‡ß$…jçªÔ²÷ ï¢8ù&H_P͔‡oJç.>Á 5ò„ΖšLb'ä ³I°à`¾aJ<úÕÈ2pÖˆ@ I¹1 ×úc»³/S¹l6¼-Xº%*ñ™æQ«¸NMýû|B¨m¸A1’j£ëRËèrˆzÚžPx38…!(ñ>eE(j¹€ÊõÜIDÌ+y?GL.L­R‰ ²lŠl%Î÷\¾~JnùÁmŸºd¡þÁòÞ@A2 p¬KxpceÍMZ‰&T ¤jN;$/ $Úg(ª$ÀZ11’ .¶£ÔÊÑ}.Êë±Áö1%I”š{µ9X©!Æ}s)ãÄxètQ¼B½f—UpÓy!ž÷A•;ïAÖÇêR°n+û*Å©¸[Ð3÷ÌœvìÄ?áò8(uR×*|QÓB0…{±€#!hd$‘Áñ®„¹Ì‰D/8]Ë,•ÈhÉz EE%Škû¨¤êË¢,Ô’éëÂL6Ê¾Š ƒ~«ƒ’Q¤î8'Áµ£B× ¬$•ñÄÏñ]KÌÅÿ÷‰'λtåEͽ`& 0)2¹`õvm01°–ÀÜX6„û„PŒä%Q`5Là e` {*4„B'Šä‰]Òõiê%—ºÀcRÆPBˆÉ/±rDB‘@PÆ€H½ÁÄõ7gÑêbÄ9Ó+5PyôÑ9ç³p0ØëâV¡‹‘¸öŒ'Œc„°yèˆel]»!La›Îêy)žË„ ¡£^¤à/Ôö@ZèJЦ$C©¨†£zHâ¸Ä×\‘¿õo_¹d@<ÜȺ¤`Š«ç¬¼>˜€=jÅvŸ›øÍ¼k¡ø-ã@8Ž¿ÙE-48U®4Y0U1æÐ`')ôÄ×E9ØT%.qÁе1^¯@‡®Ö(Mµ"qõy•_T¶\*/¾ 8B)téàŠò0°ª”#pé>$… +<•A藍´rêvuÚ߆3 …°5jÍy z… )+Ñ€ÓeBQ›NéYâöGÿSÄ¡d2pÞHÂõK nÎF¶¡¦¥&&¸®¬Z‰jžˆÀ!S u#è± ÁJ Á@jw6aæe©t”ëa6IaKÍ.Šíí0¬ƒ½b` G!R(R¹\.†®œ¸ì¯ ¿H–ܞЭýÁ D>Pe±] Á§àãyj×)\-Æa§©«›a¯ ¹pÝÂ¥öIGXx˜oQŒãÅqªêÔÿ¢¨š¢‚âà9JÅXh˦_üÑY?:°ëηØu.¸ÞRÿ#øÚ®]?zqÓ–í‡b) gÉ/z­B1JË!>…Ä¥”Ú¸Dý ‰¯ £:{–Ìd«T:uÜ*2A4…ZšMÔî‘P•&ª·H©ÊSd )!Vi†h•xT$µåÀ¹O½÷”ºÎå¿ÐõÔ¹öWÄWùõÞ{O{ç.Ü+zy"‡Bhɦk ç 5F…_Hà¢)2×ׂ:¸È ‰ AQîƒkÈºï šÄŸõP c¦hâƒ\ÐL†ƒèÌ%.HñgÛõzÞo¥€×¹BIŒ¶Ñ¼÷Þ¹»¶ä¤DRs(O´=ŸN¨ ¢Î6BT† p,.õMc¹%ꞸXˆ­›¸t”\–Ÿë‰'¬¼Š[P„Žâo×Ê¡")ÙPp%WÂãïCÄOþ{ï½÷uv])ÉÔ/q±¿ê)ùý@(om—*rq ø˜[Á Á¬¦CGìÚ" ¤ ¾Š,¥¸‡áx.L ‰ÏaãY 2~â+QjÁ¹JcžAÁÃAFzaŸÙ…ÀËàÅ\ ™§´f¼wåc}æ]ÿ¤þ_úì³Ç{ìʯ¿g„òÔSB"Nåõ½®å¾„í  xº¨ /…wG^ú`)VmÆo{,ôƒtêÕu­Ž®I$² 1Œ±Aí ¬Q öo$Ñ4¦“Ö+\È…J O] žöé_}ݸzaµœs‚„ZY:IÈÌVÍÚ˜ð5œ½ÛA#/¨"@Õ*[ʹJAë•à© !_ÙUÀé2åÄ ô‰vnª„¸AGFTÉ•(œ˜µààŠYìq”°½û+™@Š»¤@žúì‹^L$òzïBi³°ÂÇþÛ 7¬Zqðð²e3fÒØM8H8€Œ?JwÑLz®•®)F&ËLýSŠ+³”Pß°kO—0]qžÔGûÝlu$n˜±Œ=‡7z½ò´É…s¹OÏÉóýÖc_X ŸýÓ•J"wòtŽþÎí‘Dpã¬3–-;{L d‹r"Þgè Bqôz)t`檃WìïõL®¥Gq1c‹N…©Eèm¡!*ž#õ¡Íd-°kèˆöÈ|С‰^åZ<°â°T‡™Õ¡Y¡¿JØÜçpl’ì׿”@>S~èAéD^ó*3ûW\6\‡ìWŸ)02k“Ô»{‚.¥Ý@¬‚"BèìÅ]±ÃŽùÁVµ,ê›Ý5'êoà¢4¡þúMܰ€=ø^Ò‘Àyß]0«7 ¾bx ×rÉ«¹@^|ê˺a³Þ‚N„Œð7IôŠg.ÏòƒùVôŠsÙ¼aÅaqzV\<»Ä‘ vëQœ¥ÀB0׋xÏÕ™:à$ –¦PHT@},z0±»ñà»WíÌÈL%a·‚ì¡;È//;8KúvùΉ xZ¸ë©·¾´ a6K…¾Â‰ \àê±qÅ2Ï`^0k#¿¹e{Fø£ÌÜ3Cþ…5 üîØ¸Ÿx”A<£¦Ê­òkÓy§®Š¿‚×1,U ta‘€âskLE#¯Ai.±qÁ»ËÌ xóñdæAi²øoÇVÝ d¿ÑA)&'æÛuô¸(b‚¬s?ûì?ʼnˆÜp˜9—Ì:¨Å±â &~sËV˜¤\ýWLB”h°„e?Ó«Lgðõh¶ µ´,+pàå £‹«½£¶)µ>>I¥ˆkÏ?Y¼ì‰Ò=ËXHûF0!Æ£™„øC_±â]ùðÜHìOaÅþ=Êt°/F” ^Ê-VL¥_R žÙ¤ Œ,àöÞ’‰ƒåA7Äÿ²{ ¼±â]íæ—­J€°i$ظгUû1Àeg£‡·\«”A©íJ€É"‹I&šX BÙ]ÄpÛ‘°2Õ›˜uŒÅ÷{6JK”¸AœÁ‘UǤ’Þ³¿W $‘ðÎ䲫H@¾šôé2ê}ëÊ/+éDž:P5ø ¸—©sñF"H\õÿ÷³(ø°w+ü·Y/ÏfÊqÇ+f¼û.;4Ä€ŒNh•Ø M$S³2®Çó!çàbh¨Ñ”¯ŽÿP¡Ý4ÄIàf˜+¹ƒ»q—­ Š/J}Xuð˜°Ó «3ÉÂbYIYIJE 䟾¤@Tnø¯ž8«gŽÞX&ÝÖ¢ÖHüý—홹j†Tõqszø€«’®,‰åÑza@ƒ½”Øfh,Ϧ‚ñÊï ¿&~0" ˜¯Íµº‰V ŸG3™5:,³ )Y¸cÕ èÌ™¦ w•çGg¼Á}nïíܧ«¨÷ËútãD‰Ôp!ó_Ü¥³8P\tDÏà TØÅ£Ž;øWnì•¥®ý³Vð¢ÂŒ#®br½õMíQÜlµëž@üÈK˜CZ¼¨Oã7BíÖÿÕ›à¡>ÿè‰À,%ÊÎçï.Û“`ßn¾ÆëL”ñFŸªd=õÙ—¾”/R©aÀí½cÖ*–¢*ÅæÔ;"ìÍb¯;ƒ#Þö,àçê0³V«…AJqol8"ÛÀÓ¿˜ÇJþ¼vêˆíÁµŠ_¨CKð0Š _Ÿ€áUR`ŸŒ=èÃÌ8g-ólIÌÜ£d¦!âÛÞ×îgÇV6Crçþ'äéâzO:‘UO„ǽ„þ¼Ê¥x~ŒEëâ+ìÖ™B9¤˜Äz<M„ -Ñ’Ò¸BJm­!Ô*¢[œ.Ö+Cø!~˜V*®¸±ÀLžd>¼g•0ÊAñ³½½&ìÜÓ«ŠGòkï®`‰‘ÍUÉúú—È•R »¤WOÍ  `bt3«D(éNåÀ {.SâØìÅX¡6†‰9 *"F2Hdâª~fز{˰lvÏ×»cÀJ"±ŠÛñ‘ÝPp¢„çÇßÝðP&Á+Ø'ߘÐýô-²[xå—ˆ—Æx"R9³ÁŠGö«œõð»,¦à‘®ˆÑg\%”ƒ‹ƒi͈‹éDJƒf-ªG3ì§MÀ„ Ô´p!Š c¢.7q}t»ð[‹nÞË8ŒEù Þ•PxIùæ‰;ö›-“/o'7¼!ê{½3+‡Ù£{ð?ɧÛ^}n$Ä„ûtÏy¸ ñ)TZœ"þå™Á^0« Ž)"Ê!´‹Ñ!%x#uwœx!˜KÌFÛSÄàZŒVöÆõ è°– Ä»ÉÞ óã,r¹!að—$‘áRMÌ-Ÿ~îgÿ ×SÈ«áô¯r¾Ë´ƒ«d¤ëQ—ªHíp)báÂÐ] † ñ4 šÐ¦^» Oà¼Þdj‘QÃÍc © ·W¥\„î/à[›SæÇ,x#`1cËfÈ$yú?}öO¥¯q½:ÎÕ)dûã¥PfŸfЍË‚eÇ´8îà_†Ø}°Ãüz`ÖÎã?ÏJÄkáR„ðòM4PkNÇÅ,mˆüE&ñéÅ Ô²#Ä”­)‹Š]¯a… 3Op„|µwï¡?öØ•Wþô§w¾uç‹¿øâÄowÞùÖOz¥ÀA”’ ÊÕçÌØˆïßÈkÓê†{eÙM‡û½’5ˆq wt`¦®m$ ĵ©B1/¡H Ô…•. iÌ|¤¬Ñ*Ð×<¯Ã ‹WVªô»#İ7SsK,1‘§Ë'üØ{wþhӖС|ìx±¨áˆìWD@ê …b1•Ë mßôâ?½ò±Ï€\p®N fx¿vÄ E³d'ó ²aÔ{¢p­³ !(〴.Ò7Pð0Õ^—55CmöL¼%›°ÍJHmò(n Þ¸AC‰ ˆÒgKt³gòfHáGO ü³OýôÎM[2:é•ÊÅBÕ›îüº”Ëc°;pAÂ&+‡› zß`öuÙÁ=Riðä hCÂ…jR‚RScë˜ÖNõjÔ E 6¦ã£zœI·‰Ÿ ±°Q‹…Rþi$ѫ㠂8˜íI‹Ä¢¨½?õÞSçîzñÁí™3’¾ ©üöMw¾÷˜€£ª |á¡€Å,€¢uBöÌ8¶b£(A)š1½yÓ~šôÒ﯑j ÇC`!Š‹öà¹K¬ù8bµ\(EÔÅTß”`»páÜ3 S2ž„@ºU¶”yú¡s´%¤a¹6”Ý›§ÒfËûUZ,¹Øö-îzëë ´2HöRTñcoãÆD‚…|„؃ƊÚMѲؤº #Â&hÎÁS0ññJAðÅ|?€ý±öSjÑxQÀiS,ò,Äf£†EFdžžË|ü3[”ÉV7Ö¶¶ŽŽŽ6Ôñ«ý©µµ¶±±?”åRR\~±s™Œ ³*-X0Cf4“áY ‚AóTs¾ZTŠYÆ´ßÆT…ÖhÇeQ×Úƒàú(ô-ä7š£ qŠ®ÔµõSu¼Å#“ˆèq$ì{Ôñ‹"’Êg;ê>Ú»¡þĶ¡²²²ªª¾ªª*ö‡²>þ'öß¡m'wÔoØ[ÞÞ²®¶:”OEJ ÆYòã`0A\×&éÔÏˆÝ Å^<Ÿ×µ8[­E.$*E#¿úOD‘“ UÔOZgS‘ ¸ DP<†­D»"{a ç±XðއÎ[”²d‘ÊW·Ö•w9ÁŸúÐÐÉ“'&¸N*a•8²á£ö†ÚêL®h‹¥°zÎ9ׂ iŽñj Ê÷ù‚`ƒ`"gœž9ºáiDMÊÄÐLd!ä7–ÀóÜÔ‡X ´¸ˆu• q¸÷$y艣ððœH1Ó8Z¾aÇ{ÀÛ&”Ã8¢©*:±¯§nt0›/‚WfX²ò¦ö¤ð€ÌÝ l†)î#²'ëað£KðJ{Úš* õ ‚XÃÜ÷Ìm@Œ§£GKl ñïé {˜{ÞÑxf‘T¶µnïöL·øR×I&—ªûŽìmmäRS‡sι= †Jœˆ•<  ¢“BðJHH )¨Y©FK±µ |Š—áñE¬öè6~ø„XTÓö  ¡~/‘@àê•KR@ÅLk݆“ãÊâ}ví`¿äµCþar±”}¿§®6£…ÂôdõÅ ¼®Ž È¤=9m/ò@ôˆ(O$’µP :% !êR u-Ë7x‡©÷1»…䢞G`F1 !Á4vI0°ð 3ÿìDrÕ ='¸±ñ‹aÇûÒq?Á=E•rë̵ I›&Ä4®X˜TNìm©Ž™—œsu°»I¢Û°Äžñ Ö4f õâáN4<3E¬±XÝp×cј6Ï¿ô /„˜ˆÖÌì¶£.bÕ…ÑÈÂvä¢%EgXK£±îCUC>Q(ŸÐ××yù¿âÖÅ¿vyg·¤h¤ðJ ¥jhwû ÉêK-ZbÖø€J­e´6EìO¹/¡´Y>·ÄË;Û²tB‘sEèG¼¶ R@R_aí{±X¨(ÜÅH@€ö÷¯\d”#W]·» Û)!‹mLLúÉÅÛÂát:ÚeWw´;‡ÛÚ”dØ?—wþ¬iÍÉñÄr²¬¬¾®1¥ERùÄ;„dŽ»Dt%`õÁÙ]´»ŒÂ.+õr-¨–jáZ ¶ÀbÓ ºV”…ò,J©ŸÈJ¥ÜÞÀÕ—®öÄá²-ÊÊ`,żƒ……’Cw²)šw–í¨ßÝÕÓÓÓÞþQOÏÞÝ»°H¬-œî޲¿îN‡Û¤è:ûdtVŠ1µ¡!¤Ô„9ø'Þᆠv¥)œðÇT––Ú!(jñ•"«Ãó–Ö1YØY#~%mÁΰ…þòÌ*A€.<ÊM‘—!A(ŽXëæmUÛj0aôuz²hKG“ѱªúžö†ÖÁl†%ä)S3Is¹X>”eù{C]yϾûúƺ›¢Lpq¡.R,>eÙVu¢Ü¨ÉÀy· ˆPÈÐH0K—‹ÈÿQíÁÜÁòÜâoÌŸøØ±c§¾ùæ/ßÿN=xð ûÊá»çÏÿÆâçžcbHyr)ÄX†Ù5”fjÆ…Âe²[®ú-’9Gü\Ùí ˆåµHtýM+»¡ˆ»òé•â "®ÅÖN!»5…ÆH\ü>®'d¼$xûE-Ž.pìxÿdÙÏÆ¤ÏH¦ÿ\Þš)ªÃžZ½äâÅóï>6õàÔ—ÙåáÚ^žaFÒøùß1ñLqìîù‹×¯ßZ9P”‰U~ôL8™nS2y9øúužá¸t$`-S°Â¹¡`Â}â°to†}JýBòC>CˆECð:<9¢²3€³^ƒ–JgñT"qÎjõ鋵{MÊÁìû²TcáäPy£ÔŒáØÑ9ç3÷O·Ý6  Mr½<•]\.lUÚÂ3ν}BQÆú†šœ¬ÚÐ*ψã<ºÇ[Ä×L…[13ˆu3ÞDLÎŽƒYKDõ:3¯…믘¾‘Õñ˰ÍîE³Îã9˜b°ÌÞ«“/˜ã…V=@ÌttÊ$c,­ïÈsi l]ÿ‹^»íµ+~|íÕW¿vý0£)•‚ý*3UýzyÆËø;¸\Ž1©T¦„ªD2µå÷¥y^ÙYvrò%=Õ2vŽŸ÷‹bõòù—£Q‹‚ÝêÔÊJ4±å£3\'69—ëúoyx X#hØÓµàOG+~"°2¦Ô#[n|Çû'¸Sò8ÒÊt±rýâ»§Nýå‚Y¿fÒà×µÏJ%O™]Ý}7süZ¼xþbþ_öî]øe Ú›¹P¤̵„EÀpy* — µç=%™$þüRŸ—p}»Ã}¤ÙÖVké4rÆ—¥×¹Ö!`Í©ÝMƒ»j‰Ÿ Å,$ ^¯Ôɵœ({ƳU'Ë:ãž8ÆÂõ1^_?ÿ˜|¦‡ï¹ý×LìŸgîaþa>÷̱«˜RQûÏ0/MO Tn}dýzæqf0ɼ Lرùë·çïÝ×&óý1–ùgRVßQw7°’‡[f;¼¯‰BÜRäf’ãÃqT]1´f”xó!pˆm¬ÄÛN”{[äÇkíÿ!(uQ÷Òø¿•R= ƒû¼ãùþûÛª´rð+9ê*3rƲÿã×Ïþ0˜øÓ;­¼øᧇç¶]tÝuÏ?Ë-·œ­.öÇ矿nÑÑÊÔñal}n1ëÔ©F(Çî~„e=úý˜HN¾®BJIæÜt-ØdKmj ;0x—ãZ˲í¥?j,ÚšµCïÄáoTʞݱX”}cºâ¿xB©G¦ÝsÌuô!qŒEÛ#Ç翉<Á/κý¡Kçd™%;žytÎó·\rÉ´iÿpÏ=;w®ùË_Ö°kçšüZ#þ¼ó÷O2í’³ß~þh%Wµ­ëçßm4åÍõŽÓÚdÞÍIÙŽŽãÒ¢]$ +Q{Á8|xÓRÄ—‰)&¥CŒJªWu"w xIeÏË,Ò!.ño[FA–YÒÀÿ>xÁ£Ê™·©ò|‡OÌd•ÅœG¦ÂpéØÝÏmÍǽxåÿ}çöºÿ=ûW_ý‡ñ®§Ù¿¯îä’™6íì·æ„P<™Üê8íQø~ñ±2`·NV}”‘§&µRvJ§Ïy€ ¤÷×z’Zey»¦5„ÚÛ«íÝ6”‚÷"€nÍÅ]ßþbÖ‘’àEÒ\9ùvm­NúÅÁmV­SyÌHcþ#‘ã•×ÝrÎí·_»…[×>»ñžOþát®W™\îùä’·W§œ¢ÉŒ—ïˆw§ñÆ;‡ ’©Uå­‹{€åR,Y 0ϵÙ0^ÈOœ>ó ùcÜ?û¡‰om7µ6v¸Æó[0dÞ¢½Tå‚õežµª*%&òˆ³X¨ÈÔƒóŸ¸î–i¯þeÍïߘù¬Œ¶®~væ¬ONO$\av®yuÚÙ‹RNjëüƒo..8™Nûmãñ>`·¶•Õå<åö µÅÁ^ÑLá6{·‡u"Žq0(K4 TÜRx,@ûh)X±ˆ7ý×î²óZÉÈÅRÇt»£¬s¬ôÕV•w¶Nåʱ¸2»îìOÖ¬öé“{6^«àÿvÏï_ÕnC];Ç5e¯î¼gÚ-«§rñz'2õ¿'¶[U=Ê·ä#Bêß«âZ°k´v-‚¾À~‚Lù. öøà {K8s¹4Lsâê%Ê\}T¥­U|l¼+Ùê¤î~óîõ¹áEgò—5O{õ“OÞ¸úÙÿð¶Dð¶«ÿ4wÚ%—\«·õÅÂ,îì?yúi.[2¯®¹ç’ëŠN1âÔ%K½)S“(–}¿QêHåCAßq‚މÿ(^PcµÿìÕfõµªöBÃyë÷6‹0Úø @yª_XþˆßY$åQ½»ÌËÇÆ—ÇX÷戳~}*÷ü´§wâsþûomd¡ïysÍV¦T]w¸à]<.ä2«=ËÙÓ¦ÝÃb/$•k¦=_ˆ6¤K¾+W`¶F Òµ R@Ü” >R^_iØ¢ÿEÔ¡¸ Hi@ƒ lå+|ØCrûTG¢ …¨½œ«jW­Ûd°»c"õ‡5ï çßž¶s'ÆÎ§§ýü¢XŽe¹Ð¢%?qéyß` úü»½x¾Î+Š[·˜lr•×½}É´§‘Pþr s!Uã½y¼ ¸ªÚ¹#)8ÅKƒ„¼MÎÂ`»]NÐ^&¸ÕZÀ×QS¨!¾5"&H z*È¿\VµÀ| Pè\(ëBƒŠ®Þ›PÌfu8‘Kþò*8ÚÌ\·zx8Ås½•ïÜ~û³¯_»"¸qÁ/éU³ä%Š&¢ÖûÜ#•,m^ýüÙ\(ê…Ö¼í8¥\ˆ·¶G²YVR†Ïã4þ¨-K\{í‰õŽPWxdQp¸6r±³[KJìO÷u÷)^Šå›k¡ž‰¹R'5ãTJ_β»·W³,bþ±©oÊÜîð²×+‰\ûë kòS§¾ylþâ­L(•Ï_rÏšW_Ʋ?¤'|ë*m•©V üZ‹À\¸ÁO¦yŒžØÝ"ª—‚¬VÝ x0êG«ÀU˜ÄÕ;•©‹é)ÒUÞ* åáä{ªTjÞ9¹<„Ír.agzç=g/:~|ëbPûP´t¿–9ɵ¯ývI^EE®(«oùä/g;%²ÛG2ôL£¸{¦##¨D|¶ÖäE³W߈ÚWÆæ+BÐ8)›É½¹s‚ÙS{+®½S×ÅN,èÉ£KÉ£lìtä1–Þ[p®[³óé³WW®·¤¡èx˜_¿Çláf“IÑɽ}ãÔ&'SÏË#Ù¶­ÖÓâúÝí)"}­õ :øÕ{T©ÏÀé•Gþ ¶hs¨W`$x¿6Ѿžú–y—_å»Êûãׂo,ð:T/û:TF&w¯à7Óüí‡vHp­ö#D1Û4œ0q]׿—‹â\´è‰>3vp¥mb0ë æÔ"€ÝÁºxN'qýQ$UñÓˆˆ³*3q¼<žúåÁYWÿéöwÞ™;wîC+¿1ñüoØ *ŸH®/DR/…Oãík÷$¹ä#D‘T@°^ÚŠZðxÞLV²,NS &‹À]¼ÞZëwÀrw—º>\'¾¡÷êG±~œ¾zÝ–^zéJvÉÕÛÏ_wÝÑÕ©aÞ ÚÊåcFW¦nuœÑ¦Ó{¿Ž¿(14ÖÚ”˜½Òp‹;Þ!’C`F`ÑŠ‚VÀR"à›µÎ…K0&‹ŸùÇG_@ccM­Îðâ©P,«Ø:0<<\¹úº·y‡ê“xúžom _{í –#¾¶ÖïEÃjÍÎß?ýÓ.¹ä–çñÌ02ð˜…PîˆDzÒ§{"ÀhɠБØÜ 2ÐðÏØ¬Èb¯¡ æ¾ AE=¨$Núž¢­ëâI0J`’énp¥}m¯rÇ™Éc,º9ÂK¾*àÝðâðñÕ×Ýrö´Oî…ÝWU¹ñögeJòìÕo¨’üÓÿðê«¢úûô´KX~_p†)åâáH¾¬mìŒ%²í„ÈGœÕ×ÌEt3ö²…аú‰ö"xeïeé6ç žÍBüîzI¡66Zk…eÁDÐ&:-Ugì?<›¸ûeá‹?20œ[ýöÙÓž¶Ë…L"ߺþY¯&ƒÝ·z•}?¯€U:NjëâƒLX7™@"CG²âÃ<ú‹^ ·!ùÇðP uýc® 6M Èä‹ð^#†¢ea„øzb»)MÜ.^§U~ªeg(¦"µÜfMÊ’ºbåógO»GuEìë“§ÿÛµ^Ú~ý·>)Õ ÙÉk/ÃÎÀ@ÄižÁ™0~¤lŸèë:FèXU B©o|ŠbÜ;UJ‚0©ž†X¾‰ú@VÔ?q Å[)åFpéÐj9ü£l쌯d¹ã#M©j,Ç  6»×ÜF;]B1¨ãÜ1¹ŒÙ½¦ÆÀ‚b{™Õ}D!•=”„àB1üŸÚ,¬“—ÄÓƒŽsÝ´5kJ÷5;ï¹ç“iÓ¦ñž¡¸V>ôÐܹïÜþã÷¿ñûWK‹oµã´$Ïð.4úáÿ«jPkÅU˜KÀà9ÎÝàø‡…Á«_Ñ*«ƒå#t;]mš6ô°°„ô^+;„Ê¡ŸèŒŒ%Û#ÎÛñÛž§9æjÉ¢ÊXîø0ÔÎU]2缕Ӧ=}ÏŸÛyI*RÜ—>ӻئ«(U2A\22ââ5-¢þ!Ž€áD Yj&‚{a/àð',ÔMÐx?ÆUwÇdâ4–}±ËÃgIy5xÕaqìÙo/ʰ¬¯XdÙø#Ï­_¼xñ7įŋŸ{þâ9®Ž±DʼnT.bQÀ'8$[s‹ é­Ä/p”“â߬ãuGB´hàð`¡5òOàÖhÈIÃm\W©«Àz\êú2RúktÞx¹ÀCEè@ʾ˜<˜Íêw —ìôz†L×UNUn]/EØõ¦hC±ßøß|sÕm¯ýé¹­|bN?_Â[ôö%ÓvjtÏÑÓNÓqËj¨¬êä÷9TfhCLÓm¡"‘ÁkQ¼qN@¶-˜‚TD#à—Zd~`/½qÚD³‹«MÝ‘kEI±P.dègá/(±d‹ã¼½FJã’çW/ò¤ûî—ß´*T`MÏ ¯={ÏÚ¯}gÚúç8Ö=µèÕ,|usk{ÏÌb‰éÆÎ}u­Ù|{™pì‘ÞžðoÓFØ{+tÇÖ#ƒÓç^µ×bpæˆà’¼kšý^Äm¼™Æ˜Ê}^*Ù¶¹ü¥±(\:s¤÷Õ÷ðîó•ù­‹ç§¯%rlÕ³¢Krí_{ãà/ï^¼~kA4 ?Y³“[¬Ìé;³x8ŒÆwô´ *'tÿ4}â£Í ×&2½;Ä|H]›G_%j(±WúâÕ²¼ÀÍŒ|XEGj¯PvÁÖAÀ>.w"ˆ „‘Ð÷¹Ýݶ/ç¤Bµu]CádôL¥ËDRÓ>a9ÝÀ#‹£V—dÅFÙ·úÓ¯7®ø%G/~D5p9κӊ±âmÝÉdüÏ=uµÙœ&Ü’ú~òd?gƒtÄ–_ðJã(1dnv°PkZCW{‰‹Œž® ·‹q'x‹œþr=JQe°ªÉ"ëïhßWÅN]ú „›"Gs).©/ŸÞ Û²ƒ^s÷×âÜÙ¦[ŒövO*‹t4¾¯‹)%‹TìЖ 7Š8µBá˺r^ì«[˜{J1ã õ¡Öfb‹”ß A¦Œ`Üi "׿˜Í´ó% ²ºÿ'C,—Êçäty*VÝQ¾»JŒÃžžÍú() Œô s|½|xÙžë_S•”7$‚cRa·0±ÅЇ™e­Ú×ÞÚ‹¨i÷CÛ/|üÀ.~åüQsèk`-n´̉=bRƦñ‹€Œ \¡€òÆ7ûì%‘pNG#&LêJý½W‹Vl·@ðõ"±wxpËö|NªJ!SÝP^ßÙÝÔ=¹TX²îT|yA|™Ï³-^¼~ñzy-°¹»—-x#øÚm¼âøë V(Öù©onÈbqa´íØÜИ/š³B.¿ýÂÏb’8p¿v]‰H•?Q&`Ñ­À9‚&FP¤D­NˆÍË`xÌ.Æäúkˆþey.zª¸¤„ï_¾Î)>¸ë¬ì˜øæ¦-‡<ÄTf°¥ç™¶É X<Í>ýâ©6´gªÄÁm­È¥Š˜Å¡ û…Oœ³ðögƒ‰ÿøZ€˜:?5žÅâÂ;ÒÞ¡Œç•Ý"e!…Á¯g圩óÛöÅäâ¤^ÌXfQïQЍ±ì‘~˜ÈM ¤™­)` Ìþ¼¨Â¦à¸3ß,YÛ•Gÿgõ©Èƒè³‰˥⹕PmûîNæTÆ|+zVŠ…LÏUH@I‘Êe²ýýƒòWu6ŸK Žañ[*;gå;ׯ`6ïØ8+^ÖÒŸ²åFŠ;Œ¶,<›•Û-°Ï ‰nUqaÊU¥—¼Jš…"§rm¯"ÞˆÚç(Eô}€¤Á[È‹è=obÖÂ&ær¤W39mwò%>—ÊY^¸=&ÜJ¤j-²´C‰3KÝíõ +†å“ŽcÙÚuuuÕõ]Î2uµÅ/ﻯ~oyKGu&'X~¹P=ïì©oÎ/–½óÜ“ C*FIYÈ[~<)W*2Ô%v¸>š€¤–ÄÄG. ²}=Ý+1£nº/¨ÖzÕ^P(#…‚Åe5æ5y£¤@Ní•5“ªd«±Xö'äRy|Óåës­ã Sµ:Îâ7y”Ä„áÎØLcCùî¡¶h”y p ”¿sZ³d4ÚÉSº§TæXùÕ‹qœ’p“¦'’ßôÍñÜoÞiÐØäuÜ;fÇ1ó¥„Ãdꄸ¥à &õñ`ØŠ–…~Bñ:¹•Þi•k[¼­ßÙ´k¢OÉ„Â|=gÈOÝoºmëüG*‡E:“Êv”s¯3yŠ)‹õíµ™‚#y˜KWÞ£å‘Ȧ]“CجíΠ7>ròûáׯ¡V©œR †n[yq.ñu]Å÷™=†x‹‘UÁ„s.¢¬Fh bIŽð?—þ~_x(Wüæd–«Êv–#ô•„w¤"Åã’£µýÉx2y‰eœe<] Y1ÜX¬O—„¬rL.³\èôëyž²v Ч÷É)ž–Bÿ#1£ÄFÏ›ê£îÐã K’Å›”<‘!ïP<½!;»ëÁ¢Ó_ÚdÅÃýÜP³£]Çc%¾a4ÃüZI ?¹ðÀéäÅBÌÐJ¿>p}ŽFùœES ‚‘* v‡>a0«ûšu–Z$ç¾µ¬2ÄR râgcMíãºüaY”ß8ø ÊŒ|ftC<ÙýEkÆL&Må ¥Mb¼/sš÷x W¬ïóÚ‡e› <_¿8èú #æÚ+×,Öc´GeêÄërl®w‹†¿Ä~q÷^/B,­ cÑgËé|سäœuMã ´—w~ÕÀ¯ÂñÒïwí:yÜy(²7­GGÊt.77`Á(fi bmo]1[ìU°åÑÄbs;f„Bmt‹²ì®B,¦ cÉÚÓ³Ï\ uãÁsâÉè—”ÆÄõËZ'¿kr/wÖƒ[òN{²Êˆ }/¯•G Ñ› )0o¢µŒ•Ê:.Q‘2²¼Z:¥ˆ…ÆÅ»/ü,T±Tòþ÷˜ÙÈã“”2Èßû/¹X"’ûæ¤ÂylCrLOŽT ¶‡ÔÜ„äJ'¾­SÔ¢¥Ôž®†S€lG°:µp7±–RQSõš)Jf Qæ($o_œÏxˆ”ë¬É„ràÁbª>ü_#t¹S|ðÀ¸AùY›˜0 rkF¬'/¡"”¨éà¼0`¢•‚h§°‹(4!>¾WIšž<Þ›ˆ¤#I8bö ;ôŒÌAäp³X7”Ù¾é¬]äÁ».ŒÄªâÿEÙWŒ\¸kal7¨Ý,Â[E.H€Þ“«“v ©ÛˆM´Œ|:àÓ>Ä{èÕ,]‘#º4@ñ®I©Srã¶S']ºìl+¯V­^)ÚôøxBÙµÅéÿ/RdzÂ,YCØž7Âhè¹/,ëmñ>ŸŠP´·ŒÌZ%Ô!!®Å(N5úºÖŠ;j4OW) ‘ÁÔÂè]VMTç¤:~顽-yµI%5žPvrZÿ‹\ÈX¼- Ã,Q‘¾p{>%ë—Å|cÝÞ¡hÒônâFEU.âš=¸ÔÅÌ~Ôg‡½ ‚`Á#@3XÕµaŠÔõ/ø”oß{{%H µjK'Ówµ fdã'’ŠùkªΊ9¢G¬mátZÐñ³ë kXºÖ‰™öÀƒ[¶ÇRjEf°nC»ÿ6kFWç"E™®ã5Ôtí·2v"¶3Ö¤™Zƒõ+%× YK+媻bŒy/·0Ѿ uµµ±%ÅWØ¡x°ùèŒ4$æ âcU;öõl.o¿ê›xMQ”Ï0Ìjp î: Šj‡Ä–>^Á Õ¶×÷•Ëh)ãé:ïTQŠFÈѺg— ñV¼é’Ú041(:öQµ:÷ÅþºêPýRå¶ãÜÀ„ue>Î$ÓûcÅM·W§”Lò£õÑô$:/yŽ´oîi©Í†2™Pãºò?G½·‰ÿÌ4ªDîxN¤ Ÿ| )@³Êõ¯´ãÖl¢5 J¬MëxW“ô/„Ūµ-VwtwK®àí‹hÌvt…íÚmœ+P|bq´g•ßî/_Ú|ª1ãѼ†;Ë[[ËûÒÆÀW矬X[QÑ|WÏ ’Iªv_ôLçtºÓ›ShmOl°'ÝmE¾ª—»$@̲‹ÛÌšoÖ‹ ÌB<󺀀Àæ]†È8i^# Û+í’*’Ïög³ÜaúËÃgöpºÛÊÅê.ÇÉ·´5WÔÔÜTR“›áªÖüö-ÛCùF=þsþãšS§N5×T<¼Tþ$ߊ±áŒ2ǶdW5¿ÛØo¿ý½^x᯿äó&…Æ Ò#·Þ!³õ^¼ËÎb¾.5 1>º©a|ˆkáÜí…n®YŽî[’§c,i±vx+ZÖÁu#ó¦xãwo¼ñÃé÷r’ýHõ¾äéçnáäÞjQõwÝ\QÓ|êÔÚ›ª= ‰wd6±DæñÆLÖËc˜@2ç‘2yà¥A%’Ö§ÿ®áø(Dö{ùçÞµü…_q{ÙÐFn]ty*âR¼i„¢¥ ž†‚hcYŸ.x«ÐÚ*Øz´6pS„ØV€÷G…Å*ƒŒ É}!ö¡_øÛçæúð…FŽkIŸæq'ïkV§úÉ)‹Ç¼v^c~ŸøùtOfÓQóØ”oUCÓß)ðo®Xû±I®®í4U3]ÅRp§ÿ…?G×+¿b÷ÞX•ÆÃ¹!a³lg§>N­é?)S•s)\›‡·óÙ…F 6‚#ïèH ø,Vr3 ï3/|n]לÏA ­ñÓ’H8*z8ý/±'«žqóƒyÁWOf—Õ—YÏŠµ=ÓŸÒ„};‰âé«®?-% 3y8‘{oüÜw½À>RVlƒÑ-e³.HP˜¿™Þ7$1¼.Ú7o¾rêx¦Í%†ôD•)Gjœµ¬cÅêAŒ•üˆy_-÷¨Ïÿvï0Sœøi¤ÑÑûj…óˆõH°ÙÍLßž›ÅàÓ»•+º—Éã·×”üTâ ÕN [ˆ7õH6‹u¯kíWMC¬¼[ÀLû·{åÉjO <ÊêÂá†ëæ¹Í¨¶ª{Òc°ŽÉã•Òwþùr&¬ê±6c³N¾Ÿ‘3nÖrS«ùYft/ÃõæCl=‹?\\_ÁûÇøß©‘UÇâ+½ƒ¹ï{?ï:ŸI¤e’yØpºExüï*jðçQò øÛ³ÊdÅã1Ÿ@˜+>Œ¯N¶~’4ÚÃŽüùãÞúr¦#ë¢Ðfµò×e¹!¤j7B!>nŒ@F °’R¡| l=0EJ³²ZÉ( Òt…6Ù&r_v~óùø×ù'µa³R§õ&`­Ô틵 I¯ËÈnø3µ^ Ük÷ äÔ©Šyr^3¶79ñXcv¢£Ä¬Vν bŒp =IL)†— º¾íÛä`Ø2K/¬"z¼SÍYÃB¦”ä5ÉžÔCéMÌ@Tß8Á§úü^gâzb÷}ýr¬¡¦ÆÚÛs-Qem9ë²úóyTº!WR §jjzŽ‹YùDºmg^bÂ[Å*UaM¹qr·¨g=„„Ç”ZŒý˜{qêC(¨Yyd:P.Žh)½ 1 ‚È‘€h…tèfzúHÎÉMŸèC}þ]ø´T£/…„¹ú{uÖÛcÂÅãy.‘Ç·ä;´tÓu±–šæRi®ø˜ûvg"‰9Â[ÿœE¿£É±ªUªf@Q3»™!ÄšA©½'•€~ˆZ<*˜»ÚE»°?62S¸5=Á\HtÐq¾=ñ‡úü•‚3>ÍEr_Lt»¦þð”Ù«UQ_k&³½?“o­j3)¹–‡O•¼¦Ü?]èÝññ%íao{ÍÄ·¾<礎¤unX5‘ɺYkg‘ô{Ö… üÝ¿`PP\Ôù¡EB]¸àEwO‰ŽÝî“ @šÞPpúoœD ìœÛ¶JyÔ² ù»ÿè—HEO¬Zµ$Ãñ͵åca`sbëšKjÈ”‡ÿãóå¢<›êO9›Z篓Ý:³·­27$ß‹‚xgÑWÖä!Ù¨µgÚ5ˆ$ÐNÑPké‚uu^d!ªyË]H÷à¤ZÏýú°3X:ôîòø•Lñÿñ”-’Š}±ìްš*_º4šnZÊ7t‡%4„ äTsIyð“ÿ7Áè“Û]ú,púæÜòÉn}yÞÉ=Öo}L:ßÖ“³D=‡–§¡í z)˜aÀÆ{1¨¦Ç¢oeßYÈ:Õ¼wï+:ÕßT ßÍ:¹gJ¥ié<ýp~ã½Ä¿Ù©Ø—íNóUÒñ²?ô´ó룮ñ#å{Ÿ‹&›ZJkÈ””–HHD¸åRïÍ”ûW“Þ:W‘†&ÐXéD(µÐ†ŒÂ'o‘¢€Ž!Âí¢mS8d#&±*3#w.ärA£ÿÂç§õ©Ú›J.²Â^›÷ÍSl í^ÞÁ'ü‹Þôg.SX‘ÚXËÃ>L™òoºR žß`É®y´nrïÇU$å„:ÇLõ$¢ºTðœ38V…–|º`Ð_÷CáFÁSßrM+Þ8qAʸmcmU1'tãiä•H)t\[œÏå;Õü‹ùÎÿÀ®›¬žöê\ÄÀôå8±P~óýv˜5¥Ù¼Ü57gÄ }ròÊiÜû:'Ò=‰3‘…ù•H®}°Ä^­¾ƒˆFPXW=ujQ^³sÎi‚ÿàEü¹ôoS.$Z™0±2•ß'äOE’|žÒ ÝT3åAEIäþÎ SG‹@<8P6ý Îî¹;×Ò][ñ~‚J%¦|\+¶ü4îý|ÇéHzNdÛ¸Yçm›À¸—˜ï¡\Š‚àÓ®½R-£ÖrQÑ QˆÅUqóFJæ .·ôf»FûˆŠÓ–[ZÁŸ¢qD ØZ[qWG(ãá ùPucGmuH órôŸS/t4 Æsžý«ØËï8ë¯oòq*”¾ò×{ï}¡Ä§ù0Ï‚víD$ȱÐ&ÍÔPõC=VÇЙ€f‡½"”Â$“ê-žÂ§GêT!+<+ÈO¿·1?Y‡‚¯ï8….딦ëyÚ;üR…}®=i®˜×žrRâHò‘ÜÝeÌG“Épçÿîjì*oLÉR{±nžÊ×§œú7óžÿÂ#„æš1•ëó`áûrЧOú—û•?füSØÐ¦«'ÂÊ.é%ð™cHÅo·®ERnÑÄ‚Å#®µ!ú@Œw=rO ‹²uòòèG%,Öôß,ûp¿íª¿Ú;iÇÚÆxâæÔéGi,ÿw…DÖVüQm…Šd[êã¡ÈçÓ“ét:š~²Em² ýQ(É”‡7ú7)Öš›ø ¶?ÈK×@ îüãÿõ›åþúIKT{õu«ïïUŸ ÝKMeÐŒ|¯L£µ­}nh?Ø=J-¦lNe$è}›`¸²Ýâ_sd8x#þPØŒŠ2þ”µþØèóï2?]3¥E5d{âþ¹CQ_ 'ûÚeÙ>Ò2¥¾k´á«èÅžh‰¨÷·ú7ŽÉɜ»½Ô°¬]ÔÈä¼aiÔb}õ¨ ˆ²2¯ÂpÁÆjÀ/ ûºâwáÓê2åÓã!'ó7;¾…aÐo¡gÄì#Ñ EÏó¬MÅ]’?2¸/:ÁLn[²ªAgðõ `®¾kBƒæSü•R6 8ÝeÔûÆjtÛVfuMµ“¯ò¼ú€ÁGt®s"—y u’ݱ™Hj “\P‚†æ~ÍvOv¢,J½*-|¿*}$egVËó(:-hýyÅÁ€E>Ë^jsÎçŒ?þYL¶46Oê 7ÕKÑÊú/0©ø ^’/õÒïY1µe‡ëD6tâ‚ï¥A3ŠãùRRj%%pתم‹v{"Š!5‘ô)5$¾UÎé¨vz_´ÜάnlÄŸÌÈk:³Nð‰4Õ ƒe¥ÙF"/pí:Ocr!n¦ü+>s%U¤™µì(/mrMµ%áóm'RÞaI×kóìÅ®·›Í^¡Ã}îfQ˜g,…«&aù„Ypâ…t6uؙշíì-µ¼´@X|ÆL~Rá«y¨Gp\Ô<ºšN ÔÞÖÔ#*bJ"ÿa—+~ÇÿÖ¢ñM÷hL/Ú)'®—²›¯M#^gIPoµ'ª§gÚP‚Êåm€G G‡U¨Â[=:­¿òÃý<È’ø†'ÇXf…;!Ëc¾dúc² @š„Goñ7—¤gAÈ4ÞwÚƒ=ÉzîÛ‡cÓKÕÃN5Oáö1aáÀ©¿àŒ{ßês9ÙËOBrgÑ1 0ãñQ(¡uÆ^ŸÉjáÚ ø@M`Ü^<Èrøè­œM—±ûüî„ bÜæù(ð3\t±›jJÔÿ…‡Årã¶3@=FŸ>)³üš,ÙU¸(ѹ×߃Hó«Ü3Cp,ÁY}û5+ÛÐò4k˜l4%MñçB’Ì—òtN«LD4C<ˆ\Y·]-½¦ßÿÁ¾c¢,àU¥‚”ì¾Niþü1÷בn;ƒ+]Âa4êèÞÅsûÆn‹zÈËC~믓¥¦ãrV¡ËãÏ*«ãŸkÀ› ¼\£..äƒsñŠ¥:u ’HñJÑ¥ò•d3dTYMíæy+Së¯ÿýJÇ1NG“I’…‚Ü\S²~“¨È&Ïp|°ûIQÔh(Ýcçpœ¶•å==øN‰Âå÷ìbõϼD¤\•©5EðÖ%<µèx½Ž!Áë†]‹÷I©A+*Õ÷HT¯ ²Þïc™n¸ýµÄçªÕÖŒsUf·qäÔÚS¢6Ñqæ 4QFêKÂPDrˆûñ¾gqK áhØq½ÜKDzÄtÌEOH60Ý› zåú™«Õ«j õºˆ˜KÖÍ)ˆ¨WYÉZ«ò› >×wÀœN|ŒG޹×K*H…ˆ‡ýöJðP'›š’ãÌ>19— èO)ÏTs,¬ZÕÞü‡ãø–ZßZO j’ŠC®µ£% ‡AA„"ìÌêUL`€Aˆ‰V%ŒpZŽ8‘£…'N\žÎZ…“êŸËËÕYh¢gº{F±¤‚TTññØl§¯Çß·¡¼®nݺººò}eé’NQQÎï¨ð÷e¶ž¯jÃÔ@ÊS”PíÈÀ‡¸“õÐr'ëó^fHñ   ñj X¤!n T#E.¨ûRÀb&¾}„ò¨7óþÿ÷¯ÊàÚû‡±>äÛº‡[ôoÆ¢ÿ×VJAÖÎã®9Wß탚>S3ýXu˾°ßÉ´urÕ‹|P1ŽâáÉp~ áG›^p2ÆŒO±;¶¶AÀ¨­ƒ¡4¤$Ù»kuᜂ|©Þ™œ7Yu§¶±Ðc¦§Jä|ýw¹?«Sßö´Õ5kÇ5Xv··-ÙÙS­ø{ó­µÕ^OîoïôqkE÷ò6oÿ©µ>¼oņã±"¦H¶{>ЗòëT^Tó08Uwía@›Ã8û8¶\zÙ @.4µ ó{‚yÌs¸yWYé#ÅHþɃ,}ÎX^¨;†ÑÍÌxäkÒ\±Ö]ÊM}£õ £uÞÂÍ…ú[¨YûÀ]OŽÆØ+–û&¥’\ù˯×TÜ\¿®?”áW晡Œo ”T*Rܰ͋{³j«ˆ§žŸÂËv(¶j& -û꧘Éa†`×µzöà5Z,Kw1óõ7dhýŸ*{²ôÖMþÐb¡L¾ãÉ›k*j Pšetz$çxzø0PݯÏ;µ¶fÊ©æææšŠŠ›ÚSN¤¶ÌÊÃÏpÛ’‘æšš»Ê«ó±L&VÀl¨¼CU}͸áÈߘuÞìÑ”V‰tN%»ÖÇb†0]ÙÐõRIÀýn«Þ»¦34ÏÀK‹çÜûûe|}-n–øTß1ˆÌ†&Í_-ª¸Õ±XuCWòæ‡+j<˜BE—x2Xmï þqJ^³FaÕT,ígJr$ê#3a/ñR…ªJÖecùL«°yEȜƩ–S;ÁÅ“³z_Ö”ÊÔ˜%ž 3ê[°›ko½òï[׌5 ™m¼å•ŠrF¥!Uœ• d|½ç'¬G9êônn–F—Vµ7fb±LõºÍ¿>¥†7ü¤‚`U[ºÕqbûjJà~+汸;ódÚjÌrÕ(±A5ü®#Ëg×m/‘ÂO²àB•u—çü·þ ˆãÕ¸™@j…@FêBÁaRîq¡˜­ÉÁóƒ^ULP¡h "ó†Tñ½ÝÒ®1¯LÚ+ÅHÌcnŠ–[O4íÛÛ2˜Í3¡dkÛÿxsMMM’+¢ˆG;'»ô~ãœ+˜¹RU3/ËtÄBÁñ.f¤ØTþ·æõ̓ÌVU×I§ã²Þº–|ýã¥P…W, ©ÓµQ]|”J0î‘ïR W„{ì‰ ø²¬½{f9ЇÁäщd-+vi8XCüÇLg!kP›øQ µ‰¡õ±#å ¡<{lýuKïç®ÎØ3 ÝT¡uâƒòÑu ››ÔÜBÅOŽûð^Ýûx Uw?ûÞ†l,–mÝÜ'\Td蛹Wü|œÀ7µÜÒ:­!"p8zÇõmX¸8ߦZÔÖÐQàE4½öúcÉ@ª#†Ùbûd´ÑéÓïfíOU˜®ÓBãBD«°Zšw§9‘S&“µÇD UdY¸ì5MÖV”çe&R¬Uß ^ƒÁ§ñ0wÙ¿·3Gl?â1?ð„½o7b¢É{Ȭk¬ÎZ×±±†ËÑý#p—·¿³Aá”è„»†ëäSˆÈÖzE"Ù °Å…tTŒ†äe7„ ÄÒŽq/]7aºSèJ{5=û±ÇÃÑð3íÕ™L†©˜I•ðœre¯j¦lvœ\þ_Ü㤤ۮyýˆµ‚˜Ù¦\>ĤQ[·oÌÄÅœWÞZLO7j/w¾3Q- kˆ@“ Á«ÙÀRI¼Í›¢=ÃÔ N,n_ÅA‡± Þ‹â¹z"hˆ˜ÅLpJŒjeú5ÄW8>Ì!ygzwÁךà3Ñeí¡ËáÓå©[èéÎ+–V;Ç·p´]ßÜ^tŽÿ]H¤‚Y9kUHøH.Ê7n~)Žˆ°ÚÊxÅ#v_¦ñqàÕÞ¿±äDiÜGìõÌ¥–ÉG`FÌÆØ4[z€ òVˆÊâ@ëT“Â(g>ü||¯n [¿ÍèGÜ–ù×­„›vç Ø¥óÙéQÆ©-çä—”šv]Xp²BPU¶•c¾¿rª—ZkÎ+Ìsœ0r"E}¦¦cˆR^^QV«ÒW·½¡/·)a)^vë‘ñj°5ÅãëPkd '.ÚßÊþíåÃSÞ‡“¢Dûðóq˽fð{{mè“uâ ú’< JA‹Å‘ñ›×ÊjmÌÉØeö{Dœ ¢öÅÜŠÅ:+ñÂ2jN+ÁÄDz¾“À'Dt‰å"xcù€Ž.=š&áCP’€wwA'¹–M, Ä †]—ZTA˜5@üEïõœŠ´ÚHzw1RÀ ¿é17ËO¦,ÓÙ›…!aÊãéîtZ[yŽF žãÑFÇ­¸‘F'–“x<å Š\£‚Å –xE<á>«-=´»ëȘpëbFDgCºh`°c mV”@ ,ºÜ†ò®!pé<„ßD‚‹–®B†e"±½€k’4Ã>#Å5J-ïÄÑH­Z¨3þs,bã_Ñqb¯œÙ0@|ðR¯SÍëZáxOúÑö#2 Š÷exÇ"‰J‘ˆ$j¨Øí6íú&"ÿ•ºÃ«‘H­ØÏÕs÷Ý Wµô‡B¡A9—lô÷Õ¹f´üö°£Ð¤ß¹Ñ7M¡ &S—sŠ\ÌæßE´– gT:ub–è@áz‚Ws$Ä‹vÅlˆ£ÿ8ã ÛÈäéj«Zãt˜Á²÷4;ÒÎ {îáÿÓš mÏdBuc\{ø²¼©ˆ!e«¹¹ÚÉìÂôØ‘ÈOø_UlpìcïË ÐO[Õ`f˃›¶„ä˜hrÐ/¶¡,Z½R-¢êÐ þ‰„H愆[‹ZÖ£½„–Ø)ß½ŠŠë§Í„ îYƒ^2Eጒ„¡èòþ”˜Ëu‡Þ‡f·ÿ=ßðÚ÷R¹Æï]=‹3˜¡&û8MÌ~×f68pÖ¦l~Çévoæ‡æ<êµ«š¥‹² äß+äŠã£ö–ÞLfÜu€9žþüº°Já#Ö¢Œ(ËP㻯Üû{Ï÷O }ÏqªÍ~—~¯–e­ ;Û¨'„¿Æô|£\;a(± DÎÒ‹ãl¿-56}9†*óB‰1(ÕrDº=/Öøfž³™tóa‘â¾4~X­5ª#^|ü€-‘ŠTü{Ä—r™;ÕÑ'CrÁ̳¶gØë Ø›K¸¦O:³ó[ÇY§©þNŠ~¯öⵑ`XO…<<ðÚ®š1ôtI2¢™a®¢þ‡È‰h£!¼¬Q;ùÒw@ÙÄK¤[¢ñ±Á¬b[z03È\ySƒ ÷Œ2 l¹_µ­¬µ\ ‚KŽkˆD‹âL´]qqœµkS¾.]ZCx{CgJ^ÌÚ•WáŽáœñ1õBÔ q!×"ÜRo˜QC;V.µúŽˆhˆ›·ÀE–ÉÚWÄçÒt!)t‚ãéjqNÃ;²[¼3¿=;Ô6ÖÔa²ÏBÝý§äÌMî,¬!‡"Å»„@ö:Vr1&_*ÔÝ¢ ¶Ì4t%"~ Ávn2Y>à¤^Òô²§~qÐëßù¯DïÓq ¿¸™¦R™‹ž r#µG`„¥èd$‘‚üJ-ð(~òõuLA AjH}FïKëÏ…¹bé‹#Tˆç©èpRØd•V™!ßi­½•å˰ȦLK”»$¿†ˆJ΄Ô9*Eé;é¡NvëQußþnvð[!,ZßM}|¦ I¨=éí•bHôî¿`á‹.Z¹ò¢‡^m DDñ“}Ÿ Y¶¸ð!-Ép}¨Z=âo†ÙiÈX‹ØÖ Éýu<êÅKa$0¸¹™I­±{¬”@ê$' ·o™Íé±hKDFÃÖ739_™”ð¤Aƒ­·u%ê„=¢@0H$z{{GFøé%x²ÃAr‰Ú{ çq&¹$ÁÅлæõs:gåy/9ºz ¥°ÅÊÕäC¸GœlŽõÃsÌèy¥ùƒMÆËúC;¿ iÕ •Ro…4Š/KC"‡vÁýÚ1ÑòàñW1â#f–&+Ý•‘FñÀã!ÎÎa¯ÏÒÉnqbîƒéÇ#ÅÝqMð‘ÈU.}‡Tv-\xÁõ3gÞqG/“LP^4ú¨ˆ®±¥žÉ"Ö°ýe„Ébÿ­|bÎ’£•r ³ƒ¦Ã£!‚”çr“ŒÞÿ†åÑÿYø4O ;X”5ª¢¬³²![5­ó9ufÞeöÇ‹º@EìbžX¦ðLw|Ëâ"ÕaÄ  1å|Ní\â¼HëˆX˜0ñ§`q{uZ“ŪIõ”ZÖP,¦*W]ôè’%KæÌ™sñ—žóÐûÁÓæp^MmióŠ‹„ lÝ[î_xéœÕh\PÝ;f‹9޲ÆDd”ŸHEød’EÌÄ+M<1 ¿”=ôà]ïÏsa&Þ{»»Ø¡P™:³+© åºö3L™›9 PÍÍyàZ˜9é¦ÛÒ»³™M/>¾©_²˜Ê(«Ë¨çÞëø+qEœò¤ao)Á`€®ÈÀê%—Î%ÁÍÒdH ¯æZ3ì}è‰Õo‹¼|ÕT.–Édû9÷6ÿ§¿¶®Åò!cá?ç"%S=Úê'pà6[ÙÓ=ÙÌöj–GwŸÜ-*P°çÝVÅžv­HDÖ>Àr½â!±îñ-Ì Çd×JÀ¸ìR¾@ªrØhz÷`†_£¢ôŸä%¨ân¿@°÷ {®þƼT¼Í ²Nôu8ãŠÂ;ÉÃŽSxtåÕA×€âQÃÜCà5ƺJ ç<ªíùVêÖºò  /}ý¦yóN­÷À¼yüßµ5÷‡ÅÔFÕ @GÊíò¸Ö+1NëiÕuEéOÓGF«C¡ÆvYOLoˆÈŒƒ¬ŠI!‘š›ÉIþPŽû´þ¦ ÝÂõ-ê50Ñ¡LwvÕ5ÔýAjP´ºÄ\›âÍbލñÆqó¨H%¶`•2UÅ\>[=ر®¡½½½eݺÑÖÚÆþP®ÑçúèEABQcÜ… <â~‡Z\ø¨,äkÛw×¼S5KPSóðÚµÍÍÍkÙÅ~kV³“µ@CÆÂey>$3ŽÊvëÇT”â<„iŠè~ˆÞ«.ÁÿQnäŸ$µ9Y‚6Œ[ørÿ_ˆFIÁ¡¹¸#yolc€æXÞfM33æNÞßêJ×/:¡±6Èî´{œ œÂçÙ—ó$ö»'ͫƢ[*&xRÍìVOqâgõÕ#¾Ýö.¦‰Eh`ª¦¢òTT4ŸšäªY*ÐX ñhoFÙ~ä|N¨ÛÛTjÍ/ý­‹ú¾¼´';ní‘ßÛø@ 8„&÷w®©Ÿ ¶ åc¡Ø`_ï–J×"‘q7(Æ“íÇYªõ[t²þö½<9'¤[X{D^X^1ÉÓZ{[Î,U)]m”[Ú )ŸçM,ažóãû'‡z‰„ÔAÑ¥›®Èo@=èÆóÅÃåì‘èxæ!ÒŸ¶¶Šômhi e3 µ( çø±îhöM.4ß—è7• ;åQC.Êv´×÷¥ÍŽH¡mlPLveøº÷•#Þ8ýÛ‚\­GïÙ‘ÛÚþ½bÒçu#j`?'šuV@9‚¶…ˆF¹3X3¾<šÕuª¹æ¦˜!:1Ë ãÑò{çâ¯þ:}ùò—Oá·Y.ŽâºÎÒC´âÉ@ôG8ßÝÂa¥!ŽÂAl’Y8µó¬_±VP5FJ­5˜ü-!S-ß]¥4E´ì#å°ÕV‰……þßÜû×oß».[àŸ"5—ŸBG½U"á—u‚ItdË›WÓPE7;†©ž B 8>éÌâ´–ûǕũ)SþyÞ¼ÿyóÏþ¿~þßóš…­žJ>ÓZ”s4±Ø€ÈfœHu×8KæÇÂOr#¼¹ÛSŽô3탡X,»®þÞ3¬N[ÕXa'ÿ^aÎLsEÅOÎàx{‰åëâåu÷W¼ÞÕÊ…Ì„RÏ—/‰J<ýë64œX—Üí} d ©ª LrU0Ä׿`Þk}é.bÝHÌeyèG%dqŠIâç_ù__ûÚ¿ªëkÿ¯Òüá>)8ú(µr›äeJ nw¿–+ËŸX“Ü7¶w]–ÙüÖ ¯³çÌŸY{…x´®È^¶0ºôaÖ0Ÿ>åc¹f$ßSŠ‘WôU"W°à½yiOk6ˇ[öÅ›x‡qª%Vuµæ½Ä¸û¬LµML‡T7¯m†Wilf™› 6$Hoúü¬]󚶉…> >‘7eÞÍ_Ñ’ðò¯ü^"jæ³Ó"Dw´fr<[Ê ÖIG'˜¡åU+A¬Ž±Še7/mæ §, b<Ù#(ª‹åßuÓMÉ—ZÄŠ*'RûÇR.Jpv¨ªf&“»ööç¹L8NÕ9–ˆ8’U=-­ƒƒµ í»ÇÌÆ7³6ZñĶþóÍ?gVƒýö?YÞüÏS¦œ*!Îè‘’{2tz‰ÜŽ ör½IÌ-ZiféOâúJÿ°a$í‹ûÖÎu>³oþ'–NN¼·C8‘H×Òt}C,Ö_÷÷”=’ã{ -z_­\¼)¦xÍS. û¨ôv¡ ξ =®Sñðëõ£Ù|>#ƒ¦ÉySÒMMét² ­˜³ƒ¬HÝWÐQýÚ×¾òó›™\°Px&]\€³J'µÂ¡$H Íüük¶f°7d¿ýëÏ…ù¬+oŸd<œÖ¸´‰>2u:ëê;B¹Lë¾›k̰aMÓñR(ºp´k0ç&ÌTuy[iLá$ûÀZ”Ô¼¾·6+`=Ý+n|z¸‡ÿç+%,˼¼ÎL ‚ñÔœ-o>ÄNß©@¿9(¨Öâ`/ÿ•Ÿÿü+übäÿü\œ¾Ñ¾qVÿÿì½t”å½/"GH‰À„ÌÒ’C ÍIg“4”H†\N³‰ o²7lr‚&*õ.¸iןZ*¹T©“«˜S…1îYÝJoV]µ±Uc[Û骇Ef­¦múœUWá”k×ßûü~¾ßç Aáüuß BH&ó¾ßçûûûý|¦¾áßÊ÷#Éþª§.âdT/©»¯Ý]ý|³ˆiÎsÌLâŠ\&QδØ*»e8£èg­dMéǬGñŸfˆ4# ¦5Cp0ôXµ,8àH'šêTçZ§JùÇó¿ñ—œ«ZÓY9Ü80¿„ù¬„ÕÅKWNâ(Æ{Ó‘Hãö{ Ýpû»b+:š…ÿ¥¤¡zß7÷nß¾÷ä–ꆜ(ëDLVÄV.»'tO@i/#(ëÓŠJV¤†Uþ‘/N,?¯@Qh¼õŠx”e¯)D6X'h†žóÚ|f!yö#þÔ><WHÒ¦#"´µT‚_VÕ ûžÏúj‹SÑähç·³„ò…ç]ÉÌæ†¬¯Ø}“Ø @u“_¾°˜§s¶¡=ÙÝ0 –‡u!ã§Ä2~ä×› ØÕνúê/~ü—ß=(-‰ÉñfýC96B×Ùˆˆ»BHÃË<¯ÓÞA ׎ßT‰´È©ùG3Ÿ©TÑÚg‰Vº´&’-Yã~Ñ+Š=[nUX ‰¨>…]é>)`k“èæïVN’…”Œ$[Ý–“oÚæé èD=”1þ‹ÕL2éomùõ÷•½¯™¦Ã^¯N'ꆬР¾¹0±*ú /e™ºÞMnÞÊ=x•†’?5^TwtÙÒcËWV,®Èœqœˆ¸ªqéPU£HLYiCº·g@¬C:%Œtlï•K¤{DÔ::³”$ÔI.š¾zäy™HkÁÁŠb›!-øŒÂþ6KŒÏ_X˜š­Í’ŒÆóNâÃq@pa°ß-—'ÿª7çz^ÿ4#f¯ªfBt°|Þ±™Gæo¢bô! ÏödD³ëäRΈÈÜ‹¢™ ÝY¿Š§³¢Ä\üîc¢H»b©–òè_Ͷ{ÑhN–ê†avêÒ²cKC€ܺb1 œ™·ìè²òÓ§ëŠã€îy¿«Á§Œ–˜Y`²ÔÈ¿ñjolÁZ#šß øÂ¢eËlòÅEQ$¢äáº1»³jÛ˜jìUªá½rý…›Çr˜íj^¡Ë`Vö˜5Lž»2À¬†ž )÷?¦e­*e_JQ…yïô‰›ï/§(é<ç(Šu!gzÄÔby8Ô×÷ðãÏÚ³iáâ#3—Ï]z´®Hö¤ªx W³Uƒ5ž¢ÅËÉ‚`™¯P…ÌùÖóÍÀò=!1Í!©¨ k³ž‘€jŒœ[/T#Z´íŽÌ+÷>ð艢`ƒõ’Ù=Ȫ"+ü?ˆH&ÝÒ=uˆ¿Úu2Ÿç ‹ÿ)¨ ÿ½5—‚xÞý>ðáôÑmwl‹ EilayºQëBvÈ(äwB&&&ú&êë¹õ…êéÂ#+ëx ô`Õ&=qêcô,n³Ìä"Ü!4´8M—” 9^À‹Æáz‚WÝÅï%¶±àÙëm¨]QÕ ÑÛþýß¾î¥ÁÓ?Ÿ=ûöW¼tV›¥Pbƒ^dZ3‡€\EQ¯q|ªJÒÛ&yÿÁ|‡ òoòzˆ <œ¹ùÙ·ßï-zïíE7=#&o2›G´¢ØÊ¢4ç3CÝ#žL0ó™£Jþ†çÐ ä¼Ó¨ý\•¢j/ŠW²éÛ4ªûL -^tŒw‰r¢q”£‰“g`½—e#›×GøÑÊÔ½r÷è#ï½~à ï=ŸþÆì7nŽ:xÖF„Ne P5Vø¿¬—4„í%SI­KI¬ Äÿ¦Y*¦ 01züîw’^Ý£ìôÜ~äõ^¿î½mƒ÷—Ǥ¢TqÈyË+)·Ù8®‹I"@˜_äó(KI {šaâ(+_¡3ujÝõúhMÕ›¥@2œ~n_+BF0]Í*9Y5Ìß¾—.¿ú…{oönbò¸áº;¼ë˜=ûB‘—ÊËZ`m3E¸O °ÛÿY³*Ý5θÇÉÎQùÅÿ! иFãÏ© |ˆÿþÛgÿüB¬è:öÎox{°üÃ.ÜüŠ`„eвoŠ9ëò>Báf§ÎÄÅyý5ˆÒÌ~O"`;Èú`Á)Ë ‰0Ç`:Ä,ìeé%BPªŠ(º@yu9<ŇA’Ûî¸cìæ™NL{†ßÖë‹Æî¾}ö‰_ÉÅ`(½/“g•Ç'Ÿü]‚짇”LR¬¬­®îY/÷è¼5}û?¡ø/6ŠŠOgÂì¹6?p³§Þ¹÷«ÛgÏ~àÞ¢m7=Ã=µWe ¨$¡ú±0qv¢Ô¸H…H¾ª ²G³2 §JЂ.Œ² hOD*‘@¢E{úˆÁgÅË*ª5Ò‰ì[áéhæ¦G^zïï•gÏž}¢¨èyΊ.œ`òÉxýYÝ:ßAçA*(Nk†¤.…÷¬—Ï9Ýß>ÒP-Ag®kË®JÉÆàÅö–•In6%[çÿË+ÉY{öî~Tø»›®“ºÍ ?YÿþÞuo/ÚÆ²4K…+º…ÑŠ0EÖúñÔ/ÎH— Âé–*B.=‹¡T7Q–µb2îe‰È€ND¦qŒqâD €‘C€)Z:ÎSã-ºŽK€âÙÞí-6ëa³Þ(÷‚ƒS*H«z¶®)ý¹ÅÒ/+{GÒ‡¤c‰Ê‚“% œ.O]m%ÝÕ,ªÛµ>U #o ëtLӇܗ’:åHïúÛùIŠ¿ÍOÒuEƒL_¸‹çòy dMžiNõ+F0ù\ ¥¡Áq¯Îr_³†™H QŸøCeÈê‰Þµ[ì²]ž <™@XÈ4ħ*MOJH< DϸI ùŽw{ë×m‹‹¹^üí†ënò^ášý˜WšÝ›öбþ¸F2AòÐ;›y,›`Jìïl?p²xK[IIIÛ–}ÜP°^Œ£%céŸ$êY+z,ù_« ÛÓ^Ñ a±¶ ‹õHºœ$æâ3ÌÅßðÈ iñE„¸Â’ó;›†ÿÀß\ ïJ|=s¼eB)Â@\¸Îº´d&\ÌŒÎ-:ÜÉÏ?Õ>¬b©? +©vdj¸î¼çm{‰%éÆß˜/z‰Ÿ´Gâ⤨óÒ²·s«;Ñ ÊCR£¤<&9嚎'"ý¥¥¥ý AB%ø2ÁŠ‹&8€¯Ã%¢jc§r¥4½Â¥3‹Åÿzâ ¿×¥£É_£º‰ˆu8%9¦oe,iOÀòî#‘–[PËë-â^ö2ïNÓùuÔr…J¦Õ¤~“ðêU`6‹Ó ¾­ÝøìÙžŽJ›µe[Bõ1~ ò‘¿Ù‘Ç4ÄV¿k ’öÐtsÔ‘Êߢ¦ ¤Núß/þDÛsu {¤½Aáìâƒ\#nxiÛØôÙ\³½¿NˆÇ°Yˆ®ÕØZròµ†DÓbQyáO<÷@!†"H9LqÑ.sJ^)^€×=Ãæüü?1«Íiú ~á3î+÷Ì|¯t",ŒŽq¼=»ÀuýWÆf~Txù\^„ùÓ´¢Î¢#«4ÒIg¡1ä­ÃHâï™2K´ómÊÕÚ›³ZꉠêÃ_é+Zwb¶qñìT)J:Á˜'^lY=ñÚGõ¤ë™É0èbïSʸ– jIv ‰y¸Ä²íp,Ói>^ó›dÔ«QÄ›§ˆ‘©Ä¶Öˆ@¢[[ÒÏœÈK_ü¡vãÓÇŠÞægíí"®p¸”}ØC ’pÚ£iÿ)'ÕgÙ-‰þ‘¶ -‰L¡ÓçOFúc s}r‚zf·äŠ›9’ÃàôGù±Ó±·X,г›x»:£-VÈráÀT›Jü%_E½ÜZÚåi@[L$ù35¤`zJûvfé|ó‚šoÈèÙ‡;Æ‹IL6¾CãŸþÏ_{éßnò„Í:q:*¬ñëwxws÷ȼH¶ >‘Vä>@жvš;â*)é^×½oï¹–ÎÊFqµžoßܳ¥ººzd UP–ƒ„„ÉC0¥zr96– qa§†ûÁ^{0£B®0LË£ÉC; ÅMr3p6ÙŸ¸7Ž}¿Æô§ZxåsºÀUg°Ž€ùñøCŽ<¦ò`~©Hø“´±˜25ôT&"lß/]¾ó>ŸÔ£ŒWÅÓ7󛽞)Á;ÙÏh·hÀYÜ ‡æSœ¤JE8ׯ"݆[zGNl»¥¡A²E·å•ƲՌ¥D$òJòPÎò §(:qBY`¸É1ÖÒÅ3}Q h–ãsYˆP„olAgBG¼hâ5ʧóž¨Ã5tH×­&a ¯|; Ÿ½¸1ßZ“¿žiZØÌË4râ¨åiS6kK"êÍ ðææ±€þÂ…7¦Ž*õ*òh9íÍÑœgÞúÉJ¥î‡kÉß5<h¬*îÖù~cª%[WEЈ”1¹9gc¥úÐhTä ì ©$D‡ŠmsJªÉ'2’m›§Õäó¶ÊA¤H6x“§n‡³ø*ú/ßÅÞOù¦Ÿ=Ê—r£cÜ‹<0}4÷Žzÿ÷¿äÇŪX«V¯îªÄÎŽ&Ó¨ Sµ[†cÙ²ö?þŸ"6KäÅ‚\!Þ¸PäÝñžP1ž“pI‹5@ëq1&¨rf„Þ :o-•ÖÔ¨ÒâÚÇ<iN\Ë1âÃÙ^âôqYè´Ì³£eùù¯²T}V¦§4Àg²¾hðîÄjÚv–Èüõ;M/ïIŒ 5Ýù¯ÓÓñGþMF¾åÊÀË‹ìα ÊB"A{Åsï5±õݺó›èzâ‰'v?¹$Q*7¡j/µfHÙ41Qê%Ûs7ùL]|ú"Ä’¹ÓKÛ¤Kg‘!×qc‹•w.-³Â«š™SΘùôfÕKi‡ƒçÛuŠoÑ€”U§Þ´&šók~çD–¹4Ph߬¢1>:%Y䆵µÜ{55±×™þàå—´Ì{åK7üð‹ÿ¶(>výíʯçÊ•k»EÝ[ÿÿþlòh¾Ø[£RÒ‘»KS €·$«@š+%³WërÖŠ9p¿xþoLOŽÝ¤*¢<'áå…¢×ùÛŒz‹´¸2ªëX ÈAtÛ¼*¸þøƒÇÕBÑ[q¯îñ hï-iž2Y„ —І-Äl_§È}5¿c± '²$²óB¨ÿ+ÖpŒëÅÚØûá»Z<šœÿ½¦¦7ÿêÅæoúÜ×¾ôùg¼¢ ¼Qus<=°žÝ”i¼'[Gwíc•±–^…+ªáQžxNàÕðÏuÆZsèee$+ej{Ã$# O†Þ-C¬^*b9Éæ˽›^úü—¾ö+v[+v ™^…#G‚(" !<h9®|úŲ§ÒÞÑzâbCs¨Ç€>|ÀùIë§½þffÕôsàÒ õ>öÿЄ=2ðÕѹ¨gqVïWÿ°éÍð²±coÞÕÔôòQ¦1oþè®;y¯Ü;ûÂf”Y¬Ü0¹D"OeÙâZ» ”Cù(‹Õ¡±6ÿH™KMñV«¬8æØRñs¹E!nF!¯ßÄs’GïýÙÊhÑâYM/0 §m27D,i³oˆn$È>od´ç+Âñ9mÙËÂV[¾H€ý®@š´úð¥”ZxhfaV+·@}›Ïœ»tÞÑò£óŽÍl JJU=Q_9ÀÈWï‹nxékÿü×±ØûLEúëØèâï4Ýõrx¹7ú×Yÿú³ÏÝ{7s#¹ JmÝ2€‰o( ˜Ÿµ÷ ¤>I÷ ^òÄs‘FÙ«>«œ†ÄXxq—êT÷N2±†9r zw\÷Å/~ñ%¦ Ç§)¼Œ…ýw5q­&lø5 ’ ¡Å}~jÙy• ¡Óžw.?_3 üÄcÁ§B:!˜_Í®´Y`RbÑ–Ih™—ùz(À{§—ƒ1êª[Y?¡‹.’RÝäKÜ­sÌ…±›^ÿâÛ_ª÷r×Ë˼e1U¹ë¡e^Ýž7_nzq‹}û÷•äÜðËHGrŸ«$e_g‘ªU²7Òeð–:¥p™@†k†Ü+Uë-%“̇rº*éߘÁbgéó_û‡;ñ·zWSý_G“ï¿Ìîá(sé‰TÀÒ0§oªX¾”Ï-›wlîÌŠ"ÏÛ¿ Ù4޽™bl§¾o‚øÔkßqêU±K—…¿µÕ¬sª†§ç¿ø_—†tËJÙ¬èÖyIˆÙá²l=vdÖ/¿ydtle}SSÓæÇÆæ±»¼ëÍùÌd—´M:4õ?q”¤l]$¡àj‹KµSïJ© ¬ê–T£Yfj.+|ªU5H"ÖM6sz éy¿z€ù‹{ï÷â+ÿyÓ? ϱƒŸ!foßljúÎ_Ç@Ù$OB‘ QËQÑ×7s^‘ ãð‹ãÓì,XúØÌ™G/Üó0“KŸïãÒ ˆEˆnâƒEó¼ôߌWÏ?'_Ý‹'S‰HÒLZˆo—ÄGÑ~}x¶¨EK©0µåÃ{ÿõ·–z‘=/3‰0Å[æ8žAgÎEžêu²[o)„"){*’Ø¥L]oU¬”ƒÉìîH «¹ÉêªT©6¶…e ë3ªÙY<ÙtDõŠsè÷Þû³5sŒÙ•ï½,”úèC45ÕÏKÎè®ï½ÉBdüz‚TdéŠoUjÈÄ‹óÄSÂWž!¨)ü­jdâEåóæVÌ ×ãÁjKp/ìÔD8´iæ±euƒr=0z‹™ªù~kåš‚íï<õõ·î»ï±Ç{k3oÕ=Þg{¾v€Q©H[¿êŠÜ4ŸÎ¼ø½Ÿ+b~èG?zùƒ‡æ%ÿÊ”åƒ7gŽzÑ59{ºkå3;-o‘”ý-aR»¯2Õõڌ纲„ ¤ÿžµÂ•_üs«*Ó{‘]Õ“³÷dÚºl{Oáù©±e‰ƒ³ÜK2·×ôrSÿرðšf­ƒèâb3Fdé¦.šu^¦}û 6ì*¨:ßÚ8к`-_E¢ª[:?L Ä"uT$üðÌeƒ@Ù÷Å_¿Uk¬]ûݵÍkË ÿÄûAõê»enhÊÒã·)å–[ñõ…Ù÷{c×î?oº+üA©—xÿ;RY¼hUNKRÛp@¶l½dË× HÄŒv•äur0™Té¡ÞKV o­][XxÏ®~Õ&aß>9çwïA–7•ïáféýÓÌo|éÅCÌ`ã9LìÈç¾öyvÑȇe¬h]Ó„-–Lð„̸­¹°Pck”5? œÖ>µ«½jM§€´ª”œêl:`é“’pÅiùEÑä@eËæŸ<¶J›/âm¹æ­ïr䉥:G˜£zרHT¤»‹ÆD½äÄ£w ~ñõ—¾ôµÏUļÓÌ[Þu×——M&‘KÕ—Ö¨‰…xçŸÕž[á;©ˆZ/)ÙPuþ|Aq÷% û¾½à·ëÍ|x¦õùÉ9¿»¹Û|»lj¯6­&Ÿ‰7>¿^GmÒ­Û P”y-û™÷ÄÍŒÞ|»lƒŽÞôžÈL"Þ`Qæ‡÷3Õ<ß;Éä[oɮƌî Fc•ì‰oo¨®n¨îíî.áW7ï‹T÷îëÙÜ2œÈÄ"iõÅ)°Wž3Þ]Áüùé÷ß¼ëå7÷,óF¯çõ·__”»ûöŸË²//óòN¡v² 2©PùÜhì7jÎ}J"ù6_C©™¨@Ö²x…J”£6|{íT_Š ä<{-„‹ML„1V‘¯\åkQ£bBk›'úÒoœ8í .U»÷îóN_à…£ïõ¼ÖÚIÌJmu÷¡N ê?:Iô·®©:w`Êç¿ù|ÏŠCÛ7·¯i-M$EP™Š$D6^Úž×pÙiù† 1ÞÞ¼ó_ö³Ï•³ãÂ3ô¾þH{Ão¨$ñiv£Éí#8Äb ¢RQ"efUϹOM" (:P×Ǽ¢ER,Àó¸—á¥Áz›e¾‘ÃNÑ(ËŠa4ð>AÊ™¦ç×L»8õ뻕žšYñp€,‹J… ç²øpÈO®@C¶ÖÔ<‘ÂÕcYÐÒüG*ôí~žÙõmÒ)z§…Uþð~ÏHä‘mì|N¿]ô«¼5ÀfU÷GOfu*%½ Ý#ÛÛÏ7¤’º°&¤O%J[[έØW½®7WÈVÒ“‰ •Ó’ƒ)oìþ™%eÏò©¸\?d‘‘;A^†g.”ÝÇÏ ˜qEµð¯˜³ÙM´äç_‰@x“°\¦OþZ¡lx3—Hþ0ŸÂ³{½RE¢íÅ0ôå r9èË\Æ+܈±,eµ^û™´Wtý‡·ßíEKÁH|ÉÉxjKÎü½­›ÅT—òzí*ho¯ªªjo/ؼá›#[ø§»k') ¶§2?)q'*â7xR»®cÒºò>³p Ü`y‰S;LDœ¼òu¦*ƒ|Ž×«º"¬ÖÏNuØ râ>‘u%yðÁü–elª·à˪ækç,÷i£•T¼lO‹†º’È ïÝ4è½ò«¨—úc7ZLh¼LöÀÇÝ{YJØÀ.–v—´]~×\g>ÙÏÔcÛ#R\i•~˜¬‰,cŠò¾Ú@°sŠrË+¸2p3ѓǑð&­eS²0œóxñÙ¶•1=g‰ìÐ}’ýLX½ôŒ¹Õû3,˜ÔOà™1Ž@€0±z1\ïÕºZ½áÞ@›0ê Þ$F~¸[+òâ×(ÞäÍcÞ3/Ý "¬Îb W&W7)VaÍ«W&{’ªE}DW1ñ";$¥§¯qü‚ýü™aßî, ò –¨æº6ZÌŽÉPe›—¹Y¾¸—\¤lÄK7¥özÉóq‡Dêj ¤Ê‹9ût|¾[Nü°·rÓ×"ùM8xÝ"öÐÎŒ#.!},| wË;ôW$²[â2ÝWÕ¹Ò&Úâ,‹H<6å¼°ù8ÈO“¦¸¦ÄŽ4ì–|k qo³°k¦æû1Š«†JH¤ëÑø.moÿÀ[Um—˜ç’ÑŠ•ˆŒkQóý ¼]VA1ðÕ»ørW£óO^r{/«½rBÉcPfèÂXb]Ä*qPœCeûMÍ•ä!ÏL”;À(©Zbc9óTÙâ´·x-~_…çåE¾s-§ŒÅ°¬h™Ššcì=Sn„¿Ii¢8ñ {7]÷‹¾âÛ1•à®ÜûfŸÝ‰ÄO¹Ùzw%2^ºƒ½·ûxCÊ#©œœp ­&”W)H|qï3‹i(.ï«L}ÚÚÂ)ïŸpÄ¿zC}«©W©,.þùòaVYÕhA¡„Ðb‘Kð`p®~S.ë•*^Õ”³¤LGdÒ5û®/òÆžY4èEÄ‘êa/æÅg½ø.Q`¬äo,¥z†©Çééâ‰äUÉc3òýÆì8%…åä9EûhÒÊðIwQí]ûVÁS—÷…Û=¯H¬¸ƒ…ù$f1 fóq-¡¥4Ä·$Ij–U  Œ`7²Fe#,¨bRÙ9ÏÏïóFXíÝ{3ÏÕ»ÚŠ^"€‘S½——¿bêm½ñ¨ Ëùù‰Æ~=âDXå/öiþaP:Q&Kζ˜¹¶ìÒì‹È]K,)Í?Ÿ®Ö³£“¼ÇêHÞS¶•[¬šßHbH¤ÿ˜……jYe¬-aVAzN‘+KýÆí׳À âÐW¬k͹ƒøÙ/¾ÿ4‡ íÑè˜(«±cránµó¸0:j2®_V,„Òps§žü©Ú',«Ìµ®-Sûê²Ñ^ØîÕÓŸ²y%}G=o½üêµ²?5­LW×Ú20Ÿ,ús¡Ä˜ûBïTÂ8rœl´D6RÂi·e Ë²snж!¾·Ú­”ĸ×Ä«¯ûïIo €ØU[ÝêyuÞî£Î»ã%™•0‡îµ˜ôÙšM›šÍMY;R0!`Øöù Õ¾æÚ·n‘`µkú(B9Ùvõ!¢œøkhKD$‡fA«˜3[ûÔ99oÖ|ß!38Qø–øm• uU*1´#]Zq¹Ñ: Ï•šd<ÅòC¹Dù:oN ^ûÂw`¸‚\“ëRɺ-»*3Ù"†¶K,àGï{ƒÿ¨rÖ;Ø­X‡>Þ#ù&¼1œªj ‘½öãü¬Nãó%6­¹yóŠ2ù‡ÒÏ-š›IÉ£¼ÞS Š‚OÏFYbÜ=ñ˜b¿ußÂNïïš—Ù,í•Ý’öJŽ (.ͪW;zq”L<,óu›JIÀê ¼Ì($ÂLæH^™þá…:Ns_HûÖ]ý,¤¶·¡GµÚSA4ëÞI^fœ^ÎÜÇÿ”U`5ŽXôQé@Š6Õ#OM;/¦Ó¼Ri=ÊþfÁI.–½å ÊJI2ÁÏöÚ·ââÿ‹fiÈn3.\"ÒL™ˆVÉç_xÞÛ%Ò¦hŸ6DŠÃ 1yp‘“Ã*ê5øÒhÙUU›±›2ïë0³Ut=3Öîö%oÄG#UÅ W7Ì*ih;PÉ‘~#çÖ{Þù†,Yioð¦¯È·xÃëÿÈ<ýÀ7w˜èDBùV„1þŽI³E.ý ®"¾[vÙ‰–Â)»Efß…÷"\ ÍÓ<醴,‰êÃâÞ6!sÞúBép$]Ù[q³Þ¶–S íÊgòx5£ç(1›`6%!–9¬‰zÉ 7–Ž&Wô—`K×G½ØùžË‚þ\4º{ªàcË–u,9ŒgY2ä/Sþ?ßû7UÕbñxb¯ °ÎeLWÊŒw–FJj¯¿†ëH>Ó¦½…_n=þÙ››…»„bóÅoÄt_nM‰Ò‰0^bÿßÛ.±Bâœ0ÁH¼.ǽŠæ‹r–%‹Uû/®m^ÿã¸uà ëDŸpÓ/«p‹$ùeŠ&á5øî¨®7o}Õ‹¦~zn€ïLÕmªG$pº¿¬ÓÃÄ©¤#µ%­¼£« yE½Îëi§Ç·îî]¥|è§}d2ìÜ©£¡í”€|äïgÍ>á¯øÁðÖGÁº{b^Tþî¢ÉÍc攡ôN7Õû_Üš×ÿ‹üwœŽÆôàgÙSéè>)VèÌ|;ÈÅãæ¿Ê‰´!‡ÎC„¾ñ0‹§ e ¡eOEµ)ðô«7oý ýä¬uÝüXMÔˆþ²ì+grKÇÇ‘Ž”´5ª:£èézýî9mØœ‘¨µÕ%çú™‰‰un¯ýt2©-ém(9¸ë|\H#“Ž&.uk](õ²Ak¬cn$-ËŒˆÄØÜñ3Ÿ®h¾µÑ'@­q†xéÈK7¾ÚµpJÌöÇÅÉæ3&üÌ—ÝóúÔˆ@U,`™U…('ž£ñêeo%å·Ýó†e ü·¨¶YÍ[kÕá¼=!›¥¦D…ï$»5ZR"—J•DxI1°®S2’0Ÿ¬­®-H‰yÅ5+ÚzÛ®PÕm=’«N¦rCCàÉ»ó"^2 '!“}Ìœzé* 72,ÛIJ)E)܆ĵ}Ç©¤¯PF…éCJÆTO¥Å/dGGc|¶M¶§´}Qtâ/‚¤â×Ì2ñUÑ^A¼ØïIlÒµL¢*Îúîñ|¥]v$T/+Šˆ²•j°ÒyòI˜É.þ(ŠÙ¹ßt)Ó.Ùï.¦íyí-çÔôáÀšíÅÕÕÝmSEwuCõ–?Hêù”ÑÔÎ%K’Я·‡I>ÌÜÖÆ eÑÛ¼«M·XýÈ“ qc 8qߺO½8šY§H$Ï©Ʋ[2Š@Н‡¯Í ¦ï&gBÌLlm^»¶T:iZPÈYI˜ÉÜÂÿtqóWÃ)fŽè€WA0SÈABi}Óiù4lž+ó‘îbée=œìùuÁuhTùãJ¤8âµ—N WmáO[B–áårÑÎå õ-=Ú×4ZYðÙ&%CAe5—È¡€ŽtŸŒñ@WÜ×yHfU`!4HonÌ 7­h<ñ¿ÔÔ,àïÛíœÚ€²à(õߎç?˜Ïe'G™{•™Òù}GÃôÔUjM}¿æøVŽÿp€=þÂçY\òßîËÏÏw3{R[,8ž/˜J¾?àE+B–vPL˦ o0/”Ž=Ó^ìHä`„\3‡UÛæâÄF™YA¼v{GG—t %õ¸Uk˹ Üw©¤zݺu­c.fKñÉC»Ôì"˜ÛÊ$‡¸4ص“ýe °«ö{ÉÍÕÎ8–ÙæÊ#ªJ¼„Âþ‡Ç@Ëé~ûSÃçxú||Á‚­ ØÓ깸àÝûÞ=õ*ÅàÓ)ÌbUzbáÇza¸ô©zô,­y—¹šÖ­Çÿ]*êUñ<§æû1/ùSÃ"ò”—™_ïCþC[GÐ:>"pA££P"#bÖœÓHF°Ü¥¶:ëTLgbK:–ìLšÁ«L<1P:\Ù¹F^ë++KûRq¡G5‹ £k‰¾’Ñ’G÷¾V/ 0³ ø´È-@?–NLP€ó†àŽ!O$÷ÆÌEhF¢ã»dQžå&ÌûžÓ­Ösù5<èµÕY¬#H—Ìë‹Ñȃ5<“Ùøñó¼žÌóö'&\…óŸÿƒ¨Àu Å{DêõC3Ç¢‰Œo©e‡“…io¤v]{Ú[ï.¶í‹xCòŒ§â¹¹±ñØ\&Óš!¯Ž”evDw{Òk<åiÚøÐƒ"$Gò˜÷bâ˜8ÇeOœJޱ—ü]æÀ)õ¤^äoˆz)þiþ|ÓÊç Âñ~)X+T>D½¢™FÔx,!J.Âa.‡|ìHæ¡ióB¶—Ž0%¬PˆÎØã@"¢?ÒûÇQ‡Î±„óµ® Òêq@³â©.Ù9”ÍL6Dšdž†ººÄWyìËŠ™\ÛpªÑ‹·ìƒï¤öÒ¾½‘Ñõy¦#5Ò*À¼‰z ‰B¼éL‰ð¦éÕãJAØÓþ€ãÄþ.åìS!(_ G C^ Ëc(_îEæFÀ$¢1QAä„FÞùü|YÚͼzœå 5ÇùFnEê­\•÷1#“È\/ #‘tŸÂÕÅ­^² ÛÚNu§7fžì’%];‡†bÉd&ŽŠT‡ý/=šL2Ú)M…ºFsa&wwD¼T•­Æ×nïÙqÊL4œÙq¦ÒÓòP(Š:‰ó%Õ`%dœ×´”UÆoD¢üYÖÿF£4]2¶Zóîƒ-iY¨H~ZÀmñ|ÎKîÊ÷ÜqdÅó†Êü 7€éó?~°æwí£Q>ý¨Ñå|ŠÌ( Úõ'ÔÒH4^%2r›Ã'Ôݰ+áõ¯ÈÚE/)NxIdÄï]ìÚÉDÀ¯b°‹Ÿªu9ö¶T%R–̬ø K=ÆM=1ïT©ô÷õ:ðSíµ¶¼.èUþŽS ý´”™ÿ¿›Ÿÿ ^º?÷. |<_ÌÑðyù¾†øS¯I$ÐRã¹W™Jý˜½ž(!zÉáªöó‚¡:éç·Ÿ_Oíì+[ˆ4þ©>-Û¯âqøõÚN {£-µ¹pñw¥£±zÉT®|3,'{d[þ?Öjõ0Äk*q:±ò >¾§P «ÞW³^ê|A{'¯i$×·¬)å GéÊ|£¦FŽIÒ¿åa@NAèªkB,e±¨ŸO²åÕöʤÅýÔ´L3ÛÙBv©¦¦ñr!-‘õ¶¯À$’{v •^å©Ü¤2)¶äÓ_£“óÝ^ªÕl÷Åpä! ‚E|U¯'~| t¡>ÖºobžXŸ[›æñ‰ò^cÄ%u3Ä"—œºBÌš˜˜;ªVßÅ`¹ø]áiDO/=-þP~$ì#æ*C9ÍÅì£ZŠÑ¯òÌP’`”ºíÌŠÞîÉX©7–úÔòà õ—¯†Ôc¼¸*®òPŸpeÌÓ#>æE§:'ᙫÌëêT(XtT=Ψ—Yzºîô²å{4Z ѧšb§. ä‹—Ö Í;­ LŽ‹-;]¾ìØ‘¦pSÅÜcs+&N%0ÑÙ’./jMa:¢<»×¿7ïLv%i›|ï¬û`‚eZŸN;¹®÷\®ÙU˼G’ÇȸJ£Ëön%Ÿ;EÜô–`‰ð¹ËÊÎ[9«©âزòòyË7½Y1O étEøáYM¡pŸý.mõ()PË¡ðÛn ½¿´ˆ½™¢e+›Â¡0 8 @7Ä2ZQX`ƒ`Qò‰N¼¹\íÐ&6Ãð·g|_í«¸ÕâÑO©#]õ¦–e…B}ÄÎÁQ |æý@UB) ‰Ú­(R®½òdT’3Å—¦&ÑýMvu\©Áâð''µˆµ·å9ÖŠ™«õ2õІ) iMoèÎ!ô¯!u'€S‰Yc˜†©^i’§vüKÚDöB}èÞ¨ž86?Í3ÃCž_§ÉÀh¶~ÏìÖ”DRÛËñÌ2;¯Ô` *ëu—q޵:S¼½_×bÆV†0b%¾·½Äw£~bW;8~=à]~GzˆI0œ£C¬€J‹ÍP+/¯¾+´g™†©ÊÛErfdJ"©­æ¢é¡+§Žl­¾2qŒŒ´¨íë8˨ã‹Ã>$»¡€å™bði"ò€[M¡Îhµ°›WÆ˰—ûª0ÛD$„)÷aÐí[$g·kXÒ+Z?qL—ÅW@³%D2ïÎ$"'º®Äplžœ ï\ã®8Îo(UÚÛýËdÔ«›Uïg¡CE\, £ˆðÙ§8yzc3uHø†-T Èô­Ý» íCÍ6r"4sP)Iª)ɵ¤¶ºŠ§«£SWî@Ü9UÐÕ ¸r\kõˆî¼ñìªá'C{B@aWó¶P ™<”]HÔý(8ï°BnöÔQ@'ùŒ43Ð9XÝÕy `0ý3<`É¢0ÿ¨6ÍÃ{‹Ï`‘츼{¯m(Àí;§T3=K t³ˆcGñfí=’O~ü /œÒ@â€d‘ܽIà…up›½ÊlŸbN jû¦§Z€{e;~¥‚ ê»ÓnMG—zŒŽêâ/§ëªÉÃ"ùýxñ–ËY®†4:»¼ÝêX2”ŽæfÊQ¼#(Žñâ­0²zãÇ«˜@öÇ9}ó TG ÄĶaGAéÛe€KqsQØÙ^Š{¾þÁ¨hXx(±Ðg”ú'ÕðZ™[®àÍd±¤é2•î*ÞqÆQ“¼ËY®îKç3R$—Ñ’Ž%)^L]Äs¬•ÊÇ™¼3-Þ&þÚÇ?þ˜«Èª%ìUŽ~oêÄ[GîÃ’>ËÀ‹øØ¦SX„õÁ^ƒ÷&.C< ö´Sà¨Ì!8O…$‡¸îÆ3×÷Y22ôËȪuEñø™€åš\MÚTHšNå¬öv,éâs•é*Wìµÿ ËM{ÎÅ1R0 õ—«¿VßøÂÙRQÛf…)–sˆãì-º+µ¹‰-1!b$j­¾|U?r^ëAŒ)¤ÈºéÊü”ÀÈÙÆa~ß÷˜‰ï_uöpÌØ­–3ypDÒsfdr™ô¶µ'ä“Kdžº\¡tiÄFEìà€¼³WÝ’—ÅTñP7ï\iÔxR|–­'™Z ë`’,4–E\^o„"‚{P‡&Dþ ¤lž$TKà#ENÃÈìÚ\4àîU«e ³Ë«ŸíJk‘DÚÇó܇Óó{.“Kµ¹1·ð¹i™·ñî9p(;wÅâé1Á=Yy°M 1Ku0›©bÚ‘·yØpŒu=«ÅññÇÏr¿Þ%Hûä®…-^Á< 2 ƒžÅ–Æw‰E@ [R¯Ên°ƒeŠë2>à’„œº¶}¦ß¨òø„ذY —²Ç”bGî…g7n|:aÚàý;"ámGÞ¾Ûr9”¶†KÃI0íOò+ÎôbLC=—TX.Œâ‘3Ù,•‡öåÑÄÓVÊhݘT@ÂY7NqÈãüÔÆÂžKâã”Ât“Bún ²F9iÈáâØ,J²pÅ lEýIŒ_gv3¬b÷¹ñã×’ZI„HƳÈä÷ãLQn«Íν^]ý¼¡ö²‚4%‡wµUkÅØÂ„1ž]5¤ï(5â`±.’‡0Z«^ã2·”¨žA _'±äÎΓ ÖEë¼Bñ{£çlæ²T ÅP›ÍîB:d0åjR—n]ÅZõ˘‚t­ Âotã³æ1DÚ¿Y<žåaõôpë¾o‹|°Á•?ž;_*ç°w&QÙþNuµ˜n¼m_qÞÈ™œÂ`!DO{¿™!Šw<ëˆC­U|Œ¬®©O0r *©à‹ú^ä5“«€‚h‘D¬HFKÛOçMò Ùæny||ÇÈH^“»öí+ÞÇÿÏþž7²c\:ŸžÉ$!sò¼ík,¶¯ÐŽìâP‘VJGZ¬¦kÓŽ2>äˆeóŽÀj LJŽ*i–6Ñ¢X‹P˜ZjãD`H*Dw BtÉ$y#V£%ik¹R­›Çs˜.Çáó«Gè@TüÔ™)\L{«Ôô¥¹ÅÁ#-&Ý1ë@4J©=ж'Nq]܇4n¾ FA/^oá4 sqžÃ %â0}×…„ó<í0÷è«·»qãÙþQ«&éóvLA&ŸöÚ‘—wˆ…g 8‹~v2q¨ô°‹­#aŠæª@(õT¹pLÊ }ß)$]¤5.EUk¡Y¢¸~rBÛ‹„y§Ô´>> âúq6Ûýnܸº# ‚ÖxÿùgŠóƯº0Æ™“ÚÞRš£Ùé!èN.i´^àÃD§_œð‰C‰ GÑ|Ô”òaã TŠqMÄÇa’ö 9,$ñD‰ÚªöÑŽE!  #Rôø~&ŸÍ~Ç7>»( d’‰tœÊ»šŠÂ„‘·¢ 3‡ƒòÉ®§/+ i‰d„¥‡Ôd˜'¶§å-â 3!,ÎÖtbˆíucêãÊ&DDé) $ ŠêR`šñšIvQ2ùxUc&wÑTcË®ž¼«¡);Ø«œ:g÷ •#zòÙ)‰CEZ<ü]ÎYGŸ>ÅÙ1¨P8ýnPÅÅ0ktˆáS'`îŽâ¶T–â$uzùŽ­³š#V·ùj›¨™¼ðìd7ÍÎþH&^&1¼fóŠ‘â¼‘ñÏ ‹3 Ö—¦œ¤>Õ±jãTÅÁŒÖ/¼ ’‰#Ku²;¤Â)‚Á$9Ø3B]B[:A´y†u–ÏÝâ.®s÷XÀ¡+óäeĘ®'wÆQ$šŽõwVX±£˜‡µW`¢˜(Šó¾¹¡`Í07SHéTÇÓO]Úhu¨-C¯™8ÖñŸÆÿRÝQDUt¬N>1îBƒÏØyÔP÷qÀŒ{ï6ïNR8*dÀšDÍdrÑ"aîé‰J&6P¹¦}×^qây¾1þ‡ìb`) OPFΜ:PТ6žDÇÙ+”†NFxVŸ¢¨IØqæÛp’¸TgÄ!¸ÒŽÆ¨)ê„j&¡¾ï G²•VìF.US¼y&“§S£AT÷L2R:|¾ª}󆽧zÆÙƒçÒ)–)"O wôœZ±as{ÕšÊÒX<ØtãÝø'W_¹4”Ñ’ÉÈщ‰ÀÂÁÙÞðóñWSÔÆÓÓD&3|ê*PK¼îã`Ÿ€5íD¹oòÚTø“ÕOv¥ëkr?¥"‘þÒÊÊÖNuµòÍÏH$‹«}Ýà•Œ~úÙŸJºìÛ¥ÁË@Pܤðõ’­ZŠÚ#Àݪ\Ú‚áº1u;à¯NQk8ÑnBX¤„‘U/¸E“)¯WïïJÄsPRD1_`4¨ÀLÅv¾öY„“‘è × ÈÐë€Ë‡ã¶Õ'ëÄÈÞƒ9!P — *Û^!€^ÝP˜ÈP jŠVÛ(ª6öÝÅ Vò—"Äú?ÂgŸ~­+‘LG?åE:ö¯þø³ ÃôªžLK<9b÷ õñ%СûpÚ׎`Y¯œ°áÞ¶³½Næêg°#l_•€5 D]& VÇ•¬,RYµ¿£(Ÿ‚\<ÓEL t=ù´Åg†IFv*£%;‡µÉo'`CÚ/hZ|Š6à.Eçž²b( Ôu1å*1stÇ~¬a‰šÉåCÞËJ…=ÕÕOï­±(Kf².¬³ˆv°¨HTo_Ûý‚ø¦«# m´ÎÞÈŒÖØàÂíÐe{|xXØ|'é¦vÖ S¯úºHñ*N…-ddÀL·ŠâÁpe°DÍä3(ˆ+ñŒÏîòIÑ._>wéÒ¥ÇæÎ»|åÊ™‹ç/lâ­—èk«Ï^éöªžãFëhß„9¸Äšn»Aáº.žÑ5…a8àn =ì‡ØÊõQmŒÂr:¥x¢ÂŠAAQã<+ƒu¥ý²!ÏÙ³«DZÖW(Äÿ«ïëts\-Ï^m< Œ¬¥ªÌAÁ­Ü(Âa‘d·a—©›˜<Ä8".‹§&…`4@øEÑ"ˆ*áveè…«`°²t(Ä“åWê㮥 ÌÈp½\}µ%Â;#|%šœ¢Èû€Ip“®ý™ÝÐ 4Ù%‘Óï°—¤9•@§\½Š3DQÌçåѱ†¬a]Mƒ…7ÆÕD¡¦TÑš;ñ0ß Šyµ"gPÒ‚³{9àçÓìý#J}wЀÚÅ:f[:ÄçâÄc¹úÆœe¢°ä­7ûÀ ¶*ðn…ͺú~KiE3G¹–xnœAô©ïã/¯µ®ea\`[´"Ô©¡àØ®RT£÷å\Ü52XÂbíÏÈ„´™õû™ ÜX.9{å³+3Z3¸Ñ↠ߣ¶ÃJàf†^R6éu–1ê± { ÎRÜo ~Ó‡Ëép˜b‘iÄE™®¾vƒ—•2‹ëQÖepœ©!°œ-Ê«ðæ7õ±Us ÊïÂqη¡^ ÓÕsYŽ1²³)>NÝ¥Oõ&ÄH$l†ihk™®ºfC´¸Ëùò=; –øúÉk¢ò§§$­$6óxÒ€€I^бû¹¾]!‘^![˜Ã݃ÈòæhÔ;Þ¡Å"6ä5¬¯‰ÕE%ŽùmHdÕhº°Üf õüt‘ÖFþk#/:o \«Ï®Z½Ÿ÷Y燜fÚ¦ÂPs¨Ÿáì‹¥æ² ŒÊ®92Qæ PJñæ´Œx3Ï­fùÛjt3âåí~z£qVE:’Ëà© bæÀÒO¬b?ŸÏÚOñïëYq­æ×ª§ÏŠýúz²£kIGB€\ß5A³îˆÃº9E¸ • }0ÆÅórÐÂEËíª$àcüSêãÚ +ÂûêÝ9šzí0ûØÿËýûŸV×Yq‡«åÝš£øñǯèúø…³«öÇeŒe+ 0w¥õ ãâ ìÞ¿÷«ŸóIx½Ö!¯®þ®Ò®®®ü#’bI¹Ùg¿2ìŠgÒâ2EKPä÷–Ö7Ê‚À“×Ýe¢©M”²¬#fˆÑ¾¼Ý)ÅB¢¹ð_!ÉB•¥2+ïMÞr,Å?üŠð««‘}”vtTvt¼6üš~tûÑõ4çþÝbŒ‹Ùp½ÐJ²S“DqN§3éh:G;EuP¼è§¿â›ú4£ ,¯Ýd¦Õ)„èÑ op< hH' Ãel¢¨³ìÆÔü§ô¾â›‹ŽÁÓ7I«‰] ÈV7« f DT¿þHôÁåÕ=&>u—ú|ŸB cÏpÕÉfÑpC˜ò;E+íêkaí ©Av\ 5Ö^æý¯xËCx\ ¸Ê­¦ýŒçŸv [.stf_ÈÎ: ,3˜¸K Ú.bM„†|äÃâ#èJ JVOPjÕGh9HÔ⥃ÈLI[œ‘¶øêÉãtS( A«%èæ_œ[7È<Àà`<͸2`æŠË+WŠ_C)æBøµsg—ÄÖØCÂ×~î¹ÃÏþ§çž›ñ\G×P tc– ÷áÙ¸uiÛ¨ÕgdFÜQ3¹HaÇ—‚‘ôÃlë ¢} ð¦¥qoLË"™ÚÙÕñ»~‰;“[²PtçÎÈΡâÄbìÑ$•?åLŠâc4mcÍó— üae„å1 þ'Â/\|dþÂ…ó°ÿq—ë8l/ölÙõO3fü»ž×îÝ»ùðzâ ó{±¿=·d§Š7zìÅ5&¨\eŽ3P]Ui'`Ž €8rbËZá΄bÐgìŽÂ í ¯Ô¸X|e¹cº—Ý»~Âüí þ1ƒ_ÏÉË<:æåóó«bfEÅÂúú`@uOU±`¢>ªçW(æë)C豪kû5Cþ`u¡¿ä¸øû>Ü•’‡Å;º0ì,òa„°ûo&á(œ÷Ôû!zpîòá"áMP@wÉ2ä&f¹Ób4pça!ŒWt=!žŽs©sÊw“ëH8¤®zB}¼µ&ɉqöü´ô‰ÂÚÎÝ3®æÅÞTÇ$¥*:6°Öp2”³Ž£,=9Ed’PñgÂZ> nµ"]äQe}¿\‚ïupm¸ª·¿{‰ÌõÆõÝeq×JP°!xç£K®®@ø›Ú}XÌì{IÉÛæTÌ}ƒtü2ET{²Aoá"44â ëãyD¼Œ‚Jý„†ÖqõHÞ}Õo}Ƈ3ÞÜÀ;úN«àlŠ6 QYK~bÆÕ[»;bR"!XÇA°Ù¼t«ÔTIpÇÐ Ý(µ T(–àòà¬%»¯ÁÏx⹸*·CH#âb°<ØØ$¢ËîZ¼/fa.òàâ0>Ȱ‡KÌ´›‡¸í!ú´zÇÎ8‰¡æxthx¸»ç4×Ôs»g\“ûž‘ŒJfj„郭K[O#PJœ…CÌ_yp%ÅøºMõhM¼#NiSÂØ§®‚¡ùp…שe(ŽÕ±OMLˆlpè‰ktÛ3vs, „¢f—S©±Ö}OJéÓ»g\3‰¤½!(LpÁÍÓÑè„ûøQUË">î¼Ù™X»ÒàŒb8ÎR¶k'»Sb]†„ZHÁdP|”?ÙÙy­ÂÞÛNÝ!غS§NP©‹ìo”-xš>'%Ä÷`{ïrä'6ã‰kwÓ,á¬>f&Æ/h`€ºÛ¯‚ì1Úuí"ÞœWô~½-ÌBŒ cëQ?°ù 웩ö2V*ˆ ššA6 š[ ×Èm‚S_X‚; 1\ÚÛq ßœ 9–†´±">ur4±€ábQŸøà`#\ƒ¨Cx¡—3­Š‚û’kx¥@Ò‹CÁz°… ÷À캷²2‡Ÿ¸–ïN´ø9§ŠëfëÃ:¦ÔA\Ö *Xäì ‰`ƒÝ&ûÓˆ_C¯io9ZB(uh: ­mÍÐÀ~ãTgÑøµ|sÌTÇD¿*°uë¬øQ‘D1ª®ÁËE!Àäºè´ßÑØd2ñº¶Y¢§ƒ¸'ÿ‚Tïºïñºkõ‚óâ nªú![ÑÁ™…y¦ •Ô=vV¤4Ë'ȺÅÔÐîk{ÇiÑÉ23à"<:8jœ–œ½½ýJ®*/~ÖÄPVßLâ ‘œ­O€,‡Ъ3u<=M1)ufíd2#¦›Ó»õí}Æ›´UEt‰ÚɱpÀ§á.2ØæÒ‡IF½É¡^ñ—­Þæ8|ø¹Ã3ž›üI—½ž°ñOÏÍCªËÇ~ ìk þªU 3$:@\G‘މ)Þ¤SÞ}Å·)ïRUàw“%ºq°ÈKß$vߘ˜(…€Õþ‹®AÏÌÖàJgÒÕŒ¥R¼'‘Ë~EćèÝ %D?+fº7ü›¸$‰ÄM €al1ü+ ×¢š¬ +§;[àR çËis9º8Þ‚K Éûéê¿–tu-é \;ÅCç_ÍnSô©ØÃJ§³Œ'xóB A¦Û^@)»ž¹ðìGîK®ùzæËÁ(DÎïåÿž‰òî%w¢©œª†gO*À:!pEË|Ò «Ò@+]Ë]š„8o6g¹½hp?6-fÆø¯±©<%³¯Æ—ĉ½¢ˆÄÁÙ GgF –¦Ï·W©«Eqè¶Vòk¸´±´±±¿€DÄL$&º–ªw‰>’ì_ þµìÛ*+[׬©ªj/صaÅ7æE$  Ä‹!Vo| ˆ– ]œayHÈ̘2ó2ÆŠï=¸wû®‚‚ª–óëÙ66–F"¡ÙƒÉØ`,™Œ;W§$û·¤R§„êoä×pk%ÿè<~½øhaO±ªJ€ºMH#MˆOˆËŠ×ém©UÆ®«Vç—^'~ãWo7¿Çá>yåíÙqÿî\¿ÿ.yt»×ñ—á¯ØÛ]RRRÝè ºMD)¡¦’,¢±; ÁP“–ÇQçÀÅj³10š¬p!‘6v[½ÕœT›¿AXÒÆ±÷~ûû?¾óû“ïœZá\=ï¼sò䙓'ßùsñßøÇ%~S%ÝòªîUŽ?½jùèn)õøÜÍU{cu(öWÃ5Šœ–Ôó²g*¯d-ºÚÚįöÑV¢.ø‡6þ%mꋲÅNöþŠïÓ+å>ž/Ek¹%ÞÁbÒa/!æÈ[À+#@‚¡Ì@´ ô±ßpiIm€B¼ys;LDüC?ós›âº<¡Kõ0¿áM}°µ‰ÁØ0‰NUĘ˜70Eª«O­ã|ªƒ ûÁ$”fAlqú¶¿fá™4_‰ÙnC)>¥.¿ªþÂ}ºWÙ}Ío˜“hί7ýA-ˆ4‡SZêŸ'fñ˜£ô¾?vðØÁº¥{ÒÅ!qj»P‘ ª)b²‚IMGó‹¸è}¨2ñpHç-%%Ùùê]œÝ;šY‚èiͧÁ¶¨ ÒöZoéíÕŠÙVë\@«³p[`«&íjo¯0¦òªÝ’wðäŠb¯*ä›i?°MHý >2\_×c@Ä®BáÌT" j­í¯ò›§þë¾ÛØÛí®–®Nß~›²¹W~ìjÉcÆNœ@¯"Œ‘òl¬OÔ6Û#ªøP•víÚ°÷ÐÞç{zzŠÿ˾}[ôÓå/.Ÿ«øß:}U›KZWþå·1‡~²§gï¡Clno—L) Î ’AFi\Ätðá1ß1¸I•”"Ä>’a€‚&*TQœ@–g±¨)2PÚXYÙ¹ž…‚íçìZ±âÔÁñæØ%V»s5§ãj¨t÷àÁƒ`AÀ†íœL²]ãÝ-‘—©‹†+,؆íç™R¢`` ¿‘Ǽ¥Ã•æj]¯/û¹añUìëû2KŠÇ3iÞÜÌ– 9¨£Î¡y\ìÿôgô~ l»… Ô ZEÈR¯Eèñ LU:ç·Îâvvq ðÆVq±pvÍyqÿ­9ߪ. οG¤ü!˜T¾±Š °ècß>A¥ÔðJ/çøû\“¢ ™ÜÔ@•º E@!*a!2 ;hAÁ.ÕR‡7ćè̯vâ£:ï2`0úæÑ©šäÖARé¾ßFÖ(^r¡C‡¦Õ&4ó~Ûwž\"@YÙÏ‚¨·,€ØûUðäQ<µh¿³á]ÙAú 7¿4Lºõ³A†@'?Ñ´,:Yö¯´%ªÿó².Ed9AüÓƒƒEu§ËΛwlîò™G†ŌقfŒ¬ãb5€SRiƒrê„Ì™D””7ÐD~hSÅÌ•s-·ìè麢ÁÁx&š£ò1¥gõ¢9Ÿài¾ÅfpîÌ µƒÚFúèböþV®\>wù±cKÍcײ£GÙ{åW‘¼'¹ÄÔ±¯-gßľyé±¥s—/_¹²‚CÝ,Ü3ëázƒ¶RÖ¤lÃÖIÌ}‡ó6%E@—xœÞøÔãò«É7T?ñðãM .^\QÁïîÒ¥ìý=z´œÝM¿+&,þ+ H\\ý‹½ýºröüÌãp?Ë9ÚÏÊŠ=}¼bBä 2Y*ÇUïÏ<7QÓGæ×ãM³øÇÂÜ×ûâ šø÷Mðnû:|’»oBL†?Ðú@q-qÑà ‰.[êUêÛóŽHuÕ$ ,šéÕ¥-ÎQÍÞ2Ÿ7G·žxüñ‡gmÚ³i“¾ÉÅk¾þ§=›f±_?þðáz1W Ÿe%ÁT¸¤0b$AHÉk#¯ pÕç¼úúÌéoTZ©[Ä>…‹P˜Ý‘ZÖ'³öQAÞVá,†w L»ûNU?ZçÆÞÖâII±;ëÓw \âÖûäïìKù7€s+;Ñ@aë ô}|'P¤n J]._ŠàA«†üÔy¡¹›D;̳’ÚNÁ›š`tÄYòñʤ¶aÄš³@9Y™q‚p1Ñ”žÃ@Ý΀³F!™"æA±ÊnR+á¶ p\ ÉàÁ&qðù(êÈ9ù¶—:Ì·nÑ$Ù”¸¸ .í­Ûç“oÜFYwUz8¶<îŒ!>1˜RÛÅ~TŠÁˆhsØw¬ªßA”vJß>!.¶›]°0ppA ‚±ÙIÀº ‚8Ûô«ï„Uº™I`i@ö¨y.-Šñ3¸dÖTâDeÅÄ%œDI³‹2!· ]‚ç8@•-Lw`;‚kIÔ£4°å¯LÜÊ'Ôâéø•³Á3äÌ¢»ÜöxZ b_8 s€ÛAK?0¥¤ n ¨1äS‡òÒ‡^pSéÝ![÷ À¢øÚFCbqDh vœIÅB…0…IµÏ‚Á`+áŠÞÈXQÝc!Ôð…ØCȺ¾Ëÿ¢M=¡²™ ŽónýÒðÀ}+Ÿâõ5Š {|´ºî»”îÎȘO­VLôŠæ“åNBt@7‰ï¦ù.²›µSð©R@"Dà™õ© tïvXòa•ZƒÑI»ÛŒä„8FÚadýØÿ>z(ïV"]6L 51f'ö­[…[n¾ì…°Ñ3ßvÍ® ¨5”9<ö>~tÃÀÐKŠ`†%iŒ81 Îi, /šsØÕý,NáóøøŒû4ë>»øãCy_ñþE0)€Ið. ylŸøS´uSœصgåËwœºCiCi€x€¸²i6N^°¢aÆ5ñš?Ú”¡N.íëéVÿ zgÄq”ú°Óhrp|Ηó¾úÐGî¡;±«ym¢²8¬aº ÜTgh(ˆÃùkç®ä ËçHð¦A¿£è0õ©ƒ¬êÄÒW:AñºïzŒ”oa‘ž[Kf0ä·úC(òØìÏs¾÷­/DhÁ7÷Ž^Ê¥84+ºÔ£Ø)w–P—[×Btº@GˆlNµÊ5Ü*¡4ˆ ™-«p[ž(ðpV™ ?u—ËlOÝÝK“‹acœWºdé@Méœ[ó>ùÂ\<ÄD0xذ†¸n¾Æ¸ ĸè+PÎçòÃÛÀ ÎÂy1ÓÝÄ}g| \V*¥x„Úá4áL|\ß§d(Ž/Ñ{ ~€«¢'êïšó•¼O¾2â2á$J­iDžbüMŠ6 Ë ¬$‚GFPÐ:¡®>ü>ü˜ \è¢Ü0„ß‚M1àì°ØgÁÉ.êÒ Q›À¿» ®nX§¡0ÒE+~¤æ8ˆ6¦ä‚s ÊÏLK.^0%Aü&ê6ã³0…£>2ujËøÀqk§Pçž{Šk±nMÈò6b88êP|¸g"BI¢7·»ïg«AÙ*ÂÆ¢Ì…|’÷É'_žƒ¶ïÜ ñ©±ƒKV΃MQY¤&ÔAŸPq¤¢¦>H \.=ÄÝvö·-+²’Aö:Ã~šÜ–Rß)égm»Æ&îDSì á­ä@qoÊ‘ÈW¸@¸Í²#Cf´NˆÃZè+ˆ5!‡¥-š­za¥€/ š)fÁ âi~ñ;~ ñ)un»¡¾£ÁÉYhT)ø‘Ø-kDP¡*€jc1J<ýÎW¹@¾e_“õÝ‘"\ž@¤q † ¨Ðk-…pçö!. S7¢Ô‘úÁ­U˜ôä RgÁI=P{`˜Ê‡k¤0¦…yMV5˽ƒÝ j•PçÜú È'·Î!>°vzR°0N0S4(óA‹,H,ˆÞ‡„#.±}3„âéPŠß‘«Á4À{‚aY@Ô 87çõaÝV qX[àÆ-#‹n¯Ã™íƒ1Ž9}U D¨ukô¥T ûÔnÔ“ñ1è,•$>~Ë.iÜ»5þšøèõ)^Ã4lBG‘"!èÁ9%:››'„B4j˜Å%¦Ô˜¹t)æE¬×§Ø2úxùCáŽZãw–zðú¨Ž»Õ()CýÐ Õ$ñ¸1 :…ÔÒbéP¾yŠÏ2q¹-Ú,¬8B¤«,6ÃŽ¦2ÈCQßY€ÏiýGßù–È·x #{”ä£ Æ aM¤r6‚ JĶìðž*qëJ ñ݈s6ºËàAÖb|c{“N‚ëp$‹éF\ƆùYê66ú°ù91ˆbtîA”@>ù*ùn9¥Zäv²÷¹€¬+ ¨¨âÀZJFvœE?@]RqjÑÔ}ç-Bœ¾4jÞàöuªµ”:_‡z065³´ƒ´(ß–Ú@ É<$¢±´„ÁÒáFËÝwƒ‘Ì (¶¢Ù Ð}âu4d‡ìQœê>]P­pp¡(†sÐdÃ2‰Ã6ïÚ_“ºý~T¿µÛ(Êp†EA>+šº #_lÎC_…a‘–‹2‹ÙˆƒUE#;‘AN}gü‘P˜À¯v€¹¨c)(¦ÒDå-„Q¨}ŠS„ÕP@ (¾Vç pŠÂ—ìÕ}´Œë; P?æ#*åaòÉæÐÀÀ“it†ÍN€–däÛ©Ý!¥Hgˆæ3(üÉÔ¶vñê¤7Cî’¸ éDœ¹Œ¬ë¢-9½ˆDÍ¥Y÷‘=³YFM5=ʱݗˆ‹Å†zôÎà*I€‰öH¨;EP͘"x (¢IÆÖLX‚HÈÇ)¤Á[­®-Çž I§NGs”¸q6…J3Tu]•»­<€@¾uëÜMòqÛÏÑR‡Vlj"Ðz‚e¸ ašÚƆø‘‚<) s•aM|g9†`êYPç€54 ¬uÔ@u´«uÒå;p&Ï;$¥AU4ú5‡y0?Bç@ˆ@?ð£ÐÐÍ2 ‡TÌÉÔöá–3ŽE)^c LA¬©ÈÌÜ §áÁòˆÔ4ŠÍ&Íâ ζ¼%ŸŒ Â1ĉ–'Õþ< O¾úÐÌÕm Bݲ%ÅOŒbœx<œD@Ò[;Æõ¡©C"ø Š0ꉇà­õUÌŒÁq!s —:Ç¥cs³ºÀWQç P$¿>šó…o}’C Ìl}4û š­ª6ÓX”Úr;¦wÍÐØuÀÿ‘,(^¥%4¸–NqpJ€ªØ_&Tø¸ a1ŠWL܆ÅÈ>NÀÉœ¶=ªhŠ&â(uÉg( †N樇 ˜K¢!w)šQ£p£<ÇPEØ”ðFø—ΙóЭßúdR0‘ÜJæ0™àZ'E):2öé0Ž‚"ÌÚcS© f˜  àYnÌóî@;ë]5dH ë,˜ì&[ÂÔŽ°Í-–¡µ l•JÔ¢zEÄwgKõ.õ2$÷ôsæÌù²+Žla†ë+_xˆùGüû¿.÷Á¿Zyðß‚Ÿ¯ü´þ¥¿Àü;xG¹R®¯†·’å=ÛEŸÌqËî“w`ïÞ†üàÏvÎÿË·~%ËÃÏ&.“¯~åÖ/|áËÿÿum®/ÜzëW¾šýÉÿ xG˜­äýòIEND®B`‚motor-2.3.0/doc/api-asyncio/000077500000000000000000000000001373322416100156375ustar00rootroot00000000000000motor-2.3.0/doc/api-asyncio/aiohttp.rst000066400000000000000000000003641373322416100200440ustar00rootroot00000000000000:mod:`motor.aiohttp` - Integrate Motor with the aiohttp web framework ===================================================================== .. currentmodule:: motor.aiohttp .. automodule:: motor.aiohttp :members: :no-inherited-members: motor-2.3.0/doc/api-asyncio/asyncio_gridfs.rst000066400000000000000000000352221373322416100214000ustar00rootroot00000000000000asyncio GridFS Classes ====================== .. currentmodule:: motor.motor_asyncio Store blobs of data in `GridFS `_. .. seealso:: :ref:`Differences between PyMongo's and Motor's GridFS APIs `. .. class:: AsyncIOMotorGridFSBucket Create a new instance of :class:`AsyncIOMotorGridFSBucket`. Raises :exc:`TypeError` if `database` is not an instance of :class:`AsyncIOMotorDatabase`. Raises :exc:`~pymongo.errors.ConfigurationError` if `write_concern` is not acknowledged. :Parameters: - `database`: database to use. - `bucket_name` (optional): The name of the bucket. Defaults to 'fs'. - `chunk_size_bytes` (optional): The chunk size in bytes. Defaults to 255KB. - `write_concern` (optional): The :class:`~pymongo.write_concern.WriteConcern` to use. If ``None`` (the default) db.write_concern is used. - `read_preference` (optional): The read preference to use. If ``None`` (the default) db.read_preference is used. .. mongodoc:: gridfs .. coroutinemethod:: delete(self, file_id) Delete a file's metadata and data chunks from a GridFS bucket:: async def delete(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) # Get _id of file to delete file_id = await fs.upload_from_stream("test_file", b"data I want to store!") await fs.delete(file_id) Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists. :Parameters: - `file_id`: The _id of the file to be deleted. .. coroutinemethod:: download_to_stream(self, file_id, destination) Downloads the contents of the stored file specified by file_id and writes the contents to `destination`:: async def download(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) # Get _id of file to read file_id = await fs.upload_from_stream("test_file", b"data I want to store!") # Get file to write to file = open('myfile','wb+') await fs.download_to_stream(file_id, file) file.seek(0) contents = file.read() Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists. :Parameters: - `file_id`: The _id of the file to be downloaded. - `destination`: a file-like object implementing :meth:`write`. .. coroutinemethod:: download_to_stream_by_name(self, filename, destination, revision=-1) Write the contents of `filename` (with optional `revision`) to `destination`. For example:: async def download_by_name(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) # Get file to write to file = open('myfile','wb') await fs.download_to_stream_by_name("test_file", file) Raises :exc:`~gridfs.errors.NoFile` if no such version of that file exists. Raises :exc:`~ValueError` if `filename` is not a string. :Parameters: - `filename`: The name of the file to read from. - `destination`: A file-like object that implements :meth:`write`. - `revision` (optional): Which revision (documents with the same filename and different uploadDate) of the file to retrieve. Defaults to -1 (the most recent revision). :Note: Revision numbers are defined as follows: - 0 = the original stored file - 1 = the first revision - 2 = the second revision - etc... - -2 = the second most recent revision - -1 = the most recent revision .. method:: find(self, *args, **kwargs) Find and return the files collection documents that match ``filter``. Returns a cursor that iterates across files matching arbitrary queries on the files collection. Can be combined with other modifiers for additional control. For example:: async def find(): cursor = fs.find({"filename": "lisa.txt"}, no_cursor_timeout=True) async for grid_data in cursor: data = grid_data.read() iterates through all versions of "lisa.txt" stored in GridFS. Setting no_cursor_timeout may be important to prevent the cursor from timing out during long multi-file processing work. As another example, the call:: most_recent_three = fs.find().sort("uploadDate", -1).limit(3) returns a cursor to the three most recently uploaded files in GridFS. Follows a similar interface to :meth:`~AsyncIOMotorCollection.find` in :class:`AsyncIOMotorCollection`. :Parameters: - `filter`: Search query. - `batch_size` (optional): The number of documents to return per batch. - `limit` (optional): The maximum number of documents to return. - `no_cursor_timeout` (optional): The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. Set this option to True prevent that. - `skip` (optional): The number of documents to skip before returning. - `sort` (optional): The order by which to sort results. Defaults to None. .. coroutinemethod:: open_download_stream(self, file_id) Opens a stream to read the contents of the stored file specified by file_id:: async def download_stream(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) # get _id of file to read. file_id = await fs.upload_from_stream("test_file", b"data I want to store!") grid_out = await fs.open_download_stream(file_id) contents = await grid_out.read() Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists. :Parameters: - `file_id`: The _id of the file to be downloaded. Returns a :class:`AsyncIOMotorGridOut`. .. coroutinemethod:: open_download_stream_by_name(self, filename, revision=-1) Opens a stream to read the contents of `filename` and optional `revision`:: async def download_by_name(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) # get _id of file to read. file_id = await fs.upload_from_stream("test_file", b"data I want to store!") grid_out = await fs.open_download_stream_by_name(file_id) contents = await grid_out.read() Raises :exc:`~gridfs.errors.NoFile` if no such version of that file exists. Raises :exc:`~ValueError` filename is not a string. :Parameters: - `filename`: The name of the file to read from. - `revision` (optional): Which revision (documents with the same filename and different uploadDate) of the file to retrieve. Defaults to -1 (the most recent revision). Returns a :class:`AsyncIOMotorGridOut`. :Note: Revision numbers are defined as follows: - 0 = the original stored file - 1 = the first revision - 2 = the second revision - etc... - -2 = the second most recent revision - -1 = the most recent revision .. method:: open_upload_stream(self, filename, chunk_size_bytes=None, metadata=None) Opens a stream for writing. Specify the filename, and add any additional information in the metadata field of the file document or modify the chunk size:: async def upload(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) grid_in, file_id = fs.open_upload_stream( "test_file", chunk_size_bytes=4, metadata={"contentType": "text/plain"}) await grid_in.write(b"data I want to store!") await grid_in.close() # uploaded on close Returns an instance of :class:`AsyncIOMotorGridIn`. Raises :exc:`~gridfs.errors.NoFile` if no such version of that file exists. Raises :exc:`~ValueError` if `filename` is not a string. In a Python 3.5 native coroutine, the "async with" statement calls :meth:`~AsyncIOMotorGridIn.close` automatically:: async def upload(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) async with await fs.new_file() as gridin: await gridin.write(b'First part\n') await gridin.write(b'Second part') # gridin is now closed automatically. :Parameters: - `filename`: The name of the file to upload. - `chunk_size_bytes` (options): The number of bytes per chunk of this file. Defaults to the chunk_size_bytes in :class:`AsyncIOMotorGridFSBucket`. - `metadata` (optional): User data for the 'metadata' field of the files collection document. If not provided the metadata field will be omitted from the files collection document. .. method:: open_upload_stream_with_id(self, file_id, filename, chunk_size_bytes=None, metadata=None) Opens a stream for writing. Specify the filed_id and filename, and add any additional information in the metadata field of the file document, or modify the chunk size:: async def upload(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) grid_in, file_id = fs.open_upload_stream_with_id( ObjectId(), "test_file", chunk_size_bytes=4, metadata={"contentType": "text/plain"}) await grid_in.write(b"data I want to store!") await grid_in.close() # uploaded on close Returns an instance of :class:`AsyncIOMotorGridIn`. Raises :exc:`~gridfs.errors.NoFile` if no such version of that file exists. Raises :exc:`~ValueError` if `filename` is not a string. :Parameters: - `file_id`: The id to use for this file. The id must not have already been used for another file. - `filename`: The name of the file to upload. - `chunk_size_bytes` (options): The number of bytes per chunk of this file. Defaults to the chunk_size_bytes in :class:`AsyncIOMotorGridFSBucket`. - `metadata` (optional): User data for the 'metadata' field of the files collection document. If not provided the metadata field will be omitted from the files collection document. .. coroutinemethod:: rename(self, file_id, new_filename) Renames the stored file with the specified file_id. For example:: async def rename(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) # get _id of file to read. file_id = await fs.upload_from_stream("test_file", b"data I want to store!") await fs.rename(file_id, "new_test_name") Raises :exc:`~gridfs.errors.NoFile` if no file with file_id exists. :Parameters: - `file_id`: The _id of the file to be renamed. - `new_filename`: The new name of the file. .. coroutinemethod:: upload_from_stream(self, filename, source, chunk_size_bytes=None, metadata=None) Uploads a user file to a GridFS bucket. Reads the contents of the user file from `source` and uploads it to the file `filename`. Source can be a string or file-like object. For example:: async def upload_from_stream(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) file_id = await fs.upload_from_stream( "test_file", b"data I want to store!", chunk_size_bytes=4, metadata={"contentType": "text/plain"}) Raises :exc:`~gridfs.errors.NoFile` if no such version of that file exists. Raises :exc:`~ValueError` if `filename` is not a string. :Parameters: - `filename`: The name of the file to upload. - `source`: The source stream of the content to be uploaded. Must be a file-like object that implements :meth:`read` or a string. - `chunk_size_bytes` (options): The number of bytes per chunk of this file. Defaults to the chunk_size_bytes of :class:`AsyncIOMotorGridFSBucket`. - `metadata` (optional): User data for the 'metadata' field of the files collection document. If not provided the metadata field will be omitted from the files collection document. Returns the _id of the uploaded file. .. coroutinemethod:: upload_from_stream_with_id(self, file_id, filename, source, chunk_size_bytes=None, metadata=None) Uploads a user file to a GridFS bucket with a custom file id. Reads the contents of the user file from `source` and uploads it to the file `filename`. Source can be a string or file-like object. For example:: async def upload_from_stream_with_id(): my_db = AsyncIOMotorClient().test fs = AsyncIOMotorGridFSBucket(my_db) file_id = await fs.upload_from_stream_with_id( ObjectId(), "test_file", b"data I want to store!", chunk_size_bytes=4, metadata={"contentType": "text/plain"}) Raises :exc:`~gridfs.errors.NoFile` if no such version of that file exists. Raises :exc:`~ValueError` if `filename` is not a string. :Parameters: - `file_id`: The id to use for this file. The id must not have already been used for another file. - `filename`: The name of the file to upload. - `source`: The source stream of the content to be uploaded. Must be a file-like object that implements :meth:`read` or a string. - `chunk_size_bytes` (options): The number of bytes per chunk of this file. Defaults to the chunk_size_bytes of :class:`AsyncIOMotorGridFSBucket`. - `metadata` (optional): User data for the 'metadata' field of the files collection document. If not provided the metadata field will be omitted from the files collection document. .. autoclass:: AsyncIOMotorGridIn :members: .. autoclass:: AsyncIOMotorGridOut :members: .. autoclass:: AsyncIOMotorGridOutCursor :members: motor-2.3.0/doc/api-asyncio/asyncio_motor_change_stream.rst000066400000000000000000000003131373322416100241330ustar00rootroot00000000000000:class:`~motor.motor_asyncio.AsyncIOMotorChangeStream` ====================================================== .. currentmodule:: motor.motor_asyncio .. autoclass:: AsyncIOMotorChangeStream :members: motor-2.3.0/doc/api-asyncio/asyncio_motor_client.rst000066400000000000000000000007001373322416100226110ustar00rootroot00000000000000:class:`~motor.motor_asyncio.AsyncIOMotorClient` -- Connection to MongoDB ========================================================================= .. autoclass:: motor.motor_asyncio.AsyncIOMotorClient :members: .. describe:: client[db_name] || client.db_name Get the `db_name` :class:`AsyncIOMotorDatabase` on :class:`AsyncIOMotorClient` `client`. Raises :class:`~pymongo.errors.InvalidName` if an invalid database name is used. motor-2.3.0/doc/api-asyncio/asyncio_motor_client_session.rst000066400000000000000000000003561373322416100243630ustar00rootroot00000000000000:class:`~motor.motor_asyncio.AsyncIOMotorClientSession` -- Sequence of operations ================================================================================= .. autoclass:: motor.motor_asyncio.AsyncIOMotorClientSession :members: motor-2.3.0/doc/api-asyncio/asyncio_motor_collection.rst000066400000000000000000000121531373322416100234730ustar00rootroot00000000000000:class:`~motor.motor_asyncio.AsyncIOMotorCollection` ==================================================== .. currentmodule:: motor.motor_asyncio .. autoclass:: AsyncIOMotorCollection :members: :exclude-members: create_index, inline_map_reduce .. describe:: c[name] || c.name Get the `name` sub-collection of :class:`AsyncIOMotorCollection` `c`. Raises :class:`~pymongo.errors.InvalidName` if an invalid collection name is used. .. attribute:: database 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`: Used to create an expiring (TTL) collection. MongoDB will automatically delete documents from this collection after 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 motor-2.3.0/doc/api-asyncio/asyncio_motor_database.rst000066400000000000000000000006771373322416100231140ustar00rootroot00000000000000:class:`~motor.motor_asyncio.AsyncIOMotorDatabase` ================================================== .. currentmodule:: motor.motor_asyncio .. autoclass:: AsyncIOMotorDatabase :members: .. describe:: db[collection_name] || db.collection_name Get the `collection_name` :class:`AsyncIOMotorCollection` of :class:`AsyncIOMotorDatabase` `db`. Raises :class:`~pymongo.errors.InvalidName` if an invalid collection name is used. motor-2.3.0/doc/api-asyncio/cursors.rst000066400000000000000000000006641373322416100200770ustar00rootroot00000000000000:class:`~motor.motor_asyncio.AsyncIOMotorCursor` ================================================ .. currentmodule:: motor.motor_asyncio .. autoclass:: AsyncIOMotorCursor :members: :inherited-members: :class:`~motor.motor_asyncio.AsyncIOMotorCommandCursor` ======================================================= .. currentmodule:: motor.motor_asyncio .. autoclass:: AsyncIOMotorCommandCursor :members: :inherited-members: motor-2.3.0/doc/api-asyncio/index.rst000066400000000000000000000006031373322416100174770ustar00rootroot00000000000000Motor asyncio API ================= .. toctree:: asyncio_motor_client asyncio_motor_client_session asyncio_motor_database asyncio_motor_collection asyncio_motor_change_stream cursors asyncio_gridfs aiohttp .. seealso:: :doc:`../tutorial-asyncio` This page describes using Motor with asyncio. For Tornado integration, see :doc:`../api-tornado/index`. motor-2.3.0/doc/api-tornado/000077500000000000000000000000001373322416100156405ustar00rootroot00000000000000motor-2.3.0/doc/api-tornado/cursors.rst000066400000000000000000000006121373322416100200710ustar00rootroot00000000000000:class:`~motor.motor_tornado.MotorCursor` ========================================= .. currentmodule:: motor.motor_tornado .. autoclass:: MotorCursor :members: :inherited-members: :class:`~motor.motor_tornado.MotorCommandCursor` ================================================ .. currentmodule:: motor.motor_tornado .. autoclass:: MotorCommandCursor :members: :inherited-members: motor-2.3.0/doc/api-tornado/gridfs.rst000066400000000000000000000351111373322416100176510ustar00rootroot00000000000000Motor GridFS Classes ==================== .. currentmodule:: motor.motor_tornado Store blobs of data in `GridFS `_. .. seealso:: :ref:`Differences between PyMongo's and Motor's GridFS APIs `. .. seealso:: :doc:`web` .. class:: MotorGridFSBucket Create a new instance of :class:`MotorGridFSBucket`. Raises :exc:`TypeError` if `database` is not an instance of :class:`MotorDatabase`. Raises :exc:`~pymongo.errors.ConfigurationError` if `write_concern` is not acknowledged. :Parameters: - `database`: database to use. - `bucket_name` (optional): The name of the bucket. Defaults to 'fs'. - `chunk_size_bytes` (optional): The chunk size in bytes. Defaults to 255KB. - `write_concern` (optional): The :class:`~pymongo.write_concern.WriteConcern` to use. If ``None`` (the default) db.write_concern is used. - `read_preference` (optional): The read preference to use. If ``None`` (the default) db.read_preference is used. .. mongodoc:: gridfs .. coroutinemethod:: delete(self, file_id)) 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, file_id = fs.open_upload_stream( "test_file", chunk_size_bytes=4, metadata={"contentType": "text/plain"}) await grid_in.write(b"data I want to store!") await grid_in.close() # uploaded on close Returns an instance of :class:`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.new_file() as gridin: await gridin.write(b'First part\n') await gridin.write(b'Second part') # gridin is now closed automatically. :Parameters: - `filename`: The name of the file to upload. - `chunk_size_bytes` (options): The number of bytes per chunk of this file. Defaults to the chunk_size_bytes in :class:`MotorGridFSBucket`. - `metadata` (optional): User data for the 'metadata' field of the files collection document. If not provided the metadata field will be omitted from the files collection document. .. method:: open_upload_stream_with_id(self, file_id, filename, chunk_size_bytes=None, metadata=None) Opens a stream for writing. Specify the filed_id and filename, and add any additional information in the metadata field of the file document, or modify the chunk size:: async def upload(): my_db = MotorClient().test fs = MotorGridFSBucket(my_db) grid_in, file_id = fs.open_upload_stream_with_id( ObjectId(), "test_file", chunk_size_bytes=4, metadata={"contentType": "text/plain"}) 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: .. autoclass:: MotorGridOut :members: .. autoclass:: MotorGridOutCursor :members: motor-2.3.0/doc/api-tornado/index.rst000066400000000000000000000005161373322416100175030ustar00rootroot00000000000000Motor Tornado API ================= .. toctree:: motor_client motor_client_session motor_database motor_collection motor_change_stream cursors gridfs web .. seealso:: :doc:`../tutorial-tornado` This page describes using Motor with Tornado. For asyncio integration, see :doc:`../api-asyncio/index`. motor-2.3.0/doc/api-tornado/motor_change_stream.rst000066400000000000000000000002661373322416100224160ustar00rootroot00000000000000:class:`~motor.motor_tornado.MotorChangeStream` =============================================== .. currentmodule:: motor.motor_tornado .. autoclass:: MotorChangeStream :members: motor-2.3.0/doc/api-tornado/motor_client.rst000066400000000000000000000006611373322416100210730ustar00rootroot00000000000000:class:`~motor.motor_tornado.MotorClient` -- Connection to MongoDB ================================================================== .. currentmodule:: motor.motor_tornado .. autoclass:: MotorClient :members: .. describe:: client[db_name] || client.db_name Get the `db_name` :class:`MotorDatabase` on :class:`MotorClient` `client`. Raises :class:`~pymongo.errors.InvalidName` if an invalid database name is used. motor-2.3.0/doc/api-tornado/motor_client_session.rst000066400000000000000000000004011373322416100226260ustar00rootroot00000000000000:class:`~motor.motor_tornado.MotorClientSession` -- Sequence of operations ========================================================================== .. currentmodule:: motor.motor_tornado .. autoclass:: motor.motor_tornado.MotorClientSession :members: motor-2.3.0/doc/api-tornado/motor_collection.rst000066400000000000000000000121631373322416100217500ustar00rootroot00000000000000:class:`~motor.motor_tornado.MotorCollection` ============================================= .. currentmodule:: motor.motor_tornado .. autoclass:: MotorCollection :members: :exclude-members: create_index, inline_map_reduce .. describe:: c[name] || c.name Get the `name` sub-collection of :class:`MotorCollection` `c`. Raises :class:`~pymongo.errors.InvalidName` if an invalid collection name is used. .. attribute:: database 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`: Used to create an expiring (TTL) collection. MongoDB will automatically delete documents from this collection after 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 motor-2.3.0/doc/api-tornado/motor_database.rst000066400000000000000000000006341373322416100213610ustar00rootroot00000000000000:class:`~motor.motor_tornado.MotorDatabase` =========================================== .. currentmodule:: motor.motor_tornado .. autoclass:: MotorDatabase :members: .. describe:: db[collection_name] || db.collection_name Get the `collection_name` :class:`MotorCollection` of :class:`MotorDatabase` `db`. Raises :class:`~pymongo.errors.InvalidName` if an invalid collection name is used. motor-2.3.0/doc/api-tornado/web.rst000066400000000000000000000003441373322416100171500ustar00rootroot00000000000000:mod:`motor.web` - Integrate Motor with the Tornado web framework ================================================================= .. currentmodule:: motor.web .. automodule:: motor.web :members: :no-inherited-members: motor-2.3.0/doc/changelog.rst000066400000000000000000001266771373322416100161270ustar00rootroot00000000000000Changelog ========= .. currentmodule:: motor.motor_tornado Motor 2.3 --------- Motor 2.3 adds support for contextvars. New features: - Added supported for the contextvars module. Specifically, it is now possible to access context variables inside :class:`~pymongo.monitoring.CommandListener` callbacks. Bug-fixes: - Fixed a bug that prohibited users from subclassing the :class:`motor.motor_asyncio.AsyncIOMotorClient`, :class:`motor.motor_asyncio.AsyncIOMotorDatabase`, and :class:`motor.motor_asyncio.AsyncIOMotorCollection` classes. - Updated the documentation to indicate full support for Windows. Previously, the documentation stated that Windows support was experimental. Issues Resolved ~~~~~~~~~~~~~~~ See the `Motor 2.3 release notes in JIRA`_ for the complete list of resolved issues in this release. .. _Motor 2.3 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=29836 Motor 2.2 --------- Motor 2.2 adds support for MongoDB 4.4 features. It depends on PyMongo 3.11 or later. Motor continues to support MongoDB 3.0 and later. Motor 2.2 also drops support for Python 2.7 and Python 3.4. New features: - Added the ``AsyncIOMotorCursor`` method :meth:`~motor.motor_asyncio.AsyncIOMotorCursor.next` that advances the cursor one document at a time, similar to to the ``AsyncIOMotorChangeStream`` method :meth:`~motor.motor_asyncio.AsyncIOMotorChangeStream.next`. - Added index-hinting support to the :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.replace_one`, :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.update_one`, :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.update_many`, :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.delete_one`, :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.delete_many`, :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_one_and_replace`, :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_one_and_update`, and :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find_one_and_delete` methods. - Added support for the ``allow_disk_use`` parameter to :meth:`~motor.motor_asyncio.AsyncIOMotorCollection.find`. - Modified the :meth:`~motor.motor_asyncio.AsyncIOMotorChangeStream` class' async context manager such that the change stream cursor is now created during the call to ``async with``. Previously, the cursor was only created when the application iterated the :meth:`~motor.motor_asyncio.AsyncIOMotorChangeStream` object which could result in the application missing some changes. - Motor now advertises the framework used by the application to the MongoDB server as ``asyncio`` or ``Tornado``. Previously, no framework information was reported if the application used ``asyncio``. Bug-fixes: - Fixed a bug that caused calls to the :meth:`~motor.motor_asyncio.AsyncIOMotorGridOut.open()` method to raise :exc:`AttributeError`. - Fixed a bug that sometimes caused :meth:`~asyncio.Future.set_result` to be called on a cancelled :meth:`~asyncio.Future` when iterating a :meth:`~motor.motor_asyncio.AsyncIOMotorCommandCursor`. Deprecations: - Deprecated ``AsyncIOMotorCursor`` method :meth:`~motor.motor_asyncio.AsyncIOMotorCursor.next_object` and property :attr:`~motor.motor_asyncio.AsyncIOMotorCursor.fetch_next`. Applications should use ``async for`` to iterate over cursors instead. - Deprecated the :meth:`~motor.motor_asyncio.AsyncIOMotorClient.fsync` method. Applications should run the `fsync command `_ directly with :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command` instead. Issues Resolved ~~~~~~~~~~~~~~~ See the `Motor 2.2 release notes in JIRA`_ for the complete list of resolved issues in this release. .. _Motor 2.2 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=24884 Motor 2.1 --------- Motor 2.1 adds support for MongoDB 4.2 features. It depends on PyMongo 3.10 or later. Motor continues to support MongoDB 3.0 and later. Motor 2.1 also adds support for Python 3.8. Motor now offers experimental support for Windows when it is using the asyncio event loop. This means it supports Windows exclusively with Python 3, either integrating with asyncio directly or with Tornado 5 or later: starting in version 5, Tornado uses the asyncio event loop on Python 3 by default. Additional changes: - Support for MongoDB 4.2 sharded transactions. Sharded transactions have the same API as replica set transactions. - New method :meth:`~motor.motor_asyncio.AsyncIOMotorClientSession.with_transaction` to support conveniently running a transaction in a session with automatic retries and at-most-once semantics. - Added the ``max_commit_time_ms`` parameter to :meth:`~motor.motor_asyncio.AsyncIOMotorClientSession.start_transaction`. - The ``retryWrites`` URI option now defaults to ``True``. Supported write operations that fail with a retryable error will automatically be retried one time, with at-most-once semantics. - Support for retryable reads and the ``retryReads`` URI option which is enabled by default. See the :class:`~pymongo.mongo_client.MongoClient` documentation for details. Now that supported operations are retried automatically and transparently, users should consider adjusting any custom retry logic to prevent an application from inadvertently retrying for too long. - Support zstandard for wire protocol compression. - Support for periodically polling DNS SRV records to update the mongos proxy list without having to change client configuration. - New method :meth:`motor.motor_asyncio.AsyncIOMotorDatabase.aggregate` to support running database level aggregations. - Change stream enhancements for MongoDB 4.2: - Resume tokens can now be accessed from a ``AsyncIOMotorChangeStream`` cursor using the :attr:`~motor.motor_asyncio.AsyncIOMotorChangeStream.resume_token` attribute. - New ``AsyncIOMotorChangeStream`` method :meth:`~motor.motor_asyncio.AsyncIOMotorChangeStream.try_next` and attribute :attr:`~motor.motor_asyncio.AsyncIOMotorChangeStream.alive`. - New parameter ``start_after`` for change stream :meth:`motor.motor_asyncio.AsyncIOMotorCollection.watch`, :meth:`motor.motor_asyncio.AsyncIOMotorDatabase.watch`, and :meth:`motor.motor_asyncio.AsyncIOMotorClient.watch` methods. - New parameters ``bucket_name``, ``chunk_size_bytes``, ``write_concern``, and ``read_preference`` for :class:`motor.motor_asyncio.AsyncIOMotorGridFSBucket`. Issues Resolved ~~~~~~~~~~~~~~~ See the `Motor 2.1 release notes in JIRA`_ for the complete list of resolved issues in this release. .. _Motor 2.1 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=11182&version=20187 Motor 2.0 --------- Motor 2.0 drops support for MongoDB 2.6 and adds supports MongoDB 4.0 features, including multi-document transactions, and change stream notifications on entire databases or entire MongoDB servers. It adds support for Python 3.7. This version of Motor requires PyMongo 3.7 or later. This is a major release that removes previously deprecated APIs. To support multi-document transactions, Motor had to make breaking changes to the session API and release a major version bump. Since this is a major release it also deletes many helper methods and APIs that had been deprecated over the time since Motor 1.0, most notably the old CRUD methods ``insert``, ``update``, ``remove``, and ``save``, and the original callback-based API. Read the :doc:`migrate-to-motor-2` carefully to upgrade your existing Motor application. Documentation is updated to warn about obsolete TLS versions, see :doc:`configuration`. Motor is now tested on Travis in addition to MongoDB's `Evergreen `_ system. Added support for `aiohttp`_ 3.0 and later, and dropped older aiohttp versions. The aiohttp integration now requires Python 3.5+. The ``MotorDatabase.add_user`` and ``MotorDatabase.remove_user`` methods are deleted. Manage user accounts with four database commands: createUser_, usersInfo_, updateUser_, and dropUser_. You can run any database command with the :meth:`MotorDatabase.command` method. .. _createUser: https://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 :class:`~motor.motor_asyncio.AsyncIOMotorGridFSBucket`, which conform to driver specs for GridFS. Additional changes: - New methods for retrieving batches of raw BSON: - :meth:`MotorCollection.find_raw_batches` - :meth:`MotorCollection.aggregate_raw_batches` - Motor adds its name, version, and Tornado's version (if appropriate) to the client data logged by the MongoDB server when Motor connects, in addition to the data added by PyMongo. - Calling :meth:`~MotorCommandCursor.batch_size` on a cursor returned from :meth:`~MotorCollection.aggregate` no longer raises ``AttributeError``. Motor 1.3.1 ----------- Fix a Python 3.7 compatibility bug caused by importing "async", which is a keyword in Python 3.7. Drop support for Python 3.4.3 and older. Motor 1.3.0 ----------- Deprecate Motor's old callback-based async API in preparation for removing it in Motor 2.0. Raise ``DeprecationWarning`` whenever a callback is passed. See the :doc:`migrate-to-motor-2`. Motor 1.2.5 ----------- Fix a Python 3.7 compatibility bug caused by importing "async", which is a keyword in Python 3.7. Drop support for Python 3.4.3 and older. Motor 1.2.4 ----------- Fix a Python 3.7 compatibility bug in the :class:`MotorChangeStream` class returned by :meth:`MotorCollection.watch`. It is now possible to use change streams in ``async for`` loops in Python 3.7. Motor 1.2.3 ----------- Compatibility with latest Sphinx and document how to use the latest TLS protocols. Motor 1.2.2 ----------- Motor 1.2.0 requires PyMongo 3.6 or later. The dependency was properly documented, but not enforced in ``setup.py``. PyMongo 3.6 is now an install-time requirement; thanks to Shane Harvey for the fix. Motor 1.2.1 ----------- An asyncio application that created a Change Stream with :meth:`MotorCollection.watch` and shut down while the Change Stream was open would print several errors. I have rewritten :meth:`MotorChangeStream.next` and some Motor internals to allow clean shutdown with asyncio. Motor 1.2 --------- Motor 1.2 drops support for MongoDB 2.4 and adds support for MongoDB 3.6 features. It depends on PyMongo 3.6 or later. Motor continues to support MongoDB 2.6 and later. Dropped support for Python 2.6 and 3.3. Motor continues to support Python 2.7, and 3.4+. Dropped support for Tornado 3. A recent version of Tornado 4 is required. Dropped support for the `Python 3.5.0 and Python 3.5.1 "async for" protocol `_. Motor allows "async for" with cursors in Python 3.5.2 and later. See the :ref:`Compatibility Matrix ` for the relationships among Motor, Python, Tornado, and MongoDB versions. Added support for `aiohttp`_ 2.0 and later, and dropped older aiohttp versions. Highlights include: - New method :meth:`MotorCollection.watch` to acquire a Change Stream on a collection. - New Session API to support causal consistency, see :meth:`MotorClient.start_session`. - Support for array_filters in :meth:`~MotorCollection.update_one`, :meth:`~MotorCollection.update_many`, :meth:`~MotorCollection.find_one_and_update`, :meth:`~MotorCollection.bulk_write`. - :meth:`MotorClient.list_databases` and :meth:`MotorClient.list_database_names`. - Support for mongodb+srv:// URIs. See :class:`~pymongo.mongo_client.MongoClient` for details. - Support for retryable writes and the ``retryWrites`` URI option. See :class:`~pymongo.mongo_client.MongoClient` for details. The maximum number of workers in the thread pool can be overridden with an environment variable, see :doc:`configuration`. :class:`MotorCollection` accepts codec_options, read_preference, write_concern, and read_concern arguments. This is rarely needed; you typically create a :class:`MotorCollection` from a :class:`MotorDatabase`, not by calling its constructor directly. Deleted obsolete class ``motor.Op``. Motor 1.1 --------- Motor depends on PyMongo 3.4 or later. It wraps the latest PyMongo code which support the new server features introduced in MongoDB 3.4. (It is a coincidence that the latest MongoDB and PyMongo versions are the same number.) Highlights include: - Complete support for MongoDB 3.4: - Unicode aware string comparison using collations. See :ref:`PyMongo's examples for collation `. - :class:`MotorCursor` and :class:`MotorGridOutCursor` have a new attribute :meth:`~MotorCursor.collation`. - 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. - :meth:`~MotorCollection.parallel_scan` supports maxTimeMS. - :class:`~pymongo.write_concern.WriteConcern` is automatically applied by all helpers for commands that write to the database when connected to MongoDB 3.4+. This change affects the following helpers: - :meth:`MotorClient.drop_database` - :meth:`MotorDatabase.create_collection` - :meth:`MotorDatabase.drop_collection` - :meth:`MotorCollection.aggregate` (when using $out) - :meth:`MotorCollection.create_indexes` - :meth:`MotorCollection.create_index` - :meth:`MotorCollection.drop_indexes` - :meth:`MotorCollection.drop_indexes` - :meth:`MotorCollection.drop_index` - :meth:`MotorCollection.map_reduce` (when output is not "inline") - :meth:`MotorCollection.reindex` - :meth:`MotorCollection.rename` - Improved support for logging server discovery and monitoring events. See :mod:`PyMongo's monitoring documentation ` for examples. - Support for matching iPAddress subjectAltName values for TLS certificate verification. - TLS compression is now explicitly disabled when possible. - The Server Name Indication (SNI) TLS extension is used when possible. - PyMongo's `bson` module provides finer control over JSON encoding/decoding with :class:`~bson.json_util.JSONOptions`. - Allow :class:`~bson.code.Code` objects to have a scope of ``None``, signifying no scope. Also allow encoding Code objects with an empty scope (i.e. ``{}``). .. warning:: Starting in PyMongo 3.4, :attr:`bson.code.Code.scope` may return ``None``, as the default scope is ``None`` instead of ``{}``. .. note:: PyMongo 3.4+ attempts to create sockets non-inheritable when possible (i.e. it sets the close-on-exec flag on socket file descriptors). Support is limited to a subset of POSIX operating systems (not including Windows) and the flag usually cannot be set in a single atomic operation. CPython 3.4+ implements `PEP 446`_, creating all file descriptors non-inheritable by default. Users that require this behavior are encouraged to upgrade to CPython 3.4+. .. _PEP 446: https://www.python.org/dev/peps/pep-0446/ 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: http://api.mongodb.com/python/current/changelog.html#changes-in-version-3-0 :class:`MotorReplicaSetClient` is removed ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In Motor 1.0, :class:`MotorClient` is the only class. Connect to a replica set with a "replicaSet" URI option or parameter:: MotorClient("mongodb://hostname/?replicaSet=my-rs") MotorClient(host, port, replicaSet="my-rs") New features ~~~~~~~~~~~~ New classes :class:`~motor.motor_tornado.MotorGridFSBucket` and :class:`~motor.motor_asyncio.AsyncIOMotorGridFSBucket` conform to the `GridFS API Spec `_ for MongoDB drivers. These classes supersede the old ``MotorGridFS`` and ``AsyncIOMotorGridFS``. See `GridFS`_ changes below, especially note the **breaking change** in :class:`~motor.motor_web.GridFSHandler`. Serve GridFS files over HTTP using `aiohttp`_ and :class:`~motor.aiohttp.AIOHTTPGridFS`. .. _aiohttp: https://aiohttp.readthedocs.io/ :class:`MotorClient` changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Removed: - :meth:`MotorClient.open`; clients have opened themselves automatically on demand since version 0.2. - :attr:`MotorClient.seeds`, use :func:`pymongo.uri_parser.parse_uri` on your MongoDB URI. - :attr:`MotorClient.alive` Added: - :attr:`MotorClient.event_listeners` - :attr:`MotorClient.max_idle_time_ms` - :attr:`MotorClient.min_pool_size` Unix domain socket paths must be quoted with :func:`urllib.parse.quote_plus` (or ``urllib.quote_plus`` in Python 2) before they are included in a URI: .. code-block:: python path = '/tmp/mongodb-27017.sock' MotorClient('mongodb://%s' % urllib.parse.quote_plus(path)) :class:`~motor.motor_tornado.MotorCollection` changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Added: - :meth:`MotorCollection.create_indexes` - :meth:`MotorCollection.list_indexes` New ``bypass_document_validation`` parameter for :meth:`~.MotorCollection.initialize_ordered_bulk_op` and :meth:`~.MotorCollection.initialize_unordered_bulk_op`. Changes to :meth:`~motor.motor_tornado.MotorCollection.find` and :meth:`~motor.motor_tornado.MotorCollection.find_one` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following find/find_one options have been renamed: These renames only affect your code if you passed these as keyword arguments, like ``find(fields=['fieldname'])``. If you passed only positional parameters these changes are not significant for your application. - spec -> filter - fields -> projection - partial -> allow_partial_results The following find/find_one options have been added: - cursor_type (see :class:`~pymongo.cursor.CursorType` for values) - oplog_replay - modifiers The following find/find_one options have been removed: - network_timeout (use :meth:`~motor.motor_tornado.MotorCursor.max_time_ms` instead) - read_preference (use :meth:`~motor.motor_tornado.MotorCollection.with_options` instead) - tag_sets (use one of the read preference classes from :mod:`~pymongo.read_preferences` and :meth:`~motor.motor_tornado.MotorCollection.with_options` instead) - secondary_acceptable_latency_ms (use the ``localThresholdMS`` URI option instead) - max_scan (use the new ``modifiers`` option instead) - snapshot (use the new ``modifiers`` option instead) - tailable (use the new ``cursor_type`` option instead) - await_data (use the new ``cursor_type`` option instead) - exhaust (use the new ``cursor_type`` option instead) - as_class (use :meth:`~motor.motor_tornado.MotorCollection.with_options` with :class:`~bson.codec_options.CodecOptions` instead) - compile_re (BSON regular expressions are always decoded to :class:`~bson.regex.Regex`) The following find/find_one options are deprecated: - manipulate The following renames need special handling. - timeout -> no_cursor_timeout - By default, MongoDB closes a cursor after 10 minutes of inactivity. In previous Motor versions, you disabled the timeout by passing ``timeout=False`` to :meth:`.MotorCollection.find` or :meth:`.MotorGridFS.find`. The ``timeout`` parameter has been renamed to ``no_cursor_timeout``, it defaults to ``False``, and you must now pass ``no_cursor_timeout=True`` to disable timeouts. :class:`~motor.motor_tornado.MotorCursor` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Added: - :attr:`.MotorCursor.address` - :meth:`.MotorCursor.max_await_time_ms` Removed: - :attr:`.MotorCursor.conn_id`, use :attr:`~.MotorCursor.address` GridFS ~~~~~~ The old GridFS classes ``MotorGridFS`` and ``AsyncIOMotorGridFS`` are deprecated in favor of :class:`~motor.motor_tornado.MotorGridFSBucket` and :class:`~motor.motor_asyncio.AsyncIOMotorGridFSBucket`, which comply with MongoDB's cross-language driver spec for GridFS. The old classes are still supported, but will be removed in Motor 2.0. **BREAKING CHANGE**: The overridable method :class:`~motor.web.GridFSHandler.get_gridfs_file` of :class:`~motor.web.GridFSHandler` now takes a :class:`~motor.motor_tornado.MotorGridFSBucket`, not a :class:`~motor.motor_tornado.MotorGridFS`. It also takes an additional ``request`` parameter. :class:`~motor.motor_tornado.MotorGridOutCursor` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Added: - :attr:`.MotorGridOutCursor.address` - :meth:`.MotorGridOutCursor.max_await_time_ms` Removed: - :attr:`.MotorGridOutCursor.conn_id`, use :attr:`~.MotorGridOutCursor.address` :class:`~motor.motor_tornado.MotorGridIn` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New method :meth:`.MotorGridIn.abort`. In a Python 3.5 native coroutine, the "async with" statement calls :meth:`~MotorGridIn.close` automatically:: async def upload(): my_db = MotorClient().test fs = MotorGridFSBucket(my_db) async with await fs.new_file() as gridin: await gridin.write(b'First part\n') await gridin.write(b'Second part') # gridin is now closed automatically. :class:`~motor.motor_tornado.MotorGridOut` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :class:`~motor.motor_tornado.MotorGridOut` is now an async iterable, so reading a chunk at a time is much simpler with a Python 3 native coroutine:: async def read_file(file_id): fs = motor.motor_tornado.MotorGridFS(db) gridout = await fs.get(file_id) async for chunk in gridout: sys.stdout.write(chunk) sys.stdout.flush() Documentation ~~~~~~~~~~~~~ The :doc:`/api-asyncio/index` is now fully documented, side by side with the :doc:`/api-tornado/index`. New :doc:`developer-guide` added. Motor 0.7 --------- For asynchronous I/O Motor now uses a thread pool, which is faster and simpler than the prior implementation with greenlets. It no longer requires the ``greenlet`` package, and now requires the ``futures`` backport package on Python 2. This version updates the PyMongo dependency from 2.8.0 to 2.9.x, and wraps PyMongo 2.9's new APIs. Most of Motor 1.0's API is now implemented, and APIs that will be removed in Motor 1.0 are now deprecated and raise warnings. :class:`MotorClient` changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :class:`~MotorClient.get_database` method is added for getting a :class:`MotorDatabase` instance with its options configured differently than the MotorClient's. New read-only attributes: - :attr:`~MotorClient.codec_options` - :attr:`~MotorClient.local_threshold_ms` - :attr:`~MotorClient.max_write_batch_size` :class:`MotorReplicaSetClient` changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`~MotorReplicaSetClient.get_database` method is added for getting a :class:`MotorDatabase` instance with its options configured differently than the MotorReplicaSetClient's. New read-only attributes: - :attr:`~MotorReplicaSetClient.codec_options` - :attr:`~MotorReplicaSetClient.local_threshold_ms` :class:`MotorDatabase` changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`~MotorDatabase.get_collection` method is added for getting a :class:`MotorCollection` instance with its options configured differently than the MotorDatabase's. The ``connection`` property is deprecated in favor of a new read-only attribute :attr:`~MotorDatabase.client`. New read-only attribute: - :attr:`~MotorDatabase.codec_options` :class:`MotorCollection` changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`~MotorCollection.with_options` method is added for getting a :class:`MotorCollection` instance with its options configured differently than this MotorCollection's. New read-only attribute: - :attr:`~MotorCollection.codec_options` The following methods wrap PyMongo's implementation of the standard `CRUD API Spec`_ for MongoDB Drivers: - :meth:`~MotorCollection.bulk_write` - :meth:`~MotorCollection.insert_one` - :meth:`~MotorCollection.insert_many` - :meth:`~MotorCollection.update_one` - :meth:`~MotorCollection.update_many` - :meth:`~MotorCollection.replace_one` - :meth:`~MotorCollection.delete_one` - :meth:`~MotorCollection.delete_many` - :meth:`~MotorCollection.find_one_and_delete` - :meth:`~MotorCollection.find_one_and_replace` - :meth:`~MotorCollection.find_one_and_update` These new methods do not apply SON Manipulators. .. _CRUD API Spec: https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst :doc:`GridFS ` changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New :class:`MotorGridOutCursor` methods: - :meth:`~MotorGridOutCursor.add_option` - :meth:`~MotorGridOutCursor.remove_option` - :meth:`~MotorGridOutCursor.clone` Added :class:`MotorGridOut` documentation: - :attr:`~MotorGridOut.aliases` - :attr:`~MotorGridOut.chunk_size` - :meth:`~MotorGridOut.close` - :attr:`~MotorGridOut.content_type` - :attr:`~MotorGridOut.filename` - :attr:`~MotorGridOut.length` - :attr:`~MotorGridOut.md5` - :attr:`~MotorGridOut.metadata` - :attr:`~MotorGridOut.name` - :attr:`~MotorGridOut.upload_date` Bugfix ~~~~~~ `MOTOR-124 `_: an import deadlock in Python 2 and Tornado 3 led to an :exc:`~pymongo.errors.AutoReconnect` exception with some replica sets. Motor 0.6.2 ----------- Fix "from motor import \*" for Python 3. Motor 0.6.1 ----------- Fix source distribution, which hadn't included the "frameworks" submodules. 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These modules have been moved from: - `motor_asyncio.py` - `motor_tornado.py` To: - `motor_asyncio/__init__.py` - `motor_tornado/__init__.py` 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. Database and collection names with leading underscores ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A database or collection whose name starts with an underscore can no longer be accessed as a property:: # Now raises AttributeError. db = MotorClient()._mydatabase collection = db._mycollection subcollection = collection._subcollection Such databases and collections can still be accessed dict-style:: # Continues to work the same as previous Motor versions. db = MotorClient()['_mydatabase'] collection = db['_mycollection'] To ensure a "sub-collection" with a name that includes an underscore is 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'] These changes solve problems with iPython code completion and the Python 3 :class:`ABC` abstract base class. Motor 0.5 --------- asyncio ~~~~~~~ Motor can now integrate with asyncio, as an alternative to Tornado. My gratitude to Rémi Jolin, Andrew Svetlov, and Nikolay Novik for their huge contributions to Motor's asyncio integration. Python 3.5 ~~~~~~~~~~ Motor is now compatible with Python 3.5, which required some effort. Motor not only supports users' coroutines, it uses coroutines to implement some of its own features, like :meth:`~MotorClient.open` and :meth:`~MotorGridFS.put`. There is no single way to return a value from a Python 3.5 native coroutine or a Python 2 generator-based coroutine, so Motor internal coroutines that return values were rewritten. (See `commit message dc19418c`_ for an explanation.) .. _commit message dc19418c: https://github.com/mongodb/motor/commit/dc19418c `async` and `await` ~~~~~~~~~~~~~~~~~~~ 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`:: async def f(): async for doc in collection.find(): do_something_with(doc) .. _aggregate_changes_0_5: :meth:`~MotorCollection.aggregate` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :meth:`MotorCollection.aggregate` now returns a cursor by default, and the cursor is returned immediately without a `yield`. The old syntax is no longer supported:: # Motor 0.4 and older, no longer supported. cursor = yield collection.aggregate(pipeline, cursor={}) while (yield cursor.fetch_next): doc = cursor.next_object() print(doc) In Motor 0.5, simply do:: # Motor 0.5: no "cursor={}", no "yield". cursor = collection.aggregate(pipeline) while (yield cursor.fetch_next): doc = cursor.next_object() print(doc) Or with Python 3.5 and later:: # Motor 0.5, Python 3.5. async for doc in collection.aggregate(pipeline): print(doc) MongoDB versions 2.4 and older do not support aggregation cursors. For compatibility with older MongoDBs, :meth:`~MotorCollection.aggregate` now takes an argument ``cursor=False``, and returns a Future that you can yield to get all the results in one document:: # Motor 0.5 with MongoDB 2.4 and older. reply = yield collection.aggregate(cursor=False) for doc in reply['results']: print(doc) Deprecations ~~~~~~~~~~~~ Motor 0.5 deprecates a large number of APIs that will be removed in version 1.0: `MotorClient`: - `~MotorClient.host` - `~MotorClient.port` - `~MotorClient.document_class` - `~MotorClient.tz_aware` - `~MotorClient.secondary_acceptable_latency_ms` - `~MotorClient.tag_sets` - `~MotorClient.uuid_subtype` - `~MotorClient.disconnect` - `~MotorClient.alive` `MotorReplicaSetClient`: - `~MotorReplicaSetClient.document_class` - `~MotorReplicaSetClient.tz_aware` - `~MotorReplicaSetClient.secondary_acceptable_latency_ms` - `~MotorReplicaSetClient.tag_sets` - `~MotorReplicaSetClient.uuid_subtype` - `~MotorReplicaSetClient.alive` `MotorDatabase`: - `~MotorDatabase.secondary_acceptable_latency_ms` - `~MotorDatabase.tag_sets` - `~MotorDatabase.uuid_subtype` `MotorCollection`: - `~MotorCollection.secondary_acceptable_latency_ms` - `~MotorCollection.tag_sets` - `~MotorCollection.uuid_subtype` Cursor slicing ~~~~~~~~~~~~~~ Cursors can no longer be indexed like ``cursor[n]`` or sliced like ``cursor[start:end]``, see `MOTOR-84 `_. If you wrote code like this:: cursor = collection.find()[i] yield cursor.fetch_next doc = cursor.next_object() Then instead, write:: cursor = collection.find().skip(i).limit(-1) yield cursor.fetch_next doc = cursor.next_object() The negative limit ensures the server closes the cursor after one result, saving Motor the work of closing it. See `cursor.limit `_. SSL hostname validation error ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When you use Motor with Tornado and SSL hostname validation fails, Motor used to raise a :exc:`~pymongo.errors.ConnectionFailure` with a useful messsage like "hostname 'X' doesn't match 'Y'". The message is now empty and Tornado logs a warning instead. Configuring uuid_subtype ~~~~~~~~~~~~~~~~~~~~~~~~ You can now get and set :attr:`~MotorClient.uuid_subtype` on :class:`MotorClient`, :class:`MotorReplicaSetClient`, and :class:`MotorDatabase` instances, not just on :class:`MotorCollection`. Motor 0.4.1 ----------- Fix `MOTOR-66 `_, deadlock when initiating :class:`MotorReplicaSetClient` connection from multiple operations at once. Motor 0.4 --------- Supports MongoDB 3.0. In particular, supports MongoDB 3.0's new SCRAM-SHA-1 authentication mechanism and updates the implementations of :meth:`MotorClient.database_names` and :meth:`MotorDatabase.collection_names`. Updates PyMongo dependency from 2.7.1 to 2.8, therefore inheriting `PyMongo 2.7.2's bug fixes `_ and `PyMongo 2.8's bug fixes `_ and `features `_. Fixes `a connection-pool timeout when waitQueueMultipleMS is set `_ and `two bugs in replica set monitoring `_. The ``copy_database`` method has been removed. It was overly complex and no one used it, see `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 ``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 `_. Motor 0.3.3 ----------- Fix `MOTOR-45 `_, a stack-context leak in domain name resolution that could lead to an infinite loop and rapid memory leak. Document Motor's :doc:`requirements` in detail. Motor 0.3.2 ----------- Fix `MOTOR-44 `_, a socket leak in :class:`MotorClient.copy_database` and :class:`MotorReplicaSetClient.copy_database`. Motor 0.3.1 ----------- Fix `MOTOR-43 `_, a TypeError when using :class:`~motor.web.GridFSHandler` with a timezone-aware :class:`~motor.motor_tornado.MotorClient`. Fix GridFS examples that hadn't been updated for Motor 0.2's new syntax. Fix a unittest that hadn't been running. Motor 0.3 --------- No new features. * Updates PyMongo dependency from 2.7 to 2.7.1, therefore inheriting `PyMongo 2.7.1's bug fixes `_. * Motor continues to support Python 2.6, 2.7, 3.3, and 3.4, but now with single-source. 2to3 no longer runs during installation with Python 3. * ``nosetests`` is no longer required for regular Motor tests. * Fixes `a mistake in the docstring `_ for aggregate(). Motor 0.2.1 ----------- Fixes two bugs: * `MOTOR-32 `_: The documentation for :meth:`MotorCursor.close` claimed it immediately halted execution of :meth:`MotorCursor.each`, but it didn't. * `MOTOR-33 `_: An incompletely iterated cursor's ``__del__`` method sometimes got stuck and cost 100% CPU forever, even though the application was still responsive. Motor 0.2 --------- This version includes API changes that break backward compatibility with applications written for Motor 0.1. For most applications, the migration chores will be minor. In exchange, Motor 0.2 offers a cleaner style, and it wraps the new and improved PyMongo 2.7 instead of 2.5. Changes in Dependencies ~~~~~~~~~~~~~~~~~~~~~~~ Motor now requires PyMongo 2.7.0 exactly and Tornado 3 or later. It drops support for Python 2.5 since Tornado 3 has dropped it. Motor continues to work with Python 2.6 through 3.4. It still requires `Greenlet`_. API Changes ~~~~~~~~~~~ open_sync ''''''''' The ``open_sync`` method has been removed from :class:`MotorClient` and :class:`MotorReplicaSetClient`. Clients now connect to MongoDB automatically on first use. Simply delete the call to ``open_sync`` from your application. If it's important to test that MongoDB is available before continuing your application's startup, use ``IOLoop.run_sync``:: loop = tornado.ioloop.IOLoop.current() client = motor.MotorClient(host, port) try: loop.run_sync(client.open) except pymongo.errors.ConnectionFailure: print "Can't connect" Similarly, calling :meth:`MotorGridOut.open` is now optional. :class:`MotorGridIn` and :class:`MotorGridFS` no longer have an ``open`` method at all. .. _changelog-futures: Futures ''''''' Motor 0.2 takes advantage of Tornado's tidy new coroutine syntax:: # Old style: document = yield motor.Op(collection.find_one, {'_id': my_id}) # New style: document = yield collection.find_one({'_id': my_id}) To make this possible, Motor asynchronous methods (except :meth:`MotorCursor.each`) now return a :class:`~tornado.concurrent.Future`. Using Motor with callbacks is still possible: If a callback is passed, it will be executed with the ``(result, error)`` of the operation, same as in Motor 0.1:: def callback(document, error): if error: logging.error("Oh no!") else: print document collection.find_one({'_id': my_id}, callback=callback) If no callback is passed, a Future is returned that resolves to the method's result or error:: document = yield collection.find_one({'_id': my_id}) ``motor.Op`` works the same as before, but it's deprecated. ``WaitOp`` and ``WaitAllOps`` have been removed. Code that used them can now yield a ``Future`` or a list of them. Consider this function written for Tornado 2 and Motor 0.1:: @gen.engine def get_some_documents(): cursor = collection.find().sort('_id').limit(2) cursor.to_list(callback=(yield gen.Callback('key'))) do_something_while_we_wait() try: documents = yield motor.WaitOp('key') print documents except Exception, e: print e The function now becomes:: @gen.coroutine def f(): cursor = collection.find().sort('_id').limit(2) future = cursor.to_list(2) do_something_while_we_wait() try: documents = yield future print documents except Exception, e: print e Similarly, a function written like so in the old style:: @gen.engine def get_two_documents_in_parallel(collection): collection.find_one( {'_id': 1}, callback=(yield gen.Callback('one'))) collection.find_one( {'_id': 2}, callback=(yield gen.Callback('two'))) try: doc_one, doc_two = yield motor.WaitAllOps(['one', 'two']) print doc_one, doc_two except Exception, e: print e Now becomes:: @gen.coroutine def get_two_documents_in_parallel(collection): future_0 = collection.find_one({'_id': 1}) future_1 = collection.find_one({'_id': 2}) try: doc_one, doc_two = yield [future_0, future_1] print doc_one, doc_two except Exception, e: print e to_list ''''''' Any calls to :meth:`MotorCursor.to_list` that omitted the ``length`` argument must now include it:: result = yield collection.find().to_list(100) ``None`` is acceptable, meaning "unlimited." Use with caution. Connection Pooling '''''''''''''''''' :class:`MotorPool` has been rewritten. It supports the new options introduced in PyMongo 2.6, and drops all Motor-specific options. :class:`MotorClient` and :class:`MotorReplicaSetClient` have an option ``max_pool_size``. It used to mean "minimum idle sockets to keep open", but its meaning has changed to "maximum sockets open per host." Once this limit is reached, operations will pause waiting for a socket to become available. Therefore the default has been raised from 10 to 100. If you pass a value for ``max_pool_size`` make sure it's large enough for the expected load. (Sockets are only opened when needed, so there's no cost to having a ``max_pool_size`` larger than necessary. Err towards a larger value.) If you've been accepting the default, continue to do so. ``max_pool_size`` is now synonymous with Motor's special ``max_concurrent`` option, so ``max_concurrent`` has been removed. ``max_wait_time`` has been renamed ``waitQueueTimeoutMS`` for consistency with PyMongo. If you pass ``max_wait_time``, rename it and multiply by 1000. The :exc:`MotorPoolTimeout` exception is gone; catch PyMongo's :exc:`~pymongo.errors.ConnectionFailure` instead. DNS ''' Motor can take advantage of Tornado 3's `asynchronous resolver interface`_. By default, Motor still uses blocking DNS, but you can enable non-blocking lookup with a threaded resolver:: Resolver.configure('tornado.netutil.ThreadedResolver') Or install `pycares`_ and use the c-ares resolver:: Resolver.configure('tornado.platform.caresresolver.CaresResolver') MotorCursor.tail '''''''''''''''' The ``MotorCursor.tail`` method has been removed. It was complex, diverged from PyMongo's feature set, and encouraged overuse of MongoDB capped collections as message queues when a purpose-built message queue is more appropriate. An example of tailing a capped collection is provided instead: :doc:`examples/tailable-cursors`. MotorClient.is_locked ''''''''''''''''''''' ``is_locked`` has been removed since calling it from Motor would be bizarre. If you called ``MotorClient.is_locked`` like:: locked = yield motor.Op(client.is_locked) you should now do:: result = yield client.admin.current_op() locked = bool(result.get('fsyncLock', None)) The result is ``True`` only if an administrator has called `fsyncLock`_ on the mongod. It is unlikely that you have any use for this. GridFSHandler ''''''''''''' :meth:`~web.GridFSHandler.get_gridfs_file` now returns a Future instead of accepting a callback. .. _Greenlet: http://pypi.python.org/pypi/greenlet/ .. _asynchronous resolver interface: http://www.tornadoweb.org/en/stable/netutil.html#tornado.netutil.Resolver .. _pycares: https://pypi.python.org/pypi/pycares .. _fsyncLock: http://docs.mongodb.org/manual/reference/method/db.fsyncLock/ New Features ~~~~~~~~~~~~ The introduction of a :ref:`Futures-based API ` is the most pervasive new feature. In addition Motor 0.2 includes new features from PyMongo 2.6 and 2.7: * :meth:`MotorCollection.aggregate` can return a cursor. * Support for all current MongoDB authentication mechanisms (see PyMongo's `authentication examples`_). * A new :meth:`MotorCollection.parallel_scan` method. * An :doc:`API for bulk writes `. * Support for wire protocol changes in MongoDB 2.6. * The ability to specify a server-side timeout for operations with :meth:`~MotorCursor.max_time_ms`. * A new :meth:`MotorGridFS.find` method for querying GridFS. .. _authentication examples: http://api.mongodb.org/python/current/examples/authentication.html Bugfixes ~~~~~~~~ ``MotorReplicaSetClient.open`` threw an error if called without a callback. ``MotorCursor.to_list`` `ignored SON manipulators `_. (Thanks to Eren Güven for the report and the fix.) `The full list is in Jira `_. Motor 0.1.2 ----------- Fixes innocuous unittest failures when running against Tornado 3.1.1. Motor 0.1.1 ----------- Fixes issue `MOTOR-12`_ by pinning its PyMongo dependency to PyMongo version 2.5.0 exactly. Motor relies on some of PyMongo's internal details, so changes to PyMongo can break Motor, and a change in PyMongo 2.5.1 did. Eventually PyMongo will expose stable hooks for Motor to use, but for now I changed Motor's dependency from ``PyMongo>=2.4.2`` to ``PyMongo==2.5.0``. .. _MOTOR-12: https://jira.mongodb.org/browse/MOTOR-12 motor-2.3.0/doc/conf.py000066400000000000000000000152121373322416100147230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Motor documentation build configuration file # # This file is execfile()d with the current directory set to its containing dir. import sys, os sys.path[0:0] = [os.path.abspath('..')] 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'] primary_domain = 'py' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. 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 # built documents. # # The short X.Y version. version = motor.version # The full version, including alpha/beta/rc tags. release = motor.version # List of documents that shouldn't be included in the build. unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #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' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for extensions ---------------------------------------------------- autoclass_content = 'init' doctest_path = [os.path.abspath('..')] # Don't test examples pulled from PyMongo's docstrings just because they start # with '>>>' doctest_test_doctest_blocks = '' doctest_global_setup = """ import pprint import sys from datetime import timedelta from tornado import gen from tornado.ioloop import IOLoop import pymongo from pymongo.mongo_client import MongoClient sync_client = MongoClient() ismaster = sync_client.admin.command('isMaster') server_info = sync_client.server_info() if 'setName' in ismaster: raise Exception( "Run doctests with standalone MongoDB 4.0 server, not a replica set") if ismaster.get('msg') == 'isdbgrid': raise Exception( "Run doctests with standalone MongoDB 4.0 server, not mongos") if server_info['versionArray'][:2] != [4, 0]: raise Exception( "Run doctests with standalone MongoDB 4.0 server, not %s" % ( server_info['version'], )) sync_client.drop_database("doctest_test") db = sync_client.doctest_test import motor from motor import MotorClient """ # -- Options for HTML output --------------------------------------------------- html_copy_source = False # Theme gratefully vendored from CPython source. html_theme = "pydoctheme" html_theme_path = ["."] html_theme_options = {'collapsiblesidebar': True} html_static_path = ['static'] html_sidebars = { 'index': ['globaltoc.html', 'searchbox.html'], } # These paths are either relative to html_static_path # or fully qualified paths (eg. https://...) # Note: html_js_files was added in Sphinx 1.8. html_js_files = [ 'delighted.js', ] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #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 # 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 # 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'] # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Motor' + release.replace('.', '_') # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Motor.tex', u'Motor Documentation', u'A. Jesse Jiryu Davis', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True autodoc_default_flags = ['inherited-members'] autodoc_member_order = 'groupwise' pymongo_inventory = ('https://pymongo.readthedocs.io/en/%s/' % pymongo_version, None) intersphinx_mapping = { 'bson': pymongo_inventory, 'gridfs': pymongo_inventory, 'pymongo': pymongo_inventory, 'aiohttp': ('http://aiohttp.readthedocs.io/en/stable/', None), 'tornado': ('http://www.tornadoweb.org/en/stable/', None), 'python': ('https://docs.python.org/3/', None), } motor-2.3.0/doc/configuration.rst000066400000000000000000000035331373322416100170300ustar00rootroot00000000000000Configuration ============= TLS Protocol Version '''''''''''''''''''' Industry best practices, and some regulations, require the use of TLS 1.1 or newer. Though no application changes are required for Motor to make use of the newest protocols, some operating systems or versions may not provide an OpenSSL version new enough to support them. Users of macOS older than 10.13 (High Sierra) will need to install Python from `python.org`_, `homebrew`_, `macports`_, or another similar source. Users of Linux or other non-macOS Unix can check their OpenSSL version like this:: $ openssl version If the version number is less than 1.0.1 support for TLS 1.1 or newer is not available. Contact your operating system vendor for a solution or upgrade to a newer distribution. You can check your Python interpreter by installing the `requests`_ module and executing the following command:: python -c "import requests; print(requests.get('https://www.howsmyssl.com/a/check', verify=False).json()['tls_version'])" You should see "TLS 1.X" where X is >= 1. You can read more about TLS versions and their security implications here: ``_ .. _python.org: https://www.python.org/downloads/ .. _homebrew: https://brew.sh/ .. _macports: https://www.macports.org/ .. _requests: https://pypi.python.org/pypi/requests Thread Pool Size '''''''''''''''' Motor uses the Python standard library's :class:`~concurrent.futures.ThreadPoolExecutor` to defer network operations to threads. By default, the executor uses at most five threads per CPU core on your system; to override the default set the environment variable ``MOTOR_MAX_WORKERS``. Some additional threads are used for monitoring servers and background tasks, so the total count of threads in your process will be greater. motor-2.3.0/doc/contributors.rst000066400000000000000000000005571373322416100167210ustar00rootroot00000000000000Contributors ============ The following is a list of people who have contributed to **Motor**. If you belong here and are missing please let us know (or send a pull request after adding yourself to the list): - A\. Jesse Jiryu Davis - Eren Güven - Jorge Puente Sarrín - Rémi Jolin - Andrew Svetlov - Nikolay Novik - Prashant Mital - Shane Harvey - Bulat Khasanov motor-2.3.0/doc/coroutine_annotation.py000066400000000000000000000016521373322416100202420ustar00rootroot00000000000000"""Gratefully adapted from aiohttp, provides coroutine support to autodoc.""" from sphinx.domains.python import PyModulelevel, PyClassmember from sphinx import addnodes class PyCoroutineMixin(object): def handle_signature(self, sig, signode): ret = super().handle_signature(sig, signode) signode.insert(0, addnodes.desc_annotation('coroutine ', 'coroutine ')) return ret class PyCoroutineFunction(PyCoroutineMixin, PyModulelevel): def run(self): self.name = 'py:function' return PyModulelevel.run(self) class PyCoroutineMethod(PyCoroutineMixin, PyClassmember): def run(self): self.name = 'py:method' return PyClassmember.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} motor-2.3.0/doc/developer-guide.rst000066400000000000000000000106471373322416100172450ustar00rootroot00000000000000=============== Developer Guide =============== Some explanations for those who would like to contribute to Motor development. Compatibility ------------- Motor supports the asyncio module in the standard library of Python 3.5.3 and later. Motor also works with Tornado 5.0 and later along with all the Python versions it supports. Each new Motor feature release depends on the latest PyMongo minor version release or newer, up to the next PyMongo major version release. For example, if 3.10 is the latest available PyMongo version when Motor 2.1 is being released, Motor 2.1 will require 3.10<=PyMongo<4. Frameworks ---------- Motor abstracts the differences between Tornado and asyncio by wrapping each in a "framework" interface. A Motor framework is a module implementing these properties and functions: - ``CLASS_PREFIX`` - ``add_future`` - ``call_soon`` - ``chain_future`` - ``chain_return_value`` - ``check_event_loop`` - ``coroutine`` (**DEPRECATED**) - ``get_event_loop`` - ``get_future`` - ``is_event_loop`` - ``is_future`` - ``platform_info`` - ``pymongo_class_wrapper`` - ``run_on_executor`` - ``yieldable`` (**DEPRECATED**) See the ``frameworks/tornado`` and ``frameworks/asyncio`` modules. .. note:: Starting in Motor 2.2, the functions marked **DEPRECATED** in the list above are not used internally in Motor. Instead of being removed from the codebase, they have been left in a deprecated state to avoid breaking any libraries built on top of Motor. These deprecated functions will be removed in Motor 3.0. A framework-specific class, like ``MotorClient`` for Tornado or ``AsyncIOMotorClient`` for asyncio, is created by the ``create_class_with_framework`` function, which combines a framework with a framework-agnostic class, in this case ``AgnosticClient``. Wrapping PyMongo ---------------- For each PyMongo class, Motor declares an equivalent framework-agnostic class. For example, the ``AgnosticClient`` class is a framework-agnostic equivalent to PyMongo's ``MongoClient``. This agnostic class declares each method and property of the PyMongo class that it intends to wrap. These methods and properties begin life as type ``MotorAttributeFactory``. When ``create_class_with_framework`` creates a framework-specific class from an agnostic class, it creates methods and properties for that class which wrap the equivalent PyMongo methods and properties. For example, the ``AgnosticClient`` class declares that ``drop_database`` is an ``AsyncCommand``, which is a subclass of ``MotorAttributeFactory``. At import time, ``create_class_with_framework`` calls the ``create_attribute`` method of each ``MotorAttributeFactory`` on the ``AgnosticClient``, which results in framework-specific implementations of each method and property. So at import time, ``create_class_with_framework`` generates framework-specific wrappers of ``drop_database`` for ``MotorClient`` and ``AsyncIOMotorClient``. These wrappers use framework-specific features to run the ``drop_database`` method asynchronously. Asynchronization ---------------- This is the heart of Motor's implementation. The ``create_attribute`` method for asynchronous methods like ``drop_database`` wraps the equivalent PyMongo method in a Motor method. This wrapper method uses either the Tornado or asyncio framework to: - get a reference to the framework's event loop - start the PyMongo method on a thread in the global ``ThreadPoolExecutor`` - create a ``Future`` that will be resolved by the event loop when the thread finishes - returns the ``Future`` to the caller This is what allows Tornado or asyncio awaitables to call Motor methods with ``await`` to await I/O without blocking the event loop. Synchro ------- A common kind of bug in Motor arises when PyMongo adds a feature, like a new method or new optional behavior, which we forget to wrap with Motor. Since PyMongo adds a test to its suite for each new feature, we could catch these omissions by applying PyMongo's latest tests to Motor. Then a missing method or feature would cause an obvious test failure. But PyMongo is synchronous and Motor is async; how can Motor pass PyMongo's tests? Synchro is a hacky little module that re-synchronizes all Motor methods using the Tornado IOLoop's ``run_sync`` method. ``synchrotest.py`` overrides the Python interpreter's import machinery to allow Synchro to masquerade as PyMongo, and runs PyMongo's test suite against it. Use ``tox -e synchro37`` to check out PyMongo's test suite and run it with Synchro. motor-2.3.0/doc/differences.rst000066400000000000000000000105301373322416100164310ustar00rootroot00000000000000.. currentmodule:: motor.motor_tornado ===================================== Differences between Motor and PyMongo ===================================== .. important:: This page describes using Motor with Tornado. Beginning in version 0.5 Motor can also integrate with asyncio instead of Tornado. Major differences ================= Connecting to MongoDB --------------------- Motor provides a single client class, :class:`MotorClient`. Unlike PyMongo's :class:`~pymongo.mongo_client.MongoClient`, Motor's client class does not begin connecting in the background when it is instantiated. Instead it connects on demand, when you first attempt an operation. Coroutines ---------- Motor supports nearly every method PyMongo does, but Motor methods that do network I/O are *coroutines*. See :doc:`tutorial-tornado`. Threading and forking --------------------- Multithreading and forking are not supported; Motor is intended to be used in a single-threaded Tornado application. See Tornado's documentation on `running Tornado in production`_ to take advantage of multiple cores. .. _`running Tornado in production`: http://www.tornadoweb.org/en/stable/guide/running.html Minor differences ================= .. _gridfs-differences: GridFS ------ - File-like 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 file API and aren't suitable in the same circumstances as files. - Setting properties In PyMongo, you can set arbitrary attributes 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) grid_in, file_id = fs.open_upload_stream('test_file') grid_in.close() grid_in.my_field = 'my_value' # Sends update to server. Updating metadata on a :class:`MotorGridIn` is asynchronous, so the API is different:: async def f(): fs = motor.motor_tornado.MotorGridFSBucket(db) grid_in, file_id = fs.open_upload_stream('test_file') await grid_in.close() # Sends update to server. await grid_in.set('my_field', 'my_value') .. seealso:: :doc:`../api-tornado/gridfs`. is_locked --------- In PyMongo ``is_locked`` is a property of :class:`~pymongo.mongo_client.MongoClient`. Since determining whether the server has been fsyncLocked requires I/O, Motor has no such convenience method. The equivalent in Motor is:: result = await client.admin.current_op() locked = bool(result.get('fsyncLock', None)) system_js --------- PyMongo supports Javascript procedures stored in MongoDB with syntax like: .. code-block:: python >>> db.system_js.my_func = 'function(x) { return x * x; }' >>> db.system_js.my_func(2) 4.0 Motor does not. Cursor slicing -------------- In PyMongo, the following raises an ``IndexError`` if the collection has fewer than 101 documents: .. code-block:: python # Can raise IndexError. doc = db.collection.find()[100] In Motor, however, no exception is raised. The query simply has no results: .. code-block:: python async def f(): cursor = db.collection.find()[100] # Iterates zero or one times. async for doc in cursor: print(doc) The difference arises because the PyMongo :class:`~pymongo.cursor.Cursor`'s slicing operator blocks until it has queried the MongoDB server, and determines if a document exists at the desired offset; Motor simply returns a new :class:`MotorCursor` with a skip and limit applied. Creating a collection --------------------- There are two ways to create a capped collection using PyMongo: .. code-block:: python # Typical: db.create_collection( 'collection1', capped=True, size=1000) # Unusual: collection = Collection( db, 'collection2', capped=True, size=1000) Motor can't do I/O in a constructor, so the unusual style is prohibited and only the typical style is allowed: .. code-block:: python async def f(): await db.create_collection( 'collection1', capped=True, size=1000) motor-2.3.0/doc/docs-requirements.txt000066400000000000000000000000201373322416100176250ustar00rootroot00000000000000tornado aiohttp motor-2.3.0/doc/examples/000077500000000000000000000000001373322416100152415ustar00rootroot00000000000000motor-2.3.0/doc/examples/aiohttp_example.py000066400000000000000000000026241373322416100210020ustar00rootroot00000000000000# These comments let tutorial-asyncio.rst include this code in sections. # -- setup-start -- import asyncio from aiohttp import web from motor.motor_asyncio import AsyncIOMotorClient async def setup_db(): db = AsyncIOMotorClient().test await db.pages.drop() 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.')}) 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') # Retrieve the long-lived database handle. 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='No page named {!r}'.format(page_name)) return web.Response(body=document['body'].encode(), content_type='text/html') # -- handler-end -- # -- main-start -- loop = asyncio.get_event_loop() db = loop.run_until_complete(setup_db()) app = web.Application() app['db'] = db # Route requests to the page_handler() coroutine. app.router.add_get('/pages/{page_name}', page_handler) web.run_app(app) # -- main-end -- motor-2.3.0/doc/examples/aiohttp_gridfs_example.py000066400000000000000000000032511373322416100223350ustar00rootroot00000000000000"""Serve pre-compressed static content from GridFS with aiohttp. Requires Python 3.5 or later and aiohttp 3.0 or later. Start a MongoDB server on its default port, run this script, and visit: http://localhost:8080/fs/my_file """ # -- include-start -- import asyncio import gzip import tempfile import aiohttp.web from motor.aiohttp import AIOHTTPGridFS from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorGridFSBucket 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: for _ in range(10): 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}) asyncio.get_event_loop().run_until_complete(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' 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) aiohttp.web.run_app(app) motor-2.3.0/doc/examples/aiohttp_gridfs_example.rst000066400000000000000000000010501373322416100225100ustar00rootroot00000000000000AIOHTTPGridFS Example ===================== Serve pre-compressed static content from GridFS over HTTP. Uses the `aiohttp`_ web framework and :class:`~motor.aiohttp.AIOHTTPGridFS`. .. _aiohttp: https://aiohttp.readthedocs.io/ Instructions ------------ Start a MongoDB server on its default port and run this script. Then visit: http://localhost:8080/fs/my_file Serve compressed static content from GridFS ------------------------------------------- .. literalinclude:: aiohttp_gridfs_example.py :language: python3 :start-after: include-start motor-2.3.0/doc/examples/authentication.rst000066400000000000000000000016011373322416100210100ustar00rootroot00000000000000.. currentmodule:: motor.motor_tornado Authentication With Motor ========================= This page describes using Motor with Tornado. Beginning in version 0.5 Motor can also integrate with asyncio instead of Tornado. To use authentication, you must start ``mongod`` with ``--auth`` or, for replica sets or sharded clusters, ``--keyFile``. Create an admin user and optionally normal users or read-only users. .. seealso:: `MongoDB Authentication `_ To create an authenticated connection use a `MongoDB connection URI`_:: uri = "mongodb://user:pass@localhost:27017/database_name" client = motor.motor_tornado.MotorClient(uri) Motor logs in to the server on demand, when you first attempt an operation. .. _MongoDB connection URI: http://docs.mongodb.org/manual/reference/connection-string/ motor-2.3.0/doc/examples/bulk.rst000066400000000000000000000140431373322416100167320ustar00rootroot00000000000000.. currentmodule:: motor.motor_tornado .. _bulk-write-tutorial: Bulk Write Operations ===================== .. testsetup:: client = MotorClient() db = client.test_database IOLoop.current().run_sync(db.test.drop) This tutorial explains how to take advantage of Motor's bulk write operation features. Executing write operations in batches reduces the number of network round trips, increasing write throughput. This example describes using Motor with Tornado. Beginning in version 0.5 Motor can also integrate with asyncio instead of Tornado. Bulk Insert ----------- A batch of documents can be inserted by passing a list or generator to the :meth:`~MotorCollection.insert_many` method. Motor will automatically split the batch into smaller sub-batches based on the maximum message size accepted by MongoDB, supporting very large bulk insert operations. .. doctest:: >>> async def f(): ... await db.test.insert_many(({'i': i} for i in range(10000))) ... count = await db.test.count_documents({}) ... print("Final count: %d" % count) >>> >>> IOLoop.current().run_sync(f) Final count: 10000 Mixed Bulk Write Operations --------------------------- Motor also supports executing mixed bulk write operations. A batch of insert, update, and remove operations can be executed together using the bulk write operations API. .. _ordered_bulk: Ordered Bulk Write Operations ............................. Ordered bulk write operations are batched and sent to the server in the order provided for serial execution. The return value is an instance of :class:`~pymongo.results.BulkWriteResult` describing the type and count of operations performed. .. doctest:: :options: +NORMALIZE_WHITESPACE >>> 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})]) ... pprint(result.bulk_api_result) ... >>> IOLoop.current().run_sync(f) {'nInserted': 3, 'nMatched': 2, 'nModified': 2, 'nRemoved': 10000, 'nUpserted': 1, 'upserted': [{'_id': 4, 'index': 5}], 'writeConcernErrors': [], 'writeErrors': []} The first write failure that occurs (e.g. duplicate key error) aborts the remaining operations, and Motor raises :class:`~pymongo.errors.BulkWriteError`. The :attr:`details` attribute of the exception instance provides the execution results up until the failure occurred and details about the failure - including the operation that caused the failure. .. doctest:: :options: +NORMALIZE_WHITESPACE >>> from pymongo import InsertOne, DeleteOne, ReplaceOne >>> from pymongo.errors import BulkWriteError >>> async def f(): ... requests = [ ... 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: ... pprint(bwe.details) ... >>> IOLoop.current().run_sync(f) {'nInserted': 0, 'nMatched': 1, 'nModified': 1, 'nRemoved': 0, 'nUpserted': 0, 'upserted': [], 'writeConcernErrors': [], 'writeErrors': [{'code': 11000, 'errmsg': '... duplicate key error ...', 'index': 1, 'op': {'_id': 4}}]} .. _unordered_bulk: Unordered Bulk Write Operations ............................... Unordered bulk write operations are batched and sent to the server in **arbitrary order** where they may be executed in parallel. Any errors that occur are reported after all operations are attempted. In the next example the first and third operations fail due to the unique constraint on _id. Since we are doing unordered execution the second and fourth operations succeed. .. doctest:: :options: +NORMALIZE_WHITESPACE >>> async def f(): ... requests = [ ... 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: ... pprint(bwe.details) ... >>> IOLoop.current().run_sync(f) {'nInserted': 0, 'nMatched': 1, 'nModified': 1, 'nRemoved': 1, 'nUpserted': 0, 'upserted': [], 'writeConcernErrors': [], 'writeErrors': [{'code': 11000, 'errmsg': '... duplicate key error ...', 'index': 0, 'op': {'_id': 1}}, {'code': 11000, 'errmsg': '... duplicate key error ...', 'index': 2, 'op': {'_id': 3}}]} Write Concern ............. Bulk operations are executed with the :attr:`~pymongo.collection.Collection.write_concern` of the collection they are executed against. Write concern errors (e.g. wtimeout) will be reported after all operations are attempted, regardless of execution order. .. doctest:: :options: +SKIP .. Standalone MongoDB raises "can't use w>1" with this example, so skip it. >>> from pymongo import WriteConcern >>> async def f(): ... 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)]) ... except BulkWriteError as bwe: ... pprint(bwe.details) ... >>> IOLoop.current().run_sync(f) {'nInserted': 4, 'nMatched': 0, 'nModified': 0, 'nRemoved': 0, 'nUpserted': 0, 'upserted': [], 'writeConcernErrors': [{'code': 64, 'errInfo': {'wtimeout': True}, 'errmsg': 'waiting for replication timed out'}], 'writeErrors': []} motor-2.3.0/doc/examples/index.rst000066400000000000000000000003771373322416100171110ustar00rootroot00000000000000Motor Examples ============== .. seealso:: :doc:`../tutorial-tornado` .. toctree:: bulk monitoring tailable-cursors tornado_change_stream_example authentication aiohttp_gridfs_example See also :ref:`example-web-application-aiohttp`. motor-2.3.0/doc/examples/monitoring.rst000066400000000000000000000110741373322416100201630ustar00rootroot00000000000000.. currentmodule:: motor.motor_tornado Application Performance Monitoring (APM) ======================================== 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. Motor wraps PyMongo, and it shares PyMongo's API for monitoring. To receive notifications about events, you subclass one of PyMongo's four listener classes, :class:`~pymongo.monitoring.CommandListener`, :class:`~pymongo.monitoring.ServerListener`, :class:`~pymongo.monitoring.TopologyListener`, or :class:`~pymongo.monitoring.ServerHeartbeatListener`. Command Monitoring ------------------ Subclass :class:`~pymongo.monitoring.CommandListener` to be notified whenever a command starts, succeeds, or fails. .. literalinclude:: monitoring_example.py :language: py3 :start-after: command logger start :end-before: command logger end Register an instance of ``MyCommandLogger``: .. literalinclude:: monitoring_example.py :language: py3 :start-after: command logger register start :end-before: command logger register end You can register any number of listeners, of any of the four listener types. Although you use only APIs from PyMongo's :mod:`~pymongo.monitoring` module to configure monitoring, if you create a :class:`MotorClient` its commands are monitored, the same as a PyMongo :class:`~pymongo.mongo_client.MongoClient`. .. literalinclude:: monitoring_example.py :language: py3 :start-after: motorclient start :end-before: motorclient end This logs something like: .. code-block:: text Command insert with request id 50073 started on server ('localhost', 27017) Command insert with request id 50073 on server ('localhost', 27017) succeeded in 362 microseconds See PyMongo's :mod:`~pymongo.monitoring` module for details about the event data your callbacks receive. Server and Topology Monitoring ------------------------------ Subclass :class:`~pymongo.monitoring.ServerListener` to be notified whenever Motor detects a change in the state of a MongoDB server it is connected to. .. literalinclude:: monitoring_example.py :language: py3 :start-after: server logger start :end-before: server logger end Subclass :class:`~pymongo.monitoring.TopologyListener` to be notified whenever Motor detects a change in the state of your server topology. Examples of such topology changes are a replica set failover, or if you are connected to several mongos servers and one becomes unavailable. .. literalinclude:: monitoring_example.py :language: py3 :start-after: topology logger start :end-before: topology logger end Motor monitors MongoDB servers with periodic checks called "heartbeats". Subclass :class:`~pymongo.monitoring.ServerHeartbeatListener` to be notified whenever Motor begins a server check, and whenever a check succeeds or fails. .. literalinclude:: monitoring_example.py :language: py3 :start-after: heartbeat logger start :end-before: heartbeat logger end Thread Safety ------------- Watch out: Your listeners' callbacks are executed on various background threads, *not* the main thread. To interact with Tornado or Motor from a listener callback, you must defer to the main thread using :meth:`IOLoop.add_callback `, which is the only thread-safe :class:`~tornado.ioloop.IOLoop` method. Similarly, if you use asyncio instead of Tornado, defer your action to the main thread with :meth:`~asyncio.AbstractEventLoop.call_soon_threadsafe`. There is probably no need to be concerned about this detail, however: logging is the only reasonable thing to do from a listener, and `the Python logging module is thread-safe `_. Further Information ------------------- See also: * PyMongo's :mod:`~pymongo.monitoring` module * `The Command Monitoring Spec`_ * `The Topology Monitoring Spec`_ * The `monitoring.py`_ example file in the Motor repository .. _The Command Monitoring Spec: .. _Command Monitoring: https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst .. _The Topology Monitoring Spec: .. _Topology Monitoring: https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring-monitoring.rst .. _monitoring.py: https://github.com/mongodb/motor/blob/master/doc/examples/monitoring.py motor-2.3.0/doc/examples/monitoring_example.py000066400000000000000000000107361373322416100215220ustar00rootroot00000000000000# Copyright 2016 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. """Application Performance Monitoring (APM) example""" # command logger start import logging import sys from pymongo import monitoring logging.basicConfig(stream=sys.stdout, level=logging.INFO) class CommandLogger(monitoring.CommandListener): def started(self, event): logging.info("Command {0.command_name} with request id " "{0.request_id} started on server " "{0.connection_id}".format(event)) def succeeded(self, event): logging.info("Command {0.command_name} with request id " "{0.request_id} on server {0.connection_id} " "succeeded in {0.duration_micros} " "microseconds".format(event)) def failed(self, event): logging.info("Command {0.command_name} with request id " "{0.request_id} on server {0.connection_id} " "failed in {0.duration_micros} " "microseconds".format(event)) # command logger end # command logger register start monitoring.register(CommandLogger()) # command logger register end # 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!'}) # For this example, wait 10 seconds for more monitoring events to fire. await gen.sleep(10) ioloop.IOLoop.current().run_sync(do_insert) # motorclient end # server logger start class ServerLogger(monitoring.ServerListener): def opened(self, event): logging.info("Server {0.server_address} added to topology " "{0.topology_id}".format(event)) def description_changed(self, event): previous_server_type = event.previous_description.server_type new_server_type = event.new_description.server_type if new_server_type != previous_server_type: logging.info( "Server {0.server_address} changed type from " "{0.previous_description.server_type_name} to " "{0.new_description.server_type_name}".format(event)) def closed(self, event): 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("Topology with id {0.topology_id} " "opened".format(event)) def description_changed(self, event): 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( "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("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("Heartbeat sent to server " "{0.connection_id}".format(event)) def succeeded(self, event): logging.info("Heartbeat to server {0.connection_id} " "succeeded with reply " "{0.reply.document}".format(event)) def failed(self, event): logging.warning("Heartbeat to server {0.connection_id} " "failed with error {0.reply}".format(event)) monitoring.register(HeartbeatLogger()) # heartbeat logger end motor-2.3.0/doc/examples/tailable-cursors.rst000066400000000000000000000037071373322416100212550ustar00rootroot00000000000000.. currentmodule:: motor.motor_tornado Motor Tailable Cursor Example ============================= By default, MongoDB will automatically close a cursor when the client has exhausted all results in the cursor. However, for capped collections you may use a tailable cursor that remains open after the client exhausts the results in the initial cursor. The following is a basic example of using a tailable cursor to tail the oplog of a replica set member: .. code-block:: python 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() print(first) ts = first['ts'] while True: # For a regular capped collection CursorType.TAILABLE_AWAIT is the # only option required to create a tailable cursor. When querying the # oplog, the oplog_replay option enables an optimization to quickly # find the 'ts' value we're looking for. The oplog_replay option # can only be used when querying the oplog. Starting in MongoDB 4.4 # this option is ignored by the server as queries against the oplog # are optimized automatically by the MongoDB query engine. cursor = oplog.find({'ts': {'$gt': ts}}, cursor_type=CursorType.TAILABLE_AWAIT, oplog_replay=True) while cursor.alive: async for doc in cursor: ts = doc['ts'] print(doc) # We end up here if the find() returned no documents or if the # tailable cursor timed out (no new documents were added to the # collection for more than 1 second). await sleep(1) .. seealso:: `Tailable cursors `_ motor-2.3.0/doc/examples/tornado_change_stream_example.py000066400000000000000000000067011373322416100236600ustar00rootroot00000000000000import logging import os 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 tornado.options import define, options define("port", default=8888, help="run on the given port", type=int) define("debug", default=False, help="reload on source changes") define("mongo", default="mongodb://localhost", help="MongoDB URI") define("ns", default="test.test", help="database and collection name") class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", MainHandler), (r"/socket", ChangesHandler)] templates = os.path.join(os.path.dirname(__file__), "tornado_change_stream_templates") super().__init__(handlers, template_path=templates, template_whitespace="all", debug=options.debug) class MainHandler(tornado.web.RequestHandler): def get(self): self.render("index.html", changes=ChangesHandler.cache) class ChangesHandler(tornado.websocket.WebSocketHandler): waiters = set() cache = [] cache_size = 5 def open(self): ChangesHandler.waiters.add(self) def on_close(self): ChangesHandler.waiters.remove(self) @classmethod def update_cache(cls, change): cls.cache.append(change) if len(cls.cache) > cls.cache_size: cls.cache = cls.cache[-cls.cache_size:] @classmethod def send_change(cls, change): change_json = json_util.dumps(change) for waiter in cls.waiters: try: waiter.write_message(change_json) except Exception: logging.error("Error sending message", exc_info=True) @classmethod def on_change(cls, change): logging.info("got change of type '%s'", change.get('operationType')) # Each change notification has a binary _id. Use it to make an HTML # element id, then remove it. html_id = urlsafe_b64encode(change['_id']['_data']).decode().rstrip('=') change.pop('_id') change['html'] = '
    %s
    ' % ( html_id, tornado.escape.xhtml_escape(pformat(change))) change['html_id'] = html_id ChangesHandler.send_change(change) ChangesHandler.update_cache(change) change_stream = None async def watch(collection): global change_stream async with collection.watch() as change_stream: async for change in change_stream: ChangesHandler.on_change(change) def main(): tornado.options.parse_command_line() if '.' not in options.ns: sys.stderr.write('Invalid ns "%s", must contain a "."' % (options.ns,)) sys.exit(1) db_name, collection_name = options.ns.split('.', 1) client = MotorClient(options.mongo) collection = client[db_name][collection_name] app = Application() app.listen(options.port) loop = tornado.ioloop.IOLoop.current() # Start watching collection for changes. loop.add_callback(watch, collection) try: loop.start() except KeyboardInterrupt: pass finally: if change_stream is not None: change_stream.close() if __name__ == "__main__": main() motor-2.3.0/doc/examples/tornado_change_stream_example.rst000066400000000000000000000027561373322416100240460ustar00rootroot00000000000000.. _tornado_change_stream_example: Tornado Change Stream Example ============================= .. currentmodule:: motor.motor_tornado Watch a collection for changes with :meth:`MotorCollection.watch` and display each change notification on a web page using web sockets. Instructions ------------ Start a MongoDB server on its default port and run this script. Then visit: http://localhost:8888 Open a ``mongo`` shell in the terminal and perform some operations on the "test" collection in the "test" database: .. code-block:: none > use test switched to db test > db.test.insertOne({}) > db.test.updateOne({}, {$set: {x: 1}}) > db.test.deleteOne({}) The application receives each change notification and displays it as JSON on the web page: .. code-block:: none Changes {'documentKey': {'_id': ObjectId('5a2a6967ea2dcf7b1c721cfb')}, 'fullDocument': {'_id': ObjectId('5a2a6967ea2dcf7b1c721cfb')}, 'ns': {'coll': 'test', 'db': 'test'}, 'operationType': 'insert'} {'documentKey': {'_id': ObjectId('5a2a6967ea2dcf7b1c721cfb')}, 'ns': {'coll': 'test', 'db': 'test'}, 'operationType': 'update', 'updateDescription': {'removedFields': [], 'updatedFields': {'x': 1.0}}} {'documentKey': {'_id': ObjectId('5a2a6967ea2dcf7b1c721cfb')}, 'ns': {'coll': 'test', 'db': 'test'}, 'operationType': 'delete'} Display change notifications over a web socket ---------------------------------------------- .. literalinclude:: tornado_change_stream_example.py :language: python3 motor-2.3.0/doc/examples/tornado_change_stream_templates/000077500000000000000000000000001373322416100236455ustar00rootroot00000000000000motor-2.3.0/doc/examples/tornado_change_stream_templates/index.html000066400000000000000000000023241373322416100256430ustar00rootroot00000000000000

    Changes

    {% for change in changes %} {% raw change['html'] %} {% end %}
    motor-2.3.0/doc/features.rst000066400000000000000000000025671373322416100160050ustar00rootroot00000000000000============== Motor Features ============== .. currentmodule:: motor.motor_tornado Non-Blocking ============ Motor is an asynchronous driver for MongoDB. It can be used from Tornado_ or asyncio_ applications. Motor never blocks the event loop while connecting to MongoDB or performing I/O. .. _Tornado: http://tornadoweb.org/ .. _asyncio: https://docs.python.org/3/library/asyncio.html 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` ============================= The :mod:`tornado.gen` module lets you use coroutines to simplify asynchronous code. Motor methods return Futures that are convenient to use with coroutines. Configurable IOLoops ==================== Motor supports Tornado applications with multiple :class:`IOLoops `. Pass the ``io_loop`` argument to :class:`MotorClient` to configure the loop for a client instance. Streams Static Files from GridFS ================================ Motor can stream data from `GridFS `_ to a Tornado :class:`~tornado.web.RequestHandler` using :meth:`~MotorGridOut.stream_to_handler` or the :class:`~motor.web.GridFSHandler` class. It can also serve GridFS data with aiohttp using the :class:`~motor.aiohttp.AIOHTTPGridFS` class. motor-2.3.0/doc/index.rst000066400000000000000000000057231373322416100152730ustar00rootroot00000000000000Motor: Asynchronous Python driver for MongoDB ============================================= .. image:: _static/motor.png :align: center About ----- Motor presents a coroutine-based API for non-blocking access to MongoDB from Tornado_ or asyncio_. The `source is on GitHub `_ 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* Install with:: $ python -m pip install motor .. _Tornado: http://tornadoweb.org/ .. _asyncio: https://docs.python.org/3/library/asyncio.html Getting Help ------------ If you're having trouble or have questions about Motor, ask your question on our `MongoDB Community Forum `_. You may also want to consider a `commercial support subscription `_. Once you get an answer, it'd be great if you could work it back into this documentation and contribute! Issues ------ All issues should be reported (and can be tracked / voted for / commented on) at the main `MongoDB JIRA bug tracker `_, in the "Motor" project. Feature Requests / Feedback --------------------------- Use our `feedback engine `_ to send us feature requests and general feedback about PyMongo. Contributing ------------ **Motor** has a large :doc:`community ` and contributions are always encouraged. Contributions can be as simple as minor tweaks to this documentation. To contribute, fork the project on `GitHub `_ and send a pull request. Changes ------- See the :doc:`changelog` for a full list of changes to Motor. Contents -------- .. toctree:: :maxdepth: 1 differences features installation requirements configuration tutorial-tornado tutorial-asyncio examples/index changelog migrate-to-motor-2 developer-guide contributors Classes ------- .. toctree:: api-tornado/index api-asyncio/index .. getting the caption italicized with a hyperlink in it requires some RST hackage *Logo by* |musho|_ .. _musho: http://whimsyload.com .. |musho| replace:: *Musho Rodney Alan Greenblat* motor-2.3.0/doc/installation.rst000066400000000000000000000005561373322416100166640ustar00rootroot00000000000000Installation ============ Install Motor from PyPI_ with pip_:: $ python3 -m pip install motor Pip automatically installs Motor's prerequisite packages. See :doc:`requirements`. To install Motor from sources, you can clone its git repository and do:: $ python3 -m pip install . .. _PyPI: http://pypi.python.org/pypi/motor .. _pip: http://pip-installer.org motor-2.3.0/doc/make.bat000066400000000000000000000057751373322416100150460ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Motor.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Motor.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end motor-2.3.0/doc/migrate-to-motor-2.rst000066400000000000000000000165241373322416100175320ustar00rootroot00000000000000Motor 2.0 Migration Guide ========================= .. currentmodule:: motor.motor_tornado Motor 2.0 brings a number of changes to Motor 1.0's API. The major version is required in order to update the session API to support multi-document transactions, introduced in MongoDB 4.0; this feature is so valuable that it motivated me to make the breaking change and bump the version number to 2.0. Since this is the first major version number in almost two years, it removes a large number of APIs that have been deprecated in the time since Motor 1.0. Follow this guide to migrate an existing application that had used Motor 1.x. Check compatibility ------------------- Read the :doc:`requirements` page and ensure your MongoDB server and Python interpreter are compatible, and your Tornado version if you are using Tornado. If you use aiohttp, upgrade to at least 3.0. Upgrade to Motor 1.3 -------------------- The first step in migrating to Motor 2.0 is to upgrade to at least Motor 1.3. If your project has a requirements.txt file, add the line:: motor >= 1.3, < 2.0 Enable Deprecation Warnings --------------------------- Starting with Motor 1.3, :exc:`DeprecationWarning` is raised by most methods removed in Motor 2.0. Make sure you enable runtime warnings to see where deprecated functions and methods are being used in your application:: python -Wd Warnings can also be changed to errors:: python -Wd -Werror Migrate from deprecated APIs ---------------------------- The following features are deprecated by PyMongo and scheduled for removal; they are now deleted from Motor: - ``MotorClient.kill_cursors`` and ``close_cursor``. Allow :class:`MotorCursor` to handle its own cleanup. - ``MotorClient.get_default_database``. Call :meth:`MotorClient.get_database` with a database name of ``None`` for the same effect. - ``MotorDatabase.add_son_manipulator``. Transform documents to and from their MongoDB representations in your application code instead. - The server command ``getLastError`` and related commands are deprecated, their helper functions are deleted from Motor: - ``MotorDatabase.last_status`` - ``MotorDatabase.error`` - ``MotorDatabase.previous_error`` - ``MotorDatabase.reset_error_history`` Use acknowledged writes and rely on Motor to raise exceptions. - The server command ``parallelCollectionScan`` is deprecated and ``MotorCollection.parallel_scan`` is removed. Use a regular :meth:`MotorCollection.find` cursor. - ``MotorClient.database_names``. Use :meth:`~MotorClient.list_database_names`. - ``MotorDatabase.eval``. The server command is deprecated but still available with ``MotorDatabase.command("eval", ...)``. - ``MotorDatabase.group``. The server command is deprecated but still available with ``MotorDatabase.command("group", ...)``. - ``MotorDatabase.authenticate`` and ``MotorDatabase.logout``. Add credentials to the URI or ``MotorClient`` options instead of calling ``authenticate``. To authenticate as multiple users on the same database, instead of using ``authenticate`` and ``logout`` use a separate client for each user. - ``MotorCollection.initialize_unordered_bulk_op``, ``initialize_unordered_bulk_op``, and ``MotorBulkOperationBuilder``. Use :meth:`MotorCollection.bulk_write``, see :ref:`Bulk Writes Tutorial `. - ``MotorCollection.count``. Use :meth:`~MotorCollection.count_documents` or :meth:`~MotorCollection.estimated_document_count`. - ``MotorCollection.ensure_index``. Use :meth:`MotorCollection.create_indexes`. - Deprecated write methods have been deleted from :class:`MotorCollection`. - ``save`` and ``insert``: Use :meth:`~MotorCollection.insert_one` or :meth:`~MotorCollection.insert_many`. - ``update``: Use :meth:`~MotorCollection.update_one`, :meth:`~MotorCollection.update_many`, or :meth:`~MotorCollection.replace_one`. - ``remove``: Use :meth:`~MotorCollection.delete_one` or :meth:`~MotorCollection.delete_many`. - ``find_and_modify``: Use :meth:`~MotorCollection.find_one_and_update`, :meth:`~MotorCollection.find_one_and_replace`, or :meth:`~MotorCollection.find_one_and_delete`. - ``MotorCursor.count`` and ``MotorGridOutCursor.count``. Use :meth:`MotorCollection.count_documents` or :meth:`MotorCollection.estimated_document_count`. Migrate from the original callback API -------------------------------------- Motor was first released before Tornado had introduced Futures, generator-based coroutines, and the ``yield`` syntax, and long before the async features developed during Python 3's career. Therefore Motor's original asynchronous API used callbacks: .. code-block:: python3 def callback(result, error): if error: print(error) else: print(result) collection.find_one({}, callback=callback) Callbacks have been largely superseded by a Futures API intended for use with coroutines, see :doc:`tutorial-tornado`. You can still use callbacks with Motor when appropriate but you must add the callback to a Future instead of passing it as a parameter: .. code-block:: python3 def callback(future): try: result = future.result() print(result) except Exception as exc: print(exc) future = collection.find_one({}) future.add_done_callback(callback) The :meth:`~asyncio.Future.add_done_callback` call can be placed on the same line: .. code-block:: python3 collection.find_one({}).add_done_callback(callback) In almost all cases the modern coroutine API is more readable and provides better exception handling: .. code-block:: python3 async def do_find(): try: result = await collection.find_one({}) print(result) except Exception as exc: print(exc) Upgrade to Motor 2.0 -------------------- Once your application runs without deprecation warnings with Motor 1.3, upgrade to Motor 2.0. Update any calls in your code to :meth:`MotorClient.start_session` or :meth:`~pymongo.client_session.ClientSession.end_session` to handle the following change. :meth:`MotorClient.start_session` is a coroutine ------------------------------------------------ In the past, you could use a client session like: .. code-block:: python3 session = client.start_session() doc = await client.db.collection.find_one({}, session=session) session.end_session() Or: .. code-block:: python3 with client.start_session() as session: doc = client.db.collection.find_one({}, session=session) To support multi-document transactions, in Motor 2.0 :meth:`MotorClient.start_session` is a coroutine, not a regular method. It must be used like ``await client.start_session()`` or ``async with await client.start_session()``. The coroutine now returns a new class :class:`~motor.motor_tornado.MotorClientSession`, not PyMongo's :class:`~pymongo.client_session.ClientSession`. The ``end_session`` method on the returned :class:`~motor.motor_tornado.MotorClientSession` is also now a coroutine instead of a regular method. Use it like: .. code-block:: python3 session = await client.start_session() doc = await client.db.collection.find_one({}, session=session) await session.end_session() Or: .. code-block:: python3 async with client.start_session() as session: doc = await client.db.collection.find_one({}, session=session) motor-2.3.0/doc/mongo_extensions.py000066400000000000000000000056661373322416100174100ustar00rootroot00000000000000# Copyright 2009-present MongoDB, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # 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. """MongoDB specific extensions to Sphinx.""" from docutils import nodes from docutils.parsers import rst from sphinx import addnodes class mongodoc(nodes.Admonition, nodes.Element): pass class mongoref(nodes.reference): pass def visit_mongodoc_node(self, node): self.visit_admonition(node, "seealso") def depart_mongodoc_node(self, node): self.depart_admonition(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)) def depart_mongoref_node(self, node): self.body.append('') if not isinstance(node.parent, nodes.TextElement): self.body.append('\n') class MongodocDirective(rst.Directive): has_content = True required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False option_spec = {} def run(self): node = mongodoc() title = 'The MongoDB documentation on' node += nodes.title(title, title) self.state.nested_parse(self.content, self.content_offset, node) return [node] def process_mongodoc_nodes(app, doctree, fromdocname): for node in doctree.traverse(mongodoc): anchor = None for name in node.parent.parent.traverse(addnodes.desc_signature): anchor = name["ids"][0] break if not anchor: for name in node.parent.traverse(nodes.section): anchor = name["ids"][0] break for para in node.traverse(nodes.paragraph): tag = str(para.traverse()[1]) link = mongoref("", "") link["refuri"] = "http://dochub.mongodb.org/core/%s" % tag link["name"] = anchor link.append(nodes.emphasis(tag, tag)) new_para = nodes.paragraph() new_para += link node.replace(para, new_para) 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_directive("mongodoc", MongodocDirective) app.connect("doctree-resolved", process_mongodoc_nodes) motor-2.3.0/doc/motor_extensions.py000066400000000000000000000155221373322416100174210ustar00rootroot00000000000000# Copyright 2012-present MongoDB, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # 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. """Motor specific extensions to Sphinx.""" 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.util.inspect import safe_getattr import motor import motor.core # This is a place to store info while parsing, to be used before generating. motor_info = {} def has_node_of_type(root, klass): if isinstance(root, klass): return True for child in root.children: if has_node_of_type(child, klass): return True return False def find_by_path(root, classes): if not classes: return [root] _class = classes[0] rv = [] for child in root.children: if isinstance(child, _class): rv.extend(find_by_path(child, classes[1:])) return rv docstring_warnings = [] def maybe_warn_about_code_block(name, content_node): if has_node_of_type(content_node, (literal_block, doctest_block)): docstring_warnings.append(name) def has_coro_annotation(signature_node): try: return 'coroutine' in signature_node[0][0] except IndexError: return False def process_motor_nodes(app, doctree): # Search doctree for Motor's methods and attributes whose docstrings were # copied from PyMongo, and fix them up for Motor: # 1. Add a 'coroutine' annotation to the beginning of the declaration. # 2. Remove all version annotations like "New in version 2.0" since # PyMongo's version numbers are meaningless in Motor's docs. # 3. Remove "seealso" directives that reference PyMongo's docs. # # We do this here, rather than by registering a callback to Sphinx's # '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'): signature_node = find_by_path(objnode, [desc_signature])[0] name = '.'.join([ signature_node['module'], signature_node['fullname']]) 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']): maybe_warn_about_code_block(name, desc_content_node) 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']) signature_node.insert(0, coro_annotation) 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]) for version_node in version_nodes: version_node.parent.remove(version_node) # Remove all "seealso" directives that contain :doc: # references from PyMongo's docs seealso_nodes = find_by_path(desc_content_node, [seealso]) for seealso_node in seealso_nodes: if 'reftype="doc"' in str(seealso_node): seealso_node.parent.remove(seealso_node) def get_motor_attr(motor_class, name, *defargs): """If any Motor attributes can't be accessed, grab the equivalent PyMongo attribute. While we're at it, store some info about each attribute in the global motor_info dict. """ attr = safe_getattr(motor_class, name, *defargs) # Store some info for process_motor_nodes() full_name = '%s.%s.%s' % ( motor_class.__module__, motor_class.__name__, name) full_name_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) if is_async_method or is_cursor_method: pymongo_method = getattr( motor_class.__delegate_class__, attr.pymongo_method_name) else: pymongo_method = None # attr.doc is set by statement like 'error = AsyncRead(doc="OBSOLETE")'. 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, } return attr pymongo_ref_pat = re.compile(r':doc:`(.*?)`', re.MULTILINE) def _sub_pymongo_ref(match): ref = match.group(1) return ':doc:`%s`' % ref.lstrip('/') def process_motor_docstring(app, what, name, obj, options, lines): if name in motor_info and motor_info[name].get('is_pymongo_docstring'): joined = '\n'.join(lines) subbed = pymongo_ref_pat.sub(_sub_pymongo_ref, joined) lines[:] = subbed.split('\n') def build_finished(app, exception): if not exception and docstring_warnings: print("PyMongo docstrings with code blocks that need update:") for name in sorted(docstring_warnings): print(name) 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} motor-2.3.0/doc/pydoctheme/000077500000000000000000000000001373322416100155645ustar00rootroot00000000000000motor-2.3.0/doc/pydoctheme/static/000077500000000000000000000000001373322416100170535ustar00rootroot00000000000000motor-2.3.0/doc/pydoctheme/static/pydoctheme.css000066400000000000000000000056441373322416100217370ustar00rootroot00000000000000@import url("classic.css"); body { background-color: white; margin-left: 1em; margin-right: 1em; } div.related { margin-bottom: 1.2em; padding: 0.5em 0; border-top: 1px solid #ccc; margin-top: 0.5em; } div.related a:hover { color: #0095C4; } div.related:first-child { border-top: 0; border-bottom: 1px solid #ccc; } div.sphinxsidebar { background-color: #eeeeee; border-radius: 5px; line-height: 130%; font-size: smaller; } div.sphinxsidebar h3, div.sphinxsidebar h4 { margin-top: 1.5em; } div.sphinxsidebarwrapper > h3:first-child { margin-top: 0.2em; } div.sphinxsidebarwrapper > ul > li > ul > li { margin-bottom: 0.4em; } div.sphinxsidebar a:hover { color: #0095C4; } div.sphinxsidebar input { font-family: 'Lucida Grande',Arial,sans-serif; border: 1px solid #999999; font-size: smaller; border-radius: 3px; } div.sphinxsidebar input[type=text] { max-width: 150px; } /* override an apparently misguided "hyphens: auto" style in Sphinx basic.css */ div.body p, div.body dd, div.body li, div.body blockquote { -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; } div.body { padding: 0 0 0 1.2em; } div.body p { line-height: 140%; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { margin: 0; border: 0; padding: 0.3em 0; } div.body hr { border: 0; background-color: #ccc; height: 1px; } div.body pre { border-radius: 3px; border: 1px solid #ac9; } div.body div.admonition, div.body div.impl-detail { border-radius: 3px; } div.body div.impl-detail > p { margin: 0; } div.body div.seealso { border: 1px solid #dddd66; } div.body a { color: #0072aa; } div.body a:visited { color: #6363bb; } div.body a:hover { color: #00B0E4; } tt, code, pre { font-family: monospace, sans-serif; font-size: 96.5%; } div.body tt, div.body code { border-radius: 3px; } div.body tt.descname, div.body code.descname { font-size: 120%; } div.body tt.xref, div.body a tt, div.body code.xref, div.body a code { font-weight: normal; } .deprecated { border-radius: 3px; } table.docutils { border: 1px solid #ddd; min-width: 20%; border-radius: 3px; margin-top: 10px; margin-bottom: 10px; } table.docutils td, table.docutils th { border: 1px solid #ddd !important; border-radius: 3px; } table p, table li { text-align: left !important; } table.docutils th { background-color: #eee; padding: 0.3em 0.5em; } table.docutils td { background-color: white; padding: 0.3em 0.5em; } table.footnote, table.footnote td { border: 0 !important; } div.footer { line-height: 150%; margin-top: -2em; text-align: right; width: auto; margin-right: 10px; } div.footer a:hover { color: #0095C4; } .refcount { color: #060; } .stableabi { color: #229; } motor-2.3.0/doc/pydoctheme/theme.conf000066400000000000000000000010231373322416100175310ustar00rootroot00000000000000[theme] inherit = classic stylesheet = pydoctheme.css pygments_style = sphinx [options] bodyfont = 'Lucida Grande', Arial, sans-serif headfont = 'Lucida Grande', Arial, sans-serif footerbgcolor = white footertextcolor = #555555 relbarbgcolor = white relbartextcolor = #666666 relbarlinkcolor = #444444 sidebarbgcolor = white sidebartextcolor = #444444 sidebarlinkcolor = #444444 bgcolor = white textcolor = #222222 linkcolor = #0090c0 visitedlinkcolor = #00608f headtextcolor = #1a1a1a headbgcolor = white headlinkcolor = #aaaaaa motor-2.3.0/doc/requirements.rst000066400000000000000000000155171373322416100167110ustar00rootroot00000000000000Requirements ============ The current version of Motor requires: * CPython 3.5 and later. * PyMongo_ 3.11 and later. Motor can integrate with either Tornado or asyncio. 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: http://sphinx.pocoo.org/ .. _compatibility-matrix: Compatibility Matrix -------------------- 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+ | +-------------------+-----------------+ Motor and MongoDB ````````````````` +---------------------------------------------------------------------------+-----+ | MongoDB Version | +=====================+=====+=====+=====+=====+=====+=====+=====+=====+=====+=====+ | | 2.2 | 2.4 | 2.6 | 3.0 | 3.2 | 3.4 | 3.6 | 4.0 | 4.2 | 4.4 | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | Motor Version | 1.0 | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**|**N**| +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | | 1.1 | Y | Y | Y | Y | Y | Y |**N**|**N**|**N**|**N**| +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | | 1.2 |**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**| +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | | 1.3 |**N**|**N**| Y | Y | Y | Y | Y |**N**|**N**|**N**| +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | | 2.0 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y |**N**| +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | | 2.1 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y |**N**| +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | | 2.2 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | | 2.3 |**N**|**N**|**N**| Y | Y | Y | Y | Y | Y | Y | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ There is no relationship between PyMongo and MongoDB version numbers, although the numbers happen to be close or equal in recent releases of PyMongo and MongoDB. Use `the PyMongo compatibility matrix`_ to determine what MongoDB version is supported by PyMongo. Use the compatibility matrix above to determine what MongoDB version Motor supports. .. _the PyMongo compatibility matrix: https://docs.mongodb.com/drivers/pymongo#mongodb-compatibility Motor and Tornado ````````````````` Where "N" appears in this matrix, the versions of Motor and Tornado are known to be incompatible, or have not been tested together. +---------------------------------------------+ | Tornado Version | +=====================+=====+=====+=====+=====+ | | 3.x | 4.x | 5.x | 6.x | +---------------+-----+-----+-----+-----+-----+ | Motor Version | 1.0 | Y | Y |**N**|**N**| +---------------+-----+-----+-----+-----+-----+ | | 1.1 | Y | Y |**N**|**N**| +---------------+-----+-----+-----+-----+-----+ | | 1.2 |**N**| Y |**N**|**N**| +---------------+-----+-----+-----+-----+-----+ | | 1.3 |**N**| Y |**N**|**N**| +---------------+-----+-----+-----+-----+-----+ | | 2.0 |**N**| Y | Y |**N**| +---------------+-----+-----+-----+-----+-----+ | | 2.1 |**N**| Y | Y | Y | +---------------+-----+-----+-----+-----+-----+ | | 2.2 |**N**|**N**| Y | Y | +---------------+-----+-----+-----+-----+-----+ | | 2.3 |**N**|**N**| Y | Y | +---------------+-----+-----+-----+-----+-----+ Motor and Python ```````````````` Motor 1.2 dropped support for the short-lived version of the "async for" protocol implemented in Python 3.5.0 and 3.5.1. Motor continues to work with "async for" loops in Python 3.5.2 and later. Motor 1.2.5 and 1.3.1 add compatibility with Python 3.7, but at the cost of dropping Python 3.4.3 and older. Motor 2.2 dropped support for Pythons older than 3.5.2. +-------------------------------------------------------------------------------------+ | Python Version | +=====================+=====+=====+=====+=====+=====+=======+=======+=====+=====+=====+ | | 2.5 | 2.6 | 2.7 | 3.3 | 3.4 | 3.5.0 | 3.5.2 | 3.6 | 3.7 | 3.8 | +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ | Motor Version | 1.0 |**N**| Y | Y | Y | Y | Y | Y | Y |**N**|**N**| +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ | | 1.1 |**N**| Y | Y | Y | Y | Y | Y | Y |**N**|**N**| +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ | | 1.2 |**N**|**N**| Y |**N**| Y |**N** | Y | Y | Y |**N**| +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ | | 1.3 |**N**|**N**| Y |**N**| Y |**N** | Y | Y | Y |**N**| +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ | | 2.0 |**N**|**N**| Y |**N**| Y |**N** | Y | Y | Y |**N**| +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ | | 2.1 |**N**|**N**| Y |**N**| Y |**N** | Y | Y | Y | Y | +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ | | 2.2 |**N**|**N**|**N**|**N**|**N**|**N** | Y | Y | Y | Y | +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ | | 2.3 |**N**|**N**|**N**|**N**|**N**|**N** | Y | Y | Y | Y | +---------------+-----+-----+-----+-----+-----+-----+-------+-------+-----+-----+-----+ Not Supported ------------- Motor does not support Jython or IronPython. motor-2.3.0/doc/static/000077500000000000000000000000001373322416100147125ustar00rootroot00000000000000motor-2.3.0/doc/static/delighted.js000066400000000000000000000035431373322416100172060ustar00rootroot00000000000000/* eslint-disable */ // Delighted !function(e,t,r,n,a){if(!e[a]){for(var i=e[a]=[],s=0;s