Compare commits

...

7 Commits
master ... v2.5

Author SHA1 Message Date
Steven Silvester
0cbd45f581
MOTOR-902 [v2.5] Since Python 3.10 asyncio.get_event_loop() is deprecated (#159) 2022-04-21 12:50:37 -05:00
Shane Harvey
88119c849c MOTOR-911 Skip version API test for count (#152)
(cherry picked from commit 57dd5773a3)
2022-03-18 15:54:00 -07:00
Steven Silvester
057b1afa06
MOTOR-906 [v2.5] Doc Build and Test are Failing (#151) 2022-03-16 17:05:39 -05:00
Amin Alaee
1f6a3d692c MOTOR-857 Test Python 3.10 support
(cherry picked from commit 66fde7189d)
2022-03-15 13:52:22 -07:00
Shane Harvey
aac3bc527f BUMP 2.5.2.dev0 2022-03-15 13:50:52 -07:00
Shane Harvey
fd4eceddae MOTOR-852 Use https:// instead of unauthenticated git:// for git clone
(cherry picked from commit e4b0591538)
2022-03-15 13:49:51 -07:00
Steven Silvester
5ded4425fb
MOTOR-884 [v2.5] Mirror all PyMongo extras (#149)
Co-authored-by: Tushar Singh <tusharvickey1999@gmail.com>
2022-03-09 16:10:42 -06:00
18 changed files with 214 additions and 56 deletions

View File

@ -94,7 +94,7 @@ functions:
# If this was a patch build, doing a fresh clone would not actually test the patch
cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS
else
git clone git://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS
git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS
fi
echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config
@ -854,6 +854,18 @@ axes:
TOX_ENV: "synchro37"
PYTHON_BINARY: "/opt/python/3.7/bin/python3"
- id: tox-env-ubuntu
display_name: "Tox Env Ubuntu"
values:
- id: "tornado6-py310"
variables:
TOX_ENV: "tornado6-py310"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
- id: "asyncio-py310"
variables:
TOX_ENV: "asyncio-py310"
PYTHON_BINARY: "/opt/python/3.10/bin/python3"
# Test Python 3.6 only on Mac.
- id: tox-env-osx
display_name: "Tox Env OSX"
@ -900,6 +912,11 @@ axes:
run_on: "amazon1-2018-test"
variables:
TOX_BINARY: "/opt/python/3.6/bin/tox"
- id: "ubuntu-20"
display_name: "Ubuntu"
run_on: "ubuntu2004-small"
variables:
TOX_BINARY: "/opt/python/3.6/bin/tox"
- id: "win"
display_name: "Windows"
run_on: "windows-64-vsMulti-small"
@ -948,6 +965,17 @@ buildvariants:
- ".3.4"
- ".3.2"
- matrix_name: "test-ubuntu"
display_name: "${os}-${tox-env-ubuntu}-${ssl}"
matrix_spec:
os: "ubuntu-20"
tox-env-ubuntu: "*"
ssl: "*"
tasks:
- ".latest"
- ".5.0"
- ".4.4"
- matrix_name: "test-win"
display_name: "${os}-${tox-env-win}-${ssl}"
matrix_spec:

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

@ -0,0 +1,5 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: debug-statements

View File

@ -101,6 +101,47 @@ asyncio. It requires:
* PyMongo_ >=3.12,<4
* Python 3.5+
Optional dependencies:
Motor supports same optional dependencies as PyMongo. Required dependencies can be installed
along with Motor.
GSSAPI authentication requires ``gssapi`` extra dependency. The correct
dependency can be installed automatically along with Motor::
$ pip install "motor[gssapi]"
similarly,
MONGODB-AWS authentication requires ``aws`` extra dependency::
$ pip install "motor[aws]"
Support for mongodb+srv:// URIs requires ``srv`` extra dependency::
$ pip install "motor[srv]"
OCSP requires ``ocsp`` extra dependency::
$ pip install "motor[ocsp]"
Wire protocol compression with snappy requires ``snappy`` extra dependency::
$ pip install "motor[snappy]"
Wire protocol compression with zstandard requires ``zstd`` extra dependency::
$ pip install "motor[zstd]"
Client-Side Field Level Encryption requires ``encryption`` extra dependency::
$ pip install "motor[encryption]"
You can install all dependencies automatically with the following
command::
$ pip install "motor[gssapi,aws,ocsp,snappy,srv,zstd,encryption]"
See `requirements <https://motor.readthedocs.io/en/stable/requirements.html>`_
for details about compatibility.

View File

@ -14,3 +14,4 @@ The following is a list of people who have contributed to
- Shane Harvey
- Bulat Khasanov
- William Zhou
- Tushar Singh

View File

@ -39,8 +39,7 @@ async def page_handler(request):
# -- handler-end --
# -- main-start --
loop = asyncio.get_event_loop()
db = loop.run_until_complete(setup_db())
db = asyncio.run(setup_db())
app = web.Application()
app['db'] = db
# Route requests to the page_handler() coroutine.

View File

@ -33,7 +33,7 @@ async def put_gridfile():
metadata={'contentType': 'text',
'compressed': True})
asyncio.get_event_loop().run_until_complete(put_gridfile())
asyncio.run(put_gridfile())
# Add "Content-Encoding: gzip" header for compressed data.

View File

@ -101,5 +101,4 @@ async def main():
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
asyncio.run(main())

View File

@ -70,5 +70,4 @@ async def main():
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
asyncio.run(main())

View File

@ -65,5 +65,4 @@ async def main():
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
asyncio.run(main())

View File

@ -98,5 +98,4 @@ async def main():
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
asyncio.run(main())

View File

@ -12,6 +12,66 @@ To install Motor from sources, you can clone its git repository and do::
$ python3 -m pip install .
Dependencies
------------
Motor works in all the environments officially supported by Tornado or by
asyncio. It requires:
* Unix (including macOS) or Windows.
* PyMongo_ >=3.12,<4
* Python 3.5+
Optional dependencies:
Motor supports same optional dependencies as PyMongo. Required dependencies can be installed
along with Motor.
GSSAPI authentication requires ``gssapi`` extra dependency. The correct
dependency can be installed automatically along with Motor::
$ pip install "motor[gssapi]"
similarly,
`MONGODB-AWS <https://pymongo.readthedocs.io/en/stable/examples/authentication.html#mongodb-aws>`_
authentication requires ``aws`` extra dependency::
$ pip install "motor[aws]"
Support for mongodb+srv:// URIs requires ``srv`` extra dependency::
$ pip install "motor[srv]"
`OCSP <https://pymongo.readthedocs.io/en/stable/examples/tls.html#ocsp>`_ requires ``ocsp`` extra dependency::
$ pip install "motor[ocsp]"
Wire protocol compression with snappy requires ``snappy`` extra dependency::
$ pip install "motor[snappy]"
Wire protocol compression with zstandard requires ``zstd`` extra dependency::
$ pip install "motor[zstd]"
`Client-Side Field Level Encryption
<https://pymongo.readthedocs.io/en/stable/examples/encryption.html#client-side-field-level-encryption>`_
requires ``encryption`` extra dependency::
$ pip install "motor[encryption]"
You can install all dependencies automatically with the following
command::
$ pip install "motor[gssapi,aws,ocsp,snappy,srv,zstd,encryption]"
See `requirements <https://motor.readthedocs.io/en/stable/requirements.html>`_
for details about compatibility.
.. _PyPI: http://pypi.python.org/pypi/motor
.. _pip: http://pip-installer.org
.. _PyMongo: http://pypi.python.org/pypi/pymongo/

View File

@ -12,14 +12,16 @@ Tutorial: Using Motor With :mod:`asyncio`
import pymongo
import motor.motor_asyncio
import asyncio
db = motor.motor_asyncio.AsyncIOMotorClient().test_database
client = motor.motor_asyncio.AsyncIOMotorClient()
db = client.test_database
.. testsetup:: after-inserting-2000-docs
import pymongo
import motor.motor_asyncio
import asyncio
db = motor.motor_asyncio.AsyncIOMotorClient().test_database
client = motor.motor_asyncio.AsyncIOMotorClient()
db = client.test_database
pymongo.MongoClient().test_database.test_collection.insert_many(
[{'i': i} for i in range(2000)])
@ -144,7 +146,7 @@ store a document in MongoDB, call :meth:`~AsyncIOMotorCollection.insert_one` in
...
>>>
>>> import asyncio
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_insert())
result ObjectId('...')
@ -166,7 +168,7 @@ Insert documents in large batches with :meth:`~AsyncIOMotorCollection.insert_man
... [{'i': i} for i in range(2000)])
... print('inserted %d docs' % (len(result.inserted_ids),))
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_insert())
inserted 2000 docs
@ -183,7 +185,7 @@ less than 1:
... document = await db.test_collection.find_one({'i': {'$lt': 1}})
... pprint.pprint(document)
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_find_one())
{'_id': ObjectId('...'), 'i': 0}
@ -211,7 +213,7 @@ To find all documents with "i" less than 5:
... for document in await cursor.to_list(length=100):
... pprint.pprint(document)
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_find())
{'_id': ObjectId('...'), 'i': 0}
{'_id': ObjectId('...'), 'i': 1}
@ -234,7 +236,7 @@ You can handle one document at a time in an ``async for`` loop:
... async for document in c.find({'i': {'$lt': 2}}):
... pprint.pprint(document)
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_find())
{'_id': ObjectId('...'), 'i': 0}
{'_id': ObjectId('...'), 'i': 1}
@ -250,7 +252,7 @@ You can apply a sort, limit, or skip to a query before you begin iterating:
... async for document in cursor:
... pprint.pprint(document)
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_find())
{'_id': ObjectId('...'), 'i': 2}
{'_id': ObjectId('...'), 'i': 1}
@ -274,7 +276,7 @@ that match a query:
... n = await db.test_collection.count_documents({'i': {'$gt': 1000}})
... print('%s documents where i > 1000' % n)
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_count())
2000 documents in collection
999 documents where i > 1000
@ -299,7 +301,7 @@ replacement document. The query follows the same syntax as for :meth:`find` or
... new_document = await coll.find_one({'_id': _id})
... print('document is now %s' % pprint.pformat(new_document))
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_replace())
found document: {'_id': ObjectId('...'), 'i': 50}
replaced 1 document
@ -322,7 +324,7 @@ operator to set "key" to "value":
... new_document = await coll.find_one({'i': 51})
... print('document is now %s' % pprint.pformat(new_document))
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_update())
updated 1 document
document is now {'_id': ObjectId('...'), 'i': 51, 'key': 'value'}
@ -353,7 +355,7 @@ Deleting Documents
... result = await db.test_collection.delete_many({'i': {'$gte': 1000}})
... print('%s documents after' % (await coll.count_documents({})))
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(do_delete_many())
2000 documents before calling delete_many()
1000 documents after
@ -373,7 +375,7 @@ the :meth:`~motor.motor_asyncio.AsyncIOMotorDatabase.command` method on
... response = await db.command(SON([("distinct", "test_collection"),
... ("key", "i")]))
...
>>> loop = asyncio.get_event_loop()
>>> loop = client.get_io_loop()
>>> loop.run_until_complete(use_distinct_command())
Since the order of command parameters matters, don't use a Python dict to pass

View File

@ -14,7 +14,7 @@
"""Motor, an asynchronous driver for MongoDB."""
version_tuple = (2, 5, 1)
version_tuple = (2, 5, 2, 'dev0')
def get_version_string():

View File

@ -149,7 +149,8 @@ class AgnosticClient(AgnosticBaseProperties):
io_loop = kwargs.pop('io_loop')
self._framework.check_event_loop(io_loop)
else:
io_loop = self._framework.get_event_loop()
io_loop = None
self._io_loop = io_loop
kwargs.setdefault('connect', False)
kwargs.setdefault(
@ -158,7 +159,12 @@ class AgnosticClient(AgnosticBaseProperties):
delegate = self.__delegate_class__(*args, **kwargs)
super().__init__(delegate)
self.io_loop = io_loop
@property
def io_loop(self):
if self._io_loop is None:
self._io_loop = self._framework.get_event_loop()
return self._io_loop
def get_io_loop(self):
return self.io_loop
@ -985,22 +991,13 @@ class AgnosticCollection(AgnosticBaseProperties):
change_stream.close()
# asyncio
from asyncio import get_event_loop
def main():
loop = get_event_loop()
task = loop.create_task(watch_collection)
try:
loop.run_forever()
except KeyboardInterrupt:
try:
asyncio.run(watch_collection)
except KeyboardInterrupt:
pass
finally:
if change_stream is not None:
change_stream.close()
# Prevent "Task was destroyed but it is pending!"
loop.run_until_complete(task)
finally:
if change_stream is not None:
change_stream.close()
The :class:`~MotorChangeStream` async iterable blocks
until the next change document is returned or an error is raised. If
@ -1835,11 +1832,17 @@ class AgnosticClientEncryption(AgnosticBase):
if io_loop:
self._framework.check_event_loop(io_loop)
else:
io_loop = self._framework.get_event_loop()
io_loop = None
sync_client = key_vault_client.delegate
delegate = self.__delegate_class__(kms_providers, key_vault_namespace, sync_client, codec_options)
super().__init__(delegate)
self.io_loop = io_loop
self._io_loop = io_loop
@property
def io_loop(self):
if self._io_loop is None:
self._io_loop = self._framework.get_event_loop()
return self._io_loop
def get_io_loop(self):
return self.io_loop

View File

@ -39,7 +39,11 @@ CLASS_PREFIX = 'AsyncIO'
def get_event_loop():
return asyncio.get_event_loop()
try:
return asyncio.get_running_loop()
except RuntimeError:
# Workaround for bugs.python.org/issue39529.
return asyncio.get_event_loop_policy().get_event_loop()
def is_event_loop(loop):

View File

@ -1,6 +1,12 @@
import sys
from distutils.cmd import Command
from distutils.errors import DistutilsOptionError
if sys.version_info[:2] < (3, 10):
from distutils.cmd import Command
from distutils.errors import DistutilsOptionError as OptionError
else:
from setuptools import Command
from setuptools.errors import OptionError
from setuptools import setup
if sys.version_info[:2] < (3, 5):
@ -17,6 +23,7 @@ Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Operating System :: MacOS :: MacOS X
Operating System :: Unix
Operating System :: Microsoft :: Windows
@ -34,7 +41,15 @@ pymongo_ver = ">=3.12,<4"
install_requires = ["pymongo" + pymongo_ver]
extras_require = {'encryption': ["pymongo[encryption]" + pymongo_ver]}
extras_require = {
"encryption": ["pymongo[encryption]" + pymongo_ver],
"ocsp": ["pymongo[ocsp]" + pymongo_ver],
"snappy": ["pymongo[snappy]" + pymongo_ver],
"zstd": ["pymongo[zstd]" + pymongo_ver],
"aws": ["pymongo[aws]" + pymongo_ver],
"srv": ["pymongo[srv]" + pymongo_ver],
"gssapi": ["pymongo[gssapi]" + pymongo_ver],
}
tests_require = ['mockupdb>=1.4.0']
@ -62,8 +77,7 @@ class test(Command):
if self.test_suite is None and self.test_module is None:
self.test_module = 'test'
elif self.test_module is not None and self.test_suite is not None:
raise DistutilsOptionError(
"You may specify a module or suite, but not both")
raise OptionError("You may specify a module or suite, but not both")
def run(self):
# Installing required packages, running egg_info and build_ext are
@ -135,7 +149,7 @@ packages = ['motor', 'motor.frameworks', 'motor.frameworks.tornado',
setup(name='motor',
version='2.5.1',
version='2.5.2.dev0',
packages=packages,
description=description,
long_description=long_description,

View File

@ -16,6 +16,7 @@
import asyncio
import datetime
import unittest
from io import StringIO
from unittest.mock import patch
@ -1139,6 +1140,7 @@ class TestExamples(AsyncIOTestCase):
uri, server_api=ServerApi("1", deprecation_errors=True))
# End Versioned API Example 4
@unittest.skip("MOTOR-908 count has been added to API version 1")
@env.require_version_min(4, 7)
# Only run on RS until https://jira.mongodb.org/browse/SERVER-58785 is resolved.
@env.require_replica_set

11
tox.ini
View File

@ -9,7 +9,7 @@ envlist =
tornado5-{pypy35,pypy36,py35,py36,py37},
# Tornado 6 supports Python 3.5+.
tornado6-{pypy35,pypy36,py35,py36,py37,py38,py39},
tornado6-{pypy35,pypy36,py35,py36,py37,py38,py39,py310},
# Test Tornado's dev version in a few configurations.
tornado_git-{py36,py37},
@ -21,7 +21,7 @@ envlist =
py3-sphinx-doctest,
# asyncio without Tornado.
asyncio-{pypy35,pypy36,py35,py36,py37,py38,py39},
asyncio-{pypy35,pypy36,py35,py36,py37,py38,py39,py310},
# Test PyMongo 3.12 because 4.x breaks some APIs
py3-pymongo-v3.12,
@ -44,6 +44,7 @@ basepython =
py37,synchro37: {env:PYTHON_BINARY:python3.7}
py38: {env:PYTHON_BINARY:python3.8}
py39: {env:PYTHON_BINARY:python3.9}
py310: {env:PYTHON_BINARY:python3.10}
pypy35: {env:PYTHON_BINARY:pypy3}
pypy36: {env:PYTHON_BINARY:pypy3}
@ -55,17 +56,19 @@ deps =
tornado6: tornado>=6,<7
tornado_git: git+https://github.com/tornadoweb/tornado.git
{py35,py36,py37,py38,py39}: aiohttp
{py35,py36,py37,py38,py39,py310}: aiohttp
sphinx: sphinx
sphinx: aiohttp
sphinx: git+https://github.com/tornadoweb/tornado.git
sphinx: tornado
py3-pymongo-v3.12: tornado>=5,<6
synchro37: tornado>=6,<7
synchro37: nose
setenv =
PYTHONWARNINGS="error,ignore:The distutils package is deprecated:DeprecationWarning"
commands =
python --version
python setup.py test --xunit-output=xunit-results {posargs}