Перейти к содержимому

Практика 8. Практическая работа 8. Документация OpenAPI/Swagger и зависимости

Цель

  1. Научиться работать с автодокументацией FastAPI: Swagger UI (/docs), ReDoc (/redoc) и схемой /openapi.json.
  2. Обогащать документацию: summary, description, response_model, примеры (examples), теги (tags).
  3. Освоить систему зависимостей FastAPI (Depends): вынос общего кода в переиспользуемые зависимости (пагинация, текущий пользователь-заглушка, «подключение»).
  4. Собрать сервис «Заметки» (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 uvicorn
uvicorn 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, List
from uuid import uuid4, UUID
from fastapi import FastAPI, Depends, HTTPException, Query, Header, status
from 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 в рамках запроса по умолчанию вычисляет один раз и кэширует результат.

Проверка

  1. На /docs проверьте группировку по тегам, summary, описания и примеры; сравните с /redoc и /openapi.json.
  2. Создайте 3–5 заметок, проверьте пагинацию (limit, offset).
  3. Вызовите /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% — чистота кода, осмысленные имена, понятные описания в документации.

Вопросы для самопроверки

  1. По каким источникам FastAPI генерирует схему OpenAPI? Чем отличаются /docs, /redoc и /openapi.json?
  2. Как задать короткий заголовок и подробное описание эндпойнта в документации? За что отвечает response_model?
  3. Как добавить пример значения для поля модели, чтобы он отобразился в Swagger?
  4. Зачем нужны теги (tags) и как их описать на уровне приложения?
  5. Что такое зависимость и в какой момент FastAPI её вычисляет? Что делает Depends?
  6. Как вынести параметры пагинации в переиспользуемую зависимость? Почему её параметры видны в документации?
  7. Чем удобна зависимость-заглушка «текущий пользователь»? Какой код состояния вернуть при отсутствии/неверности токена?
  8. Как организовать вложенные зависимости (одна зависит от другой) на примере require_admin?
  9. Что даёт зависимость с yield и когда выполняется код после yield?
  10. Кэшируется ли результат одной и той же зависимости в пределах запроса? Как это влияет на повторные вызовы?