Skip to main content

Extras for mongoengine

Project description

mongoengine-plus

codecov test

Extra field types, function helpers and asyncio support for mongoengine

Installation

pip install mongoengine-plus

Testing

Requirements

Docker

Make sure you have Docker installed, as the unit tests require a real Mongo database instance. It is required in order to test Client-Side Field Level Encryption (CSFLE) not supported by mongomock. Don't worry about setting up Mongo manually; the test suite will handle that for you.

Self-signed certificates

To test CSFLE, you need to mock a KMS provider that supports HTTPS requests. We use moto as a KMS mock server running on localhost. This allows us to set up the required SSL certificates so that the pymongo.encryption classes don't raise any complaints. The current testing certificates are located in tests/localhost.*. The configuration file for the certificates is /tests/cert.conf.

If you need to create new self-signed certificates, you can follow these steps:

cd tests
openssl genrsa -out localhost.key 2048
openssl req -new -key localhost.key -out localhost.csr -config cert.conf
# creates a certificate valid for 1 year
openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt -extensions v3_utils -extfile cert.conf

Clone, install requirements and run tests

git clone git@github.com:cuenca-mx/mongoengine-plus.git
cd mongoengine-plus
make install-test
# Since we're using a self-signed certificate with the moto_server
# for testing we need to configure the localhost certificate so 
# ClientEncryption object can connect to the mock KMS instance
export SSL_CERT_FILE=$(pwd)/tests/localhost.crt
make test
unset SSL_CERT_FILE

Mongoengine + Asyncio

Asynchronous Documents operation

Mongoengine-plus brings the power of asynchronous programming to your document operations. We've introduced an AsyncDocument class that extends the standard Document class, allowing you to perform operations like save(), delete() and reload() asynchronously.

To get started, simply inherit your document classes from AsyncDocument instead of Document. You'll then have access to async counterparts of the familiar methods:

  • async_save(): Save your document asynchronously.
  • async_delete(): Delete your document asynchronously.
  • async_reload(): Reload your document asynchronously.

These async methods maintain the same signature as their synchronous counterparts, so you can seamlessly transition your existing codebase to take advantage of asynchronous operations.

from mongoengine import StringField
from mongoengine_plus.aio import AsyncDocument

class User(AsyncDocument):
    name = StringField(required=True)

async def main():
    user = User(name='Jane')
    # Asynchronously save the user document
    await user.async_save() 
    
    # Asynchronously update the user document
    user.name = 'John'
    await user.async_save() 
    
    # Asynchronously reload the user document
    await user.async_reload()  
    print(f"Reloaded user: {user.name}")
    
    # Asynchronously delete the user document
    await user.async_delete()  

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Asynchronous Querying the database

You can do asynchronous queries to your document collections. We've introduced an AsyncQuerySet class that extends the standard QuerySet, providing you with async versions of familiar query operations.

You don't need to interact with AsyncQuerySet directly. It's already configured as the queryset_class for the AsyncDocument class. Once you inherit your document classes from AsyncDocument, the objects property will give you access to asynchronous query methods:

  • async_first(): Retrieve the first document asynchronously.
  • async_get(): Retrieve a specific document asynchronously.
  • async_count(): Count the number of documents asynchronously.
  • async_update(): Update documents asynchronously.
  • async_insert(): Insert documents asynchronously.
  • async_delete(): Delete documents asynchronously.
async def get_first_user():
    user = await User.objects.async_first()
    if user:
        print(f"First user: {user.name}")
    else:
        print("No users found")

async def get_user_by_name(name):
    user = await User.objects(name=name).async_get()
    print(f"User with name '{name}': {user.name}")

async def count_users():
    count = await User.objects.async_count()
    print(f"Number of users: {count}")

async def update_user_name(old_name, new_name):
    updated = await User.objects(name=old_name).async_update(name=new_name)
    print(f"Updated {updated} user(s) from '{old_name}' to '{new_name}'")

async def insert_users(user_names):
    users = [User(name=name) for name in user_names]
    await User.objects.async_insert(users)
    
async def delete_users(name):
    await User.objects(name=name).async_delete()

async def main():
    await insert_users(["Jane", "John"])
    await get_first_user()
    await get_user_by_name("John")
    await count_users()
    await update_user_name("John", "Johnny")
    await get_user_by_name("Johnny")
    await delete_users("Johnny")

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

To iterate over the result set of a query asynchronously, you can use the async_to_list() method:

users = await User.objects(name='Jane').async_to_list()
async for user in users:
    # Process each user 
    ...    

We recommend using async_to_list() for small result sets.

Client-side Field Level Encryption

Mongoengine-plus introduces a new field type called EncryptedStringField that implements Client-side Field Level Encryption (CSFLE) using pymongo encryption classes. This feature allows explicit data encryption before sending it over the network to MongoDB, and automatic data decryption after reading from MongoDB. It supports both synchronous and asynchronous operations. Currently, the EncryptedStringField implementation supports the AWS KMS service as the Key Management Service (KMS) provider.

from mongoengine import Document, StringField
from mongoengine_plus.types import EncryptedStringField
from pymongo.encryption import Algorithm


class User(Document):
    id = StringField(primary_key=True)
    ssn = EncryptedStringField(
        algorithm=Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic
    )


user = User(id='US1', ssn='12345')
user.save()
print(user.ssn)  # Output: '12345'

user_ = User.objects.get(id='US1')
print(user_.ssn)  # Output: '12345'

There are a few steps before you can start using EncryptedStringField.

1. Create a Data Encryption Key (DEK)

Before using EncryptedStringField, you'll need to create a Data Encryption Key (DEK) for encrypting and decrypting your data. The DEK should follow the recommended requirements described in the official MongoDB documentation on Keys and Key Vaults. We've provided a helper method to create your DEK easily.

from mongoengine import connect
from mongoengine_plus.types.encrypted_string.base import create_data_key

connect(host='mongo://localhost:27017/db')

create_data_key(
    kms_provider=dict(
        aws=dict(
            accessKeyId='your-aws-key-id',
            secretAccessKey='your-aws-secret-access-key'
        )
    ),
    key_namespace='encryption.__keyVault',
    key_arn='arn:aws:kms:us-east-1:111122223333:key/your-key-id',
    key_name='my_key_name',
    kms_connection_url='https://kms.us-east-1.amazonaws.com',
    kms_region_name='us-east-1',
)

You'll need to execute this step only once during the project setup. Ensure that your MongoDB user has the necessary permissions for collection and index creation, and access to the AWS KMS key.

2. Configure EncryptedStringField

Since EncryptedStringField needs to read the DEK from your MongoDB instance and access the KMS key for encryption/decryption, you'll need to configure it as follows. This configuration might be in your __init__.py file and should be executed once.

from mongoengine import Document, StringField
from mongoengine_plus.types import EncryptedStringField
from pymongo.encryption import Algorithm

EncryptedStringField.configure_aws_kms(
    'encryption.__keyVault',
    'my_key_name',
    'your-aws-key-id',
    'your-aws-secret-access-key',
    'us-east-1',
)


class User(Document):
    id = StringField(primary_key=True)
    ssn = EncryptedStringField(
        algorithm=Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic
    )

Now you are ready to go!

3. Optimize KMS requests (optional)

There's a caveat in the EncryptedStringField implementation. Every time EncryptedStringField needs to encrypt or decrypt data, it uses the pymongo.encryption.ClientEncryption, which makes a request to the AWS KMS service endpoint. This can potentially slow down the performance of reading and writing encrypted data to MongoDB. As a workaround, we've created a function that patches this behavior and caches the data key.

from mongoengine_plus.types.encrypted_string import cache_kms_data_key


cache_kms_data_key(
    'encryption.__keyVault',
    'my_key_name',
    'your-aws-key-id',
    'your-aws-secret-access-key',
    'us-east-1',
    'https://kms.us-east-1.amazonaws.com',
)

You should execute this function once before making any database write or read operations, perhaps in your __init__.py file. It will retrieve the KMS key and cache it for subsequent requests.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mongoengine_plus-0.2.2.tar.gz (19.1 kB view details)

Uploaded Source

Built Distribution

mongoengine_plus-0.2.2-py3-none-any.whl (21.5 kB view details)

Uploaded Python 3

File details

Details for the file mongoengine_plus-0.2.2.tar.gz.

File metadata

  • Download URL: mongoengine_plus-0.2.2.tar.gz
  • Upload date:
  • Size: 19.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.0 CPython/3.12.4

File hashes

Hashes for mongoengine_plus-0.2.2.tar.gz
Algorithm Hash digest
SHA256 f073686d67f5d193408b32e3c8e048d7ae30b16faedfc7e74516d85023f09cbb
MD5 31761484675fbaba27b0b465b11c6db9
BLAKE2b-256 f5df9753d6e40c4959ccf5af6338f0e707702231eb7bea01e5c2ee62a4ec9d14

See more details on using hashes here.

File details

Details for the file mongoengine_plus-0.2.2-py3-none-any.whl.

File metadata

File hashes

Hashes for mongoengine_plus-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 fe69abe84349b9452b77941025fbbcbfc4c1b4c60a1210a7bede10d5641f87a8
MD5 65769da188c65b452763c0f17f3b1b11
BLAKE2b-256 b3799167bea34af7f4cfdc0292e33fa5ecdc3b565deae223e8a526c42cc83812

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page