Статьи

Motor 0.5 Beta: простая агрегация, Asyncio, Async и Await

Сегодня хороший день: я опубликовал бета-версию  Motor , моего асинхронного драйвера Python для MongoDB. Эта версия — самое большое обновление. Помоги мне бета-тестирование! Установить с помощью:

python -m pip install --pre motor==0.5b0

Мотор 0.5 все еще зависит от PyMongo 2.8.0 точно. Я знаю, что эта версия PyMongo устарела, но я решил не заниматься этой проблемой прямо сейчас.

Вы меня простите, потому что этот моторный релиз огромен:

Asyncio

Мотор теперь может интегрироваться с Asyncio, как альтернатива Tornado. Я благодарен Реми Жолину, Андрею Светлову и Николаю Новику за их огромный вклад в интеграцию асинцио Мотор.

API Торнадо и Asyncio являются родственными. Вот Мотор с Торнадо:

# Tornado API
from tornado import gen, ioloop
from motor.motor_tornado import MotorClient

@gen.coroutine
def f():
    result = yield client.db.collection.insert({'_id': 1})
    print(result)

client = MotorClient()
ioloop.IOLoop.current().run_sync(f)

И вот новая интеграция asyncio:

import asyncio
from motor.motor_asyncio import AsyncIOMotorClient

@asyncio.coroutine
def f():
    result = yield from client.db.collection.insert({'_id': 1})
    print(result)

client = AsyncIOMotorClient()
asyncio.get_event_loop().run_until_complete(f())

В отличие от Tornado, Asyncio не включает в себя реализацию HTTP, а тем более веб-фреймворк. Для этих функций используйте пакет aiohttp Эндрю Светлова. Я написал вам  крошечный пример веб-приложения с Motor и aiohttp .

заполнитель

MotorCollection.aggregate теперь возвращает курсор по умолчанию, и курсор возвращается сразу без  yield. Старый синтаксис больше не поддерживается:

# Motor 0.4 and older, no longer supported.
cursor = yield collection.aggregate(pipeline, cursor={})
while (yield cursor.fetch_next):
    doc = cursor.next_object()
    print(doc)

В Мотор 0.5 просто сделайте:

# Motor 0.5: no "cursor={}", no "yield".
cursor = collection.aggregate(pipeline)
while (yield cursor.fetch_next):
    doc = cursor.next_object()
    print(doc)

В asyncio это использует  yield from вместо:

# Motor 0.5 with asyncio.
cursor = collection.aggregate(pipeline)
while (yield from cursor.fetch_next):
    doc = cursor.next_object()
    print(doc)

Python 3.5

Мотор теперь совместим с Python 3.5, что потребовало некоторых усилий. Это было сложно, потому что Motor не просто работает с вашими сопрограммами, он использует сопрограммы внутри себя для реализации некоторых своих собственных функций, таких как MotorClient.open и  MotorGridFS.put. У меня был метод для написания сопрограмм, который работал в Python 2.6 — 3.4, но 3.5 наконец сломал его. Не существует единого способа вернуть значение из собственной сопрограммы Python 3.5 или сопрограммы на основе генератора Python 2, поэтому все внутренние сопрограммы Motor, возвращающие значения, были переписаны с помощью обратных вызовов. (См.  Сообщение фиксации dc19418c  для объяснения.)

Async и Await

Это награда за мои усилия в Python 3.5. Мотор работает с носителями сопрограмм, написанных с  async и  await синтаксисом:

async def f():
    await collection.insert({'_id': 1})

Курсоры из  MotorCollection.findMotorCollection.aggregateили MotorGridFS.find могут быть элегантно и очень эффективно повторены в нативных сопрограммах с  async for:

async def f():
    async for doc in collection.find():
        print(doc)

Насколько это эффективно? Для коллекции с 10 000 документов этот код старого стиля в моей системе занимает 0,14 секунды:

# Motor 0.5 with Tornado.
@gen.coroutine
def f():
    cursor = collection.find()
    while (yield cursor.fetch_next):
        doc = cursor.next_object()
        print(doc)

Следующий код, который просто заменяет  gen.coroutine и  yield на async и  await, выполняет примерно то же самое:

# Motor 0.5 with Tornado, using async and await.
async def f():
    cursor = collection.find()
    while (await cursor.fetch_next):
        doc = cursor.next_object()
        print(doc)

Но с  async for этим требуется 0,04 секунды, в три раза быстрее!

# Motor 0.5 with Tornado, using async for.
async def f():
    cursor = collection.find()
    async for doc in cursor:
        print(doc)

Тем не менее, MotorCursor по-  to_list прежнему царит:

# Motor 0.5 with Tornado, using to_list.
async def f():
    cursor = collection.find()
    docs = await cursor.to_list(length=100)
    while docs:
        for doc in docs:
            print(doc)
        docs = await cursor.to_list(length=100)

Функция с  to_list в два раза быстрее  async for, но она не изящна и требует от вас выбора размера чанка. Я думаю, что  async for это стильно и достаточно быстро для большинства применений.

Попробуй меня!

Я не всегда публиковал бета-версии перед выпуском Motor, но на этот раз все по-другому. Интеграция Asyncio является совершенно новым. А поскольку требовался повсеместный рефакторинг ядра Motor, существующая интеграция Tornado также была переписана. Поддержка Python 3.5 требует еще одного внутреннего пересмотра. Я очень хочу получить ранние отчеты обо всем моем новом коде в дикой природе.

Кроме того, изменение  aggregate является разрывом API. (Есть также  два более тонких изменения, см. Список изменений .) Так что я даю вам возможность явного выбора,  pip install --pre прежде чем я сделаю Motor 0.5 официальным.

Пожалуйста, попробуйте! Установите бета-версию:

python -m pip install --pre motor==0.5b0

Протестируйте свое приложение с новым кодом. Если вы обнаружите проблемы, сообщите об ошибке, и я быстро отвечу.  И если бета пройдет гладко, не  молчите  ! Это единственный способ узнать, что бета работает на вас.