Практика 8. Практическая работа 8. Документация OpenAPI/Swagger и зависимости
Цель
- Научиться работать с автодокументацией FastAPI: Swagger UI (
/docs), ReDoc (/redoc) и схемой/openapi.json. - Обогащать документацию:
summary,description,response_model, примеры (examples), теги (tags). - Освоить систему зависимостей FastAPI (
Depends): вынос общего кода в переиспользуемые зависимости (пагинация, текущий пользователь-заглушка, «подключение»). - Собрать сервис «Заметки» (
notes) без БД (хранение в памяти) с качественной документацией.
Теория
Автодокументация
FastAPI автоматически строит описание API по стандарту OpenAPI из аннотаций типов, моделей Pydantic и параметров эндпойнтов. Сразу доступны три ресурса:
- Swagger UI —
/docs— интерактивная страница: можно отправлять запросы прямо из браузера. - ReDoc —
/redoc— статичная документация, удобная для чтения. - Схема —
/openapi.json— машиночитаемое описание (по нему генерируют SDK и тесты).
URL-адреса настраиваются в конструкторе FastAPI(docs_url=..., redoc_url=...) или отключаются через None.
Обогащение схемы
Качество документации зависит от подробности описаний моделей и маршрутов:
- в
Field(...)указываютdescriptionиexamplesдля полей модели; - в декораторе маршрута задают
summary(заголовок),description(пояснение),response_model(схема ответа),status_code,tags(группировка),responses={...}(альтернативные ответы, например404).
Зависимости (Depends)
Зависимость — функция (или класс), результат которой FastAPI вычисляет до вызова эндпойнта и передаёт в его аргумент через Depends(...). Это механизм внедрения зависимостей (dependency injection). Зачем нужно:
- переиспользование общего кода (пагинация, проверка пользователя, «подключение») — пишем один раз, подключаем во многих маршрутах;
- параметры зависимостей попадают в документацию;
- зависимости могут зависеть друг от друга (вложенность);
- зависимость с
yieldвыполняет код после ответа (освобождение ресурсов).
from fastapi import Depends
def common_pagination(limit: int = 10, offset: int = 0): return {"limit": limit, "offset": offset}
@app.get("/items/")def list_items(pg: dict = Depends(common_pagination)): return pgЗадание
Подготовка
Используйте окружение из практической работы 1 (FastAPI + uvicorn). Создайте файл notes.py:
pip install fastapi uvicornuvicorn notes:app --reloadДокументация: http://127.0.0.1:8000/docs и http://127.0.0.1:8000/redoc.
Задание 1. Приложение с метаданными и модели с примерами
Создайте приложение с описанием и тегами, а в моделях добавьте description и examples — они попадут в Swagger.
from typing import Optional, Listfrom uuid import uuid4, UUID
from fastapi import FastAPI, Depends, HTTPException, Query, Header, statusfrom pydantic import BaseModel, Field
app = FastAPI( title="Notes API (Practice 8)", description="Учебный сервис заметок: документация OpenAPI и зависимости.", version="1.0.0", docs_url="/docs", redoc_url="/redoc", openapi_tags=[ {"name": "notes", "description": "Операции с заметками."}, {"name": "system", "description": "Служебные эндпойнты."}, ],)
class NoteCreate(BaseModel): title: str = Field(..., min_length=1, description="Заголовок", examples=["Купить молоко"]) text: str = Field(..., description="Текст заметки", examples=["2 литра, до пятницы"]) tags: List[str] = Field(default_factory=list, description="Метки", examples=[["дом", "покупки"]])
class Note(NoteCreate): id: UUID = Field(..., description="Идентификатор заметки") owner: str = Field(..., description="Логин владельца")
DB: dict[UUID, Note] = {}Задание 2. Обогащённые эндпойнты (summary, description, response_model, теги, responses)
Опишите создание и получение заметки максимально подробно — проверьте результат на /docs.
@app.get("/", tags=["system"], summary="Корень API")def root(): return {"msg": "Notes API. Откройте /docs."}
@app.post( "/notes/", response_model=Note, status_code=status.HTTP_201_CREATED, summary="Создать заметку", description="Создаёт новую заметку и возвращает её представление с присвоенным `id`.", tags=["notes"],)def create_note(payload: NoteCreate): note = Note(id=uuid4(), owner="demo", **payload.model_dump()) DB[note.id] = note return note
@app.get( "/notes/{note_id}", response_model=Note, summary="Получить заметку по id", tags=["notes"], responses={404: {"description": "Заметка не найдена"}},)def get_note(note_id: UUID): note = DB.get(note_id) if not note: raise HTTPException(status_code=404, detail="Note not found") return noteЗадание 3. Переиспользуемая зависимость пагинации
Вынесите параметры limit/offset в отдельную зависимость и подключите её к списку заметок. Параметры зависимости автоматически появятся в Swagger.
def pagination_params( limit: int = Query(10, ge=1, le=100, description="Сколько вернуть"), offset: int = Query(0, ge=0, description="Сколько пропустить"),) -> dict: return {"limit": limit, "offset": offset}
@app.get("/notes/", response_model=List[Note], summary="Список заметок", tags=["notes"])def list_notes(pg: dict = Depends(pagination_params)): items = list(DB.values()) return items[pg["offset"] : pg["offset"] + pg["limit"]]Задание 4. Зависимость «текущий пользователь» (заглушка)
Сделайте зависимость, которая «определяет» пользователя по заголовку X-Token (без реальной аутентификации — это заглушка). Подключите её к защищённому эндпойнту.
class User(BaseModel): username: str is_admin: bool = False
# заглушка «базы пользователей»FAKE_USERS = {"secret-token": User(username="demo", is_admin=True)}
def get_current_user(x_token: Optional[str] = Header(None, description="Токен доступа")) -> User: user = FAKE_USERS.get(x_token) if user is None: raise HTTPException(status_code=401, detail="Invalid or missing X-Token") return user
@app.get("/notes/me/profile", summary="Текущий пользователь", tags=["notes"])def my_profile(user: User = Depends(get_current_user)): my_notes = [n for n in DB.values() if n.owner == user.username] return {"user": user, "notes_count": len(my_notes)}В Swagger у этого эндпойнта появится поле для заголовка X-Token. Запрос без верного токена вернёт 401.
Задание 5. Вложенные зависимости и зависимость с yield
Покажите, что зависимости комбинируются: require_admin зависит от get_current_user. А зависимость-«подключение» с yield выполняет код после ответа.
def require_admin(user: User = Depends(get_current_user)) -> User: if not user.is_admin: raise HTTPException(status_code=403, detail="Admin rights required") return user
def get_connection(): conn = {"status": "open"} # имитация открытия ресурса print("CONNECT") try: yield conn finally: conn["status"] = "closed" # код после ответа print("DISCONNECT")
@app.delete("/notes/{note_id}", status_code=204, summary="Удалить заметку", tags=["notes"])def delete_note( note_id: UUID, admin: User = Depends(require_admin), conn: dict = Depends(get_connection),): if note_id not in DB: raise HTTPException(status_code=404, detail="Note not found") del DB[note_id] returnПодсказка: одну и ту же зависимость (
get_current_user) FastAPI в рамках запроса по умолчанию вычисляет один раз и кэширует результат.
Проверка
- На
/docsпроверьте группировку по тегам,summary, описания и примеры; сравните с/redocи/openapi.json. - Создайте 3–5 заметок, проверьте пагинацию (
limit,offset). - Вызовите
/notes/me/profileсX-Token: secret-tokenи без него (ожидайте401); удалите заметку и посмотрите в консолиCONNECT/DISCONNECT.
Критерии оценки
- 20% — приложение запускается, доступны
/docs,/redoc,/openapi.json; заданыtitle,description,version, теги. - 20% — модели обогащены:
Fieldсdescriptionиexamples; эндпойнты используютresponse_model,summary,description,tags,responses. - 20% — зависимость пагинации вынесена и переиспользуется через
Depends; параметры видны в Swagger. - 20% — зависимость «текущий пользователь» (заглушка по
X-Token) работает; неверный токен даёт401. - 15% — вложенная зависимость
require_admin(403без прав) и зависимость сyield(код после ответа). - 5% — чистота кода, осмысленные имена, понятные описания в документации.
Вопросы для самопроверки
- По каким источникам FastAPI генерирует схему OpenAPI? Чем отличаются
/docs,/redocи/openapi.json? - Как задать короткий заголовок и подробное описание эндпойнта в документации? За что отвечает
response_model? - Как добавить пример значения для поля модели, чтобы он отобразился в Swagger?
- Зачем нужны теги (
tags) и как их описать на уровне приложения? - Что такое зависимость и в какой момент FastAPI её вычисляет? Что делает
Depends? - Как вынести параметры пагинации в переиспользуемую зависимость? Почему её параметры видны в документации?
- Чем удобна зависимость-заглушка «текущий пользователь»? Какой код состояния вернуть при отсутствии/неверности токена?
- Как организовать вложенные зависимости (одна зависит от другой) на примере
require_admin? - Что даёт зависимость с
yieldи когда выполняется код послеyield? - Кэшируется ли результат одной и той же зависимости в пределах запроса? Как это влияет на повторные вызовы?