Основы Litestar

Курс знакомит с Litestar — современным ASGI-фреймворком для разработки быстрых и типобезопасных веб‑API на Python. Вы изучите структуру проекта, маршрутизацию, обработку запросов и ответов, валидацию данных, зависимости и работу с базой данных.

1. Введение в Litestar и настройка окружения

Введение в Litestar и настройка окружения

Что такое Litestar

Litestar — это современный Python-фреймворк для создания веб‑API и веб‑приложений поверх стандарта ASGI. На практике это означает, что приложение Litestar запускается через ASGI‑сервер (например, Uvicorn), умеет работать с async-кодом и хорошо подходит для высоконагруженных API.

Ключевые причины, почему Litestar часто выбирают для новых проектов:

  • ASGI-архитектура: удобная работа с асинхронностью и современными серверами.
  • Чёткая структура: маршруты, обработчики, зависимости, конфигурация.
  • Типизация: типы в сигнатурах помогают IDE и упрощают поддержку кода.
  • Экосистема: интеграции через плагины и расширяемость.
  • Полезные источники:

  • Документация Litestar
  • Репозиторий Litestar на GitHub
  • Базовые понятия, которые понадобятся дальше

    Перед тем как писать код, зафиксируем несколько терминов простыми словами:

  • ASGI — стандарт, который описывает, как веб‑сервер общается с Python‑приложением. Стандарт: ASGI Documentation.
  • ASGI-сервер — процесс, который принимает HTTP‑запросы и передаёт их приложению (пример: Uvicorn).
  • Приложение — объект Litestar, который содержит маршруты, настройки, middleware и т.д.
  • Маршрут — правило вида URL + HTTP‑метод (например, GET /).
  • Обработчик (handler) — функция/метод, который выполняется, когда запрос попал в маршрут.
  • !Упрощённая схема пути запроса от клиента до обработчика Litestar и обратно

    Подготовка окружения

    Чтобы примеры из курса работали одинаково у всех, важно изолировать зависимости проекта и фиксировать их.

    Требования

  • Python (обычно используют актуальные версии 3.10+; точные требования всегда можно проверить в документации Litestar)
  • pip для установки пакетов: Документация pip
  • Создание виртуального окружения

    Виртуальное окружение позволяет держать зависимости проекта отдельно от глобальных пакетов системы.

  • Создайте папку проекта и перейдите в неё:
  • Создайте виртуальное окружение:
  • Активируйте виртуальное окружение:
  • Справка по venv: Документация venv

    Установка Litestar и сервера Uvicorn

    Минимально нам нужны два пакета:

  • litestar — сам фреймворк
  • uvicorn — ASGI‑сервер для запуска приложения
  • Установите зависимости:

    Про Uvicorn: Документация Uvicorn

    Первый запуск: минимальное приложение

    Создайте файл app.py в корне проекта:

    Что здесь происходит:

  • @get("/") регистрирует обработчик для HTTP‑метода GET и пути /.
  • hello() возвращает словарь, который Litestar автоматически преобразует в JSON‑ответ.
  • app = Litestar(...) создаёт ASGI‑приложение.
  • Запустите приложение:

    Разбор команды:

  • app:app означает модуль app.py и объект приложения app внутри него.
  • --reload включает автоперезагрузку при изменениях кода (удобно для разработки).
  • --port 8000 задаёт порт.
  • Проверьте, что всё работает:

    Ожидаемый результат:

    Рекомендуемая структура проекта

    На старте можно держать всё в одном файле, но уже со второго-третьего эндпоинта удобнее перейти к структуре модулей.

    Пример простой структуры:

    Идея такая:

  • app/main.py — точка входа, создаёт Litestar(...).
  • app/routes.py — обработчики и маршруты.
  • В следующих статьях курса мы будем развивать проект именно в таком стиле, чтобы код оставался читаемым.

    Полезные инструменты для разработки (по желанию)

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

  • ruff для линтинга и форматирования: Документация Ruff
  • pytest для тестов: Документация pytest
  • httpx для HTTP‑клиента в тестах: Документация HTTPX
  • mypy для проверки типов: Документация mypy
  • pre-commit для автоматических проверок перед коммитом: Документация pre-commit
  • Частые проблемы при первом запуске

  • Команда uvicorn не найдена: чаще всего виртуальное окружение не активировано.
  • Ошибка импорта app:app: убедитесь, что файл называется app.py, а объект приложения действительно называется app.
  • Порт занят: измените порт, например --port 8001.
  • Итоги

    Вы:

  • узнали, что такое Litestar и как он связан с ASGI;
  • создали виртуальное окружение и установили зависимости;
  • написали минимальное Litestar‑приложение и запустили его через Uvicorn.
  • Дальше в курсе мы начнём расширять приложение: добавим несколько маршрутов, разберём параметры запроса и ответы, а затем перейдём к валидации, структуре проекта и более практичным API‑паттернам.

    2. Маршрутизация и обработчики запросов

    Маршрутизация и обработчики запросов

    Как это связано с предыдущей статьёй

    В прошлой статье вы установили Litestar и Uvicorn и запустили минимальное приложение с одним маршрутом GET /. Теперь расширим этот фундамент: разберём, как Litestar сопоставляет входящий запрос с обработчиком и как удобно описывать маршруты для реального API.

    !Упрощённая схема: как запрос попадает в нужный обработчик и возвращается ответ

    Что такое маршрут и обработчик

    Маршрут — это правило, которое описывает:

  • HTTP-метод (например, GET, POST)
  • путь (например, /users, /users/{user_id})
  • Обработчик (handler) — это функция (или метод класса), которая выполняется, когда запрос совпал с маршрутом.

    Litestar ищет подходящий маршрут по комбинации метод + путь и вызывает соответствующий обработчик.

    Базовые HTTP-методы в Litestar

    В Litestar чаще всего используют декораторы:

  • @get(path) — чтение данных
  • @post(path) — создание
  • @put(path) — полная замена
  • @patch(path) — частичное обновление
  • @delete(path) — удаление
  • Эти декораторы одновременно:

  • регистрируют маршрут
  • сообщают Litestar, какие параметры нужно взять из пути, query-параметров или тела запроса
  • Пример: несколько маршрутов в одном приложении

    Рекомендуемый минимальный шаг от app.py из прошлой статьи — вынести маршруты в отдельный файл.

    Структура:

    app/routes.py:

    app/main.py:

    Запуск (из корня проекта):

    Что важно понимать про пример:

  • GET / и GET /health возвращают словарь, а Litestar автоматически сериализует его в JSON.
  • В POST /echo параметр data: dict будет считан из тела запроса (обычно это JSON).
  • Параметры пути: /{param}

    Очень часто часть пути является переменной: например, GET /users/42.

    В Litestar это описывается так:

    Здесь:

  • {user_id:int} означает параметр пути с именем user_id
  • :int — конвертер, который говорит Litestar: приведи значение к целому числу
  • Если вы вызовете GET /users/abc, маршрут не совпадёт из-за int.

    Часто используемые конвертеры:

  • str — строка (используется по умолчанию)
  • int — целое число
  • uuid — UUID
  • Query-параметры: ?page=1&limit=20

    Query-параметры — это часть URL после ?. Они обычно применяются для пагинации, фильтрации и сортировки.

    Litestar может автоматически положить query-параметры в аргументы обработчика по имени:

    Примеры:

  • GET /items вернёт значения по умолчанию: page=1, limit=10
  • GET /items?page=2&limit=50 вернёт page=2, limit=50
  • Что здесь происходит:

  • Litestar берёт строковые значения из URL и пытается привести их к типам из сигнатуры (int).
  • Значения по умолчанию задают необязательные параметры.
  • Тело запроса: данные в POST, PUT, PATCH

    Для методов, которые отправляют данные на сервер, обычно используется JSON-тело запроса.

    Самый простой вариант — принять dict:

    Проверка через curl:

    На практике для валидации и удобной типизации часто используют модели данных (например, на базе Pydantic), но это отдельная тема, к которой стоит перейти после уверенного понимания маршрутов и обработчиков.

    Объект запроса: когда нужны заголовки, IP, URL

    Иногда нужно прочитать детали запроса: заголовки, URL, метод, cookies. Для этого обработчик может принять объект Request.

    Полезно помнить:

  • request.headers — заголовки
  • request.url — информация об URL
  • request.method — HTTP-метод
  • Что можно возвращать из обработчика

    Самые частые варианты:

  • dict / list — Litestar вернёт JSON
  • str — текстовый ответ
  • кортеж с данными и статусом (подходит для простых случаев)
  • Пример со статусом:

    Если нужен полный контроль (заголовки, cookies, медиа-тип), используют Response.

    Группировка маршрутов: контроллеры

    Когда эндпоинтов становится много, удобнее группировать их по смыслу.

    В Litestar для этого есть Controller: класс, который объединяет несколько обработчиков под общим префиксом.

    Подключение контроллера:

    Что это даёт:

  • общий префикс /users
  • логическая группировка API
  • проще масштабировать проект
  • Конфликты маршрутов и порядок

    Иногда два маршрута могут выглядеть похоже:

  • GET /users/me
  • GET /users/{user_id:int}
  • Чтобы не получить неожиданное поведение:

  • используйте конвертеры (int, uuid), чтобы маршруты были однозначнее
  • старайтесь делать специальные пути вроде /me, /search отдельными маршрутами, а параметры пути — типизированными
  • Итоги

    Вы научились:

  • определять маршруты в Litestar через @get, @post и другие декораторы
  • работать с параметрами пути (/{param}) и query-параметрами (?a=b)
  • принимать JSON-тело запроса в обработчике
  • возвращать данные и управлять статусом ответа
  • группировать маршруты через контроллеры
  • Дальше логичный шаг — разобраться, как делать строгую валидацию входных данных и формировать стабильные схемы запросов и ответов, чтобы API было безопаснее и удобнее поддерживать.

    3. Request/Response: параметры, заголовки, куки, файлы

    Request/Response: параметры, заголовки, куки, файлы

    Связь с предыдущими темами

    В прошлых статьях вы научились поднимать Litestar-приложение и описывать маршруты, параметры пути и query-параметры, а также принимать JSON-тело запроса и возвращать ответы.

    Теперь соберём это в более практичную картину работы с HTTP:

  • как читать данные из заголовков и cookies;
  • когда нужен объект Request;
  • как формировать Response с нужным статусом, заголовками и cookies;
  • как принимать multipart/form-data и загружать файлы.
  • !Куда именно “кладутся” параметры, заголовки, куки и тело запроса, и чем отвечает сервер

    Откуда Litestar берёт данные для аргументов обработчика

    Litestar умеет автоматически заполнять аргументы функции-обработчика (handler) из разных частей HTTP-запроса — в зависимости от имени аргумента, типа и того, помечен ли он специальным параметром.

    Ниже — полезная шпаргалка источников данных.

    | Что это | Где находится в HTTP | Как получить в Litestar | |---|---|---| | Параметр пути | /users/42 | @get("/users/{user_id:int}") и аргумент user_id: int | | Query-параметр | /items?page=2 | аргумент page: int = 1 | | Заголовок | User-Agent: ... | аргумент с Header() или через Request.headers | | Cookie | Cookie: session=... | аргумент с Cookie() или через Request.cookies | | Тело JSON | Content-Type: application/json | аргумент data: dict (или модель данных) | | Форма / файлы | Content-Type: multipart/form-data | UploadFile, Request или multipart-обработчик |

    > Термины headers, cookies, multipart/form-data — это стандартные части HTTP. Хорошая справка: Документация MDN по HTTP.

    Работа с заголовками

    Когда заголовки нужны

    Заголовки (headers) часто используют для:

  • аутентификации (Authorization);
  • версионирования API (X-API-Version);
  • трассировки запросов (X-Request-ID);
  • информации о клиенте (User-Agent).
  • Справка: MDN: HTTP headers.

    Получение заголовка через объект Request

    Это самый понятный путь, когда вам нужен доступ к разным деталям запроса.

    Особенности:

  • имена заголовков в HTTP регистронезависимы, поэтому обычно используют нижний регистр;
  • .get() безопаснее, чем прямой доступ — вернёт None, если заголовка нет.
  • Получение заголовка как аргумента обработчика

    Если нужен один-два заголовка — удобно получить их прямо параметрами функции.

    Что важно:

  • Header(...) явно говорит Litestar: этот аргумент берётся из заголовков;
  • default=None делает заголовок необязательным.
  • Работа с cookies

    Что такое cookies в контексте API

    Cookie — это пара ключ=значение, которую клиент хранит и автоматически отправляет на сервер в заголовке Cookie. Сервер может установить cookie через заголовок Set-Cookie.

    Справка: MDN: HTTP cookies.

    Чтение cookies

    Вариант через Request:

    Вариант как аргумент обработчика:

    Установка cookies в ответ

    Чтобы установить cookie, обычно возвращают объект Response, чтобы управлять заголовками ответа.

    Рекомендации:

  • httponly=True помогает защититься от чтения cookie через JavaScript;
  • secure=True стоит включать в продакшене под HTTPS;
  • samesite влияет на отправку cookie при кросс-сайтовых запросах.
  • Удаление cookies

    Удаление обычно делается установкой cookie с истёкшим сроком (библиотека делает это за вас).

    Тело запроса: когда JSON, а когда форма

    Вы уже использовали JSON-тело запроса (например, data: dict). Это удобно для API.

    Но для загрузки файлов и HTML-форм часто применяют multipart/form-data.

    Справка: MDN: multipart/form-data.

    Загрузка файлов: multipart/form-data

    Основная идея

    При загрузке файлов браузер или клиент отправляет запрос с типом:

  • Content-Type: multipart/form-data
  • Тело запроса состоит из частей (parts): текстовые поля формы и файлы.

    Приём файла через UploadFile

    Litestar предоставляет тип UploadFile для работы с загруженными файлами.

    Что здесь происходит:

  • media_type=RequestEncodingType.MULTI_PART сообщает, что обработчик ожидает multipart/form-data;
  • UploadFile даёт доступ к имени, типу и содержимому;
  • чтение через await file.read() делает обработчик безопасным для async-нагрузки.
  • Пример запроса через curl:

    Приём файла и дополнительных полей формы

    Обычно вместе с файлом отправляют метаданные (например, описание).

    Запрос:

    Практические замечания:

  • не читайте большие файлы целиком без необходимости; если файл потенциально большой, продумайте потоковую обработку или ограничения по размеру;
  • всегда проверяйте content_type, имя файла и ожидаемые форматы.
  • Ответ (Response): статус, заголовки, cookies и файлы

    Самый простой ответ

    Как вы уже видели, если вернуть dict или list, Litestar вернёт JSON.

    Управление статус-кодом

    Для простых случаев можно вернуть кортеж (content, status_code).

    Добавление заголовков ответа

    Когда нужно добавить заголовки (например, X-Request-ID или версию API), используйте Response.

    Справка по заголовкам кеширования: MDN: Cache-Control.

    Возврат файла клиенту

    Иногда API должен отдать файл (например, отчёт). Для этого используют специальный тип ответа файла.

    Идея:

  • сервер не сериализует файл в JSON;
  • клиент получает корректные заголовки для скачивания.
  • Если вы отдаёте пользовательские файлы, убедитесь, что путь к файлу не строится напрямую из ввода пользователя без проверки, чтобы избежать чтения произвольных файлов на сервере.

    Мини-итог

    В этой теме вы научились работать с прикладными деталями HTTP, без которых реальное API почти невозможно:

  • читать заголовки и cookies (через Request и через параметры обработчика);
  • устанавливать и удалять cookies в Response;
  • принимать multipart/form-data и загружать файлы через UploadFile;
  • управлять статусом и заголовками ответа;
  • отдавать файл как ответ.
  • Дальше по курсу логично перейти к валидации и схемам данных (модели запросов/ответов), чтобы входные данные были строго проверяемыми, а API — предсказуемым.

    4. Схемы данных и валидация (Pydantic и типы)

    Схемы данных и валидация (Pydantic и типы)

    Связь с предыдущими темами

    В предыдущих статьях вы научились:

  • поднимать Litestar-приложение и описывать маршруты;
  • получать параметры пути и query-параметры;
  • читать заголовки и cookies;
  • принимать JSON и multipart/form-data;
  • формировать Response со статусами и заголовками.
  • Следующий шаг, без которого сложно делать поддерживаемое API: описывать входные и выходные данные как схемы, чтобы Litestar мог:

  • автоматически проверять входные данные;
  • отдавать предсказуемые ошибки клиенту;
  • помогать вам типами в IDE.
  • В этой статье мы разберём схемы на базе Pydantic и то, как типы в сигнатуре обработчика превращаются в валидацию.

    !Где именно происходит валидация и как данные проходят от запроса до ответа

    Зачем нужны схемы вместо dict

    Приём data: dict удобен для первых примеров, но быстро создаёт проблемы:

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

    Установка Pydantic

    Если Pydantic ещё не установлен, добавьте его в проект:

    Полезные ссылки:

  • Документация Pydantic
  • Документация Litestar
  • Pydantic-модель как схема запроса

    Базовая модель

    Опишем модель входных данных для создания товара.

    Теперь используем её в обработчике. Litestar:

  • прочитает JSON из тела запроса;
  • попытается провалидировать его как ItemCreate;
  • передаст в функцию уже типизированный объект.
  • Ограничения полей через Field

    Чаще всего нужны ограничения: длина строки, минимальное значение, обязательность.

    Что означает:

  • min_length и max_length ограничивают длину строки;
  • gt=0 означает строго больше нуля.
  • > Ограничения проверяются автоматически до входа в бизнес-логику.

    Что происходит при ошибке валидации

    Если клиент отправит некорректный JSON, обработчик не будет вызван. Litestar вернёт ошибку (обычно 400 Bad Request) с описанием проблем.

    Пример запроса:

    Смысл ответа будет таким:

  • сервер сообщает, какие поля не прошли проверку;
  • вы не пишете ручные if price <= 0: ... во всех обработчиках.
  • Схемы ответов

    Возвращаем Pydantic-модель

    Pydantic полезен не только для входа, но и для выхода: так вы фиксируете контракт ответа.

    Плюсы:

  • единый формат ответа;
  • типы и структура описаны явно;
  • проще поддерживать изменения.
  • Коллекции моделей

    Разные схемы для создания, чтения и обновления

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

    Практичнее разделять:

  • создание — что клиент обязан прислать;
  • чтение — что сервер возвращает (например, с id);
  • обновление — часто частичное, поля должны быть необязательными.
  • Пример:

    И использование в обработчиках:

    Типы как часть валидации: путь и query

    Параметры пути

    Вы уже использовали конвертеры пути:

    Здесь валидация происходит на уровне маршрутизации:

  • если user_id не приводится к int, маршрут не подходит.
  • Query-параметры

    Для query-параметров Litestar берёт строковые значения из URL и приводит к типам из сигнатуры.

    Если клиент передаст limit=abc, Litestar не сможет привести к int и вернёт ошибку.

    Справка по типам:

  • Документация Python typing
  • Строгость схем: лишние поля, алиасы и предсказуемость

    Запрет лишних полей

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

    В Pydantic это настраивается через конфигурацию модели.

    Теперь запрос вида { "name": "Book", "price": 10, "unexpected": true } будет считаться ошибочным.

    Алиасы полей

    Иногда внешний контракт требует другое имя поля, чем вам удобно в Python.

    Идея:

  • снаружи клиент отправляет id;
  • внутри вашего кода вы работаете с user_id.
  • Практический мини-пример: аккуратный API без dict

    Ниже пример в стиле курса: контроллер, схемы на Pydantic, типизация пути.

    Итоги

    Теперь у вас есть ключевой инструмент для надёжного API:

  • Pydantic-модели как схемы входных и выходных данных;
  • автоматическая валидация JSON-тела запроса до вызова обработчика;
  • более чистые обработчики без ручных проверок;
  • отдельные схемы для create, read, update как базовый паттерн;
  • типизация параметров пути и query-параметров как часть валидации.
  • Дальше логично переходить к темам, которые делают API “продакшен-готовым”: зависимости, обработка ошибок, авторизация и тестирование.

    5. Зависимости, middleware и жизненный цикл приложения

    Зависимости, middleware и жизненный цикл приложения

    Связь с предыдущими темами

    Ранее вы научились:

  • поднимать Litestar-приложение и подключать маршруты;
  • принимать параметры пути, query-параметры и тело запроса;
  • работать с Request и Response, заголовками, cookies и файлами;
  • описывать схемы данных через Pydantic и получать автоматическую валидацию.
  • Следующий шаг к реальному приложению: организовать общий код так, чтобы он не дублировался в каждом обработчике. Для этого в Litestar используются:

  • зависимости (dependency injection) для передачи в обработчики общих объектов и сервисов;
  • middleware для обработки запроса вокруг ваших маршрутов;
  • жизненный цикл приложения (startup/shutdown) для создания и корректного закрытия ресурсов.
  • !Диаграмма показывает порядок: lifecycle, middleware, роутинг, зависимости и формирование ответа

    Термины, которые нужны в этой теме

  • Зависимость: функция или объект, который Litestar может автоматически предоставить обработчику как аргумент.
  • Инъекция зависимостей: механизм, при котором обработчик не создаёт нужные ему сервисы сам, а получает их готовыми.
  • Middleware: слой, который получает запрос раньше маршрутизации и может изменить запрос, ответ или обработать ошибки.
  • Жизненный цикл приложения: события запуска и остановки приложения, где обычно создают и закрывают общие ресурсы.
  • Состояние приложения: место, куда можно положить общие объекты, доступные из разных частей приложения (обычно app.state).
  • Официальная документация: Litestar Documentation

    Зависимости в Litestar

    Зачем нужны зависимости

    Зависимости помогают:

  • не создавать одинаковые объекты в каждом обработчике;
  • централизовать конфигурацию и доступ к ресурсам;
  • упрощать тестирование, подменяя зависимости на фейки.
  • Типичные примеры зависимостей:

  • настройки приложения;
  • текущий пользователь (после аутентификации);
  • клиент базы данных или внешнего API;
  • генератор request_id для трассировки.
  • Базовая идея на простом примере

    Представим, что вам нужен объект настроек в нескольких эндпоинтах.

    Что здесь важно:

  • В dependencies ключ "settings" означает имя аргумента обработчика.
  • Provide(get_settings) говорит Litestar, как получить значение.
  • В обработчик приходит уже готовый Settings.
  • Зависимости могут быть async

    Если зависимость обращается к сети или диску, её удобно сделать асинхронной.

    Кэширование зависимости в рамках запроса

    Иногда одна и та же зависимость нужна нескольким другим зависимостям и обработчику. Чтобы она вычислялась один раз на запрос, обычно включают кэширование.

    Смысл cache=True:

  • в рамках одного запроса значение будет вычислено один раз;
  • если один запрос вызывает зависимость повторно, Litestar возьмёт результат из кэша.
  • Зависимости как способ стандартизировать доступ к контексту

    Ранее вы читали заголовки через Request. Это удобно, но если заголовок нужен в десятках обработчиков, лучше сделать зависимость.

    Плюсы:

  • обработчики остаются простыми;
  • изменение логики чтения версии делается в одном месте.
  • Практика: зависимость + Pydantic-схемы

    Зависимости хорошо сочетаются со схемами из прошлой темы: схема валидирует тело запроса, зависимость даёт сервис.

    Middleware в Litestar

    Что такое middleware и когда он нужен

    Middleware выполняется для каждого запроса (или для группы запросов, если вы подключаете его не на всё приложение), и решает поперечные задачи:

  • логирование;
  • добавление заголовков ответа;
  • измерение времени выполнения;
  • CORS;
  • обработка ошибок на едином уровне.
  • Главная идея: middleware оборачивает приложение как матрёшка.

    Порядок выполнения

    Если подключено несколько middleware, они вызываются:

  • на входе запроса: сверху вниз;
  • на выходе ответа: снизу вверх.
  • Это важно, если одно middleware зависит от того, что сделал другой слой.

    Пример простого ASGI-middleware

    Litestar работает поверх ASGI, поэтому ASGI-middleware обычно подходит.

    Что делает этот middleware:

  • измеряет время обработки запроса;
  • добавляет заголовок X-Response-Time-Ms в ответ.
  • Важные ограничения middleware

    Middleware хорошо подходит для общих задач, но не всегда лучший инструмент.

    Таблица для выбора подхода:

    | Задача | Лучше dependency | Лучше middleware | |---|---|---| | Передать сервис или настройки в обработчик | да | нет | | Добавить заголовки всем ответам | иногда | да | | Логирование всех запросов | нет | да | | Доступ к Request внутри обработчика | да | нет | | Глобальная обработка ошибок | нет | да |

    Практическое правило:

  • если вам нужно дать обработчику значение, используйте зависимость;
  • если вам нужно обернуть обработку запросов целиком, используйте middleware.
  • Жизненный цикл приложения

    Что обычно делают на startup и shutdown

    В веб-приложении есть ресурсы, которые дорогие в создании, но должны жить долго:

  • пул соединений к базе данных;
  • HTTP-клиент для внешнего сервиса;
  • загрузка конфигурации и секретов;
  • прогрев кэша.
  • Такие ресурсы создают при старте приложения и закрывают при остановке.

    Спецификация ASGI lifespan: ASGI Lifespan

    Пример: создать ресурс на запуске и закрыть при остановке

    Ниже пример с условным ресурсом (вместо реальной БД). Важен именно паттерн.

    Что здесь важно:

  • app.state используется как контейнер для общих объектов.
  • В обработчике доступ к app.state можно получить через request.app.state.
  • На shutdown ресурс корректно закрывается.
  • Соединяем жизненный цикл и зависимости

    Частый практический подход:

  • создать ресурс на startup и положить в app.state;
  • сделать зависимость, которая достаёт ресурс из app.state.
  • Плюсы такого решения:

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

    Как не превратить зависимости в хаос

  • Давайте зависимостям имена по роли, а не по технологии: items_service, а не postgres_client.
  • Разделяйте зависимости по модулям, например app/dependencies.py.
  • Делайте зависимости маленькими: одна функция должна давать один объект.
  • Как аккуратно внедрять middleware

  • Начните с одного-двух слоёв: логирование и трассировка.
  • Следите за порядком: внешний middleware должен быть тем, кто должен видеть самый полный контекст.
  • Не складывайте бизнес-логику в middleware: для неё есть обработчики и зависимости.
  • Итоги

    Вы получили три ключевых инструмента, которые делают Litestar-приложение масштабируемым:

  • зависимости для передачи сервисов и контекста в обработчики без дублирования;
  • middleware для общей обработки запросов и ответов вокруг маршрутов;
  • жизненный цикл приложения (on_startup, on_shutdown) и app.state для управления долгоживущими ресурсами.
  • Дальше эти механизмы обычно используют вместе с обработкой ошибок, авторизацией и тестированием, чтобы приложение было готово к продакшену.

    6. Ошибки, статусы, исключения и логирование

    Ошибки, статусы, исключения и логирование

    Связь с предыдущими темами

    В прошлых статьях вы научились строить маршруты, принимать данные (путь, query, JSON, файлы), валидировать вход через Pydantic, а также подключать зависимости, middleware и жизненный цикл.

    Теперь соберём важный «продакшен»-слой вокруг API:

  • как выбирать HTTP-статусы и формировать ответы об ошибках;
  • как работать с исключениями в Litestar (встроенными и пользовательскими);
  • как сделать единый формат ошибок для всего приложения;
  • как организовать логирование, чтобы было проще отлаживать и поддерживать сервис.
  • !Диаграмма показывает, где возникают ошибки и где они преобразуются в HTTP-ответ

    HTTP-статусы: какие бывают и как выбирать

    HTTP-статус — это число, которое описывает итог обработки запроса. Клиент в первую очередь ориентируется именно на него.

    Справочник: MDN: HTTP response status codes

    Практичная шпаргалка статусов для API

    | Ситуация | Статус | Когда использовать | |---|---:|---| | Успешное чтение | 200 | GET вернул данные | | Успешное создание | 201 | POST создал ресурс | | Успешно, но без тела | 204 | например, DELETE или POST, где нечего возвращать | | Некорректные входные данные | 400 | JSON/поля невалидны (часто это делает фреймворк автоматически) | | Не аутентифицирован | 401 | нет/неверный токен, нужна авторизация | | Нет прав | 403 | токен есть, но доступ запрещён | | Не найдено | 404 | ресурс отсутствует | | Конфликт | 409 | нарушение уникальности, гонки, конфликт версий | | Ошибка сервера | 500 | непредвиденная ошибка в коде/инфраструктуре |

    Как задавать статус в Litestar

    Самые распространённые варианты:

  • вернуть кортеж (content, status_code);
  • вернуть Response, если нужно управлять заголовками, cookies и телом.
  • Пример (кортеж):

    Пример (Response):

    Ошибки в API: что важно для клиента

    Ошибку в API важно сделать:

  • предсказуемой по формату;
  • однозначной по статусу;
  • достаточно информативной, но без утечек внутренностей.
  • Обычно клиенту нужны:

  • статус (например, 404);
  • короткий текст (например, Item not found);
  • иногда детали (например, какие поля не прошли валидацию).
  • Исключения в Litestar: как превращать ошибки в ответы

    Подходы к ошибкам

    В Litestar вы обычно комбинируете два подхода:

  • ожидаемые ошибки (не найдено, нет прав, конфликт) вы превращаете в 4xx;
  • неожиданные ошибки (баг, падение зависимости) должны стать 5xx и попасть в логи.
  • Встроенное HTTP-исключение

    Litestar предоставляет исключение, которое сразу «несёт» HTTP-статус. Общий паттерн: если условие не выполнено, вы raise и дальше фреймворк сформирует ответ.

    Результат для клиента:

  • статус будет 404;
  • тело будет содержать сообщение об ошибке (точный формат зависит от конфигурации приложения).
  • Ошибки валидации

    В прошлой теме вы видели, что при невалидном JSON или данных, не проходящих Pydantic-валидацию, обработчик не вызывается. Это важно:

  • ваша бизнес-логика не должна вручную проверять базовые ограничения типов и обязательности;
  • формат и статус ошибки валидации лучше сделать единообразным для всего проекта.
  • Единый формат ошибок: пользовательские исключения и обработчики

    Когда проект растёт, удобно договориться о едином формате ошибок, например:

    Пользовательское исключение

    Опишем исключение уровня домена (не привязанное к HTTP):

    Обработчик исключения

    Далее добавим обработчик, который превращает его в HTTP-ответ.

    Что это даёт:

  • обработчики остаются чистыми: они бросают понятные доменные исключения;
  • HTTP-детали (статус, формат ответа) централизованы;
  • формат ошибок стабилен для клиента.
  • Логирование: что логировать и где

    Логи отвечают на два практических вопроса:

  • что случилось?
  • почему это случилось?
  • База для Python: Документация logging

    Минимальные уровни логов

  • DEBUG — детали для разработки.
  • INFO — нормальный ход приложения (запрос обработан, ресурс создан).
  • WARNING — подозрительная ситуация (не критично, но важно).
  • ERROR — ошибка, запрос мог завершиться 4xx/5xx.
  • CRITICAL — приложение не может продолжать работу.
  • Что обычно логируют в веб-API

  • метод и путь (GET /items/1);
  • статус ответа (200, 404, 500);
  • длительность обработки;
  • идентификатор запроса (request id), чтобы склеивать события.
  • > В логи не стоит писать пароли, токены, полные номера карт, содержимое приватных файлов и другие секреты.

    Middleware для логирования запросов и ответов

    Логирование удобно делать на уровне middleware, потому что оно видит:

  • входящий запрос;
  • исходящий статус;
  • время обработки;
  • исключения, если они «вылетели» наружу.
  • Пример простого ASGI-middleware, который пишет лог после формирования ответа:

    Практические замечания:

  • scope — это ASGI-структура запроса; для простого access-log чаще всего достаточно method и path.
  • status_code мы достаём из сообщения http.response.start.
  • extra=... помогает писать структурированные логи (их проще анализировать).
  • Логирование ошибок и стек-трейсов

    Главное правило

  • 4xx часто являются ожидаемыми (ошибка клиента) и не всегда требуют ERROR-логов.
  • 5xx почти всегда требуют ERROR (или выше) и должны сопровождаться контекстом.
  • Где фиксировать необработанные ошибки

    Если ошибка не была превращена в аккуратный 4xx, она может стать 5xx. Такие случаи важно:

  • логировать со стек-трейсом;
  • связывать с request id.
  • В приложениях на ASGI это нередко решается комбинацией:

  • middleware, который ловит исключения, логирует их и пробрасывает дальше;
  • глобальных обработчиков исключений, которые возвращают единый формат ответа.
  • Как связать логи одного запроса: request id

    На практике очень помогает, когда каждый запрос имеет идентификатор:

  • клиент передал X-Request-ID, и сервер его подхватил;
  • или сервер создал свой ID и вернул клиенту в ответ.
  • В Litestar это удобно делать через зависимость (как в прошлой статье) и использовать этот ID в middleware/логах. Важно, чтобы:

  • request id попадал и в ответ (заголовком);
  • request id присутствовал в логах.
  • Итоги

    В этой теме вы добавили к Litestar-приложению «каркас надёжности»:

  • научились выбирать HTTP-статусы и отличать 4xx от 5xx;
  • разобрали, как бросать HTTP-исключения и как делать доменные исключения с глобальными обработчиками;
  • увидели, как стандартизировать формат ошибок для всего API;
  • настроили базовый подход к логированию и показали пример access-log middleware.
  • Эти практики напрямую дополняют зависимости и middleware из прошлой статьи: зависимости дают контекст (например, request id), middleware и обработчики исключений делают поведение приложения предсказуемым и удобным для сопровождения.

    7. Доступ к данным: репозитории, ORM и миграции (обзор)

    Доступ к данным: репозитории, ORM и миграции (обзор)

    Связь с предыдущими темами курса

    К этому моменту у вас уже есть каркас Litestar-приложения:

  • маршруты и контроллеры
  • Request/Response и формирование ответов
  • схемы данных и валидация через Pydantic
  • зависимости, middleware и жизненный цикл приложения
  • обработка ошибок и логирование
  • Логичный следующий шаг: понять, как в таком приложении организовать доступ к данным.

    Litestar не навязывает конкретную базу данных или ORM. Обычно Litestar отвечает за HTTP-слой, а слой данных вы выбираете сами.

    !Схема типичного разделения слоёв: HTTP, бизнес‑логика и доступ к данным

    Термины, которые нужны в этой теме

  • Хранилище данных: система, где лежат данные, чаще всего база данных (PostgreSQL, SQLite и т.д.).
  • SQL: язык запросов к реляционным базам данных.
  • ORM: библиотека, которая позволяет работать с таблицами и запросами через Python-объекты.
  • Сессия: объект, через который ORM выполняет запросы и управляет транзакциями.
  • Репозиторий: слой, который прячет детали хранения и даёт понятные методы вроде get_by_id().
  • Миграции: механизм, который применяет изменения схемы базы данных версионно и воспроизводимо.
  • Способы доступа к данным в проектах на Litestar

    Варианты подхода

    | Подход | Что это | Когда подходит | Компромиссы | |---|---|---|---| | Чистый SQL | запросы строками SQL | максимальный контроль, сложные запросы | больше ручного кода, сложнее переиспользование | | Query builder | конструктор запросов без “магии” ORM | хотите контроль, но удобнее чем строки | нужно выстроить архитектуру самим | | ORM | Python-модели + запросы через ORM | типовые CRUD API, ускорение разработки | важно понимать сессии, транзакции, загрузку связей |

    Популярные библиотеки для Python

  • SQLAlchemy: де-факто стандарт ORM/SQL toolkit для Python, поддерживает sync и async. Документация SQLAlchemy
  • Alembic: миграции для SQLAlchemy. Документация Alembic
  • SQLModel: модели в стиле Pydantic + SQLAlchemy, удобен для небольших проектов. Документация SQLModel
  • Tortoise ORM: асинхронная ORM, ближе по стилю к Django ORM. Документация Tortoise ORM
  • В этом курсе мы будем держаться общего паттерна: Litestar + DI + слой сервисов + слой репозиториев, а конкретная ORM будет “сменным модулем”.

    Зачем нужен слой репозиториев

    Если писать запросы напрямую в обработчиках, быстро появляются проблемы:

  • обработчики разрастаются и смешивают HTTP-детали с SQL
  • сложно тестировать бизнес-логику без реальной базы
  • трудно менять библиотеку доступа к данным или схему хранения
  • Репозиторий решает это тем, что:

  • предоставляет методы предметной области: create_user(), list_items()
  • прячет детали ORM и SQL
  • упрощает подмену слоя данных в тестах
  • Базовая схема ролей

  • Handler/Controller: принимает запрос, валидирует вход (Pydantic), вызывает сервис, формирует ответ.
  • Service: бизнес-правила, транзакционные сценарии, преобразования.
  • Repository: операции чтения и записи в хранилище.
  • Это хорошо ложится на зависимости Litestar: сервис и репозиторий можно “вколоть” через Provide, а ресурсы (engine, sessionmaker) создать на on_startup.

    Мини-пример архитектуры без привязки к ORM

    Ниже упрощённый пример интерфейса репозитория и сервиса. Он показывает идею: обработчик не знает, как именно данные читаются и пишутся.

    Дальше вы решаете, какой будет реализация ItemsRepository: на SQLAlchemy, на Tortoise, на чистом SQL, или даже “в памяти” для тестов.

    Пример интеграции SQLAlchemy в Litestar на уровне обзора

    Ниже не “полное приложение”, а типовая схема подключения.

    Что обычно создают на запуске приложения

  • engine: объект подключения к БД
  • sessionmaker: фабрика сессий
  • Для асинхронного приложения чаще используют AsyncEngine и AsyncSession.

    Ключевая идея из прошлой темы про жизненный цикл:

  • создаём общий ресурс один раз
  • кладём в app.state
  • закрываем корректно на shutdown
  • Как “прокинуть” фабрику сессий через dependency

    Чтобы не импортировать app.state в каждый модуль, обычно делают dependency.

    И подключают в Litestar(...):

    Как выглядит репозиторий на SQLAlchemy (упрощённо)

    Репозиторий получает sessionmaker и сам управляет временем жизни сессии.

    Почему это удобно:

  • обработчики не думают о сессиях
  • репозиторий централизует доступ к данным
  • в тестах можно подменить sessionmaker или сам репозиторий
  • > В реальных проектах вы также будете продумывать транзакции: где делать commit, где делать rollback, и как объединять несколько операций в один сценарий. Обычно это ответственность сервиса или специального “unit of work”, а не каждого метода репозитория.

    ORM и Pydantic-схемы

    В прошлой статье про схемы вы использовали Pydantic для входа и выхода.

    Практическое правило:

  • модели ORM отражают таблицы и связи
  • Pydantic-схемы отражают контракт API
  • Не всегда стоит возвращать ORM-модель напрямую наружу. Причины:

  • в ORM могут быть поля, которые нельзя отдавать (например, хеш пароля)
  • структура таблиц не обязана совпадать с контрактом API
  • ORM-объекты могут тянуть связанные данные “лениво”, что иногда приводит к неожиданным запросам
  • Типовой паттерн:

  • репозиторий возвращает ORM-объект
  • сервис превращает его в доменную структуру или Pydantic-модель ответа
  • Миграции: зачем они нужны и как выглядят

    Когда приложение развивается, схема данных меняется:

  • добавили колонку
  • переименовали поле
  • создали индекс
  • вынесли таблицу для связи many-to-many
  • Если такие изменения применять вручную, неизбежны расхождения между окружениями.

    Миграции решают проблему так:

  • каждое изменение схемы фиксируется как отдельный шаг
  • шаги применяются последовательно
  • состояние базы данных становится воспроизводимым
  • Самый популярный стек для миграций в мире SQLAlchemy

  • SQLAlchemy для моделей
  • Alembic для миграций
  • Ссылки:

  • Документация Alembic
  • Документация SQLAlchemy
  • Типовой рабочий процесс с Alembic

  • Инициализировать Alembic в репозитории: alembic init alembic
  • Настроить строку подключения и импорт метаданных моделей
  • Создавать миграции при изменении моделей:
  • Применять миграции на окружение:
  • При необходимости откатывать:
  • Практическая рекомендация:

  • автогенерация ускоряет работу, но миграции нужно просматривать вручную, особенно для переименований и удаления полей
  • Где в Litestar “место” для слоя данных

    С учётом тем курса, обычно получается такая раскладка:

  • on_startup и on_shutdown: создание и закрытие engine/клиентов
  • app.state: хранение долгоживущих объектов (engine, sessionmaker)
  • dependencies: выдача репозиториев/сервисов и контекста
  • exception_handlers: преобразование доменных ошибок доступа к данным в единый формат ответа
  • middleware: логирование и, при необходимости, добавление request id для трассировки проблем с БД
  • Типовые ошибки новичков и как их избежать

  • Держать SQL/ORM в обработчиках.
  • Создавать engine или подключение на каждый запрос.
  • Делать commit в каждом методе репозитория без понимания сценария.
  • Смешивать ORM-модели и публичные схемы ответа без фильтрации.
  • Не использовать миграции и “править базу руками”.
  • Итоги

    Вы получили обзор того, как обычно строят слой данных в проектах на Litestar:

  • Litestar отвечает за HTTP, а слой данных подключается через выбранную ORM или SQL
  • репозитории помогают изолировать доступ к данным и упростить тестирование
  • зависимости и жизненный цикл приложения удобны для “прокидывания” sessionmaker и сервисов
  • миграции, чаще всего через Alembic, делают изменения схемы воспроизводимыми
  • Дальше (в практической части курса или в вашем проекте) вы обычно выбираете конкретную библиотеку (часто SQLAlchemy) и доводите этот обзор до полноценного CRUD-модуля: модели, репозитории, транзакции, миграции, тесты.