Беглый опрос коллег на моем текущем проекте показал, что при словах "ORM и работа с БД" в подавляющем большинстве случаев звучат слова "Алхимия" и "Django ORM". Знания этих двух слов, в общем, достаточно, чтобы писать чистый, аккуратный и рабочий код. Но расширение инженерного кругозора пока еще никому не вредило, поэтому сегодня мы добавим в нашу картину мира несколько (возможно, до этого дня незнакомых) классных штук для работы с БД.
Yoyo
Сегодня любая ORM идет со внутренней системой миграции БД. Простой и классный подход, когда ORM следит за структурой таблиц, в общем, всех устраивает. Но у этого решения случая, когда пользоваться им неудобно:
Есть парни, которые фигачат запросы в БД, минуя ORM. При этом используется что-то типа asyncpg и небольшая самописная нашлепка для упрощения составления запросов.
Почему эти парни отказываются от удобств ORM? Да потому что любая обертка для БД сжирает некоторое количество системных ресурсов, а этим парням нужно писать высокопроизводительный код. ORM они выкинули, а базу мигрировать им как-то надо.
- Встроенные миграторы могут иметь свои специфические взгляды на построение связей и индексирование записей. Порой победить эти воззрения ORM на структуру таблиц не удается, приходится самому нашлепывать исправления запросов.
В обоих этих случаях удобно примененять миграции, составленных вручную — мы не опираемся на модели ORM, а втупую руками набиваем необходимые SQL инструкции и кормим их в простенький мигратор, который последовательно применяет изменения структуры к БД.
На выходе получается чистая, понятная и полностью управляемая структура таблиц, составленная с умом.
Для такого вот ручного подхода существует мигратор схем БД yoyo.
pip install yoyo-migrations
Дальше вся работа по управлению миграциями идет с помощью исполняемого файла yoyo
yoyo new ./migrations -m "Add column to foo"
Команда создает файлик, в который можно вписать одну или несколько инструкций для миграции схемы
from yoyo import step
steps = [
step("CREATE TABLE foo (id INT, bar VARCHAR(20), PRIMARY KEY (id))",
"DROP TABLE foo"),
]
После этого миграции можно катить на базу
yoyo apply --database postgresql://scott:tiger@localhost/db ./migrations
Все просто, как полено. И при этом у вас абсолютно полный контроль над тем, как выглядит БД.
Минусов у такого подхода два
- За набором полей в таблицах и их параметрами придется следить ручками. Каждое изменение приводит к необходимости писать ALTER TABLE самому, теряется возможность мигрировать все в один клик.
- Мерджить такие миграции тоже придется всегда руками и головой. Это, конечно, лишний труд. Но на практике конфликты и сложные мерджи миграций встречаются редко.
Peewee
Маленькая и не самая популярная ORM (хотя про нее писали здесь уже не один раз), которая, тем не менее, имеет свою аудиторию.
Peewee создана быть максимально тупой и простой оберткой для БД, с максимально понятным механизмом выполнения запросов и легко читаемым кодом.
Users.select().where(
Users.user_id == user_id
).get()
Несмотря на простоту, минималистичность и малое количество кода, в peewee есть все, что нужно для вменяемой работы.
- Адекватная производительность (хотя и не самая большая скорость выполнения запросов)
- Все необходимые плюшки — различные наборы полей, связи между сущностями, пулы коннектов, наборы плагинов и расширений.
- Есть даже более-менее вменяемая асинхронщина, добавляемая сторонним модулем peewee_async.
Pony ORM
Pony — легендарная своей скоростью обертка. Слой работы с БД, написанный с помощью этой ORM, может по скорости в разы надрать зад другим решениям. Магии в ее скорости нет, есть очень грамотная политика кеширования запросов в базы, кучи оптимизаций и хитрости с кодом. В сумме это приводит к тому что Пони жарит с конской скоростью.
Минусом можно назвать синтаксис запросов в этой ORM — он весьма специфический, с применением генераторов, лямбд и прочих языковых плюшек Питона.
query = select(c for c in Customer
if sum(o.total_price for o in c.orders) > 1000)
Product.select().order_by(lambda p: desc(sum(p.order_items.quantity))).first()
Такой подход требует определенного слома мозга.
Tortoise ORM
Копаясь в разных питонячих репо, я нашел сборник тестов разных ORM с замерами по скорости. Помимо уже упомянутой Pony ORM в списке самых шустрых оказалась некая Tortoise ORM. Понятное дело, что результаты тесты зависят от того, кто тесты пишет и как их потом запускает, но посмотреть поближе на эту штуку надо.
Tortoise — относительно молодой проект, который пока что находится в стадии активной разработки. Хорошая производительность этой библиотеки объясняется тем, что ORM не содержит ниего лишнего и из коробки заточена под асинхронщину. А еще в ней предполагается использование uvloop, который работает быстрее, чем родные питонячие циклы эвентов.
ORM эта еще слишком сыра для применения в бою (например, пока даже не реализованы пулы коннектов), но посматривать за развитием этой либы стоит. Если у разработчиков все пойдет нормально — в ближайший год мы получим действительно быструю обертку для БД с хорошей скоростью и без странного синтаксиса в стиле Pony.
Комментарии (9)
evocatus
25.07.2019 18:02А мне кажется, что синтаксис PonyORM как раз очень удобный. Как можно не любить генераторы и лямбы?
evocatus
25.07.2019 18:33P.S. Подскажите, кто знает — PonyORM продолжает развиваться? Потому что уже больше года как назад видел шикарную презентацию его создателей, где они грозились выпустить версию 0.8 с поддержкой миграций, но пока нет её.
andreymal
26.07.2019 13:42Миграции есть в отдельной ветке на гитхабе, можно тестить уже сейчас. Хотя отсутствие релиза действительно печалит
MikiRobot
26.07.2019 00:13Альтернатива yoyo: alembic.sqlalchemy.org использовать алхимию не обязательно.
workbohdan
26.07.2019 00:36Ребята! Подскажите, пожалуйста, проверенную в бою асинхронную ORM питоновскую для postgres. Хочу что-то такое попробовать, но не знаю на чем остановится: GINO, asyncpg, aiopg
andreymal
26.07.2019 13:34Минусом [Pony] можно назвать синтаксис запросов в этой ORM — он весьма специфический, с применением генераторов, лямбд и прочих языковых плюшек Питона.
По-моему это наоборот большой плюс, которым я активно пользуюсь, так как позволяет писать по сути обычный питоновый код, который в большинстве случаев просто работает. Правда, pylint выпадает в осадок от таких лямбд
DSolodukhin
Когда я последний раз смотрел Peewee, там не было встроенной поддержки One-to-Many, Many-to-Many. Т.е. в pony, например, можно сделать так:
И у вас есть готовое поле для связанной коллекции категорий. В peewee есть такое?
В yoyo можно выполнить миграцию вне транзакции, столкнулся с такой необходимостью, но не могу найти в документации ничего на этот счет?