Skip to content

Commit

Permalink
MOTOR-689: Add async wrapper for pymongo.encryption.ClientEncryption (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
guanlinzhou committed Mar 31, 2021
1 parent 8a8313d commit 1b5a21a
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ python:
services: mongodb

install:
- pip install -U pip
- pip install tornado
- pip install -e '.[encryption]'

script: "python setup.py test"

Expand Down
7 changes: 7 additions & 0 deletions doc/api-asyncio/asyncio_motor_client_encryption.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:class:`~motor.motor_asyncio.AsyncIOMotorClientEncryption`
==========================================================

.. currentmodule:: motor.motor_asyncio

.. autoclass:: AsyncIOMotorClientEncryption
:members:
1 change: 1 addition & 0 deletions doc/api-asyncio/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Motor asyncio API
asyncio_motor_database
asyncio_motor_collection
asyncio_motor_change_stream
asyncio_motor_client_encryption
cursors
asyncio_gridfs
aiohttp
Expand Down
1 change: 1 addition & 0 deletions doc/api-tornado/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Motor Tornado API
motor_database
motor_collection
motor_change_stream
motor_client_encryption
cursors
gridfs
web
Expand Down
7 changes: 7 additions & 0 deletions doc/api-tornado/motor_client_encryption.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:class:`~motor.motor_tornado.MotorClientEncryption`
===================================================

.. currentmodule:: motor.motor_tornado

.. autoclass:: MotorClientEncryption
:members:
2 changes: 1 addition & 1 deletion doc/examples/encryption.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ you will need to install the
as well as the driver itself. Install both the driver and a compatible
version of pymongocrypt like this::

$ python -m pip install 'pymongo[encryption]'
$ python -m pip install 'motor[encryption]'

Note that installing on Linux requires pip 19 or later for manylinux2010 wheel
support. For more information about installing pymongocrypt see
Expand Down
49 changes: 48 additions & 1 deletion motor/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from pymongo.cursor import Cursor, RawBatchCursor, _QUERY_OPTIONS
from pymongo.database import Database
from pymongo.driver_info import DriverInfo
from pymongo.encryption import ClientEncryption

from . import version as motor_version
from .metaprogramming import (AsyncCommand,
Expand Down Expand Up @@ -140,7 +141,7 @@ def __init__(self, *args, **kwargs):
:Parameters:
- `io_loop` (optional): Special event loop
instance to use instead of default
instance to use instead of default.
"""
if 'io_loop' in kwargs:
io_loop = kwargs.pop('io_loop')
Expand Down Expand Up @@ -1795,3 +1796,49 @@ def __enter__(self):

def __exit__(self, exc_type, exc_val, exc_tb):
pass

class AgnosticClientEncryption(AgnosticBase):
"""Explicit client-side field level encryption."""

__motor_class_name__ = 'MotorClientEncryption'
__delegate_class__ = ClientEncryption

create_data_key = AsyncCommand(doc=create_data_key_doc)
encrypt = AsyncCommand()
decrypt = AsyncCommand()
close = AsyncCommand(doc=close_doc)

def __init__(self, kms_providers, key_vault_namespace, key_vault_client, codec_options, io_loop=None):
"""Explicit client-side field level encryption.
Takes the same constructor arguments as
:class:`pymongo.encryption.ClientEncryption`, as well as:
:Parameters:
- `io_loop` (optional): Special event loop
instance to use instead of default.
"""
if io_loop:
self._framework.check_event_loop(io_loop)
else:
io_loop = self._framework.get_event_loop()
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

def get_io_loop(self):
return self.io_loop

async def __aenter__(self):
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
if self.delegate:
await self.close()

def __enter__(self):
raise RuntimeError('Use {} in "async with", not "with"'.format(self.__class__.__name__))

def __exit__(self, exc_type, exc_val, exc_tb):
pass
24 changes: 24 additions & 0 deletions motor/docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1268,3 +1268,27 @@ async def coro():
.. _$expr: https://docs.mongodb.com/manual/reference/operator/query/expr/
.. _$where: https://docs.mongodb.com/manual/reference/operator/query/where/
"""

create_data_key_doc = """Create and insert a new data key into the key vault collection.
Takes the same arguments as
:class:`pymongo.encryption.ClientEncryption.create_data_key`,
with only the following slight difference using async syntax.
The following example shows creating and referring to a data
key by alternate name::
await client_encryption.create_data_key("local", keyAltNames=["name1"])
# reference the key with the alternate name
await client_encryption.encrypt("457-55-5462", keyAltName="name1",
algorithm=Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random)
"""

close_doc = """Release resources.
Note that using this class in a with-statement will automatically call
:meth:`close`::
async with AsyncIOMotorClientEncryption(...) as client_encryption:
encrypted = await client_encryption.encrypt(value, ...)
decrypted = await client_encryption.decrypt(encrypted)
"""
6 changes: 5 additions & 1 deletion motor/motor_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .frameworks import asyncio as asyncio_framework
from .metaprogramming import create_class_with_framework

__all__ = ['AsyncIOMotorClient']
__all__ = ['AsyncIOMotorClient','AsyncIOMotorClientEncryption']


def create_asyncio_class(cls):
Expand Down Expand Up @@ -70,3 +70,7 @@ def create_asyncio_class(cls):

AsyncIOMotorGridOutCursor = create_asyncio_class(
motor_gridfs.AgnosticGridOutCursor)


AsyncIOMotorClientEncryption = create_asyncio_class(
core.AgnosticClientEncryption)
5 changes: 4 additions & 1 deletion motor/motor_tornado.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .frameworks import tornado as tornado_framework
from .metaprogramming import create_class_with_framework

__all__ = ['MotorClient']
__all__ = ['MotorClient', 'MotorClientEncryption']


def create_motor_class(cls):
Expand Down Expand Up @@ -60,3 +60,6 @@ def create_motor_class(cls):


MotorGridOutCursor = create_motor_class(motor_gridfs.AgnosticGridOutCursor)


MotorClientEncryption = create_motor_class(core.AgnosticClientEncryption)
8 changes: 7 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@
with open("README.rst") as readme:
long_description = readme.read()

install_requires = ['pymongo>=3.11,<4']
pymongo_ver = ">=3.11,<4"

install_requires = ["pymongo" + pymongo_ver]

extras_require = {'encryption': ["pymongo[encryption]" + pymongo_ver]}

tests_require = ['mockupdb>=1.4.0']


class test(Command):
description = "run the tests"

Expand Down Expand Up @@ -138,6 +143,7 @@ def run(self):
url='https://github.com/mongodb/motor/',
python_requires='>=3.5.2',
install_requires=install_requires,
extras_require=extras_require,
license='http://www.apache.org/licenses/LICENSE-2.0',
classifiers=[c for c in classifiers.split('\n') if c],
keywords=[
Expand Down

0 comments on commit 1b5a21a

Please sign in to comment.