Motor 2.0 Migration Guide

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 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, 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 <your application>

Warnings can also be changed to errors:

python -Wd -Werror <your application>

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 MotorCursor to handle its own cleanup.

  • MotorClient.get_default_database. Call 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 MotorCollection.find() cursor.

  • MotorClient.database_names. Use 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 MotorCollection.bulk_write`(), see Bulk Writes Tutorial.

  • MotorCollection.count. Use count_documents() or estimated_document_count().

  • MotorCollection.ensure_index. Use MotorCollection.create_indexes().

  • Deprecated write methods have been deleted from MotorCollection.

  • MotorCursor.count and MotorGridOutCursor.count. Use MotorCollection.count_documents() or 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:

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 Tutorial: Using Motor With 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:

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 add_done_callback() call can be placed on the same line:

collection.find_one({}).add_done_callback(callback)

In almost all cases the modern coroutine API is more readable and provides better exception handling:

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 MotorClient.start_session() or end_session() to handle the following change.

MotorClient.start_session() is a coroutine

In the past, you could use a client session like:

session = client.start_session()
doc = await client.db.collection.find_one({}, session=session)
session.end_session()

Or:

with client.start_session() as session:
   doc = client.db.collection.find_one({}, session=session)

To support multi-document transactions, in Motor 2.0 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 MotorClientSession, not PyMongo’s ClientSession. The end_session method on the returned MotorClientSession is also now a coroutine instead of a regular method. Use it like:

session = await client.start_session()
doc = await client.db.collection.find_one({}, session=session)
await session.end_session()

Or:

async with client.start_session() as session:
   doc = await client.db.collection.find_one({}, session=session)