Быстрый старт¶
Рабочий API примерно за минуту. Без конфигов и регистрации маршрутов — вы создаёте папки и файлы, EndoCore их отдаёт. На этой странице разобрано ровно то, что происходит на каждом шаге и почему, включая пару моментов, на которых обычно спотыкаются в первый раз (они явно обозначены ниже, а не оставлены на самостоятельное открытие).
1. Сгенерируйте проект¶
pip install "endocore[watch]" # [watch] включает автоперезагрузку dev-сервера — см. шаг 2
endo new blog
cd blog
PowerShell
В PowerShell пишите endo вместо endo (endo new blog) — endo там
зарезервированное слово (ключевое слово блока PowerShell). Тот же CLI,
другое имя; все остальные команды ниже работают одинаково что с endo,
что с endo.
endo new blog пишет ровно это и ничего больше (это код
endocore/cli/commands/new.py, а не приблизительное описание):
blog/
Api/
v1/
Health/
Get.py # GET /v1/health — проверка живости, чтобы `endo dev`
# сразу было что отдавать
Middleware/
__init__.py # пустой список `middlewares = [...]`, закомментированные примеры
Services/ Models/ Utils/ Tests/ # пустые, кроме .gitkeep в каждой
hooks.py # списки `on_startup` / `on_shutdown`, оба пустые заглушки
README.md
Файла extensions.py пока нет — он опционален и нужен только когда вы
реально подключаете интеграцию сервиса (Redis, Celery, email); см.
Расширения. Не создавайте его заранее «на всякий
случай».
2. Запустите dev-сервер¶
В консоли появится примерно такое:
[INFO] EndoCore booted: loaded 1 route, 0 middleware, 0 files with errors
[INFO] dev watcher on: .../blog/Api
[INFO] Uvicorn running on http://127.0.0.1:8000
Откройте http://127.0.0.1:8000/v1/health — получите {"status": "ok"}.
А на http://127.0.0.1:8000/docs — интерактивный Swagger UI, где каждый
endpoint можно подёргать прямо из браузера. (/docs здесь включён
автоматически, потому что endo dev запускает приложение с dev=True — в
продакшене он по умолчанию выключен; см. Конфигурация.)
Нет строки dev watcher on: ...? Перезагрузка не сработает
Внутрипроцессный вотчер файлов нуждается в опциональном пакете
watchfiles — как раз для этого в шаге 1 стоял pip install
"endocore[watch]". Без него endo dev всё равно запускает приложение
совершенно нормально, просто логирует dev watcher disabled (install
'watchfiles' for auto-reload) вместо строки про вотчер, и каждое
сохранение в шагах 3–6 ниже потребует ручного перезапуска (Ctrl+C,
снова endo dev), чтобы изменения применились. Если пропустили
экстру [watch], поставьте её сейчас: pip install watchfiles.
3. Добавьте endpoint¶
Сгенерируйте:
endo create сам переводит каждый сегмент пути в PascalCase (user →
User) и пишет минимальное тело обработчика; целится он в последнюю
существующую версию, если вы её не указали (здесь — v1, поскольку это
единственная версия). Запустите её ещё раз с теми же аргументами — она не
затрёт ваши правки, а напечатает exists: ... и оставит файл как есть.
…или создайте файл сами — endo create это удобство, а не обязательное
требование. Папка — это путь, имя файла — это метод, и точка:
# Api/v1/User/Profile/Get.py
from endocore import Request, Response
async def handler(request: Request) -> Response:
return Response.json({"name": "Ada", "role": "admin"})
Сохраните файл — при запущенном вотчере EndoCore пересканирует Api/ и
переимпортирует изменённый обработчик прямо в процессе (без рестарта
сервера, без разрыва соединений). Откройте
http://127.0.0.1:8000/v1/user/profile — endpoint уже работает. Полный
набор правил, включая несколько граничных случаев, которые стоит знать до
того, как ваше дерево разрастётся до сотни файлов, — в
Роутинге.
4. Динамические сегменты¶
Папка с именем [id] захватывает параметр пути, дословно (в отличие от
обычных папок, регистр не приводится к нижнему — см.
Роутинг):
# Api/v1/User/[id]/Get.py
from endocore import Request, Response
async def handler(request: Request) -> Response:
user_id = request.path_params["id"] # "42" для /v1/user/42 — всегда str
return Response.json({"id": user_id})
request.path_params["id"] — всегда str: EndoCore никогда не угадывает,
что "42" нужно превратить в int 42. Приводите тип сами
(int(request.path_params["id"])), если обработчику нужно число;
невалидное приведение — забота вашего кода (например, оберните его в
raise HTTPError(422, "id must be numeric")), а не роутера.
5. Чтение входных данных¶
# Api/v1/User/Post.py (POST /v1/user)
from endocore import Request, Response, HTTPError
async def handler(request: Request) -> Response:
data = await request.json()
if not data.get("name"):
raise HTTPError(422, "name is required")
return Response.json({"created": data["name"]}, status=201)
await request.json() возвращает None для пустого тела (это не ошибка) и
поднимает BadRequest (→ 400) для тела, которое не является валидным JSON —
так что data.get("name") выше поднимет AttributeError на None, если
клиент вообще не прислал тело; защититесь от этого явно (data = await
request.json() or {}), если только пустое тело действительно не должно
быть 500-й. request также даёт request.query (распарсенная строка
запроса), request.headers, request.cookies и await request.form() /
await request.files() для multipart-загрузок — см.
Request и Response.
6. Подключите ORM¶
# Models/blog.py
from endocore.orm import Model, fields, configure, create_all
configure(backend="sqlite", database="blog.db")
class Post(Model):
title = fields.CharField(max_length=200)
body = fields.TextField(default="")
create_all(Post)
# Api/v1/Post/Post.py (POST /v1/post)
from endocore import Request, Response
from Models.blog import Post
async def handler(request: Request) -> Response:
data = await request.json()
post = Post.objects.create(title=data["title"], body=data.get("body", ""))
return Response.json({"id": post.pk, "title": post.title}, status=201)
Здесь важно понять две вещи, а не просто скопировать:
configure()иcreate_all()выполняются один раз, при первом импортеModels.blog— Python кеширует модули вsys.modules, так что сколько бы разных файлов обработчиков ни делалиfrom Models.blog import Post, тело модуля (а значит иconfigure/create_all) выполнится ровно один раз, в момент первого импорта (на практике — при старте, поскольку EndoCore жадно импортирует каждый найденный обработчик). Именно поэтому пример работает без какой-либо явной обвязки «запуск приложения».- Вызывайте
configure()для соединения"default"строго в одном месте — если два разных файла моделей оба вызовутconfigure(backend= "sqlite", database=...)для одного алиаса, тот, который Python импортирует вторым, молча заменит первое соединение (configure()всегда перезаписывает; см. Транзакции). Держите один файл (этот,Models/blog.py, или отдельныйModels/db.pyв проекте побольше) единственным местом, где вызываетсяconfigure(), а остальные файлы моделей пусть просто импортируютfields, Model, полагаясь на то, что этот вызов уже отработал.
blog.db создаётся относительно текущей рабочей директории процесса
(там, откуда вы запустили endo dev), а не относительно файла модели —
стоит знать заранее, а не удивляться потом, откуда взялся файл в
неожиданном месте. Полное руководство по ORM — поля, лукапы, связи,
async-API — в руководстве по ORM.
7. Осмотрите приложение¶
endo routes # все маршруты + файл, в который они мапятся
endo check # битые хендлеры, дубли маршрутов, пропущенные файлы (с причинами)
endo openapi # печать OpenAPI-схемы
endo check стоит запускать всякий раз, когда что-то должно быть
маршрутом, но не появляется: он печатает ровно причину, по которой сканер
пропустил каждый такой файл (не та папка версии, не то расположение), а не
оставляет вас гадать.
Это весь цикл. Дальше — Туториал: соберём полноценный версионированный API с сервисами, middleware и миграциями.