Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Pony ORM: толстая пачка фич и улучшений

Дата публикации: 26-09-2026 13:00:40

DISCLAIMER: Это неофициальный форк. Но, по моим сведениям, и в официальный pony эти фичи скоро подъедут в каком-то виде.Некоторое время назад, я решил испробовать программирование с помощью ИИ-агентов и решил выбрать pony в качестве пет-проекта. О pony я уже писал в предыдущей статье. Теперь хочу поделиться результатами своего недельного спринта.В pony добавилось:
- поддержка асинхронности
- поддержка миграций
- разнообразные улучшения из серии quality-of-lifeТак что, фреймворк pony теперь чрезвычайно важен и сердит - что можно понять по этой картинке. Всё - благодаря DeepSeek 4.1 Flash и Kimi K3 (последняя - умная и дорогая, она делала ревью). Читать далее

Основное содержимое страницы с новостью.

Время на прочтение5 мин

Охват и читатели4.1K

DISCLAIMER: Это неофициальный форк. Но, по моим сведениям, и в официальный pony эти фичи скоро подъедут в каком-то виде.

Некоторое время назад, я решил испробовать программирование с помощью ИИ-агентов и решил выбрать pony в качестве пет-проекта. О pony я уже писал в предыдущей статье. Теперь хочу поделиться результатами своего недельного спринта.

В pony добавилось:
- поддержка асинхронности
- поддержка миграций
- разнообразные улучшения из серии quality-of-life

Так что, фреймворк pony теперь чрезвычайно важен и сердит - что можно понять по этой картинке. Всё - благодаря DeepSeek 4.1 Flash и Kimi K3 (последняя - умная и дорогая, она делала ревью).

7d98a8d69966cd0b139127112fb63daa.png

Я думаю, проще всего проиллюстрировать новые фичи на каком-нибудь примере, который чуть сложнее, чем hello world.

Сначала нужно установить pony. Я сделал свой собственный форк poney, поэтому, чтобы его установить, нужно выполнить

uv add poney

Кстати, приятный побочный эффект моей деятельности - в том, что авторы pony, наконец, активизировали свои усилия по реализации давно назревших фич и планируют их зарелизить уже в скором будущем. Реализация, думаю, будет отличаться - потому что мы работали независимо. Тем не менее, очень вероятно, что скоро можно будет использовать pony из апстрима с примерно тем же результатом.

Итак, мы установили пони. Дальше, нужно создать Database:

from pony.orm import Database

db = Database(provider=‘postgres’, dsn=“dbname=poney user=postgres”)

Кстати, опции dsn раньше не было - мне её предложил добавить DeepSeek, когда я его попросил расширить возможности декларации сущностей (таблиц), сделав его по возможности поближе к чистому SQL (точнее, DDL). В числе прочего, он добавил и CHECK constraints:

from pony.orm import constraint


class Person(db.Entity):
    name = Required(str)
    age = Required(int)
    cars = Set('Car')

    @constraint.check
    def adults_only(self):
        return self.age > 18


class Car(db.Entity):
    make = Required(str)
    model = Required(str)
    owner = Required(Person)

В апстримном pony, чтобы пользоваться сущностями, нужно выполнить

db.generate_mapping(create_tables=True)

create_tables можно не передавать, если таблицы в базе уже созданы. Кстати, теперь у нас есть для этого миграции. Попробуем воспользоваться ими:

db.migrations.make()

То же самое можно сделать и из командной строки (и это, на самом деле, и есть основной сценарий). Но я специально оставил дублирующий его питоновский API, чтобы, например, миграциями было удобно пользоваться в ноутбуках Jupyter. Из командной строки это делается так:

pony migrations make --db models:db

Опцию --db нужно указать всего один раз: она запишется в файл config.ini в папке migrations. Итак, запускаем:

MigrationError:
  No applications are registered:
    migrations are per-application (db.application(...), see pony-apps);
    entities without an application are not covered by migrations

Ошибка: нам говорят, что миграции поддерживаются только для сущностей, которые принадлежат какому-то "приложению".

Идея database applications взята из django. Только я пошёл ещё дальше и сделал однозначное соответствие схема <-> приложение. Правда, не все базы данных поддерживают разные схемы внутри одной базы. Для тех баз, которые не поддерживают схемы, приложения - это логическая сущность, позволяющая группировать таблицы. Никакого префикса в названия таблиц, в этом случае, не добавляется - в отличие от django.

Но, если вы используете postgres, то ваши таблицы уже принадлежат каким-то схемам (по дефолту - public). Остаётся только указать этот факт явным образом:

database = Database(provider=‘postgres’,
                    dsn=“dbname=poney user=postgres”)
db = database.application('public')

Сгенерируем миграции:

615b2f29e53a015ffb478f2e84ce5b50.png

Сгенерировался 0001_initial.sql. Выглядит он так:

CREATE TABLE "public"."person" (
  "id" SERIAL PRIMARY KEY,
  "name" TEXT NOT NULL,
  "age" INTEGER NOT NULL,
  CONSTRAINT "chk_person__adults_only" CHECK ("age" > 18)
);

CREATE TABLE "public"."car" (
  "id" SERIAL PRIMARY KEY,
  "make" TEXT NOT NULL,
  "model" TEXT NOT NULL,
  "owner" INTEGER NOT NULL
);

CREATE INDEX "idx_car__owner" ON "public"."car" ("owner");

ALTER TABLE "public"."car" ADD CONSTRAINT "fk_car__owner" FOREIGN KEY ("owner") REFERENCES "public"."person" ("id") ON DELETE CASCADE

Его можно применить командой apply:

pony migrations apply

Если что, миграции схемы выполняются внутри DDL-транзакции. Все миграции схемы - это обычный SQL. В целом, идея в том, чтобы было удобно использовать pony c уже имеющейся базой данных.

Кроме миграций схемы, ещё поддерживаются так называемые миграции данных. Это - питоновские скрипты. Миграцию данных можно создать так:

347868e740bfbb5797639d297d477905.png

Вот что создалось:

# depends: 0001_initial.sql

from pony.orm import Database


db = Database.instance().new()
db.introspect()


if __name__ == '__main__':
    pass

Это обычный питоновский скрипт почти без какой-либо магии (вся магия - в том, что он выполняется внутри db_session). Разберём, что означает каждая строчка.

Первая строчка с комментарием определяет зависимости миграции. Зависимостей у миграции может быть сколько угодно. Воркфлоу с миграциями будет хорошо знаком тем, кто работал с django. Например, при наличии "нескольких голов" вас попросят сделать мердж-миграцию:

e24225b928e8693ced5e17310c87c95d.png

Мердж-миграция - это файл с расширением txt. Выглядит она примерно так:

-- depends: 0002_add_data.py, 0002_other_migration.py

      0003_merge.txt
             │
┌────────────┴─────────────┐
│                          │
0002_add_data.py           0002_other_migration.py
│                          │
└────────────┬─────────────┘
             │
     0001_initial.sql

Но вернёмся к миграции 0002_add_data.py. Следующей строчкой идёт

db = Database.instance().new()

Ну, почему там Database.instance() - ещё можно понять: миграции знают, где взять database. Но почему там .new(), и потом .introspect()?

Дело в том, что тот объект Database, который мы импортировали из нашего проекта, нам не подходит. Он слишком свежий, и миграции нужен не он, а его "снимок" из прошлого. В django, например, написано много кода для того, чтобы уметь восстанавливать этот "снимок" по файлам миграций. Я выбрал более простой путь - режим интроспекции.

Мы пытаемся восстановить наши модели-сущности по состоянию базы данных. При этом, то, что восстановить не удается, мы руками дописываем в файл миграции:

# depends: 0001_initial.sql

from pony.orm import *

db = Database.instance().new()
public = db.application('public')

class Person(public.Entity):
    cars = Set('Car')

db.introspect()

me = public.Person(name='Vitalik', age=37)
car = public.Car(make='Toyota', model='Camry', owner=me)

Сессию в миграции создавать не нужно: она создаётся автоматически. Кстати, в рамках этой же сессии, в таблицу pony_migrations добавится строчка о том, что миграция была успешно выполнена.

Миграции данных удобно писать и отлаживать, например, в Jupyter ноутбуках:

db6a4d1c2fd20b84713b94ac706c063d.png

Для того, чтобы увидеть, что мы получаем из базы интроспекцией, есть опция dump :

db = database.new()
db.introspect('public', dump='introspected.py')

В нашем случае, файл introspected.py выглядит так:

from pony.orm import *


class Car(db.Entity):
    id = PrimaryKey(int, auto=True, name='car_pkey')
    make = Required(str)
    model = Required(str)
    owner = Required('Person', index='idx_car__owner', reverse='car_set')

class Person(db.Entity):
    id = PrimaryKey(int, auto=True, name='person_pkey')
    name = Required(str)
    age = Required(int)
    car_set = Set('Car', reverse='owner')

car_set - это дефолтный атрибут для коллекции. В нашей миграции мы его переопределили на cars.

Это всё, что я хотел сказать о миграциях - и так получилось длинно. Теперь скажу пару слов об асинхронности. Поддерживаются postgres и mariadb/mysql. Оба драйвера позволяют работать с пулом соединений, чем мы и пользуемся. После использования для какой-либо сессии, соединение возвращается в пул.

Принцип очень простой: мы пишем async with db_session, и после этого обычные методы становятся асинхронными:

async with db_session:
    me = await Person[1]

То же самое касается методов get и select, а также .load() - подгрузки частично загруженного объекта:

await obj.load()

Методы flush и commit/rollback тоже становятся асинхронными. Операции записи остаются синхронными, потому что мы всё равно записываем в базу отложенным образом - в конце сессии.

Такой подход стал возможен, потому что API pony построен вокруг работы с сессиями. Поэтому, мы точно можем сказать, находимся мы внутри блокирующей или асинхронной сессии. В django так бы сделать не получилось.

Важный момент: асинхронность в пони самая что ни на есть нативная - безо всяких гринлетов (как это сделано, например, в sqlalchemy). Миграции, кстати, асинхронность не используют, они могут быть исключительно блокирующими.

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

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1PHP: четыре Active Record в одной клетке — кто быстрее достаёт ваши данные011.126-09-2026
2MetaORM — когда устал от ORM настолько, что написал свою07.3822-08-2026
3Миграции от ИИ агента: почему я бы не пускал их в прод без отдельного firewall08.822-09-2026
4[Перевод] Нетипичные оптимизации в PostgreSQL, или Креативное ускорение запросов08.2102-03-2026
5Полиморфные ссылки в PostgreSQL: помогаем СУБД избежать провалов производительности0730-06-2026
6JOIN как в ORM: связи по foreign key в PostgreSQL07.302-08-2026
7[Перевод] Возвращение аспектно-ориентированного программирования08.403-07-2026
8ИИ-агент двойного назначения013.1529-05-2026
9Plumix: как я переписываю Flutter на C# — вместе с ИИ06.0107-07-2026
10Оптимизация без AI: как я автоматизировал API-ручки и типы-2529-06-2026

Классификация: Пресс-релизы. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 9.58. Источник: habr.com.