- PVSM.RU - https://www.pvsm.ru -
Давно преследует одна и та же боль в любом растущем API-проекте на Python: таблица маршрутов начинает жить своей жизнью. @app.post("/v2/user/role") лежит в файле, который импортируется через два уровня include_router, реальная бизнес-логика — в третьем файле, а понять «какой версии принадлежит этот эндпоинт» превращается в археологию по grep’у.
Я решил проверить гипотезу: а что если убрать таблицу маршрутов вообще? Не «сделать её удобнее» — а убрать как отдельную сущность. Пусть структура каталогов и будет таблицей маршрутов.
Так получился EndoCore — ASGI-фреймворк, который я пишу как pet-проект последние несколько месяцев. Дошёл до состояния «можно показывать людям»: 1679 тестов, PyPI, MkDocs-сайт на двух языках. Делюсь — что получилось, что скучно, и где я сам не уверен, что это хорошая идея.
Api/v1/User/[id]/Get.py -> GET /v1/user/42 (id="42")
Api/v1/User/Role/Post.py -> POST /v1/user/role
Api/v2/User/[id]/Get.py -> GET /v2/user/42 (v1 продолжает работать)
Кладёшь файл в нужную папку — эндпоинт существует. Роутинг, версионирование, end routes для интроспекции — всё это операции над одним и тем же деревом каталогов, а не три разные подсистемы, которые могут разъехаться.
Сам файл — просто функция:
# Api/v1/User/Role/Post.py -> POST /v1/user/role
from endocore import Request, Response
async def handler(request: Request) -> Response:
data = await request.json()
return Response.json({"created": data["name"]}, status=201)
Никакого app = FastAPI(), никакого декоратора, никакой регистрации руками. Удалил файл — эндпоинта нет. end routes не парсит декораторы в поисках роутов — он просто печатает дерево, потому что дерево уже и есть ответ.
end version create 2 # Api/v1 -> Api/v2, endpoints + локальные сервисы
v1 физически не может измениться, когда меняется v2 — это не два роутера, разделяющих состояние, а две независимые копии поддерева. Никаких if version == 2 веток, гниющих внутри одного хендлера годами.
Я довольно много времени убил именно на ORM, потому что для меня “фреймворк ради фреймворка” неинтересен — интересно, когда каждый примитив спроектирован так, что нельзя выстрелить себе в ногу случайно.
from endocore.orm import Model, fields, configure, create_all, Q, F
class User(Model):
name = fields.CharField(max_length=100)
age = fields.IntegerField(default=0)
active = fields.BooleanField(default=True)
configure(backend="sqlite", database="app.db") # или postgres, pool_size=10, ...
create_all(User)
User.objects.filter(age__gte=18).order_by("-age")
User.objects.filter(Q(age__lt=18) | Q(name__icontains="a"))
User.objects.filter(pk=1).update(age=F("age") + 1) # атомарный F()
# неблокирующе для ASGI:
async with endocore.orm.aatomic():
user = await User.objects.aget(pk=1)
await user.asave()
Значения всегда идут через биндинг драйвера — никогда не через f-строку. Идентификаторы (таблицы/колонки) валидируются регэкспом и квотятся. Лукапы — строгий whitelist, неизвестный лукап падает с ошибкой, а не тихо проваливается в сырой SQL. Это не «слой безопасности, который можно выключить» — это единственный способ, которым ORM вообще умеет строить запрос.
Есть connection pooling (для PostgreSQL — реальный параллелизм транзакций, не один лок на всех), миграции с откатом, шифрование файлов (AES-256-GCM, если утечёт диск — без ключа файлы бесполезны), встроенные сессии на HMAC-подписанных куках и scrypt для паролей — без сторонних auth-библиотек.
Написать «у меня асинхронно и с транзакциями» — легко. Доказать, что это реально работает под конкурентностью — совсем другое дело. Поэтому вместо одного тривиального демо-приложения я сделал три, и специально проверил их race conditions:
Kanban-борда с живыми обновлениями по WebSocket (pub/sub комнаты).
Бронирование слотов — тест на 8 одновременных запросов на один и тот же слот: ровно один получает 201, остальные 409, в базе ровно одна запись.
Магазин с идемпотентными покупками и вебхуком платёжного шлюза — 6 одновременных дублирующихся запросов не списывают деньги дважды, retry вебхука после сбоя — тоже.
Отдельно есть тестовый сьют, который гоняет пул соединений против реального PostgreSQL (tests/orm/test_postgres_pool.py), а не только против SQLite в памяти — потому что конкурентность на SQLite и на настоящей БД с несколькими воркерами это буквально разные миры, и я не хотел врать себе, что «раз тесты зелёные — значит работает везде».
Python 3.14, static GET: EndoCore 23 800 req/s vs FastAPI 11 000 req/s (2.2×)
Python 3.14, dynamic GET: EndoCore 30 800 req/s vs FastAPI 8 600 req/s (3.6×)
Но! Это чистый оверхед диспетчеризации — вызов ASGI-callable напрямую, без сети, без базы. FastAPI платит эту цену за pydantic-валидацию и сериализацию, которые включены всегда, даже если конкретному эндпоинту они не нужны. В реальном приложении с базой данных эта разница тонет в latency одного SQL-запроса. Я не хочу продавать бенчмарк как причину выбрать фреймворк — это просто честные цифры, полная методология в доках [1].
Нет своей экосистемы плагинов размером с FastAPI/Django — это моложе на порядки.
Нет ORM-миграций для сложных data-трансформаций — только структура таблиц, для сложного переноса данных пишешь скрипт руками.
WebSocket pub/sub — однопроцессный; для fan-out между воркерами нужен Redis поверх (описано в доках, но это ваша забота, не встроенное).
Это по-прежнему бета (0.7.0b2), не 1.0. Я использую его сам, но не претендую, что это готово для чужого продакшена без вдумчивого чтения кода — благо, кода мало и он читается за вечер.
pip install "endocore[watch]"
end new blog && cd blog
end dev # http://127.0.0.1:8000/docs
end — зарезервированное слово в PowerShell, там алиас endo.
GitHub: https://github.com/Drakulyonok/endocore [2]
PyPI: https://pypi.org/project/endocore/ [3]
Документация (EN/RU): https://endocore.readthedocs.io [4]
Демки с race-тестами: demos/ в репозитории (kanban, booking, shop)
Discord: https://discord.gg/jwvGj2M9EX [5]
Буду рад любой обратной связи — особенно если найдёте, где я наврал себе про безопасность или производительность. Личный проект, но отношусь к нему серьёзно.
Автор: Drakulyonok
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/455462
Ссылки в тексте:
[1] полная методология в доках: https://endocore.readthedocs.io/en/latest/benchmarks/
[2] https://github.com/Drakulyonok/endocore: https://github.com/Drakulyonok/endocore
[3] https://pypi.org/project/endocore/: https://pypi.org/project/endocore/
[4] https://endocore.readthedocs.io: https://endocore.readthedocs.io
[5] https://discord.gg/jwvGj2M9EX: https://discord.gg/jwvGj2M9EX
[6] Источник: https://habr.com/ru/articles/1062686/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1062686
Нажмите здесь для печати.