Motor is the asynchronous Python driver for MongoDB. It is compatible with Tornado and asyncio.
This is just a patch release with six bugfixes, but some fixes required tiny API changes that might affect you, so I'm bumping the version from 0.5 to 0.6, instead of to 0.5.1. Read below for the changes—if you make it to the bottom you'll learn about an abstruse asyncio optimization in Python 3.5.1!
These modules have been moved from:
Motor had to make this change in order to omit the
entirely and avoid a spurious
SyntaxError being printed when installing in
Python 2. The change should be invisible to application code. Thanks to Jordi Soucheiron for the report.
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
ABC abstract base class. Thanks to TechBK and Andrew Svetlov for reporting and diagnosing the bug.
Change to asyncio coroutines
There is also a pure bugfix with no API consequences, but it's interesting enough that I wrote it up.
Motor's internals mostly use callbacks and greenlets. Just one rarely-used function,
stream_to_handler, is a generator-based coroutine. This coroutine needs a framework-agnostic way to resolve a Future into a value:
result = yield self._framework.yieldable(some_future)
yieldable() abstracts differences between Tornado and asyncio, so the coroutine works with either framework. If the framework is asyncio, then
yieldable does some footwork to avoid the need for
yield from in Motor:
def yieldable(future): return next(iter(future))
yieldable gets the Future started, then just returns it to be yielded up the coroutine chain. When the coroutine is resumed with a call to
coro.send(value), that becomes the value of the yield expression.
This wouldn't work if Motor's coroutine called another coroutine with multiple yields. But in Motor's narrow use case, I overcome the need for
yield from with asyncio, so I can write code that works equally well with Tornado.
Recently, Yury Selivanov optimized how asyncio coroutines resolve Futures to values. When an asyncio coroutine pauses:
result = yield from some_future
... it stops within
Future.__iter__ at the
class Future: def __iter__(self): if not self.done(): # Tell Task to wait for completion. yield self # Resume coroutine with the result. return self.result()
The Task class later resumes the coroutine with a value like this:
class Task: def step(self, value): self.coro.send(value)
Yury noticed that, when the coroutine resumes, the actual result comes directly from the Future, when it returns
self.result(). That means it doesn't matter what value Task passes with
send(value)! Not only does the value not matter, but CPython chooses a faster code path when it executes
send(None) instead. So he updated asyncio to do that, and the optimization was released with Python 3.4.4 and 3.5.1.
Everybody was happy but me. I retested Motor this weekend and found that Yury's change broke my
yieldable trick. Now
stream_to_handler, and any other Motor function I write from now on that resolves a Future to a value, must resolve it in two steps:
while written < self.length: f = self._framework.yieldable(self.read(self.chunk_size)) yield f chunk = f.result()
This is a minor bug since Motor's sole coroutine,
stream_to_handler, isn't really useful with asyncio until I finish some larger feature work. But the diagnosis was a scenic trip.