Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Документирование
documentation

Документирование

OpenAPI, Swagger, ReDoc, кастомизация документации

Открыть лабораториюv1.0Запускается локально из публичного репозитория

Документирование API в FastAPI

FastAPI автоматически генерирует OpenAPI документацию. В этой теме вы научитесь кастомизировать Swagger UI, ReDoc и создавать подробную документацию для разработчиков.

#Автоматическая документация

FastAPI автоматически генерирует документацию на основе:

  • Аннотаций типов
  • Docstring функций
  • Параметров моделей Pydantic

#Стандартные endpoints документации

EndpointОписание
/docsSwagger UI (интерактивная)
/redocReDoc (статичная, для чтения)
/openapi.jsonOpenAPI схема (JSON)

#Базовая настройка

from fastapi import FastAPI app = FastAPI( title="My API", description="Полное описание моего API", version="1.0.0", terms_of_service="https://example.com/terms/", contact={ "name": "Support", "email": "support@example.com", "url": "https://example.com/support", }, license_info={ "name": "MIT", "url": "https://opensource.org/licenses/MIT", }, docs_url="/docs", # Swagger UI redoc_url="/redoc", # ReDoc openapi_url="/openapi.json" )

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

  • title, description, version — базовая информация, отображается в Swagger UI и ReDoc
  • terms_of_service, contact, license_info — метаданные API (опционально, но полезно для публичных API)
  • docs_url, redoc_url, openapi_url — кастомизация путей к документации (можно отключить, установив None)

Результат: При открытии /docs вы увидите заголовок "My API" и описание в верхней части страницы. В ReDoc (/redoc) эта же информация отобразится в шапке.


## Документирование endpoints

### Docstring и описания

```python
from fastapi import FastAPI, Query

app = FastAPI()

@app.get(
    "/users",
    summary="Получить список пользователей",
    description="""
Получает список пользователей с поддержкой пагинации.

## Возможности

- **Пагинация**: используйте параметры skip и limit
- **Сортировка**: по имени или дате создания
- **Фильтрация**: по статусу (active/inactive)

## Пример ответа

```json
{
    "users": [...],
    "total": 100,
    "page": 1
}

""", response_description="Список пользователей" ) def get_users( skip: int = Query(0, ge=0, description="Пропустить N пользователей"), limit: int = Query(10, ge=1, le=100, description="Вернуть не более N пользователей"), status: str = Query(None, description="Фильтр по статусу: active или inactive") ): """ Получает пользователей из базы данных.

# Алгоритм
1. Применяем фильтры
2. Сортируем по дате создания
3. Возвращаем страницу
"""
...

**Что здесь происходит:**
- `summary` — короткий заголовок endpoint (отображается в Swagger UI как название операции)
- `description` — развёрнутое описание с поддержкой Markdown. Отображается при раскрытии endpoint в Swagger UI
- `response_description` — описание того, что возвращает endpoint (по умолчанию "Successful Response")
- Docstring функции — используется как fallback для `description`, если параметр `description` не задан
- `Query(..., description="...")` — описание каждого query-параметра, отображается в Swagger UI при наведении или в форме для тестирования

**Результат в Swagger UI:**
При открытии `/docs` вы увидите endpoint GET /users с заголовком "Получить список пользователей". При раскрытии — описание с Markdown-форматированием и поля для ввода skip, limit, status с подсказками.

> **Совет**: `description` в Query/Path/Body обязателен для хорошей документации. Без него пользователь не поймёт, что означает параметр.

#Документирование моделей

from pydantic import BaseModel, Field, EmailStr from typing import Literal class UserCreate(BaseModel): """ Модель для создания пользователя. Используется в endpoint POST /users """ username: str = Field( ..., min_length=3, max_length=50, description="Имя пользователя (3-50 символов, только буквы и цифры)", example="john_doe" ) email: EmailStr = Field( ..., description="Email адрес пользователя", example="john@example.com" ) password: str = Field( ..., min_length=8, description="Пароль (минимум 8 символов)", example="SecurePass123" ) role: Literal["user", "admin"] = Field( default="user", description="Роль пользователя" ) class UserResponse(BaseModel): """ Модель ответа с данными пользователя. """ id: int = Field(..., description="Уникальный ID пользователя", example=1) username: str = Field(..., description="Имя пользователя", example="john_doe") email: EmailStr = Field(..., description="Email адрес", example="john@example.com") role: str = Field(..., description="Роль пользователя", example="user") created_at: str = Field(..., description="Дата создания", example="2024-01-01T12:00:00") model_config = { "json_schema_extra": { "example": { "id": 1, "username": "john_doe", "email": "john@example.com", "role": "user", "created_at": "2024-01-01T12:00:00" } } }

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

  • Field(...) — ... (Ellipsis) означает обязательное поле. Без него клиент получит ошибку валидации
  • Field(default=...) — поле с значением по умолчанию (не обязательно при создании)
  • description — описание поля, отображается в Swagger UI и ReDoc в схеме модели
  • example — пример значения для этого поля, используется Swagger UI для автозаполнения формы
  • min_length, max_length — валидация строки + автоматическая документация ограничений
  • model_config с json_schema_extra — пример всего объекта ответа (не отдельных полей). Отображается в Swagger UI как "Example Value" в секции Responses

Разница между example в Field и json_schema_extra:

  • example в Field — пример для одного поля (используется в схеме запроса)
  • json_schema_extra["example"] — пример всего ответа (отображается в Responses)

Совет: всегда указывайте example — это позволяет пользователям Swagger UI протестировать API одним кликом (кнопка "Try it out" подставит значения).

#Кастомизация Swagger UI

#Настройка темы

from fastapi import FastAPI from fastapi.openapi.docs import get_swagger_ui_html app = FastAPI() @app.get("/docs", include_in_schema=False) async def custom_swagger_ui_html(): return get_swagger_ui_html( openapi_url="/openapi.json", title="My API - Docs", swagger_ui_parameters={ "defaultModelsExpandDepth": -1, # Скрыть модели по умолчанию "defaultModelExpandDepth": 2, # Глубина раскрытия модели "docExpansion": "list", # 'none', 'list', 'full' "filter": True, # Поиск по endpoints "showExtensions": True, # Показывать расширения "syntaxHighlight.theme": "monokai", # Тема подсветки } )

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

  • Переопределяем стандартный /docs endpoint, чтобы передать кастомные параметры
  • include_in_schema=False — сам endpoint /docs не попадёт в документацию (иначе будет рекурсия)
  • openapi_url="/openapi.json" — указываем, откуда Swagger UI берёт схему (обязательно)

Параметры Swagger UI:

  • defaultModelsExpandDepth: -1 — скрыть секцию "Schemas" (модели) по умолчанию. Полезно, если моделей много и они загромождают интерфейс
  • docExpansion: "list" — показывает только пути и методы, без описаний. Варианты: "none" (свёрнуто), "list" (пути раскрыты), "full" (всё раскрыто)
  • filter: True — добавляет строку поиска вверху Swagger UI. Можно фильтровать endpoints по названию или тегу
  • syntaxHighlight.theme — тема подсветки синтаксиса. Доступны: "monokai", "agate", "arta", "github" и другие

Результат: Swagger UI будет выглядеть иначе: строка поиска вверху, модели скрыты, подсветка кода в теме Monokai.

#OAuth2 авторизация в Swagger

from fastapi import FastAPI from fastapi.security import OAuth2PasswordBearer app = FastAPI( openapi_tags=[ { "name": "users", "description": "Операции с пользователями", }, { "name": "posts", "description": "Операции с постами", } ] ) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @app.post("/token", tags=["auth"]) async def login(): """Получить токен доступа""" ... @app.get("/users/me", tags=["users"]) async def read_users_me(): """Получить текущего пользователя""" ...

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

  • OAuth2PasswordBearer(tokenUrl="token") — сообщает FastAPI, что API использует OAuth2. Swagger UI добавит кнопку "Authorize" вверху страницы
  • tokenUrl="token" — endpoint для получения токена (POST /token). В Swagger UI при нажатии "Authorize" появится форма с полями username/password
  • openapi_tags — группировка endpoints по тегам. В Swagger UI теги отображаются как заголовки секций
  • tags=["users"] в декораторе — указывает, к какой группе относится endpoint. Один endpoint может иметь несколько тегов: tags=["users", "admin"]

Как это работает в Swagger UI:

  1. Вверху страницы появится кнопка 🔓 "Authorize"
  2. При нажатии — форма с полями username и password
  3. После ввода — POST запрос на /token, получение JWT
  4. Токен автоматически добавляется в заголовок Authorization: Bearer <token> для всех защищённых endpoints

Важно: OAuth2PasswordBearer только добавляет UI для авторизации в Swagger. Реальную проверку токена нужно реализовать через Depends(oauth2_scheme) в защищённых endpoints.


## Кастомизация ReDoc

```python
from fastapi import FastAPI
from fastapi.openapi.docs import get_redoc_html

app = FastAPI()

@app.get("/redoc", include_in_schema=False)
async def custom_redoc_html():
    return get_redoc_html(
        openapi_url="/openapi.json",
        title="My API - ReDoc",
        redoc_js_url="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
        redoc_js_settings={
            "theme": {
                "colors": {
                    "primary": {
                        "main": "#1976d2"
                    }
                }
            },
            "hideHostname": True,
            "hideDownloadButton": True,
            "requiredPropsFirst": True
        }
    )

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

  • Переопределяем стандартный /redoc endpoint для кастомизации
  • redoc_js_url — URL к JavaScript бандлу ReDoc. Можно использовать CDN или захостить локально
  • redoc_js_settings — настройки внешнего вида и поведения ReDoc

Параметры ReDoc:

  • theme.colors.primary.main — основной цвет заголовков и ссылок (брендирование)
  • hideHostname: True — скрывает базовый URL сервера (меньше шума, если домен очевиден)
  • hideDownloadButton: True — скрывает кнопку скачивания OpenAPI схемы (полезно для внутренних API)
  • requiredPropsFirst: True — обязательные поля отображаются первыми в схеме (удобнее для чтения)

ReDoc vs Swagger UI:

  • ReDoc — статичная страница, лучше для чтения и печати
  • Swagger UI — интерактивный, можно тестировать endpoints прямо в браузере
  • Рекомендуется иметь оба: Swagger UI для разработчиков, ReDoc для менеджеров/клиентов

#Группировка endpoints по тегам

from fastapi import FastAPI, APIRouter app = FastAPI( openapi_tags=[ { "name": "users", "description": "Управление пользователями", "externalDocs": { "url": "https://example.com/docs/users" } }, { "name": "posts", "description": "Управление постами" }, { "name": "auth", "description": "Аутентификация и авторизация" } ] ) # Роутер с тегами users_router = APIRouter(tags=["users"]) @users_router.get("/users") def get_users(): ... @users_router.post("/users") def create_user(): ... app.include_router(users_router) # Отдельные теги для endpoint @app.post("/login", tags=["auth"]) def login(): ...

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

  • openapi_tags — глобальное определение тегов с описаниями. Порядок в списке = порядок отображения в Swagger UI
  • externalDocs — ссылка на внешнюю документацию (например, Notion, Confluence, GitHub Wiki)
  • APIRouter(tags=["users"]) — все endpoints в этом роутере автоматически получают тег users
  • tags=["auth"] в декораторе — явное указание тега для отдельного endpoint (если не используется роутер с тегами)

Зачем группировать:

  • При 10+ endpoints документация становится нечитаемой без группировки
  • Swagger UI группирует endpoints по тегам с раскрывающимися секциями
  • ReDoc создаёт навигационное меню слева с тегами как разделами
  • Можно искать по тегам в Swagger UI (строка фильтрации)

Результат в Swagger UI:

▼ auth
  POST /login — Аутентификация и авторизация

▼ users
  GET /users — Управление пользователями
  POST /users

▼ posts
  GET /posts — Управление постами
  POST /posts

#Примеры запросов и ответов

from fastapi import FastAPI from pydantic import BaseModel, Field app = FastAPI() class Item(BaseModel): name: str = Field(..., example="Laptop") price: float = Field(..., example=999.99) description: str | None = Field(None, example="Gaming laptop") @app.post( "/items", response_model=Item, responses={ 200: { "description": "Успешное создание", "content": { "application/json": { "example": { "name": "Laptop", "price": 999.99, "description": "Gaming laptop" } } } }, 400: { "description": "Ошибка валидации", "content": { "application/json": { "example": { "detail": "Invalid input" } } } }, 422: { "description": "Ошибка валидации данных" } } ) def create_item(item: Item): """ Создать новый элемент. ## Пример запроса ```json { "name": "Laptop", "price": 999.99 } ``` """ return item

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

  • response_model=Item — указывает FastAPI, какую модель использовать для ответа. Автоматически генерируется схема в OpenAPI
  • responses — кастомизация ответов для разных HTTP статусов. Без этого параметра FastAPI документирует только успешный ответ (200) и ошибки валидации (422)
  • responses[200] — пример успешного ответа. Переопределяет автогенерированный пример из response_model
  • responses[400] — документирование кастомной ошибки (например, бизнес-логика: "нельзя создать товар с отрицательной ценой")
  • responses[422] — стандартная ошибка валидации Pydantic (обычно не требует примера, т.к. формат стандартный)

Зачем указывать responses:

  • Клиенты API знают, какие ошибки ожидать и как их обрабатывать
  • Swagger UI показывает примеры для каждого статуса в секции "Responses"
  • Можно документировать ошибки: 401 (не авторизован), 403 (нет прав), 404 (не найдено), 409 (конфликт)

Совет: документируйте как минимум 200, 401 (если нужна авторизация), и 422. Кастомные ошибки (400, 404, 409) — по необходимости.

#Скрытие endpoints из документации

from fastapi import FastAPI app = FastAPI() @app.get("/internal", include_in_schema=False) def internal_endpoint(): """Этот endpoint не будет в документации""" return {"status": "internal"} @app.get("/public") def public_endpoint(): """Этот endpoint будет в документации""" return {"status": "public"}

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

  • include_in_schema=False — полностью скрывает endpoint из OpenAPI схемы, Swagger UI и ReDoc
  • Endpoint продолжает работать, просто не документируется
  • Полезно для внутренних endpoints, health checks, debug-маршрутов

Когда скрывать endpoints:

  • Health check (/health) — не нужен клиентам API
  • Metrics (/metrics) — только для мониторинговых систем
  • Internal API (/internal/*) — для внутренних сервисов
  • Debug endpoints (/debug/*) — не должны быть видны в production
  • Webhook receivers — внешние сервисы вызывают, но не документируются

Важно: скрытый endpoint не означает защищённый! Если нужно ограничить доступ — используйте авторизацию, а не скрытие из документации.

#Дополнительная документация

#Страница документации

from fastapi import FastAPI from fastapi.responses import HTMLResponse app = FastAPI() @app.get("/", response_class=HTMLResponse, include_in_schema=False) async def root(): return """ <!DOCTYPE html> <html> <head> <title>My API Documentation</title> <style> body { font-family: Arial; max-width: 800px; margin: 0 auto; padding: 20px; } h1 { color: #333; } .links { margin: 20px 0; } .links a { display: inline-block; margin-right: 20px; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; } .links a:hover { background: #0056b3; } </style> </head> <body> <h1>My API Documentation</h1> <p>Добро пожаловать в документацию My API!</p> <div class="links"> <a href="/docs">Swagger UI</a> <a href="/redoc">ReDoc</a> <a href="/openapi.json">OpenAPI Schema</a> </div> <h2>Quick Start</h2> <pre><code> # Получить токен curl -X POST http://localhost:8000/token \\ -d "username=john&password=secret" # Использовать токен curl http://localhost:8000/users/me \\ -H "Authorization: Bearer YOUR_TOKEN" </code></pre> <h2>Authentication</h2> <p>API использует OAuth2 с JWT токенами.</p> <h2>Rate Limits</h2> <ul> <li>100 запросов в минуту для аутентифицированных</li> <li>10 запросов в минуту для анонимных</li> </ul> </body> </html> """

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

  • Кастомная landing page на корне (/) вместо стандартного 404 или JSON ответа
  • response_class=HTMLResponse — указывает FastAPI вернуть HTML, а не JSON
  • Содержит quick start примеры с curl — разработчики сразу видят, как начать работу
  • Ссылки на Swagger UI, ReDoc и OpenAPI схему — единая точка входа

Зачем создавать кастомную страницу:

  • Новый разработчик открывает http://localhost:8000 и видит инструкции, а не {"detail": "Not Found"}
  • Можно описать аутентификацию, rate limits, примеры использования
  • Для внешних API — это полноценная документация уровня "Getting Started"

Альтернатива: для серьёзных проектов используйте MkDocs, Docusaurus или Sphinx для отдельного сайта документации, а не HTML внутри кода.

#Экспорт OpenAPI схемы

from fastapi import FastAPI from fastapi.responses import Response import json import yaml app = FastAPI() # ... endpoints ... # Экспорт схемы @app.get("/export/openapi.json", include_in_schema=False) def export_openapi(): return app.openapi() @app.get("/export/openapi.yaml", include_in_schema=False) def export_openapi_yaml(): openapi_schema = app.openapi() return Response( content=yaml.dump(openapi_schema), media_type="application/x-yaml" )

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

  • app.openapi() — возвращает полную OpenAPI 3.0 схему как Python dict
  • JSON endpoint — стандартный формат, совместимый со всеми инструментами
  • YAML endpoint — удобен для чтения и коммитов в Git (лучше diff, чем JSON)

Зачем экспортировать схему:

  • Генерация клиентов (openapi-generator, swagger-codegen)
  • Валидация запросов/ответов в тестах
  • Интеграция с API-гейтами (Kong, Apigee)
  • Кастомная документация (MkDocs, Stoplight)
  • Контрактное тестирование (Pact, Schemathesis)

Совет: для CI/CD можно сохранить схему в артефакт: curl http://localhost:8000/openapi.json > openapi.json

#Генерация клиентов из OpenAPI

#OpenAPI Generator

# Python клиент openapi-generator generate -i http://localhost:8000/openapi.json -g python -o ./client # TypeScript клиент openapi-generator generate -i http://localhost:8000/openapi.json -g typescript-axios -o ./client # Java клиент openapi-generator generate -i http://localhost:8000/openapi.json -g java -o ./client

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

  • openapi-generator — утилита командной строки для генерации кода из OpenAPI схемы
  • -i — входной файл (URL или локальный путь)
  • -g — генератор (язык/фреймворк). Полный список: openapi-generator list
  • -o — выходная директория

Установка:

# macOS brew install openapi-generator # Linux/Windows (через npm) npm install @openapitools/openapi-generator-cli -g # Docker docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate ...

Что генерируется:

  • Классы для всех моделей (Pydantic, TypeScript interfaces, Java POJOs)
  • API клиент с методами для каждого endpoint
  • Типизация запросов и ответов
  • Обработка ошибок

Зачем генерировать клиентов:

  • Фронтенд разработчики получают TypeScript клиент с полной типизацией
  • Мобильные разработчики — Java/Kotlin клиент для Android
  • Тестировщики — Python клиент для интеграционных тестов
  • Не нужно вручную писать код для HTTP запросов

Совет: добавьте генерацию в CI/CD пайелайн, чтобы клиенты всегда были в sync с API.

#Пример: Полная документация

from fastapi import FastAPI, Depends, HTTPException, status from pydantic import BaseModel, Field, EmailStr from typing import List, Optional app = FastAPI( title="Blog API", description=""" ## Blog API - Полнофункциональное API для блога ### Возможности - **Пользователи**: регистрация, аутентификация, профиль - **Посты**: создание, редактирование, удаление, пагинация - **Комментарии**: добавление комментариев к постам - **Теги**: категоризация постов ### Аутентификация API использует OAuth2 с JWT токенами. Получите токен через `/token` и используйте в заголовке `Authorization: Bearer <token>`. ### Rate Limits - 100 запросов/мин для аутентифицированных - 10 запросов/мин для анонимных """, version="2.0.0", contact={ "name": "API Support", "email": "api-support@example.com", "url": "https://example.com/support" }, license_info={ "name": "MIT", "url": "https://opensource.org/licenses/MIT" } ) # === Models === class PostCreate(BaseModel): """Модель для создания поста""" title: str = Field( ..., min_length=5, max_length=200, description="Заголовок поста", example="Как изучить FastAPI" ) content: str = Field( ..., min_length=10, description="Содержимое поста", example="FastAPI — современный фреймворк..." ) tags: List[str] = Field( default=[], description="Теги поста", example=["python", "fastapi", "tutorial"] ) class PostResponse(BaseModel): """Модель ответа с постом""" id: int = Field(..., description="ID поста", example=1) title: str = Field(..., description="Заголовок", example="Как изучить FastAPI") content: str = Field(..., description="Содержимое") author_id: int = Field(..., description="ID автора", example=1) tags: List[str] = Field(..., description="Теги") created_at: str = Field(..., description="Дата создания", example="2024-01-01T12:00:00") # === Endpoints === @app.post( "/posts", response_model=PostResponse, status_code=status.HTTP_201_CREATED, tags=["posts"], summary="Создать пост", description="Создаёт новый пост в блоге", response_description="Созданный пост" ) async def create_post(post: PostCreate): """ Создаёт новый пост. ## Требования - Аутентификация обязательна - Заголовок: 5-200 символов - Содержимое: минимум 10 символов ## Пример ```json { "title": "Как изучить FastAPI", "content": "FastAPI — современный...", "tags": ["python", "fastapi"] } ``` """ ... @app.get( "/posts", response_model=List[PostResponse], tags=["posts"], summary="Получить список постов" ) async def list_posts( skip: int = Field(0, ge=0, description="Пропустить N постов"), limit: int = Field(10, ge=1, le=100, description="Вернуть не более N постов"), tag: Optional[str] = Field(None, description="Фильтр по тегу") ): """ Получает список постов с пагинацией. """ ...

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

  • Полноценное приложение с полной документацией
  • description на уровне приложения описывает возможности, аутентификацию, rate limits
  • Модели с Field(..., description, example) — каждый параметр документирован
  • Endpoints с summary, description, tags, response_model
  • Docstrings с Markdown и примерами JSON

Результат:

  • Swagger UI (/docs) — интерактивная документация с возможностью тестирования
  • ReDoc (/redoc) — красивая статичная страница для чтения
  • OpenAPI схема (/openapi.json) — для генерации клиентов и интеграций

Чеклист хорошей документации:

  • title, description, version в FastAPI()
  • summary и description для каждого endpoint
  • description и example для каждого поля в моделях
  • responses с примерами ошибок (401, 404, 400)
  • tags для группировки endpoints
  • openapi_tags с описаниями групп
  • Кастомная landing page на / (опционально)

#Что вы узнали

  • Автоматическая документация — Swagger UI, ReDoc, OpenAPI
  • Документирование endpoints — summary, description, docstring
  • Модели — Field, description, example
  • Кастомизация — тема, параметры Swagger/ReDoc
  • Теги — группировка endpoints
  • Примеры — запросов и ответов
  • Скрытие — include_in_schema=False
  • Генерация клиентов — OpenAPI Generator

#Исполняемая лаборатория

Лаборатория «OpenAPI, operation_id, tags, responses» доступна в публичном GitLab. Она проверяет кастомизацию OpenAPI схемы: operation_id, tags с описаниями, responses с примерами ошибок, include_in_schema=False для скрытых эндпоинтов и Field(description=, example=).

Склонируйте репозиторий и запустите тесты из его корня:

git clone --branch v1.0 --depth 1 https://gitlab.potapov.me/courses/fastapi_pro.git cd fastapi_pro uv sync --group test uv run --group test pytest documentation/tests

#Что дальше?

В следующей теме вы изучите Docker и деплой — контейнеризация, docker-compose, production deployment.

Далее: Docker и деплой