FastAPI остаётся одним из самых удобных способов собрать HTTP API на Python: асинхронный ASGI-сервер, валидация через Pydantic, автоматическая документация OpenAPI и минимум шаблонного кода. В 2026 году он хорошо сочетается с uv, Pydantic v2 и современными драйверами баз данных. Разберём рабочий каркас сервиса, типичные ошибки производительности и проверку API тестами.

Окружение и установка

Не смешивайте зависимости проекта с системным Python. Виртуальное окружение или uv изолируют версии и делают сборку воспроизводимой.

python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]" uvicorn
fastapi --version

Пакет fastapi[standard] приносит CLI, сервер разработки и клиентские зависимости. Для продакшена достаточно fastapi и uvicorn, но команда fastapi dev удобна локально: она следит за файлами и перезапускает процесс.

Минимальное приложение

Модели Pydantic описывают вход и выход, а response_model гарантирует, что наружу не утекут лишние поля. Это не только документация, но и защита от случайной выдачи внутренних атрибутов.

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field

app = FastAPI(title="Catalog API", version="1.0")

class ItemIn(BaseModel):
    name: str = Field(min_length=1, max_length=120)
    price: float = Field(gt=0)

class Item(ItemIn):
    id: int

DB: dict[int, Item] = {}

@app.post("/items", response_model=Item, status_code=status.HTTP_201_CREATED)
async def create_item(payload: ItemIn) -> Item:
    item = Item(id=len(DB) + 1, **payload.model_dump())
    DB[item.id] = item
    return item

Обработчик объявлен async, потому что внутри нет блокирующих вызовов. Если бы там был синхронный драйвер базы или тяжёлый расчёт, быстрее было бы объявить обычную функцию: FastAPI выполнит её в пуле потоков и не заблокирует цикл событий.

Маршруты и ошибки

HTTP-коды — часть контракта. Не возвращайте 200 на ошибку и не смешивайте «не найдено» с «неверный запрос».

@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int) -> Item:
    if item_id not in DB:
        raise HTTPException(status_code=404, detail="Item not found")
    return DB[item_id]

Удаление обычно возвращает 204 No Content без тела ответа, а повторное удаление того же объекта — тоже 204, если вы хотите сделать операцию идемпотентной. Для ошибок валидации FastAPI сам формирует 422 и подробный список полей.

Валидация типов выполняется до вызова функции: строка "abc" в пути с типом int даст 422 без вашего кода. Это удобно, но не заменяет проверку бизнес-правил: уникальность имени, права пользователя и состояние объекта проверяются отдельно.

Зависимости и база данных

Depends выносит общую логику: сессию базы, текущего пользователя, настройки. Зависимость с yield закрывает ресурс после ответа, даже если обработчик упал с исключением.

async def get_session() -> AsyncSession:
    async with SessionLocal() as session:
        yield session

Фабрику SessionLocal создают один раз при старте через async_sessionmaker, привязанный к асинхронному движку. Для PostgreSQL берите драйвер asyncpg или psycopg в асинхронном режиме. Синхронный psycopg2 внутри async-функции блокирует весь воркер: под нагрузкой это выглядит как «сервер завис» при низком CPU.

Аутентификация

Защита собственного API строится на короткоживущих токенах и проверке подписи. Пароли храните только в виде хеша с солью, например через pwdlib с Argon2. Секрет подписи держите в переменной окружения, а не в репозитории.

from fastapi.security import OAuth2PasswordBearer
import jwt

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")

async def current_user(token: str = Depends(oauth2_scheme)) -> str:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")
    return payload["sub"]

Проверяйте срок действия и аудиторию токена, а не только подпись. Для внутренних сервисов вместо самописных токенов иногда разумнее mTLS или подписанные заголовки от доверенного прокси. Не храните токены в localStorage без необходимости и не логируйте их.

Запуск и производительность

Режим разработки не предназначен для нагрузки. В продакшене запускайте несколько воркеров под управлением systemd или контейнера, а TLS и сжатие отдайте обратному прокси.

fastapi dev app/main.py
uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 4
curl -s localhost:8000/items -H "Content-Type: application/json" \
  -d '{"name": "Cable", "price": 5}'
curl -s localhost:8000/openapi.json | head -c 200

Число воркеров подбирайте по ядрам и типу нагрузки, а не копируйте «4» из статьи. Для ввода-вывода хватает одного процесса с async-кодом, для CPU-задач нужны процессы или отдельная очередь. Проверяйте задержку p95 и p99, а не только среднее значение.

CORS, тесты и схема

Если API вызывается из браузера с другого домена, добавьте CORSMiddleware и перечислите разрешённые источники явно. Не ставьте allow_origins=["*"] вместе с учётными данными: браузер такую комбинацию отклонит, а широкое разрешение откроет лишнее. Проверяйте заголовки в браузере и в тестах.

Тесты через TestClient не требуют поднятого сервера и проверяют сериализацию, коды и валидацию.

from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_create_item():
    r = client.post("/items", json={"name": "Cable", "price": 5})
    assert r.status_code == 201
    assert r.json()["id"] == 1

def test_not_found():
    assert client.get("/items/999").status_code == 404

Для асинхронных зависимостей используйте httpx.AsyncClient с транспортом ASGI. Подменяйте базу на тестовую через dependency_overrides, чтобы тесты не зависели от сети и порядка запуска.

Структура проекта

  • app/main.py — создание приложения и подключение роутеров.
  • app/api/ — маршруты, разбитые по доменам.
  • app/models.py и app/schemas.py — ORM и Pydantic-модели раздельно.
  • app/deps.py — зависимости и настройки.
  • tests/ — тесты API и бизнес-логики.

Чек-лист

  • Версии зафиксированы, окружение изолировано.
  • Для каждого маршрута задан response_model и корректный код.
  • Блокирующие вызовы не выполняются внутри async-функций.
  • Секреты в переменных окружения, токены короткоживущие.
  • Есть тесты на 401, 404, 422 и успешный сценарий.
  • Документация /docs закрыта или ограничена в продакшене.

Итог

FastAPI даёт компактный и предсказуемый каркас API: Pydantic проверяет данные, зависимости управляют ресурсами, OpenAPI описывает контракт. Основные риски связаны не с фреймворком, а с блокирующими вызовами в async-коде, слабой аутентификацией и запуском dev-сервера в продакшене. Начните с явных моделей и тестов, затем добавляйте базу, кеш и воркеры — по измеренной нагрузке.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте