API в Python: от основ до практики

Курс знакомит с принципами работы API и их использованием в Python. Вы научитесь работать с HTTP, создавать собственные REST API, тестировать, документировать и обеспечивать безопасность интеграций.

1. Основы API, HTTP и форматы данных

Основы API, HTTP и форматы данных

Что такое API и зачем оно нужно

API (Application Programming Interface, программный интерфейс приложения) — это набор правил, по которым одна программа может взаимодействовать с другой.

Простая аналогия:

  • Клиент (ваша программа) хочет получить данные или выполнить действие.
  • Сервер (чужая система) предоставляет возможности через API.
  • Обмен происходит по заранее оговорённым правилам: как сформировать запрос и как понять ответ.
  • В рамках курса мы в первую очередь будем работать с Web API — API, доступными по сети (обычно через интернет) и использующими HTTP.

    Типичные задачи, решаемые через API:

  • Получить данные (погода, курсы валют, список товаров)
  • Создать/обновить сущность (заказ, пользователя, задачу)
  • Отправить событие (уведомление, платеж)
  • Интегрировать сервисы между собой
  • !Общая схема запроса и ответа при работе с Web API

    HTTP как основа Web API

    HTTP (Hypertext Transfer Protocol) — протокол, то есть набор правил передачи данных в интернете.

    > "The Hypertext Transfer Protocol (HTTP) is a stateless application-level protocol for distributed, collaborative, hypertext information systems." (RFC 9110)

    Ключевые свойства HTTP:

  • Клиент-серверная модель: клиент инициирует запрос, сервер отвечает
  • Stateless (без состояния) в базовом виде: каждый запрос самодостаточен, сервер не обязан помнить предыдущие запросы
  • Расширяемость: заголовки, методы, коды статуса
  • Из чего состоит HTTP-запрос

    HTTP-запрос обычно включает:

  • Метод — что сделать (например, получить данные)
  • URL — куда обратиться
  • Заголовки (headers) — служебная информация (формат данных, авторизация и т.д.)
  • Тело (body) — данные запроса (часто используется в POST, PUT, PATCH)
  • Пример (схематично):

    Из чего состоит HTTP-ответ

    HTTP-ответ обычно включает:

  • Код статуса — результат выполнения
  • Заголовки — служебная информация
  • Тело — данные ответа (например, JSON)
  • Пример (схематично):

    !Из каких частей состоят HTTP-запрос и HTTP-ответ

    URL, путь и параметры

    URL — адрес ресурса. В Web API под ресурсом обычно понимают сущность, с которой мы работаем: пользователь, заказ, товар.

    Пример URL:

    https://api.example.com/v1/users/42?verbose=true

    Разберём компоненты:

  • https — схема (протокол поверх TLS)
  • api.example.com — домен (хост)
  • /v1/users/42 — путь (path), часто отражает ресурс и его идентификатор
  • verbose=truequery-параметры (параметры строки запроса), обычно для фильтрации, сортировки, пагинации
  • Важно различать:

  • Path-параметр (например, /users/42) — какой конкретно ресурс
  • Query-параметры (например, ?limit=10&offset=20) — как именно получить/показать данные
  • Методы HTTP и их смысл

    Метод (иногда говорят verb) описывает намерение клиента.

    Основные методы, которые чаще всего встречаются в API:

    | Метод | Типичный смысл | Есть тело запроса | Идемпотентность | |---|---|---:|---| | GET | Получить данные | Обычно нет | Да | | POST | Создать ресурс / выполнить действие | Да | Обычно нет | | PUT | Полностью заменить ресурс | Да | Да | | PATCH | Частично изменить ресурс | Да | Часто да, но зависит от реализации | | DELETE | Удалить ресурс | Обычно нет | Да |

    Идемпотентность означает: если выполнить один и тот же запрос несколько раз подряд, итоговое состояние на сервере будет одинаковым. Например:

  • DELETE /users/42 дважды подряд в идеальном случае приводит к одному состоянию: пользователя нет
  • POST /orders дважды подряд обычно создаст два заказа, то есть состояние изменится дважды
  • Коды статуса HTTP

    Код статуса — это короткий числовой результат выполнения запроса. Важно уметь читать его смысл, чтобы правильно обрабатывать ошибки в коде.

    Основные группы:

  • 2xx — успех
  • 3xx — перенаправление
  • 4xx — ошибка клиента (проблема в запросе)
  • 5xx — ошибка сервера
  • Часто используемые коды:

    | Код | Название | Когда встречается | |---:|---|---| | 200 | OK | Успешный запрос (часто GET) | | 201 | Created | Ресурс создан (часто POST) | | 204 | No Content | Успешно, но без тела ответа (например, DELETE) | | 400 | Bad Request | Некорректные данные запроса | | 401 | Unauthorized | Нужна аутентификация (не передали или неверный токен) | | 403 | Forbidden | Доступ запрещён (прав недостаточно) | | 404 | Not Found | Ресурс не найден | | 409 | Conflict | Конфликт состояния (например, дубль уникального значения) | | 429 | Too Many Requests | Превышен лимит запросов (rate limit) | | 500 | Internal Server Error | Ошибка на стороне сервера |

    Практическое правило: при работе в Python вы почти всегда будете сначала проверять код статуса, а уже потом разбирать тело ответа.

    Заголовки HTTP: Content-Type и Accept

    Заголовки (headers) — пары вида Ключ: Значение.

    Два самых важных заголовка для форматов данных:

  • Content-Typeв каком формате отправлены данные (в запросе или ответе)
  • Acceptкакой формат клиент хочет получить в ответе
  • Примеры:

  • Content-Type: application/json — тело сообщения в JSON
  • Accept: application/json — клиент предпочитает JSON в ответе
  • Полезно помнить:

  • Если вы отправляете JSON в запросе, нужно корректно указать Content-Type
  • Если API поддерживает несколько форматов, Accept помогает договориться о формате ответа
  • Форматы данных в API

    JSON

    JSON (JavaScript Object Notation) — самый популярный формат в Web API. Он человекочитаемый, легко парсится и почти напрямую ложится на структуры данных.

    Основные элементы JSON:

  • Объект: { "key": "value" }
  • Массив: [1, 2, 3]
  • Строка, число, true/false, null
  • Пример JSON-ответа:

    Что важно на практике:

  • JSON не поддерживает комментарии
  • В JSON есть null, который обычно соответствует None в Python
  • Числа в JSON не различают int и float так строго, как типы в некоторых языках
  • Стандарт: JSON (RFC 8259)

    XML

    XML — более старый и многословный формат, но он всё ещё встречается в корпоративных системах, интеграциях и некоторых государственных сервисах.

    Пример:

    Отличия от JSON:

  • Обычно больше текста (теги)
  • Есть возможности схем/валидации, namespaces и другие сложные механики
  • В современных публичных API встречается реже
  • form-encoded и multipart/form-data

    Иногда API принимает данные как HTML-форму.

    Два популярных варианта:

  • application/x-www-form-urlencoded
  • - Похоже на query-параметры, но в теле запроса - Подходит для простых форм
  • multipart/form-data
  • - Используется для отправки файлов и смешанных частей (текст + файл)

    Признаки в заголовках:

  • Content-Type: application/x-www-form-urlencoded
  • Content-Type: multipart/form-data; boundary=...
  • Текстовые форматы

    Иногда ответ может быть просто текстом:

  • Content-Type: text/plain; charset=utf-8
  • Такое бывает, например, в простых сервисах или для отладочных endpoints.

    Кодировка текста и charset

    Когда вы отправляете или получаете текст, важно, в какой он кодировке.

  • На практике чаще всего используется UTF-8
  • Кодировка может быть указана в Content-Type, например: application/json; charset=utf-8
  • Почему это важно:

  • Если кодировка определена неверно, русские буквы могут превратиться в нечитаемые символы
  • В Python при работе с HTTP-библиотеками (например, requests) часть проблем решается автоматически, но заголовки всё равно стоит понимать
  • Ошибки API: как они обычно выглядят

    Помимо кода статуса, многие API возвращают структурированное описание ошибки, часто в JSON.

    Типичный пример:

    Хорошая практика при разработке клиента:

  • Отдельно обрабатывать сетевые ошибки (нет соединения, таймаут)
  • Отдельно обрабатывать HTTP-ошибки (4xx/5xx)
  • Пытаться читать тело ошибки, если оно есть (для понятных сообщений)
  • Мини-словарь терминов

  • Endpoint — конкретный адрес метода API, например GET /v1/users
  • Resource (ресурс) — сущность, с которой работает API (пользователь, заказ)
  • Payload — данные в теле запроса/ответа
  • Schema (схема) — описание структуры данных (какие поля и каких типов ожидаются)
  • Authentication (аутентификация) — подтверждение кто вы (например, токен)
  • Authorization (авторизация) — проверка что вам можно (права доступа)
  • Что дальше в курсе

    Дальше вы начнёте применять эти основы в Python:

  • Делать реальные HTTP-запросы
  • Передавать параметры, заголовки и тело
  • Читать коды статуса и обрабатывать ошибки
  • Работать с JSON как с основным форматом данных
  • Эти знания — фундамент: без понимания HTTP и форматов данных любая работа с API превращается в "магические" вызовы библиотек, которые сложно отлаживать и поддерживать.

    2. Клиентская работа с API: requests и обработка ошибок

    Клиентская работа с API: requests и обработка ошибок

    В предыдущей статье вы разобрали, из чего состоят HTTP-запросы и ответы: методы, URL, заголовки, коды статуса и форматы данных (особенно JSON). Теперь применим это на практике: научимся писать клиентский код на Python, который обращается к Web API, корректно передаёт параметры и данные, читает ответ и устойчиво обрабатывает ошибки.

    В этом уроке мы будем использовать библиотеку requests — де-факто стандарт для HTTP-запросов в Python.

    !Логика, которую мы будем реализовывать в коде: сетевые ошибки отдельно, HTTP-ошибки отдельно.

    Установка requests и первый запрос

    Установка:

    Базовый GET:

    Что важно понимать про response:

  • response.status_code — HTTP-код статуса (200, 404, 500 и т.д.)
  • response.headers — заголовки ответа
  • response.text — тело ответа как строка (requests сам выберет кодировку, если возможно)
  • response.json() — попытка распарсить тело ответа как JSON
  • Источник по requests: Документация requests.

    Параметры запроса и заголовки

    Query-параметры через params

    В терминах HTTP это та самая часть URL после ? (фильтрация, пагинация, режимы вывода).

    Заголовки через headers

    Заголовки нужны, чтобы договориться о формате и передать служебную информацию (например, авторизацию). Связь с прошлой статьёй прямая: Accept и Content-Type — ключевые.

    Пример: клиент предпочитает JSON и передаёт Bearer-токен.

    Важно:

  • Accept описывает, какой формат ответа вы ожидаете
  • Content-Type указывают, когда вы отправляете тело запроса (например, JSON в POST)
  • Отправка данных: JSON, формы и файлы

    JSON в теле запроса (рекомендуемый вариант)

    Чаще всего API ожидают JSON. В requests для этого есть параметр json= — он:

  • сериализует Python-словарь в JSON
  • автоматически выставляет Content-Type: application/json
  • form-encoded (application/x-www-form-urlencoded)

    Иногда API принимает данные как форму. Тогда используют data=.

    Загрузка файла (multipart/form-data)

    Как правильно читать ответ

    На практике удобно придерживаться последовательности:

  • проверить код статуса
  • понять, есть ли тело ответа
  • распарсить JSON, если ожидается JSON
  • JSON: осторожно с ошибками парсинга

    response.json() выбросит исключение, если ответ не является JSON (например, сервер вернул HTML-страницу ошибки или пустое тело).

    Полезные свойства ответа

  • response.headers.get("Content-Type") — чтобы проверить формат
  • response.content — байты (удобно для файлов)
  • response.encoding — кодировка, которую requests определил
  • Таймауты и сетевые ошибки

    Одна из самых частых проблем в коде новичков: запросы без таймаута. Без timeout программа может “зависнуть” надолго.

    Вы будете встречать два класса проблем:

  • Сетевые ошибки (сервер недоступен, DNS не резолвится, разрыв соединения)
  • Таймауты (сервер слишком долго отвечает или соединение не установилось вовремя)
  • Базовые исключения requests:

  • requests.exceptions.Timeout — истёк таймаут
  • requests.exceptions.ConnectionError — проблема соединения
  • requests.exceptions.RequestException — базовый класс для большинства ошибок requests
  • Документация по таймаутам: requests: Timeouts.

    HTTP-ошибки: 4xx/5xx и raise_for_status

    Сетевые ошибки — это исключения. А вот HTTP-ошибки (например, 404 или 500) технически являются успешным обменом по сети: сервер ответил, просто ответ “плохой” по смыслу.

    В requests есть два распространённых подхода.

    Подход 1: проверять status_code вручную

    Подход 2: raise_for_status

    response.raise_for_status() выбросит requests.exceptions.HTTPError, если статус 4xx или 5xx.

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

    Справочник по статусам: MDN: Коды состояния HTTP.

    Практический шаблон: функция запроса с понятными ошибками

    Ниже — минимальный шаблон, который разделяет:

  • сетевые сбои и таймауты
  • HTTP-ошибки
  • проблемы с JSON
  • Почему этот подход практичен:

  • вы всегда ставите timeout
  • сообщения об ошибках становятся понятными для пользователя и логов
  • вы сохраняете status_code и тело ошибки (details) для диагностики
  • Сессии: когда запросов много

    Если вы делаете много запросов к одному API, полезно использовать requests.Session():

  • переиспользуются соединения (обычно быстрее)
  • можно один раз задать общие заголовки
  • можно хранить cookies (если это нужно)
  • Документация: requests: Advanced Usage.

    Повторы (retries) и устойчивость

    Реальные API могут временно отвечать ошибками (например, 502/503) или ограничивать запросы (429). В таких случаях часто добавляют повторы с паузой.

    В связке requests + urllib3 можно настроить Retry через адаптер.

    Что означают параметры концептуально:

  • total — сколько раз максимум повторять
  • status_forcelist — при каких HTTP-статусах повторять
  • backoff_factor — множитель паузы между повторами (пауза растёт по мере повторов)
  • Документация: urllib3 Retry.

    Рекомендации для “боевого” клиента API

  • Всегда задавайте timeout, даже если API кажется быстрым
  • Разделяйте обработку сетевых ошибок и HTTP-ошибок
  • Проверяйте Content-Type, прежде чем безусловно делать response.json()
  • Логируйте: URL, метод, статус, время выполнения, фрагмент тела ошибки (без секретов)
  • Для множества запросов используйте Session
  • Уважайте rate limit: при 429 делайте паузу и повторы, если это допустимо
  • Не печатайте токены и пароли в логах и исключениях
  • Что дальше в курсе

    Следующий шаг после освоения запросов и ошибок — закрепить практику на реальных API: аутентификация, работа с пагинацией, лимитами, а также проектирование удобного клиента (структура кода, модели данных, тестирование). Это превратит отдельные вызовы requests.get() в поддерживаемую интеграцию.

    3. Аутентификация и авторизация: API ключи, OAuth, JWT

    Аутентификация и авторизация: API ключи, OAuth, JWT

    В прошлых статьях вы разобрали HTTP (методы, заголовки, коды статуса) и научились делать запросы через requests, обрабатывать таймауты, сетевые ошибки и ответы 4xx/5xx. Теперь добавим важнейший практический слой: как безопасно получать доступ к защищённым API.

    Почти любой реальный API требует контроля доступа:

  • чтобы понимать, кто делает запрос
  • чтобы ограничивать, что именно этому клиенту можно
  • чтобы защищаться от злоупотреблений и утечек данных
  • Аутентификация и авторизация: в чём разница

  • Аутентификация отвечает на вопрос: кто вы? (проверка личности клиента)
  • Авторизация отвечает на вопрос: что вам разрешено? (проверка прав)
  • В HTTP это часто проявляется так:

  • 401 Unauthorized обычно означает, что аутентификация не пройдена или отсутствует (например, токен не передан или неверный)
  • 403 Forbidden обычно означает, что аутентификация пройдена, но нужных прав нет
  • Полезная привычка из прошлой статьи: сначала смотреть status_code, затем разбирать тело ошибки и заголовки.

    Где передают данные доступа в HTTP

    На практике применяют несколько способов, но самый типичный для API-клиентов на Python это заголовок Authorization.

  • Заголовок Authorization: Bearer <token> для токенов доступа
  • Реже Authorization: Basic ... для basic-аутентификации
  • Почему заголовок лучше, чем query-параметры:

  • URL может попасть в логи прокси, браузера, истории, аналитики
  • query-параметры проще случайно “засветить” при отладке
  • Официальная справка по заголовку: MDN: Authorization header.

    API ключи

    API key это “пароль” для вашей программы. Обычно это строка, которую вы получаете в личном кабинете сервиса.

    Свойства API key:

  • чаще всего идентифицирует проект или клиента
  • иногда одновременно и аутентифицирует, и косвенно задаёт лимиты (rate limit)
  • почти всегда должен храниться как секрет
  • Как передают API key

    Наиболее распространённые варианты:

  • Authorization: Bearer <api_key>
  • X-API-Key: <api_key>
  • реже query-параметром ?api_key=... (обычно нежелательно)
  • Всегда ориентируйтесь на документацию конкретного API.

    Пример на requests

    Практические рекомендации:

  • храните ключ в переменных окружения, а не в коде
  • не логируйте ключ (ни в исключениях, ни в print)
  • поддерживайте ротацию ключей (замену без простоя)
  • Bearer-токены как общий механизм

    Формат Authorization: Bearer <token> называется Bearer Token: “кто предъявил токен, тот и имеет доступ”. Это общий способ передачи токена в HTTP.

    Стандарт описан в RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage.

    Bearer-токенами могут быть:

  • “простые” непрозрачные строки (opaque tokens)
  • JWT-токены
  • Важно: для клиента разница часто минимальна (вы просто передаёте строку), но для архитектуры и безопасности на сервере разница существенна.

    OAuth 2.0: когда API ключа недостаточно

    OAuth 2.0 это стандарт, который позволяет безопасно выдавать ограниченный доступ к ресурсам от имени пользователя или сервиса. В реальных интеграциях OAuth часто применяется, когда:

  • пользователь “подключает” ваш продукт к своему аккаунту в другом сервисе
  • доступ должен быть ограничен scope (правами)
  • нужен отзыв доступа без смены пароля
  • Базовый стандарт: RFC 6749: The OAuth 2.0 Authorization Framework.

    Основные роли в OAuth 2.0

  • Resource Owner: пользователь, владелец данных
  • Client: ваше приложение
  • Authorization Server: сервер, который выдаёт токены
  • Resource Server: API, которое принимает токены и отдаёт данные
  • Scopes: права в OAuth

    Scope это строка, которая описывает, какие действия разрешены. Например:

  • read_profile
  • write_orders
  • calendar.readonly
  • Токен обычно выдаётся с определённым набором scope, и API проверяет их при доступе к endpoint.

    Самые частые сценарии (flows)

  • Authorization Code Flow: доступ “от имени пользователя” (часто вместе с PKCE)
  • Client Credentials Flow: доступ “сервис к сервису” без пользователя
  • !Диаграмма показывает полный путь получения access token через OAuth и дальнейшие запросы к API

    PKCE: зачем оно нужно

    Если клиентское приложение не может безопасно хранить секрет (например, мобильное приложение), используют PKCE. Идея простая:

  • клиент доказывает, что именно он начал процесс авторизации
  • перехваченный authorization code становится бесполезным без code_verifier
  • На уровне Python-клиента вы чаще встречаете OAuth уже в виде готового access_token, который нужно хранить и обновлять.

    Access token и refresh token

  • Access token: короткоживущий токен для запросов к API
  • Refresh token: долгоживущий токен, чтобы получать новый access token без повторного входа пользователя
  • Типичная логика клиента:

  • использовать access token в Authorization: Bearer ...
  • если API вернул 401 из-за истёкшего токена, обновить access token через refresh token
  • повторить запрос
  • Важно: refresh token это секрет более высокого уровня, чем access token, и его нужно защищать особенно тщательно.

    JWT: что это и как его правильно понимать

    JWT (JSON Web Token) это компактный формат токена, который содержит набор “утверждений” (claims) и криптографическую подпись.

    Стандарт: RFC 7519: JSON Web Token (JWT).

    JWT обычно выглядит как строка из трёх частей, разделённых точками:

  • header.payload.signature
  • Где:

  • header описывает алгоритм подписи и тип токена
  • payload содержит claims (например, идентификатор пользователя и scope)
  • signature позволяет серверу проверить, что токен не подделан
  • !Иллюстрация помогает запомнить, из чего состоит JWT и почему его нельзя считать “зашифрованным”

    Важные свойства JWT для практики

  • JWT обычно подписан, но не обязательно зашифрован
  • - это значит, что содержимое payload может прочитать любой, у кого есть токен - поэтому нельзя класть в payload чувствительные данные (пароли, номера карт)
  • сервер доверяет claims только после проверки подписи и валидности токена
  • часто встречаемые claims:
  • - exp: время истечения токена - iat: когда выдан - sub: субъект (часто id пользователя) - aud: аудитория (для какого сервиса токен)

    На стороне Python-клиента JWT чаще всего используется как обычная строка для заголовка Authorization.

    Практика в Python: как организовать работу с токенами

    Базовый шаблон: сессия + заголовок Authorization

    Связь с прошлой статьёй:

  • используйте Session, если запросов много
  • всегда задавайте timeout
  • разделяйте сетевые ошибки и HTTP-ошибки
  • Обработка 401 и 403

    Практическая интерпретация:

  • 401: перепроверьте, что токен передан, не истёк, и что вы используете правильный тип (например, Bearer)
  • 403: токен может быть валиден, но у него нет нужных прав (scope/роль), или доступ запрещён политиками сервиса
  • Обновление access token (общая идея)

    Механика обновления зависит от API, но общий подход такой:

    Ключевая мысль: обновление токена это отдельный “канал” получения доступа, который тоже нужно обрабатывать ошибками, таймаутами и логированием без секретов.

    Рекомендации по безопасности для клиента API

  • используйте только HTTPS
  • не передавайте секреты в URL
  • не храните токены в репозитории и конфигурации в открытом виде
  • не логируйте заголовок Authorization и refresh token
  • минимизируйте права: запрашивайте только нужные scopes
  • готовьтесь к ротации: ключи и токены должны заменяться без переписывания кода
  • Полезный ориентир по типовым рискам: OWASP: API Security Top 10.

    Итог

  • API key: простой способ идентификации, подходит для сервисных интеграций, но требует строгого обращения с секретами
  • OAuth 2.0: стандарт для делегирования доступа и управления правами (scopes), часто с access token и refresh token
  • JWT: один из форматов токена (обычно подписанный), удобен как переносимый набор claims, но не является “шифрованием”
  • Дальше по курсу логично перейти к практическим кейсам: пагинация, rate limit, проектирование клиента как модуля (структура, повторное использование Session, единая обработка ошибок, обновление токенов и тестирование).

    4. Создание REST API на FastAPI

    Создание REST API на FastAPI

    В прошлых статьях вы смотрели на API глазами клиента: что такое HTTP, как формируются запросы и ответы, как обрабатывать коды статуса и ошибки в requests, и как устроены API-ключи, OAuth и JWT.

    Теперь переключимся на серверную сторону: научимся создавать собственное REST API на FastAPI. Это позволит вам:

  • быстро поднимать HTTP-сервис, который принимает и отдаёт JSON
  • валидировать входные данные и формировать предсказуемые ответы
  • документировать API автоматически
  • закладывать основу для авторизации и безопасного доступа
  • !Общая картина: как FastAPI принимает запрос, валидирует данные и возвращает ответ

    Что такое REST API в контексте FastAPI

    REST API в повседневной разработке обычно означает набор HTTP-endpoint’ов, где:

  • ресурсы описываются URL-путями (например, /users, /users/{user_id})
  • действия выражаются HTTP-методами (GET, POST, PUT, PATCH, DELETE)
  • данные передаются в JSON
  • результат выражается кодами статуса (например, 200, 201, 404)
  • Важно: REST — это не библиотека и не “переключатель режима”. Это набор подходов к проектированию API. FastAPI помогает эти подходы реализовать.

    Почему FastAPI

    FastAPI ценят за сочетание простоты и практичности:

  • высокая скорость разработки за счёт декларативных endpoint’ов
  • валидация данных и сериализация через Pydantic
  • автоматическая генерация спецификации OpenAPI и документации
  • удобные механизмы зависимостей (dependency injection)
  • Официальные источники:

  • Документация FastAPI
  • Документация Pydantic
  • Документация Uvicorn
  • Установка и запуск первого приложения

    Установим FastAPI и ASGI-сервер Uvicorn:

    Создайте файл main.py:

    Запуск:

  • main:app означает: модуль main.py, объект app
  • --reload включает авто-перезапуск при изменении кода (удобно в разработке)
  • Проверьте в браузере:

  • GET http://127.0.0.1:8000/
  • Автодокументация: OpenAPI, Swagger UI и ReDoc

    FastAPI автоматически строит спецификацию OpenAPI и интерактивную документацию.

    Доступные страницы по умолчанию:

  • GET /docs — Swagger UI
  • GET /redoc — ReDoc
  • GET /openapi.json — JSON-спецификация
  • Это полезно и для команды, и для вас как для автора API: документация становится частью продукта.

    !Как выглядит интерактивная документация Swagger UI в FastAPI

    Path-параметры и query-параметры

    Связь с HTTP из первой статьи прямая:

  • path-параметр — часть пути, идентифицирует конкретный ресурс
  • query-параметр — часть после ?, задаёт фильтрацию, режимы, пагинацию
  • Пример:

    Как это вызывается:

  • GET /users/42 вернёт user_id = 42, verbose = False
  • GET /users/42?verbose=true вернёт verbose = True
  • FastAPI сам:

  • конвертирует типы (например, user_id в int)
  • вернёт 422 Unprocessable Entity, если типы не совпали или данные невалидны
  • Модели данных: запросы и ответы через Pydantic

    В реальном API важно договориться о структуре JSON. В FastAPI это делается через модели Pydantic.

    Создадим модель входных данных (что клиент отправляет) и модель ответа (что сервер возвращает).

    Ключевые идеи:

  • BaseModel задаёт схему и правила валидации
  • FastAPI использует модели для генерации документации
  • вы получаете типы, автокомплит и меньше “магии” в формате данных
  • CRUD-пример: создаём и читаем ресурсы

    Ниже — простой пример API для сущности Item. Для обучения мы используем “память” (словарь), без базы данных.

    Что здесь важно с точки зрения HTTP и прошлых уроков:

  • POST /items возвращает 201 Created (логично для создания ресурса)
  • GET /items/{item_id} возвращает 404 Not Found, если ресурса нет
  • GET /items?limit=...&offset=... демонстрирует базовую пагинацию query-параметрами
  • Обновление и удаление: PUT, PATCH, DELETE

    Добавим обновление и удаление. Для частичного обновления удобно использовать отдельную модель, где поля опциональны.

    Практические замечания:

  • PATCH обычно используют для частичного изменения
  • DELETE часто возвращает 204 No Content, то есть успешный ответ без тела
  • Статусы и ошибки: как делать “правильные” ответы

    В клиентской статье вы разделяли:

  • сетевые ошибки и таймауты (это проблемы транспорта)
  • HTTP-ошибки (4xx/5xx), которые возвращает сервер
  • Когда вы пишете сервер на FastAPI, вы как раз создаёте эти HTTP-ответы.

    HTTPException

    Чтобы вернуть контролируемую ошибку, используйте HTTPException.

    detail попадёт в JSON-ответ и в документацию.

    Валидационные ошибки 422

    Если клиент отправит неправильный JSON или нарушит типы, FastAPI вернёт 422 Unprocessable Entity с деталями о том:

  • какое поле не прошло валидацию
  • какой тип ожидался
  • Это полезно клиентам и сильно ускоряет отладку.

    response_model

    Параметр response_model=... решает две задачи:

  • документирует форму ответа
  • фильтрует и сериализует данные ответа согласно модели
  • То есть вы можете хранить “внутренний” объект шире, а наружу отдавать только то, что разрешено.

    Зависимости (Dependencies): повторяемая логика без копипаста

    FastAPI умеет внедрять зависимости через Depends. Это полезно для:

  • извлечения токена из заголовков
  • проверки прав
  • получения подключения к базе
  • единых проверок (например, rate limit на стороне приложения)
  • Мини-пример зависимости, которая добавляет примитивный API key из заголовка X-API-Key:

    Здесь связь с темой аутентификации из прошлой статьи такая:

  • клиент передаёт секрет в заголовке, а не в URL
  • сервер централизованно проверяет секрет и выдаёт 401, если он неверный
  • Для более “стандартных” схем авторизации (Bearer-токены, OAuth2) в FastAPI есть готовые компоненты безопасности: Раздел Security в документации FastAPI.

    Структура проекта: как не превратить API в один огромный файл

    По мере роста API важно разделять ответственность.

    Один из простых подходов к структуре:

  • main.py — создание FastAPI() и подключение роутеров
  • routers/ — endpoint’ы по доменам (например, users.py, items.py)
  • schemas/ — Pydantic-модели
  • services/ — бизнес-логика
  • repositories/ — доступ к данным (БД, внешние API)
  • Пример подключения роутера:

    Этот шаг напрямую влияет на поддерживаемость: тестировать, расширять и переиспользовать код становится проще.

    Тестирование API: быстрые проверки без поднятия сервера

    FastAPI построен на Starlette, и для тестов обычно используют TestClient.

    Это удобный способ проверить:

  • коды статуса
  • формат JSON
  • ошибки валидации
  • Итог

    Вы научились собирать REST API на FastAPI, используя знания из предыдущих уроков:

  • HTTP-методы, URL, query и path-параметры
  • коды статуса и типичные ошибки (404, 401, 422)
  • JSON-форматы данных и их схемы через Pydantic
  • базовую идею авторизации через заголовки и зависимости
  • Дальше логично развивать тему в “боевую” сторону: подключение базы данных, полноценная OAuth2/JWT-авторизация, единый формат ошибок, логирование, rate limiting и версионирование API.

    5. Валидация данных и работа с базой данных

    Валидация данных и работа с базой данных

    В предыдущих уроках вы:

  • разобрали, как устроены HTTP-запросы и ответы (методы, коды статуса, заголовки, JSON)
  • научились писать клиента на requests и устойчиво обрабатывать ошибки
  • познакомились с аутентификацией (API key, OAuth, JWT)
  • подняли сервер на FastAPI и сделали CRUD на “памяти”
  • Следующий шаг к практическому API — научиться:

  • валидировать данные не только по типам, но и по правилам (длины, диапазоны, форматы)
  • корректно возвращать ошибки валидации и доменные ошибки
  • сохранять и читать данные из базы данных
  • организовать сессии и транзакции так, чтобы API было надёжным
  • !Поток: запрос → валидация → бизнес-логика → транзакция в БД → ответ

    Почему валидация и база данных идут вместе

    Когда у вас есть база данных, “плохие данные” становятся особенно дорогими:

  • их сложно исправлять (уже записаны)
  • они ломают отчёты и бизнес-логику
  • они могут нарушать ограничения БД (уникальность, внешние ключи)
  • Правильная стратегия:

  • на входе (FastAPI + Pydantic) — отсекаем всё, что не соответствует контракту API
  • на уровне БД — ставим ограничения, чтобы защититься даже если где-то есть баг
  • в коде API — обрабатываем ошибки БД и переводим их в понятные HTTP-ответы
  • Валидация данных в FastAPI и Pydantic

    FastAPI использует Pydantic-модели для:

  • валидации входного JSON
  • генерации OpenAPI-спецификации и документации /docs
  • сериализации ответа
  • Официальные источники:

  • Документация FastAPI
  • Документация Pydantic
  • Базовая валидация: типы и обязательность полей

    Если поле объявлено как str, FastAPI ожидает строку. Если поле не Optional и не имеет значения по умолчанию, оно обязательно.

    Примеры проблем, которые автоматически поймаются:

  • отсутствует name
  • price прислали строкой, которую нельзя привести к числу
  • JSON невалиден как JSON
  • В таких случаях FastAPI вернёт 422 Unprocessable Entity и структуру ошибок по полям.

    Валидация правил: длины, диапазоны, регулярные выражения

    Типов обычно недостаточно. Частые практические требования:

  • имя товара не короче 2 символов
  • цена не может быть отрицательной
  • строковые поля должны иметь разумную максимальную длину
  • В Pydantic v2 удобно использовать Field.

    Что означают параметры:

  • min_length и max_length ограничивают длину строки
  • ge=0 означает greater or equal — значение должно быть больше либо равно 0
  • Нормализация и бизнес-правила: валидаторы

    Иногда нужно не просто “проверить”, но и “привести к нормальной форме”. Например:

  • обрезать пробелы у name
  • запретить имя из одних пробелов
  • Практический смысл:

  • ошибки, которые относятся к “контракту API”, должны ловиться до БД
  • после нормализации данные становятся более предсказуемыми внутри системы
  • Разделяйте модели для создания, обновления и ответа

    Типовая ошибка новичков — использовать одну модель “на всё”. На практике лучше разделять:

  • модель для создания: обязательные поля
  • модель для частичного обновления: все поля опциональны
  • модель ответа: включает id и, возможно, служебные поля
  • Зачем from_attributes: если вы возвращаете ORM-объекты (например, из SQLAlchemy), Pydantic сможет читать поля как атрибуты, а не только как словарь.

    Подключаем базу данных: SQLAlchemy и SQLite

    Для обучения удобно начать с SQLite: она не требует отдельного сервера и работает “из файла”. Для промышленной разработки часто используют PostgreSQL, но принцип работы API почти тот же.

    Технологии, которые мы возьмём:

  • SQLAlchemy 2.0 как основной ORM
  • асинхронный драйвер aiosqlite для SQLite
  • Официальные источники:

  • Документация SQLAlchemy 2.0
  • SQLAlchemy 2.0 Tutorial
  • Документация FastAPI: SQL Databases
  • Документация FastAPI: Async SQL Databases
  • Документация aiosqlite
  • Установка зависимостей

    Настройка engine и сессий

    Создадим файл db.py.

    Ключевые моменты:

  • create_async_engine создаёт асинхронный движок
  • async_sessionmaker создаёт фабрику сессий
  • get_db станет зависимостью FastAPI, чтобы аккуратно выдавать сессию на запрос
  • Описываем таблицу: SQLAlchemy model

    Создадим файл models.py.

    На что обратить внимание:

  • unique=True на name означает, что два товара с одинаковым name создать нельзя
  • String(200) добавляет ограничение длины на уровне схемы БД
  • Numeric(10, 2) намекает на “денежный” тип (10 знаков всего, 2 после запятой)
  • Важно понимать границы ответственности:

  • Pydantic валидирует вход
  • БД гарантирует ограничения даже если вы ошиблись в коде
  • Создаём таблицы при старте приложения

    Для учебного проекта можно создать таблицы на старте. В реальных проектах обычно применяют миграции.

    Создадим main.py.

    Примечание: on_event("startup") часто используют в учебных целях. Для более крупных приложений и сложных сценариев инициализации стоит изучить lifespan-механизм FastAPI.

    CRUD с базой данных

    Создание ресурса: POST /items

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

  • db.add(item) добавляет объект в текущую транзакцию
  • commit пытается сохранить изменения
  • при конфликте уникальности SQLAlchemy бросит IntegrityError
  • rollback обязателен, чтобы сессия вернулась в рабочее состояние
  • refresh подтягивает сгенерированный id
  • Почему 409 Conflict: это стандартный смысл “конфликт состояния”, например нарушение уникальности.

    Чтение: GET /items/{item_id}

    Список с пагинацией: GET /items?limit=&offset=

    Практические замечания:

  • limit/offset — простой вариант пагинации, который вы уже видели ранее
  • в продакшене пагинация может быть сложнее (например, cursor-based), но база та же
  • Частичное обновление: PATCH /items/{item_id}

    Удаление: DELETE /items/{item_id}

    Типовые ошибки при работе с БД в API

    Ошибка: забыли rollback после ошибки

    Если вы поймали исключение во время commit и не сделали rollback, сессия может остаться в “сломленном” состоянии, и последующие операции начнут падать уже “цепочкой”.

    Практическое правило:

  • если commit не удался, всегда делайте rollback
  • Ошибка: нет ограничений в БД, есть только Pydantic

    Pydantic защищает “вход API”, но не защищает от:

  • фоновых задач
  • админских скриптов
  • будущих изменений кода
  • Поэтому уникальность, внешние ключи и критичные ограничения лучше иметь и на уровне БД.

    Ошибка: возвращаем наружу ORM-модель без response_model

    Если вы не задаёте response_model, вы хуже фиксируете контракт ответа и проще “случайно” утечь служебными полями.

    Практическое правило:

  • для публичных endpoint’ов задавайте response_model
  • Миграции схемы: что делать, когда таблицы меняются

    Создание таблиц на старте подходит только для простого обучения. В реальных проектах схема развивается:

  • добавляются поля
  • меняются типы
  • добавляются индексы
  • Для этого обычно используют миграции.

    Популярный инструмент в экосистеме SQLAlchemy:

  • Документация Alembic
  • На уровне курса важно понять идею:

  • структура БД должна меняться контролируемо и воспроизводимо
  • миграции позволяют одинаково обновить схему на разных окружениях
  • Как это связано с предыдущими уроками

    Здесь сходятся сразу несколько тем курса:

  • из HTTP-урока: корректные коды (201, 204, 404, 409, 422)
  • из урока про requests: клиент может различать ошибки валидации (422) и конфликт (409)
  • из урока про авторизацию: доступ к “изменяющим” endpoint’ам обычно защищают, а проверку выносят в зависимости FastAPI
  • из урока про FastAPI: Depends, response_model, Pydantic-модели остаются основными строительными блоками
  • Итог

  • Pydantic-валидация в FastAPI позволяет фиксировать контракт API и возвращать понятные 422-ошибки
  • правила валидации должны быть не только “про типы”, но и про ограничения и нормализацию
  • база данных нужна для долговременного хранения, поиска, уникальности и целостности
  • при работе с БД важны транзакции и корректная обработка ошибок (commit, rollback, IntegrityError)
  • API становится намного более “боевым”, когда данные валидируются на входе и защищены ограничениями на уровне БД
  • 6. Тестирование, документация и версионирование API

    Тестирование, документация и версионирование API

    Вы уже умеете:

  • работать с API как клиент через requests и обрабатывать ошибки
  • делать аутентификацию через API key, OAuth, JWT
  • создавать REST API на FastAPI
  • валидировать данные Pydantic-моделями и сохранять их в базу через SQLAlchemy
  • Теперь добавим три практических слоя, без которых API редко становится поддерживаемым продуктом:

  • тестирование: чтобы изменения не ломали поведение
  • документация: чтобы клиентам было понятно, как пользоваться API
  • версионирование: чтобы развивать API без боли для интеграций
  • !Схема трёх опор, которые делают API устойчивым в развитии

    Тестирование API

    Тесты для API отвечают на вопрос: если я изменил код, остался ли контракт и поведение правильными? Это особенно важно после добавления базы данных и валидации: изменения в моделях, статус-кодах и обработке ошибок легко ломают клиентов.

    Что именно тестировать

    Полезно мыслить в терминах слоёв:

  • контракт: пути, методы, схема запросов и ответов, статус-коды
  • валидация: что невалидный вход даёт предсказуемый 422
  • доменные ошибки: например, конфликт уникальности даёт 409
  • безопасность: что защищённые endpoint’ы без токена дают 401, а без прав 403
  • интеграция с БД: что данные реально создаются, читаются, обновляются
  • Инструменты: pytest и TestClient

    В FastAPI обычно используют pytest и TestClient. Ключевое удобство TestClient: он позволяет тестировать приложение без запуска отдельного сервера.

    Ссылки:

  • Документация pytest
  • FastAPI: Testing
  • Пример минимального теста:

    Тесты CRUD с проверкой статус-кодов и схем

    Если у вас есть endpoint’ы из прошлых уроков (например, POST /items, GET /items/{id}), тестируйте:

  • корректные статус-коды (201, 200, 404, 409, 422)
  • ключевые поля ответа
  • что сервер не отдаёт «лишнего» (это помогает контролировать response_model)
  • Пример (упрощённый, без реальной БД):

    Тесты валидации: ожидаем 422

    Сильная сторона FastAPI и Pydantic в том, что невалидные данные дают подробный 422. Это нужно закрепить тестами, чтобы при изменениях схемы не «поплыли» ошибки.

    Тесты ошибок БД: ожидаем 409 при нарушении уникальности

    В уроке про базу данных вы обрабатывали IntegrityError и возвращали 409 Conflict. Это полезный контракт для клиентов, и его стоит тестировать.

    Изоляция тестов: тестовая БД и dependency override

    Когда в приложении используется Depends(get_db), тестам нужна изоляция:

  • отдельная база для тестов
  • или база в памяти
  • или создание и очистка таблиц перед тестами
  • В FastAPI для этого часто используют app.dependency_overrides.

    Ссылка:

  • FastAPI: Dependencies with overrides
  • Схема идеи:

  • В приложении есть зависимость get_db.
  • В тесте вы подменяете её на override_get_db, которая отдаёт тестовую сессию.
  • Запросы в тестах используют подменённую зависимость.
  • Пример каркаса (без привязки к конкретной конфигурации SQLAlchemy):

    Моки внешних API

    Если ваше API ходит во внешние сервисы через requests или httpx, в тестах лучше не зависеть от интернета и чужой стабильности.

    Популярные инструменты:

  • requests-mock
  • respx
  • Цель моков:

  • зафиксировать ожидаемые запросы
  • вернуть предсказуемые ответы
  • протестировать обработку ошибок внешнего сервиса
  • Документация API

    Документация решает другую проблему: чтобы клиент мог понять API без чтения вашего исходного кода. В FastAPI документация встроена в процесс разработки.

    OpenAPI и страницы /docs и /redoc

    FastAPI генерирует спецификацию OpenAPI и показывает её в интерфейсах:

  • GET /docs (Swagger UI)
  • GET /redoc (ReDoc)
  • GET /openapi.json (машиночитаемая спецификация)
  • Ссылки:

  • FastAPI: OpenAPI
  • Спецификация OpenAPI
  • Практический вывод: если вы описываете схемы через Pydantic и задаёте response_model, вы одновременно строите контракт и документацию.

    Делайте документацию полезной, а не формальной

    Чтобы /docs реально помогали клиентам, добавляйте:

  • понятные названия и описания endpoint’ов
  • примеры запросов и ответов
  • описание типовых ошибок и статус-кодов
  • группировку по тегам
  • Пример: описание endpoint’а и теги.

    Примеры в схемах Pydantic

    Клиентам проще всего, когда в /docs есть готовые примеры JSON. В Pydantic v2 можно добавлять примеры через json_schema_extra.

    Документируйте ошибки как часть контракта

    Клиенту важно понимать не только успешный ответ, но и структуру ошибок.

    Практика, которая повышает предсказуемость:

  • договориться о едином формате ошибок
  • использовать его для доменных ошибок (404, 409, 403)
  • описывать эти ответы в документации
  • FastAPI позволяет явно добавить описание ответов в endpoint:

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

    В прошлой статье про авторизацию вы видели заголовок Authorization: Bearer .... В FastAPI можно сделать так, чтобы схема авторизации отображалась в Swagger UI.

    Ссылка:

  • FastAPI: Security
  • Даже если вы пока используете простую проверку API key через заголовок, документируйте это в явном виде, чтобы клиент понимал, что нужно передать.

    Проверяйте документацию тестами

    Полезный приём: тест, который проверяет, что OpenAPI вообще генерируется и содержит ключевые пути. Это не заменяет тестирование поведения, но ловит случайные поломки структуры.

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

    Версионирование нужно, когда API развивается: меняются поля, правила и бизнес-логика. Если делать изменения без стратегии, клиенты начинают ломаться «внезапно».

    Цель версионирования: дать предсказуемый способ выпускать изменения и управлять совместимостью.

    Что такое совместимость в API

    Упрощённо:

  • обратно совместимое изменение: старый клиент продолжает работать
  • ломающее изменение: старый клиент перестаёт работать без доработки
  • Примеры обратно совместимых изменений:

  • добавили новое опциональное поле в ответ
  • добавили новый endpoint
  • добавили новый query-параметр с значением по умолчанию
  • Примеры ломающих изменений:

  • переименовали поле в JSON
  • изменили тип поля (например, price: float на строку)
  • поменяли смысл статус-кода (например, было 404, стало всегда 200 с пустым объектом)
  • Стратегии версионирования

    На практике чаще всего встречаются три подхода.

    | Подход | Пример | Плюсы | Минусы | |---|---|---|---| | Версия в URL | /v1/items | просто и очевидно | версия размазывается по роутам | | Версия в заголовке | X-API-Version: 1 | URL остаётся чистым | сложнее дебажить и кэшировать | | Версия в media type | Accept: application/vnd.app.v1+json | строгий контракт контента | сложнее для новичков |

    Ссылки на общие рекомендации:

  • Zalando RESTful API Guidelines
  • Microsoft REST API Guidelines
  • Для учебных и многих продуктовых API хороший стартовый вариант: версия в URL (/v1).

    Как версионировать в FastAPI

    На уровне FastAPI проще всего делать версию через префикс роутера.

    Идея:

  • v1 живёт под /v1
  • v2 живёт под /v2
  • это позволяет выпускать новую версию параллельно со старой
  • Каркас:

    Правила эволюции API

    Чтобы версионирование не превратилось в хаос, придерживайтесь правил:

  • не делайте ломающие изменения внутри одной версии
  • добавляйте новое так, чтобы старое продолжало работать
  • если ломающее изменение необходимо, выпускайте новую версию
  • заранее объявляйте deprecation старых endpoint’ов и давайте срок миграции
  • !Схема жизненного цикла изменений и вывода старой версии из эксплуатации

    Deprecation и вывод версии из эксплуатации

    Технически можно использовать заголовки и документацию:

  • описывать в /docs, что endpoint устаревает
  • добавлять предупреждение в ответах (например, через заголовок)
  • фиксировать дату отключения в релиз-нотах
  • Часто используют заголовки из практик HTTP, например Deprecation и Sunset, но поддержка и ожидания зависят от экосистемы клиентов. Поэтому базовое правило простое: объявляйте заранее и дайте время на миграцию.

    Семантическое версионирование и API

    Семантическое версионирование (SemVer) хорошо подходит для библиотек и серверных релизов, но для публичного HTTP API чаще используют:

  • отдельно версию API (v1, v2)
  • отдельно версию сервиса (релизы, changelog)
  • Ссылка:

  • Semantic Versioning
  • Практический смысл: клиенты должны ориентироваться на версию API, а не на внутренние релизы сервера.

    Практический чек-лист перед публикацией API

  • у каждого endpoint’а понятные статус-коды на успех и типовые ошибки
  • есть тесты на успех, 422, 404, 409 и авторизацию
  • схемы запросов и ответов заданы через Pydantic и response_model
  • в /docs есть описания и примеры
  • выбрана стратегия версионирования, и она закреплена в роутинге
  • есть план, как вы будете объявлять deprecation и миграцию
  • Итог

  • тестирование защищает контракт и поведение API от регрессий
  • документация в FastAPI строится вокруг Pydantic-моделей и OpenAPI и должна включать примеры и ошибки
  • версионирование позволяет развивать API без внезапных поломок клиентов, а стратегия /v1 часто является наиболее простой стартовой опцией
  • 7. Безопасность, лимиты и деплой API

    Безопасность, лимиты и деплой API

    В предыдущих статьях вы прошли путь от основ HTTP и работы с requests до создания REST API на FastAPI, валидации через Pydantic, работы с базой через SQLAlchemy и практик тестирования, документации и версионирования.

    Следующий шаг к реальному продукту: сделать API безопасным, устойчивым к нагрузке и правильно развернуть его в окружении, где:

  • запросы могут быть вредоносными или просто “кривыми”
  • клиенты могут случайно (или намеренно) превысить лимиты
  • сервис должен переживать рестарты, обновления и масштабирование
  • !Общая архитектура защищённого и устойчивого API

    Модель угроз для API: что мы защищаем

    Безопасность начинается не с библиотек, а с понимания рисков. Для Web API типичные угрозы:

  • утечка секретов (API key, JWT, пароли БД)
  • обход авторизации (когда пользователь получает доступ к чужим данным)
  • инъекции и “грязные” данные (данные попали в БД в некорректном виде)
  • злоупотребление ресурсами (слишком много запросов, слишком большие payload’ы)
  • уязвимости конфигурации (открытые debug-режимы, неверные CORS-настройки)
  • Хороший обзор типовых проблем: OWASP API Security Top 10.

    Безопасность на уровне транспорта: HTTPS и доверенные заголовки

    Всегда используйте HTTPS

    Если данные авторизации (например, Authorization: Bearer ...) уходят по HTTP без TLS:

  • токен можно перехватить
  • запрос можно подменить
  • На практике часто делают так:

  • внешний мир приходит по HTTPS на реверс‑прокси (Nginx/Traefik/Ingress)
  • прокси шифрует/расшифровывает трафик
  • до приложения трафик может идти по внутренней сети (но это уже вопрос архитектуры)
  • Осторожно с X-Forwarded-For и “реальным IP”

    Когда API стоит за прокси, приложение может видеть IP прокси, а не клиента. Тогда начинают доверять заголовкам вида X-Forwarded-For. Важно:

  • доверять этим заголовкам можно только если их выставляет ваш прокси
  • если API доступно напрямую из интернета, злоумышленник может подделать X-Forwarded-For
  • Это особенно критично, если вы делаете rate limit “по IP”.

    Аутентификация и авторизация: закрепляем практику

    В статье про API key, OAuth и JWT вы уже разделяли:

  • аутентификацию: кто вы
  • авторизацию: что вам можно
  • В “боевом” API обычно добавляют два шага поверх “проверили токен”:

  • Проверка прав на конкретный ресурс
  • - пример: пользователь user_id=10 не должен читать заказ order_id=99, если он принадлежит user_id=20
  • Принцип наименьших привилегий
  • - выдавайте минимальные scope/роли - отделяйте права на чтение и на изменение

    Практическая ошибка в API: “проверили токен, но не проверили владение ресурсом”. Такие баги особенно опасны.

    Не логируйте секреты

    Правило: никогда не пишите в логи:

  • заголовок Authorization
  • API ключи
  • refresh token
  • пароли
  • Если нужно диагностировать запрос, логируйте:

  • метод, путь, статус, время
  • request_id (или сгенерированный идентификатор корреляции)
  • обезличенные идентификаторы (например, user_id, если это допустимо)
  • Валидация и защита от “неправильных данных”

    Вы уже используете Pydantic и получаете 422 Unprocessable Entity при невалидном входе. В контексте безопасности это важно, потому что:

  • уменьшается площадь атаки на бизнес-логику
  • меньше шансов записать мусор в базу
  • Практические усиления:

  • ограничивайте длины строк (max_length)
  • ограничивайте диапазоны чисел (ge, le)
  • нормализуйте данные (например, strip())
  • разделяйте модели Create, Update, Out, чтобы не принимать “лишнее”
  • Параллельно сохраняйте ограничения в БД (уникальность, not null), как вы делали в уроке про SQLAlchemy.

    CORS: безопасность для браузеров, а не для серверов

    CORS (Cross-Origin Resource Sharing) нужен, когда ваш API вызывается из браузера фронтендом с другого домена.

    Важно понимать границу:

  • CORS не защищает API от запросов не из браузера (например, через requests)
  • CORS управляет тем, какие браузерные страницы могут делать запросы и читать ответы
  • Если ваше API не предназначено для браузеров, часто лучше вообще не включать CORS.

    Если предназначено, настройте точечно, а не “открыть всё”. FastAPI даёт middleware:

    Документация: FastAPI CORS.

    Лимиты API: зачем они нужны

    Лимиты нужны не только против злоумышленников, но и против “неосторожных” клиентов и пиков нагрузки.

    Типичные виды лимитов:

  • rate limit: сколько запросов можно сделать за окно времени (например, 100 запросов в минуту)
  • quota: суммарный лимит за период (например, 1 000 000 запросов в месяц)
  • лимит размера запроса: максимальный размер body
  • лимит времени выполнения: таймауты на запрос и на доступ к внешним сервисам
  • лимит конкуренции: сколько запросов обрабатываем одновременно (особенно важно для тяжёлых endpoint’ов)
  • Как лимиты выражаются в HTTP

    Если клиент превысил rate limit, стандартный статус: 429 Too Many Requests.

    Статус описан в RFC 6585.

    Полезная практика: возвращать заголовок Retry-After, который подсказывает, когда можно повторить.

    Справка: MDN Retry-After.

    Клиентская сторона: как правильно реагировать на 429

    Связь с уроком про requests и retries:

  • 429 не является сетевой ошибкой, это осмысленный ответ сервера
  • иногда его можно повторять с паузой
  • Базовый подход:

  • если пришёл 429, смотрим Retry-After
  • ждём указанное время (или делаем экспоненциальный backoff)
  • повторяем запрос ограниченное число раз
  • Если у API есть документация по лимитам, клиент должен ей следовать.

    Серверная сторона: где делать rate limit

    Есть три уровня, где можно реализовать лимиты:

  • API gateway / ingress / прокси
  • - часто лучший вариант для базового rate limit - снимает нагрузку с приложения
  • уровень приложения (FastAPI middleware/зависимости)
  • - удобно для лимитов “по пользователю”, “по токену”, “по endpoint’у”
  • уровень базы/бизнес-логики
  • - например, ограничить “создание заказов” до N в минуту

    Важно: in-memory лимитер в нескольких процессах не работает как “общий”

    Если вы запускаете приложение в несколько воркеров (или на нескольких машинах), счётчик в памяти каждого процесса будет отдельным. Для “общего” лимита нужен общий сторедж:

  • Redis
  • или лимитирование на уровне прокси/gateway
  • Пример: лимитирование через библиотеку SlowAPI

    Для FastAPI есть готовые решения, например SlowAPI.

    Источник: SlowAPI (GitHub).

    Мини-идея выглядит так:

    Что важно понимать в этом примере:

  • get_remote_address лимитирует по IP, что не всегда справедливо для клиентов за NAT
  • для продакшена часто лимитируют по API ключу или sub в токене
  • для распределённого rate limit обычно подключают Redis как backend
  • Защита от перегрузки: таймауты, ограничения и “дорогие” endpoint’ы

    Таймауты на внешние зависимости

    Если ваш API ходит во внешние сервисы (или в БД), важно ограничивать время ожидания. Иначе один “зависший” внешний вызов может заблокировать воркеры.

    Связь с клиентской статьёй про requests: там вы всегда ставили timeout. На сервере правило то же самое, но уже для исходящих вызовов.

    Ограничивайте объём выдачи

    Даже без злоумышленников опасно отдавать “всё сразу”:

  • GET /items без пагинации может вернуть слишком много данных
  • большие ответы медленнее, дороже, сложнее кэшируются
  • Хорошая практика:

  • лимит limit с верхней границей (например, не больше 100)
  • пагинация и индексы в БД
  • Заголовки безопасности и “поведение по умолчанию”

    Для API обычно важно “быть скучным и предсказуемым”:

  • не раскрывать внутренние детали (стектрейсы, версии библиотек)
  • возвращать единый формат ошибок (как вы обсуждали в теме документации)
  • не включать debug в продакшене
  • Часть заголовков имеет смысл добавлять на уровне прокси (Nginx/Traefik), часть можно на уровне приложения.

    Пример простого middleware в FastAPI для добавления заголовков:

    Замечание: набор заголовков зависит от того, браузерный у вас клиент или нет. Для чистого JSON API часть “браузерных” заголовков не так критична, как корректная авторизация, валидация и отсутствие утечек.

    Управление секретами и конфигурацией

    Главное правило: секреты не должны быть в репозитории.

    Практический подход:

  • конфигурация приходит через переменные окружения
  • секреты хранятся в Secret Manager конкретной платформы или в CI/CD секретах
  • Классическая рекомендация: The Twelve-Factor App: Config.

    В вашем коде это обычно выглядит так:

    Дополнение: если вы используете Pydantic Settings, это делает загрузку конфигурации более структурированной, но принцип остаётся тем же.

    Деплой FastAPI: базовая практическая схема

    Как запускать приложение

    FastAPI — ASGI-приложение. Типичные варианты запуска:

  • uvicorn напрямую
  • gunicorn с uvicorn-воркерами
  • Документация: Uvicorn, Gunicorn.

    Пример запуска через gunicorn:

    Что означает --workers:

  • это количество процессов
  • больше воркеров помогает обслуживать больше одновременных запросов
  • но каждый воркер потребляет память
  • Роль реверс‑прокси

    Перед приложением обычно ставят Nginx/Traefik/Ingress, чтобы:

  • держать HTTPS
  • ограничивать размер запросов
  • делать базовый rate limit
  • отдавать статику (если нужно)
  • маршрутизировать трафик на несколько инстансов
  • Официальный ориентир по Nginx: Nginx Documentation.

    Docker как стандарт упаковки

    Docker не “обязателен”, но упрощает воспроизводимый деплой.

    Минимальный Dockerfile:

    Идея: в продакшене часто добавляют:

  • фиксирование зависимостей (requirements/lockfile)
  • запуск не под root
  • healthcheck
  • Документация: Dockerfile reference.

    Миграции базы данных при деплое

    В уроке про БД вы создавали таблицы при старте приложения. Для продакшена обычно используют миграции.

    Стандартный инструмент для SQLAlchemy: Alembic.

    Практический подход:

  • миграции выполняются как отдельный шаг в CI/CD (или init-контейнер)
  • приложение стартует уже на актуальной схеме
  • Наблюдаемость: логи, метрики, health endpoints

    Чтобы понимать, что происходит в продакшене, вам нужны:

  • логи: ошибки, таймауты, 4xx/5xx, медленные запросы
  • метрики: RPS, латентность, процент ошибок, количество 429
  • трассировка (опционально): где именно тратится время
  • Минимально полезный endpoint:

    Важно: health endpoint не должен раскрывать секреты и внутренние детали. Если вы делаете отдельные readiness/liveness проверки, продумайте, какие зависимости действительно надо проверять.

    Итог

  • Безопасность API — это комбинация транспорта (HTTPS), правильной авторизации, строгой валидации, аккуратного логирования и управления секретами.
  • Лимиты (особенно 429 и Retry-After) — важная часть контракта API и инструмент устойчивости.
  • Деплой FastAPI обычно включает ASGI-сервер (uvicorn/gunicorn), реверс‑прокси, управление конфигурацией через окружение, миграции БД и базовую наблюдаемость.
  • После этого слоя ваш API начинает быть похож на “продукт”, а не на учебный пример: его можно обновлять, масштабировать и поддерживать без постоянных аварий и сюрпризов.