Статьи

Четыре стратегии обслуживания с PyMongo

Когда я начал писать Motor , мой асинхронный драйвер для Tornado и MongoDB, моей главной заботой была ремонтопригодность. Я хочу 100% функциональности с официальным драйвером PyMongo. И я не просто хочу этого сейчас: я хочу легко сохранить эту полноту в будущем, навсегда.

Поддерживаемость — это борьба за версию Tornado любой библиотеки Python. Всегда есть реализация золотого стандарта для некоторой библиотеки, написанной стандартным способом блокировки, а затем есть небольшая двоюродная сестра, написанная для Tornado, которая начинается с малого и, кажется, никогда не вырастет. Например, Python поставляется с SimpleXMLRPCServer, который является довольно полным. Если вы используете Tornado, вы должны использовать Tornado-RPC . Это не было затронуто в течение двух лет, и у него есть серьезные недостатки, например, он не работает с tornado.gen .

Gevent решает проблему поддерживаемости, исправляя существующие библиотеки, чтобы сделать их асинхронными. Когда код библиотеки изменяется, но исправление обезьян все еще работает с новой версией. Node.js, с другой стороны, является пространством, где не существует синхронных библиотек. Лучшая реализация любой библиотеки для Node — это уже асинхронная версия.

Но библиотеки Торнадо всегда играют в догонялки с более полной синхронной библиотекой и обычно не очень хорошо ее воспроизводят.

С Мотором я сделал лучшую работу, какую только могу придумать, чтобы догнать PyMongo и остаться в курсе. У меня есть 4 стратегии:

1 . Повторно использовать PyMongo. Я использую симпатичную технику с гринлетами, чтобы повторно использовать большую часть кода PyMongo и сделать его асинхронным. Я написал этот метод ранее.

2 . Непосредственно тест Мотор. Как и с любой библиотекой, тщательные тесты улавливают регрессии, и это особенно важно для Motor, потому что он может сломаться при изменении PyMongo. Тестирование асинхронного кода немного болезненно; Я написал как тесты в стиле обратного вызова, используя мой метод assertEventuallyEqual , так и тесты в стиле генератора, используя мой декоратор async_test_engine . Если основной код PyMongo изменится и сломает Motor, я сразу узнаю.

3 . Повторно использовать тесты PyMongo. Так же, как Motor оборачивает PyMongo и делает его асинхронным, я написал еще одну оболочку, которая снова делает Motor синхронной, поэтому Motor выглядит точно так же, как PyMongo. Эта обертка называется Synchro. Для каждого метода асинхронного двигателя Synchro оборачивает его следующим образом:

class Collection(object):
    """Synchro's fake Collection, which wraps MotorCollection, which
       wraps the real PyMongo Collection.
    """
    def find_one(self, *args, **kwargs):
        loop = tornado.ioloop.IOLoop.instance()
        outcome = {}

        def callback(result, error):
            loop.stop()
            outcome['result'] = result
            outcome['error'] = error

        kwargs['callback'] = callback
        self.motor_collection.find_one(*args, **kwargs)
        loop.start()

        # Now the callback has been run and has stopped the loop
        if outcome['error']:
            raise outcome['error']
        else:
            return outcome['result']

реальном коде я также добавляю таймаут в цикл, чтобы ошибка не приводила к зависанию моих тестов.)

Что это сумасшествие покупает мне? Я могу выполнить большинство тестов PyMongo, около 350 из них, против Synchro. Поскольку Synchro проходит эти тесты, я уверен, что Motor не упустит ни одной функции без моего ведома. Так, например, мы добавляем метод агрегирования в PyMongo в следующем выпуске и добавим тест в набор PyMongo, который выполняет агрегат. Этот тест не пройдёт против Synchro, так как Synchro использует Motor, а у Motor еще нет агрегата. Тесты Synchro быстро проваливаются, и я могу просто добавить в Motor строку, в которой говорится: «Асинхронизировать агрегат»

4 . Повторно использовать документацию PyMongo. Каждый метод Motor принимает те же параметры и имеет то же поведение, что и метод PyMongo, который он переносит, за исключением того, что он асинхронный и принимает обратный вызов. Я мог бы просто скопировать и вставить документы PyMongo и добавить параметр обратного вызова для каждого метода, но тогда, когда изменение документов PyMongo произойдет, Motor будет отставать. Вместо этого я написал расширение Sphinx . Для каждого метода в двигателе расширение находит аналогичную документацию PyMongo и добавляет параметр обратного вызова. Например, документы MotorCollection API в основном создаются из документов PyMongo Collection.