FastAPI: как устроен современный Python API
FastAPI - Python-фреймворк для создания HTTP API.
С его помощью можно написать сервер, который:
- принимает запросы от сайта, мобильного приложения или другого сервиса;
- проверяет входные данные;
- выполняет программную логику;
- обращается к базе данных и внешним API;
- возвращает JSON;
- автоматически создаёт документацию OpenAPI.
Минимальное приложение выглядит так:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, world"}
Здесь всего несколько строк, но за ними скрывается полный путь HTTP-запроса:
Клиент
-
сетевой сервер
-
маршрутизация
-
обработка параметров
-
функция Python
-
формирование HTTP-ответа
FastAPI не заменяет Python и не является отдельным сервером базы данных. Он связывает HTTP с обычными Python-функциями и типами.
HTTP-методы GET, POST, PUT, PATCH, DELETE и QUERY: примеры запросов
Какую задачу решает FastAPI
Обычная функция Python вызывается из другой части программы:
def calculate_total(price: float, quantity: int) -> float:
return price * quantity
total = calculate_total(1200, 3)
Но браузер или внешний сервис не может напрямую вызвать эту функцию. Он умеет отправлять HTTP-запросы.
Нужно сопоставить:
HTTP-запрос
-
Python-функция
FastAPI создаёт такое сопоставление.
@app.get("/total")
def calculate_total(
price: float,
quantity: int,
) -> dict[str, float]:
return {
"total": price * quantity,
}
Запрос:
GET /total?price=1200&quantity=3
Ответ:
{
"total": 3600.0
}
Фреймворк выполняет несколько действий:
- Находит функцию, связанную с
GET /total. - Извлекает
priceиquantityиз query-параметров. - Преобразует значения из строк в
floatиint. - Проверяет типы.
- Вызывает функцию.
- Преобразует словарь в JSON.
- Формирует HTTP-ответ.
Именно автоматическая связь между HTTP, аннотациями типов и Python-кодом является центральной идеей FastAPI.
FastAPI, Uvicorn, Starlette и Pydantic
FastAPI-приложение состоит не из одного компонента.
FastAPI
FastAPI предоставляет:
- маршрутизацию;
- описание параметров;
- dependency injection;
- интеграцию с OpenAPI;
- обработку ошибок;
- response models;
- инструменты безопасности.
Starlette
FastAPI построен поверх Starlette.
Starlette предоставляет более низкоуровневые веб-возможности:
- ASGI-приложение;
- запросы и ответы;
- middleware;
- WebSocket;
- фоновые задачи;
- статические файлы;
- тестовый клиент.
Обычно разработчик работает через интерфейс FastAPI, но многие объекты и механизмы приходят из Starlette.
Pydantic
Pydantic отвечает за работу с данными:
- проверяет типы;
- преобразует совместимые значения;
- создаёт модели;
- формирует понятные ошибки;
- сериализует результат.
Например, строка "42" может быть преобразована в целое число, если поле объявлено как int.
Uvicorn
Uvicorn - ASGI-сервер.
Он:
- открывает сетевой порт;
- принимает HTTP-соединения;
- преобразует входящие данные в ASGI-сообщения;
- передаёт их FastAPI-приложению;
- отправляет ответ клиенту.
Упрощённо:
Uvicorn - принимает сетевой запрос
FastAPI - решает, какая функция должна его обработать
FastAPI можно запускать не только через Uvicorn, но он является стандартным и распространённым вариантом.
Что такое ASGI
ASGI - интерфейс между Python-веб-приложением и сервером.
Он определяет, как сервер передаёт приложению:
- HTTP-запросы;
- WebSocket-соединения;
- события запуска и остановки.
До ASGI широко использовался WSGI. Он хорошо подходит для классических синхронных приложений, но изначально не рассчитан на WebSocket и современную асинхронную модель.
ASGI позволяет приложению эффективно работать с большим количеством операций ожидания:
- запросами к внешним API;
- обращениями к асинхронной базе данных;
- ожиданием сообщений;
- WebSocket-соединениями;
- потоковой передачей.
Важно:
ASGI не делает любой код быстрым автоматически. Он создаёт интерфейс, позволяющий использовать асинхронность и долгоживущие соединения.
Установка и запуск
Для нового проекта:
python -m venv .venv
Активация в Linux и macOS:
source .venv/bin/activate
В Windows PowerShell:
.venv\Scripts\Activate.ps1
Установка стандартного набора зависимостей:
pip install "fastapi[standard]"
Создадим main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {
"message": "FastAPI works",
}
Запуск для разработки:
fastapi dev main.py
После запуска приложение обычно доступно по адресу:
http://127.0.0.1:8000
Интерактивная документация:
http://127.0.0.1:8000/docs
Альтернативное представление документации:
http://127.0.0.1:8000/redoc
Для production используется другой режим запуска и отдельная конфигурация окружения.
Объект приложения
app = FastAPI()
app - экземпляр FastAPI-приложения.
В нём регистрируются:
- маршруты;
- middleware;
- обработчики ошибок;
- зависимости;
- события жизненного цикла;
- настройки OpenAPI.
Сервер импортирует именно этот объект.
Запись:
main:app
означает:
main
-
модуль main.py
app
-
объект внутри модуля
Маршрут и path operation
Рассмотрим:
@app.get("/users")
async def get_users():
return []
Декоратор
@app.get("/users")
Декоратор сообщает FastAPI:
HTTP-метод
-
GET
Путь
-
/users
Обработчик
-
get_users
Когда приходит GET /users, вызывается функция get_users().
Такая связка пути, метода и функции называется path operation.
Почему важен HTTP-метод
Один и тот же путь может поддерживать разные операции:
@app.get("/users")
async def get_users():
return []
@app.post("/users")
async def create_user():
return {"created": True}
GET /users - получить пользователей
POST /users - создать пользователя
FastAPI не заставляет строго следовать REST, но корректное использование методов делает API понятнее.
Параметры пути
Путь может содержать переменную:
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {
"user_id": user_id,
}
Запрос:
GET /users/42
FastAPI извлекает 42, преобразует в int и передаёт функции.
Если клиент отправит:
GET /users/abc
он получит ошибку валидации, потому что abc нельзя преобразовать в int.
Параметр пути подходит для идентификации конкретного ресурса:
/users/42
/orders/105
/products/phone-15
Query-параметры
Параметры функции, которых нет в пути, обычно интерпретируются как query-параметры.
@app.get("/products")
async def get_products(
limit: int = 20,
offset: int = 0,
search: str | None = None,
):
return {
"limit": limit,
"offset": offset,
"search": search,
}
Запрос:
GET /products?limit=10&offset=20&search=phone
Query-параметры удобно использовать для:
- фильтрации;
- поиска;
- сортировки;
- пагинации;
- необязательных настроек.
Значение по умолчанию делает параметр необязательным:
limit: int = 20
Параметр без значения по умолчанию становится обязательным:
limit: int
Проверка и ограничения параметров
Тип int говорит, какое значение ожидается. Но часто нужны дополнительные ограничения.
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/products")
async def get_products(
limit: Annotated[
int,
Query(ge=1, le=100),
] = 20,
):
return {
"limit": limit,
}
Ограничения:
ge=1 - значение не меньше 1
le=100 - значение не больше 100
Если клиент передаст limit=1000, FastAPI вернёт структурированную ошибку.
Annotated позволяет соединить Python-тип с дополнительной информацией FastAPI, не смешивая тип и значение по умолчанию.
Тело запроса и модели Pydantic
Для сложных данных используют JSON body.
Создадим модель:
from pydantic import BaseModel, Field
class ProductCreate(BaseModel):
name: str = Field(min_length=2, max_length=100)
price: float = Field(gt=0)
quantity: int = Field(ge=0)
description: str | None = None
Endpoint:
@app.post("/products")
async def create_product(
product: ProductCreate,
):
return {
"product": product,
}
Запрос:
POST /products
Content-Type: application/json
{
"name": "Keyboard",
"price": 4900,
"quantity": 12
}
FastAPI понимает, что product является сложной Pydantic-моделью, поэтому данные нужно взять из тела запроса.
Что делает Pydantic
Pydantic:
- Читает JSON.
- Проверяет наличие обязательных полей.
- Проверяет типы.
- Применяет ограничения.
- Создаёт объект
ProductCreate. - Передаёт его функции.
Внутри endpoint доступен обычный Python-объект:
product.name
product.price
product.quantity
Ошибка валидации
Если клиент отправит:
{
"name": "K",
"price": -100,
"quantity": -2
}
данные нарушают сразу несколько ограничений.
FastAPI вернёт ответ с кодом 422 и описанием ошибок.
Упрощённо:
{
"detail": [
{
"loc": ["body", "name"],
"msg": "String should have at least 2 characters"
},
{
"loc": ["body", "price"],
"msg": "Input should be greater than 0"
}
]
}
Такой ответ полезен и клиенту, и разработчику: он показывает, где именно находится проблема.
Входная и выходная модели
Одну модель не всегда стоит использовать и для входа, и для ответа.
Например, клиент отправляет пароль, но API не должен возвращать его обратно.
class UserCreate(BaseModel):
email: str
password: str
class UserPublic(BaseModel):
id: int
email: str
Endpoint:
@app.post(
"/users",
response_model=UserPublic,
)
async def create_user(
user: UserCreate,
):
created_user = {
"id": 101,
"email": user.email,
"password": user.password,
}
return created_user
Хотя функция вернула словарь с паролем, response_model=UserPublic ограничит ответ:
{
"id": 101,
"email": "user@example.com"
}
Response model:
- проверяет выходные данные;
- фильтрует лишние поля;
- формирует схему документации;
- фиксирует публичный контракт API.
Это не замена правильной работе с секретами, но важный дополнительный слой защиты.
HTTP-статусы и ошибки
Если пользователь не найден, нельзя возвращать обычный объект с текстом ошибки и статусом 200.
FastAPI предоставляет HTTPException.
from fastapi import HTTPException
@app.get("/users/{user_id}")
async def get_user(user_id: int):
if user_id != 1:
raise HTTPException(
status_code=404,
detail="User not found",
)
return {
"id": 1,
"name": "Alice",
}
Ответ:
HTTP/1.1 404 Not Found
{
"detail": "User not found"
}
Типичные статусы:
200 OK - успешное получение
201 Created - ресурс создан
204 No Content - успешно, но тело отсутствует
400 Bad Request - ошибка в логике запроса
401 Unauthorized - не выполнена аутентификация
403 Forbidden - доступ запрещён
404 Not Found - ресурс не найден
409 Conflict - конфликт состояния
422 Unprocessable Content - данные не прошли валидацию
500 Internal Server Error - необработанная ошибка сервера
Dependency injection
Во многих endpoint повторяются одинаковые действия:
- получить подключение к базе;
- проверить токен;
- загрузить пользователя;
- проверить права;
- прочитать конфигурацию.
Копировать эту логику в каждую функцию неудобно.
FastAPI использует dependency injection.
from typing import Annotated
from fastapi import Depends, Header, HTTPException
def verify_api_key(
x_api_key: Annotated[str, Header()],
) -> str:
if x_api_key != "secret":
raise HTTPException(
status_code=401,
detail="Invalid API key",
)
return x_api_key
Подключение зависимости:
@app.get("/private")
async def private_data(
api_key: Annotated[
str,
Depends(verify_api_key),
],
):
return {
"access": True,
}
Перед вызовом private_data() FastAPI:
- Вызывает
verify_api_key(). - Извлекает заголовок.
- Проверяет ключ.
- Передаёт результат в endpoint.
Зависимость сама может иметь зависимости. Так создаётся дерево подготовки запроса.
Для чего применяют зависимости
- аутентификация;
- авторизация;
- сессии базы данных;
- общие query-параметры;
- rate limiting;
- конфигурация;
- журналирование;
- загрузка текущего пользователя.
Dependency injection помогает отделить инфраструктурную логику от основной задачи endpoint.
def или async def
FastAPI поддерживает оба варианта:
@app.get("/sync")
def sync_endpoint():
return {"mode": "sync"}
@app.get("/async")
async def async_endpoint():
return {"mode": "async"}
Когда полезен async def
Асинхронная функция особенно полезна, если она ждёт I/O:
- HTTP-запрос;
- асинхронную базу данных;
- файл;
- очередь;
- таймер.
import httpx
@app.get("/external")
async def get_external_data():
async with httpx.AsyncClient() as client:
response = await client.get(
"https://example.com/api/data",
)
return response.json()
Во время await event loop может обслуживать другие запросы.
Когда async не помогает
Тяжёлое вычисление CPU:
def calculate_large_matrix():
...
не становится быстрее только из-за async def.
Если внутри асинхронного endpoint выполнить долгую синхронную операцию, она может заблокировать event loop.
Для CPU-bound задач используют:
- отдельный процесс;
- очередь задач;
- worker;
- специализированный вычислительный сервис.
Практическое правило
- Асинхронная библиотека - используйте
async defиawait. - Синхронная библиотека - обычный
defчасто безопаснее. - Не вызывайте долгий блокирующий код напрямую внутри
async def.
FastAPI умеет выполнять обычные def-обработчики в thread pool, чтобы они не блокировали основной event loop.
Middleware
Middleware оборачивает обработку каждого запроса.
Запрос
↓
middleware
↓
endpoint
↓
middleware
↓
ответ
Пример измерения времени:
import time
from fastapi import Request
@app.middleware("http")
async def add_process_time(
request: Request,
call_next,
):
started_at = time.perf_counter()
response = await call_next(request)
process_time = (
time.perf_counter() - started_at
)
response.headers["X-Process-Time"] = str(
process_time
)
return response
Middleware подходит для сквозных задач:
- логирование;
- CORS;
- измерение времени;
- трассировка;
- общие заголовки;
- обработка request ID.
Не стоит переносить в middleware всю бизнес-логику. Его задача - обработка, общая для множества маршрутов.
CORS
Браузер применяет политику same-origin.
Если frontend работает на:
https://app.example.com
а API на:
https://api.example.com
браузер считает их разными origin.
Для разрешения запросов настраивается CORS middleware.
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://app.example.com",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
CORS - механизм браузера. Он не является полноценной системой аутентификации и не защищает API от запросов, отправленных не из браузера.
Preflight - предварительный запрос OPTIONS, которым браузер проверяет, разрешён ли будущий запрос.
Фоновые задачи
Небольшую операцию можно запустить после формирования ответа.
from fastapi import BackgroundTasks
def send_email(
email: str,
message: str,
) -> None:
print("Sending email:", email, message)
@app.post("/notifications")
async def create_notification(
email: str,
background_tasks: BackgroundTasks,
):
background_tasks.add_task(
send_email,
email,
"Notification created",
)
return {
"accepted": True,
}
Клиент получает ответ, а задача выполняется после него.
BackgroundTasks подходят для небольших локальных действий:
- отправить письмо;
- записать лог;
- обработать небольшой файл.
Они не заменяют полноценную очередь.
Если задача:
- длительная;
- критически важная;
- должна пережить перезапуск;
- требует повторных попыток;
- выполняется на другом сервере,
лучше использовать отдельную систему фоновых задач.
Жизненный цикл приложения
Приложению может потребоваться создать ресурс при запуске и закрыть его при остановке:
- пул соединений;
- HTTP-клиент;
- модель машинного обучения;
- кэш;
- очередь.
Для этого используют lifespan.
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
print("Application started")
yield
print("Application stopped")
app = FastAPI(lifespan=lifespan)
Код до yield выполняется при запуске, после yield - при завершении.
Структура проекта
Один main.py подходит для обучения, но крупное приложение лучше разделить.
app/
├── __init__.py
├── main.py
├── api/
│ ├── __init__.py
│ ├── users.py
│ └── products.py
├── schemas/
│ ├── user.py
│ └── product.py
├── services/
│ ├── users.py
│ └── payments.py
├── repositories/
│ └── users.py
├── dependencies/
│ └── auth.py
├── core/
│ ├── config.py
│ └── security.py
└── db/
└── session.py
Это не единственная правильная структура.
Полезное разделение:
routers - HTTP-слой
schemas - форматы входа и выхода
services - бизнес-логика
repositories - работа с хранилищем
dependencies - повторно используемая подготовка запроса
Endpoint не должен превращаться в функцию на несколько сотен строк.
APIRouter
Маршруты можно вынести в отдельный модуль.
app/api/users.py:
from fastapi import APIRouter
router = APIRouter(
prefix="/users",
tags=["users"],
)
@router.get("/")
async def get_users():
return []
@router.get("/{user_id}")
async def get_user(user_id: int):
return {
"id": user_id,
}
Подключение в main.py:
from fastapi import FastAPI
from app.api.users import router as users_router
app = FastAPI()
app.include_router(users_router)
APIRouter помогает группировать:
- пути;
- tags;
- зависимости;
- префиксы;
- response models.
OpenAPI и автоматическая документация
FastAPI строит OpenAPI-схему на основании:
- маршрутов;
- HTTP-методов;
- параметров;
- моделей Pydantic;
- response models;
- статусов;
- описаний.
Схема обычно доступна по адресу:
/openapi.json
На её основе работают:
/docs - Swagger UI
/redoc - ReDoc
Документация не является просто красивой страницей. OpenAPI можно использовать для:
- генерации клиента;
- тестирования;
- согласования контракта с frontend;
- интеграции с API gateway;
- проверки изменений схемы.
Описание endpoint
@app.post(
"/products",
response_model=ProductPublic,
status_code=201,
summary="Create product",
tags=["products"],
)
async def create_product(
product: ProductCreate,
):
...
Эти данные попадут в OpenAPI.
Работа с базой данных
FastAPI не содержит встроенной ORM.
Можно использовать:
- SQLAlchemy;
- SQLModel;
- asyncpg;
- psycopg;
- другие библиотеки.
Это важное архитектурное разделение:
FastAPI - HTTP-слой
ORM или драйвер - работа с базой данных
Типовой endpoint не должен сам создавать новое соединение без управления жизненным циклом.
Подключение часто передают через dependency:
def get_session():
session = create_session()
try:
yield session
finally:
session.close()
FastAPI выполняет код до yield, передаёт ресурс endpoint, а затем выполняет очистку.
Аутентификация и авторизация
Эти понятия нельзя смешивать.
Аутентификация
Отвечает на вопрос:
Кто выполняет запрос?
Примеры:
- API key;
- логин и пароль;
- session cookie;
- JWT;
- OAuth2.
Авторизация
Отвечает:
Что этому пользователю разрешено?
Например:
обычный пользователь - читает собственный профиль
администратор - просматривает всех пользователей
FastAPI предоставляет инструменты описания security schemes и интеграцию с OpenAPI, но правила доступа всё равно проектирует разработчик.
Тестирование
FastAPI-приложение можно тестировать без запуска отдельного внешнего сервера.
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_root() -> None:
response = client.get("/")
assert response.status_code == 200
assert response.json() == {
"message": "Hello, world",
}
Тест может проверять:
- статус;
- JSON;
- заголовки;
- авторизацию;
- ошибки валидации;
- зависимости;
- работу маршрута.
Подмена зависимости
Dependency injection упрощает тесты.
Например, реальную базу можно заменить тестовой зависимостью:
app.dependency_overrides[get_session] = (
get_test_session
)
После теста overrides нужно очистить.
Обработка исключений
Можно зарегистрировать собственный обработчик.
from fastapi import Request
from fastapi.responses import JSONResponse
class DomainError(Exception):
def __init__(self, message: str):
self.message = message
@app.exception_handler(DomainError)
async def domain_error_handler(
request: Request,
exc: DomainError,
):
return JSONResponse(
status_code=409,
content={
"error": "domain_error",
"message": exc.message,
},
)
Это помогает создать единый формат ошибок.
Но не следует показывать клиенту внутренний traceback, SQL-запросы или секретные данные.
Что происходит при одном запросе
Соберём полный путь.
Запрос:
POST /products?notify=true
Authorization: Bearer ...
Content-Type: application/json
{
"name": "Keyboard",
"price": 4900,
"quantity": 12
}
Последовательность:
1. Uvicorn принимает соединение.
2. ASGI передаёт запрос приложению.
3. Middleware выполняет общую обработку.
4. FastAPI находит POST /products.
5. Dependencies проверяют токен.
6. Query-параметр notify преобразуется в bool.
7. JSON проверяется моделью ProductCreate.
8. Вызывается endpoint.
9. Service выполняет бизнес-логику.
10. Repository сохраняет данные.
11. Response model фильтрует ответ.
12. FastAPI сериализует JSON.
13. Middleware обрабатывает ответ.
14. Uvicorn отправляет его клиенту.
Когда эта последовательность понятна, FastAPI перестаёт выглядеть как набор магических декораторов.
Типичные ошибки
Вся логика находится в endpoint
@app.post("/orders")
async def create_order(...):
# 300 строк
Лучше оставить в HTTP-слое получение данных и формирование ответа, а бизнес-логику вынести.
Pydantic-модели смешаны с ORM-моделями
Формат API и структура базы решают разные задачи. Иногда их можно связать, но не нужно автоматически считать одним и тем же объектом.
Секреты находятся в коде
API_KEY = "real-secret"
Секреты хранят в переменных окружения или специализированном хранилище.
Блокирующий код вызывается внутри async def
Это может остановить обработку других запросов текущим event loop.
Ошибки всегда возвращаются с 200
HTTP-статус является частью контракта.
Пароль возвращается в response
Используйте отдельные входные и выходные модели.
Нет таймаутов внешних запросов
Обращение к другому сервису может зависнуть и занять все рабочие ресурсы.
Создаётся новый клиент или пул на каждый запрос
Долгоживущие ресурсы лучше создавать на lifespan и переиспользовать.
Документация считается защитой
Скрытие /docs не заменяет аутентификацию.
FastAPI и вебхуки
FastAPI хорошо подходит для приёма webhook.
@app.post("/webhooks/payment")
async def payment_webhook(
request: Request,
):
raw_body = await request.body()
# Проверка подписи по raw_body
# Проверка event_id
# Сохранение события
return {
"received": True,
}
Но FastAPI решает только HTTP-часть.
Для надёжной webhook-интеграции отдельно нужны:
- проверка подписи;
- идемпотентность;
- хранение event ID;
- быстрый ответ;
- фоновые задачи или очередь;
- retry;
- логирование.
FastAPI и машинное обучение
Модель машинного обучения можно предоставить через HTTP API.
Клиент
↓
POST /predict
↓
FastAPI
↓
подготовка признаков
↓
ML-модель
↓
прогноз
Пример:
class PredictionRequest(BaseModel):
age: int
income: float
purchases: int
class PredictionResponse(BaseModel):
probability: float
predicted_class: int
@app.post(
"/predict",
response_model=PredictionResponse,
)
async def predict(
data: PredictionRequest,
):
probability = 0.82
return PredictionResponse(
probability=probability,
predicted_class=int(
probability >= 0.5
),
)
Модель лучше загрузить один раз при старте приложения, а не читать с диска на каждый запрос.
FastAPI не обучает ML-модель. Он предоставляет сетевой интерфейс к уже написанной логике.
Разработка и production
Команда разработки обычно включает автоматическую перезагрузку:
fastapi dev
Production-среда отличается:
- отключена автоматическая перезагрузка;
- настроены HTTPS и reverse proxy;
- определено число процессов;
- включено логирование;
- есть мониторинг;
- заданы таймауты;
- секреты приходят из окружения;
- выполняются миграции базы;
- настроена корректная остановка;
- используются health checks.
Нельзя просто запустить development server и считать систему готовой к нагрузке.
Несколько процессов
Один процесс Python использует собственную память.
Если запустить несколько workers:
worker 1
worker 2
worker 3
каждый загрузит собственный экземпляр:
- кэша;
- глобальных переменных;
- ML-модели;
- соединений.
Это важно учитывать при проектировании памяти и состояния.
Глобальный set или словарь не является общей базой для нескольких workers.
Когда FastAPI подходит
FastAPI хорошо подходит для:
- REST API;
- webhook endpoint;
- backend мобильного приложения;
- внутренних микросервисов;
- ML inference API;
- интеграционного слоя;
- сервисов автоматизации;
- WebSocket-приложений;
- административного backend.
Когда нужен другой инструмент
FastAPI не обязательно является лучшим выбором, если:
- нужен большой серверный HTML-проект с готовой админкой;
- требуется только простой одноразовый скрипт;
- задача решается serverless-функцией;
- команда использует другую зрелую платформу;
- основная нагрузка является тяжёлым CPU-вычислением и HTTP-слой вторичен.
Фреймворк выбирают под архитектуру, команду и требования, а не только по скорости написания первого endpoint.
Минимальный проект целиком
main.py:
from typing import Annotated
from fastapi import (
Depends,
FastAPI,
HTTPException,
Query,
)
from pydantic import BaseModel, Field
app = FastAPI(
title="Products API",
version="1.0.0",
)
class ProductCreate(BaseModel):
name: str = Field(
min_length=2,
max_length=100,
)
price: float = Field(gt=0)
quantity: int = Field(ge=0)
class ProductPublic(ProductCreate):
id: int
products: dict[int, ProductPublic] = {}
def get_next_id() -> int:
return len(products) + 1
@app.post(
"/products",
response_model=ProductPublic,
status_code=201,
)
async def create_product(
product: ProductCreate,
product_id: Annotated[
int,
Depends(get_next_id),
],
):
created = ProductPublic(
id=product_id,
**product.model_dump(),
)
products[product_id] = created
return created
@app.get(
"/products",
response_model=list[ProductPublic],
)
async def get_products(
limit: Annotated[
int,
Query(ge=1, le=100),
] = 20,
):
return list(products.values())[:limit]
@app.get(
"/products/{product_id}",
response_model=ProductPublic,
)
async def get_product(
product_id: int,
):
product = products.get(product_id)
if product is None:
raise HTTPException(
status_code=404,
detail="Product not found",
)
return product
Этот пример показывает:
- объект приложения;
- path operations;
- GET и POST;
- path и query-параметры;
- модели Pydantic;
- response model;
- dependency;
- HTTP-ошибку;
- автоматическую документацию.
Словарь используется только для демонстрации. После перезапуска данные исчезнут, а несколько workers не будут видеть общее состояние. В реальном проекте понадобится база данных.
Итог
FastAPI связывает HTTP-запросы с Python-функциями и аннотациями типов.
Основной путь:
HTTP-запрос
-
ASGI-сервер
-
маршрут FastAPI
-
зависимости
-
валидация Pydantic
-
Python-функция
-
response model
-
JSON-ответ
FastAPI отвечает за HTTP-слой:
- маршрутизацию;
- параметры;
- валидацию;
- зависимости;
- документацию;
- обработку ошибок;
- формирование ответа.
Uvicorn принимает сетевые соединения. Starlette предоставляет ASGI-фундамент. Pydantic проверяет и сериализует данные.
Для простого приложения достаточно нескольких функций. Для серьёзной системы дополнительно нужны:
- архитектурное разделение;
- база данных;
- аутентификация и авторизация;
- тесты;
- таймауты;
- логирование;
- мониторинг;
- корректное развёртывание;
- обработка фоновых задач.
Главная ценность FastAPI не в том, что он полностью скрывает устройство backend. Наоборот, он позволяет достаточно явно описать контракт API средствами Python:
путь
+
HTTP-метод
+
типы
+
модели данных
+
зависимости
+
формат ответа
Когда понятен путь одного запроса от Uvicorn до функции и обратно, остальные возможности FastAPI складываются в единую систему.