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

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

@potapov_me

Платформа

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

Контент

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

Компания

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

Аккаунт

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

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

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Версионирование API
versioning

Версионирование API

Стратегии версионирования, обратная совместимость

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

Версионирование API в FastAPI

Версионирование позволяет изменять API, сохраняя обратную совместимость. В этой теме вы изучите стратегии версионирования, управление изменениями и лучшие практики.

#Зачем нужно версионирование?

Версионирование API — механизм управления изменениями, позволяющий клиентам продолжать работать со старой версией, пока они не обновятся.

#Когда менять версию?

ИзменениеТипВерсия
Добавление нового поляОбратноеНе требуется
Добавление нового endpointОбратноеНе требуется
Удаление поляОбратноеТребуется
Изменение типа поляОбратноеТребуется
Изменение формата ответаОбратноеТребуется
Исправление багаОбратноеНе требуется

#Стратегии версионирования

#1. Версия в URL (Path)

Наиболее распространённый подход.

from fastapi import FastAPI, APIRouter app = FastAPI() # Версия 1 router_v1 = APIRouter(prefix='/api/v1') @router_v1.get('/users') def get_users_v1(): return [{'id': 1, 'name': 'John'}] # Версия 2 router_v2 = APIRouter(prefix='/api/v2') @router_v2.get('/users') def get_users_v2(): return [ { 'id': 1, 'name': 'John', 'email': 'john@example.com' # Новое поле } ] app.include_router(router_v1) app.include_router(router_v2)

Преимущества:

  • Простота реализации
  • Явная версия в URL
  • Легко кэшировать

Недостатки:

  • Загрязнение URL
  • Нужно поддерживать несколько роутеров

#2. Версия в заголовке

from fastapi import FastAPI, Request, Header app = FastAPI() @app.get('/users') def get_users(api_version: str = Header('v1')): if api_version == 'v1': return [{'id': 1, 'name': 'John'}] elif api_version == 'v2': return [ {'id': 1, 'name': 'John', 'email': 'john@example.com'} ] else: raise HTTPException(400, "Invalid API version")

Преимущества:

  • Чистые URL
  • Гибкость

Недостатки:

  • Менее очевидно
  • Сложнее кэшировать
  • Требует настройки заголовков

#3. Версия в query параметре

from fastapi import FastAPI, Query app = FastAPI() @app.get('/users') def get_users(version: str = Query('v1')): if version == 'v1': return [{'id': 1, 'name': 'John'}] elif version == 'v2': return [ {'id': 1, 'name': 'John', 'email': 'john@example.com'} ]

Преимущества:

  • Простота
  • Гибкость

Недостатки:

  • Загрязнение query параметров
  • Может конфликтовать с другими параметрами

#4. Accept header (Media Type)

from fastapi import FastAPI, Request app = FastAPI() @app.get('/users') async def get_users(request: Request): accept = request.headers.get('Accept', 'application/json') if 'application/vnd.myapp.v1+json' in accept: return [{'id': 1, 'name': 'John'}] elif 'application/vnd.myapp.v2+json' in accept: return [ {'id': 1, 'name': 'John', 'email': 'john@example.com'} ]

Преимущества:

  • Семантически правильный (REST)
  • Чистые URL

Недостатки:

  • Сложнее в использовании
  • Требует знания спецификации

#Реализация с роутерами

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

project/
├── api/
│   ├── __init__.py
│   ├── v1/
│   │   ├── __init__.py
│   │   ├── users.py
│   │   └── posts.py
│   └── v2/
│       ├── __init__.py
│       ├── users.py
│       └── posts.py
└── main.py

#api/v1/users.py

from fastapi import APIRouter router = APIRouter() @router.get('/users') def get_users(): return [ {'id': 1, 'name': 'John'}, {'id': 2, 'name': 'Jane'} ] @router.get('/users/{user_id}') def get_user(user_id: int): return {'id': user_id, 'name': f'User {user_id}'}

#api/v2/users.py

from fastapi import APIRouter router = APIRouter() @router.get('/users') def get_users(): return [ { 'id': 1, 'name': 'John', 'email': 'john@example.com', 'role': 'user' }, { 'id': 2, 'name': 'Jane', 'email': 'jane@example.com', 'role': 'admin' } ] @router.get('/users/{user_id}') def get_user(user_id: int): return { 'id': user_id, 'name': f'User {user_id}', 'email': f'user{user_id}@example.com', 'role': 'user' }

#main.py

from fastapi import FastAPI from api.v1 import users as users_v1 from api.v2 import users as users_v2 app = FastAPI( title="My API", description="API с версионированием", version="2.0.0" ) # Версия 1 app.include_router(users_v1.router, prefix='/api/v1', tags=['v1']) # Версия 2 app.include_router(users_v2.router, prefix='/api/v2', tags=['v2']) @app.get('/') def root(): return { 'message': 'API с версионированием', 'versions': { 'v1': '/api/v1', 'v2': '/api/v2' } }

#Управление моделями

#Общие модели

# models/common.py from pydantic import BaseModel class UserBase(BaseModel): name: str # models/v1.py from pydantic import BaseModel from .common import UserBase class UserResponse(UserBase): model_config = ConfigDict(from_attributes=True) id: int # models/v2.py from pydantic import BaseModel, EmailStr, ConfigDict from .common import UserBase class UserResponse(UserBase): model_config = ConfigDict(from_attributes=True) id: int email: EmailStr role: str = 'user'

#Использование в endpoints

# api/v1/users.py from fastapi import APIRouter from models.v1 import UserResponse router = APIRouter() @router.get('/users', response_model=list[UserResponse]) def get_users(): ... # api/v2/users.py from fastapi import APIRouter from models.v2 import UserResponse router = APIRouter() @router.get('/users', response_model=list[UserResponse]) def get_users(): ...

#Deprecated endpoints

Помечайте устаревшие endpoints для клиентов.

from fastapi import FastAPI, status app = FastAPI() @app.get( '/users', deprecated=True, summary="Устаревший endpoint", description="Используйте /api/v2/users вместо этого" ) def get_users_deprecated(): return [{'id': 1, 'name': 'John'}] @app.get('/api/v2/users') def get_users_v2(): return [ {'id': 1, 'name': 'John', 'email': 'john@example.com'} ]

В Swagger UI устаревший endpoint будет зачёркнут.

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

#Раздельная документация

from fastapi import FastAPI from fastapi.openapi.docs import get_swagger_ui_html app = FastAPI(docs_url=None, redoc_url=None) # Версия 1 app_v1 = FastAPI(docs_url='/docs', redoc_url='/redoc') @app.get('/docs', include_in_schema=False) async def custom_swagger_ui_html(): return get_swagger_ui_html( openapi_url="/openapi_v1.json", title="API v1 Docs" )

#Тегирование по версиям

app = FastAPI() app.include_router( users_v1.router, prefix='/api/v1', tags=['Users v1'] ) app.include_router( users_v2.router, prefix='/api/v2', tags=['Users v2'] )

#Обратная совместимость

#Добавление полей (обратное)

# v1 class UserResponse(BaseModel): id: int name: str # v2 (добавлено новое поле) class UserResponse(BaseModel): id: int name: str email: str # Новое, необязательное поле # Обратное: старые клиенты продолжат работать

#Удаление полей (не обратное)

# v1 class UserResponse(BaseModel): id: int name: str deprecated_field: str # Устаревшее # v2 (удалено поле) class UserResponse(BaseModel): id: int name: str # Не обратное: старые клиенты сломаются # Решение: пометить deprecated в v1, удалить в v3

#Изменение типа (не обратное)

# v1 class UserResponse(BaseModel): age: int # Число # v2 (изменён тип) class UserResponse(BaseModel): age: str # Строка "25 years" # Не обратное: старые клиенты сломаются # Решение: добавить новое поле class UserResponse(BaseModel): age: int age_display: str # Новое поле

#Пример: Полноценное версионирование

from fastapi import FastAPI, APIRouter, Depends, HTTPException from pydantic import BaseModel, EmailStr, Field, ConfigDict from typing import Optional app = FastAPI(title="My API", version="2.0.0") # === Models v1 === class UserResponseV1(BaseModel): model_config = ConfigDict(from_attributes=True) id: int name: str class UserCreateV1(BaseModel): name: str = Field(..., min_length=3) # === Models v2 === class UserResponseV2(BaseModel): model_config = ConfigDict(from_attributes=True) id: int name: str email: EmailStr role: str = 'user' class UserCreateV2(BaseModel): name: str = Field(..., min_length=3) email: EmailStr role: str = 'user' # === Имитация БД === users_db = { 1: {'id': 1, 'name': 'John', 'email': 'john@example.com', 'role': 'user'}, 2: {'id': 2, 'name': 'Jane', 'email': 'jane@example.com', 'role': 'admin'}, } # === API v1 === router_v1 = APIRouter(prefix='/api/v1', tags=['Users v1']) @router_v1.get('/users', response_model=list[UserResponseV1]) def get_users_v1(): """Получить список пользователей (v1)""" return list(users_db.values()) @router_v1.get('/users/{user_id}', response_model=UserResponseV1) def get_user_v1(user_id: int): """Получить пользователя по ID (v1)""" if user_id not in users_db: raise HTTPException(404, "User not found") return users_db[user_id] @router_v1.post('/users', response_model=UserResponseV1, status_code=201) def create_user_v1(user: UserCreateV1): """Создать пользователя (v1)""" user_id = max(users_db.keys()) + 1 user_data = {'id': user_id, 'name': user.name} users_db[user_id] = user_data return user_data app.include_router(router_v1) # === API v2 === router_v2 = APIRouter(prefix='/api/v2', tags=['Users v2']) @router_v2.get('/users', response_model=list[UserResponseV2]) def get_users_v2(): """Получить список пользователей (v2)""" return list(users_db.values()) @router_v2.get('/users/{user_id}', response_model=UserResponseV2) def get_user_v2(user_id: int): """Получить пользователя по ID (v2)""" if user_id not in users_db: raise HTTPException(404, "User not found") return users_db[user_id] @router_v2.post('/users', response_model=UserResponseV2, status_code=201) def create_user_v2(user: UserCreateV2): """Создать пользователя (v2)""" user_id = max(users_db.keys()) + 1 user_data = { 'id': user_id, 'name': user.name, 'email': user.email, 'role': user.role } users_db[user_id] = user_data return user_data app.include_router(router_v2) # === Deprecated endpoint === @app.get('/users', deprecated=True, tags=['Deprecated']) def get_users_deprecated(): """ Устаревший endpoint. Используйте /api/v1/users или /api/v2/users """ return list(users_db.values()) # === Информация о версиях === @app.get('/versions') def get_versions(): """ Информация о доступных версиях API. """ return { 'versions': { 'v1': { 'url': '/api/v1', 'status': 'stable', 'deprecated': False }, 'v2': { 'url': '/api/v2', 'status': 'stable', 'deprecated': False } }, 'current_version': 'v2' }

#Миграция клиентов

#Уведомление о deprecated

from fastapi import Response @app.get('/users') def get_users(response: Response): response.headers['Deprecation'] = 'true' response.headers['Sunset'] = '2024-12-31' # Дата удаления return list(users_db.values())

#Changelog

@app.get('/changelog') def get_changelog(): return { 'v2.0.0': { 'date': '2024-01-01', 'changes': [ 'Добавлено поле email в ответе пользователя', 'Добавлено поле role', 'Исправлена пагинация' ] }, 'v1.0.0': { 'date': '2023-01-01', 'changes': [ 'Первый релиз API' ] } }

#Частые ошибки

#Ошибка 1: Изменение без версионирования

# Было class UserResponse(BaseModel): name: str # Стало (сломало клиентов) class UserResponse(BaseModel): full_name: str # Изменили имя поля

Решение: Создайте новую версию API.

#Ошибка 2: Поддержка слишком многих версий

# Не поддерживайте v1, v2, v3, v4, v5 одновременно

Решение: Установите политику: поддерживать последние 2 версии, устаревшие удалять через 6 месяцев.

#Ошибка 3: Нет документации изменений

Решение: Ведите changelog, уведомляйте клиентов о deprecated заранее.

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

Лаборатория «Версионирование API через APIRouter» доступна в публичном GitLab. Она проверяет версионирование через APIRouter с prefix, deprecated-эндпоинты, раздельные модели для разных версий и endpoint с информацией о доступных версиях.

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

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 versioning/tests

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