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 (последняя - умная и дорогая, она делала ревью).

Я думаю, проще всего проиллюстрировать новые фичи на каком-нибудь примере, который чуть сложнее, чем 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')Сгенерируем миграции:

Сгенерировался 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 уже имеющейся базой данных.
Кроме миграций схемы, ещё поддерживаются так называемые миграции данных. Это - питоновские скрипты. Миграцию данных можно создать так:

Вот что создалось:
# depends: 0001_initial.sql
from pony.orm import Database
db = Database.instance().new()
db.introspect()
if __name__ == '__main__':
pass
Это обычный питоновский скрипт почти без какой-либо магии (вся магия - в том, что он выполняется внутри db_session). Разберём, что означает каждая строчка.
Первая строчка с комментарием определяет зависимости миграции. Зависимостей у миграции может быть сколько угодно. Воркфлоу с миграциями будет хорошо знаком тем, кто работал с django. Например, при наличии "нескольких голов" вас попросят сделать мердж-миграцию:

Мердж-миграция - это файл с расширением 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 ноутбуках:

Для того, чтобы увидеть, что мы получаем из базы интроспекцией, есть опция 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). Миграции, кстати, асинхронность не используют, они могут быть исключительно блокирующими.
Извините, что получилось немного скомканно про асинхронность - но я, действительно, немного тороплюсь. И документацию я тоже не успел обновить - здесь лежит её сырая версия.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | PHP: четыре Active Record в одной клетке — кто быстрее достаёт ваши данные | 0 | 11.1 | 26-09-2026 |
| 2 | MetaORM — когда устал от ORM настолько, что написал свою | 0 | 7.38 | 22-08-2026 |
| 3 | Миграции от ИИ агента: почему я бы не пускал их в прод без отдельного firewall | 0 | 8.8 | 22-09-2026 |
| 4 | [Перевод] Нетипичные оптимизации в PostgreSQL, или Креативное ускорение запросов | 0 | 8.21 | 02-03-2026 |
| 5 | Полиморфные ссылки в PostgreSQL: помогаем СУБД избежать провалов производительности | 0 | 7 | 30-06-2026 |
| 6 | JOIN как в ORM: связи по foreign key в PostgreSQL | 0 | 7.3 | 02-08-2026 |
| 7 | [Перевод] Возвращение аспектно-ориентированного программирования | 0 | 8.4 | 03-07-2026 |
| 8 | ИИ-агент двойного назначения | 0 | 13.15 | 29-05-2026 |
| 9 | Plumix: как я переписываю Flutter на C# — вместе с ИИ | 0 | 6.01 | 07-07-2026 |
| 10 | Оптимизация без AI: как я автоматизировал API-ручки и типы | -2 | 5 | 29-06-2026 |