Архитектура¶
EndoCore — тонкий, читаемый конвейер. Эта страница показывает всю систему, чтобы вы всегда знали, где находится запрос и какой модуль за него отвечает.
Две половины¶
endocore/ ← фреймворк (устанавливаемый пакет, CLI `endo`)
your_app/ ← ваше приложение (Api/, Services/, Models/, ...)
Вы импортируете endocore; вы пишете your_app. Фреймворк не генерирует вашу
бизнес-логику — он её находит и обслуживает.
Раскладка пакета¶
endocore/
core/
discovery.py # обход дерева Api/ -> список RouteSpec (единственный обход)
router.py # разбор пути + правила матчинга (версия, [id], метод)
registry.py # trie маршрутов + резолвер (строится один раз при старте)
loader.py # динамический импорт хендлеров (устойчив к ошибкам)
request.py # Request поверх ASGI scope
response.py # Response / StreamingResponse -> ASGI send
websocket.py # WebSocket поверх ASGI websocket scope
middleware.py # onion / цепочка call_next
di.py # Depends() + разрешение провайдеров
application.py # async def app(scope, receive, send) — всё связывает
cache.py config.py signing.py pubsub.py openapi.py exceptions.py
middleware/ # поставляемый middleware: logging, cors, csrf, gzip, ...
orm/ # ORM (models, fields, query, compiler, backends, ...)
cli/ # CLI `endo` (create, dev, routes, migrate, ...)
extensions/ # интеграции Redis, Celery, Email, Cache
Последовательность старта¶
При старте Application.boot():
- Кладёт директорию приложения в
sys.path(чтобы работалfrom Services...). - Сканирует
Api/один раз черезpathlib.rglob("*.py")→ списокRouteSpec. - Импортирует каждый хендлер через
importlibвtry/except. Один сломанный файл собирается какBootError, но не фатален. - Регистрирует хендлеры в trie маршрутов (по версии → сегменту).
- Загружает пользовательский middleware (
Middleware/__init__.py), хуки (hooks.py), DI-провайдеры (providers.py) и расширения (extensions.py). - Строит конвейер один раз и логирует сводку старта
(
loaded N routes, M files with errors).
Дерево маршрутов кэшируется. В dev-режиме watcher на watchfiles
пересобирает его в процессе при изменениях — без рестарта.
Поток запроса¶
┌─────────────── цепочка middleware (onion) ───────────────┐
scope/receive → │ logging → [ ваш middleware... ] → dispatch → handler → Response│ → send
└───────────────────────────────────────────────────────────────┘
Application.__call__получает сырой ASGI(scope, receive, send).- Для
httpстроитRequestи прогоняет его через конвейер. - Внутренний слой
_dispatchрезолвит маршрут в trie, внедряет зависимости (di.solve), вызывает вашhandlerи приводит возвращённое значение кResponse. Responseпишет себя вsend.
websocket-scope обрабатывает _handle_websocket; lifespan запускает хуки
startup/shutdown и dev-watcher.
Резолвер (сердце)¶
Дан POST /v2/user/42/role, резолвер:
- разбивает путь на сегменты;
- сверяет первый сегмент с
^v\d+$→ это версия; - проходит trie, предпочитая статических потомков динамическому
(
[id]), захватывая параметры пути; - ищет метод (
POST) в найденном узле.
Исходы: 200 (найдено), 404 (нет такого пути/версии), 405 (путь есть,
неверный метод). Без префикса версии — по умолчанию 404 (опциональный
алиас "latest"). Подробности всех граничных случаев — на странице
Роутинг.
Слои в вашем приложении¶
| Слой | Роль |
|---|---|
API (Api/) |
файлы-endpoint'ы — тонкие: распарсил → позвал сервис → ответил |
| Services | вся бизнес-логика; тяжёлый код здесь |
| Models | описание данных (модели ORM) |
| Middleware | обвязка запроса (auth, логирование, CORS, ...) |
| Utils | чистые функции без побочных эффектов |
Именно это разделение делает версионирование дешёвым: новая версия копирует
тонкие endpoint'ы и локальные сервисы, а глобальные /Services/ остаются
общими.