Однако взаимодействие с внешними API — это не только запросы, но и работа с ответами. Сервисы могут возвращать данные некорректного формата, пропускать ключи или даже вовсе отвечать чем-то неприличным (например, ошибкой). Сегодня мы обсудим, как валидировать эти данные в FastAPI с помощью библиотеки Pydantic. Да, это тот самый Pydantic, который мы уже мельком видели в контексте создания моделей для ввода данных.
Давайте представим, что вы заказали пиццу онлайн. Вы ожидаете увидеть в коробке аппетитный кусок теста с сыром и начинкой, но вместо этого вам принесли суп. В программировании такая неожиданность (ну, почти) может произойти, если внешнее API вдруг вернет данные не того формата, который вы ожидали.
Вот несколько причин, почему важна валидация данных:
- Гарантия структуры данных: без валидации вы не можете быть уверены, что данные будут в нужном формате.
- Обработка ошибок: если данные не соответствуют ожиданиям, вы можете заранее предупредить приложение об этом и не допустить падения.
- Безопасность: валидация предотвращает использование некорректных данных, которые могут привести к уязвимостям.
FastAPI и Pydantic предоставляют мощные инструменты для того, чтобы данные от внешнего API сначала "прошли контроль", прежде чем попасть в ваше приложение.
Использование Pydantic для валидации
Начнем с простого примера. Допустим, мы получаем данные о погоде из OpenWeatherMap API. Ответ от API может быть следующим:
{
"city": "New York",
"temperature": 22.5,
"unit": "Celsius"
}
Если мы не проверим данные, они могут выглядеть "правильно", а могут содержать и сюрприз, например:
{
"city_name": "New York",
"temp": "22.5"
}
Это явно не то, что наш код ожидал. Используем Pydantic для валидации:
Создание Pydantic-модели
Pydantic-модель — это класс, который наследуется от BaseModel и описывает необходимую структуру данных. Например:
from pydantic import BaseModel
class WeatherResponse(BaseModel):
city: str
temperature: float
unit: str
Здесь мы ожидаем:
city— строка названия города.temperature— число с плавающей запятой.unit— строка (например, "Celsius" или "Fahrenheit").
Валидация ответа от API
Представим, что мы сделали запрос к внешнему API:
import httpx
import asyncio
async def fetch_weather():
async with httpx.AsyncClient() as client:
response = await client.get("https://example.com/weather")
return response.json()
Данные нужно валидировать. Вот как это делается:
async def get_validated_weather():
raw_data = await fetch_weather()
try:
weather = WeatherResponse(**raw_data)
return weather
except ValueError as e:
print(f"Ошибка валидации данных: {e}")
raise
Что здесь происходит? Мы передаем "сырые" данные через **raw_data в нашу модель WeatherResponse. Если структура данных не соответствует ожиданиям, Pydantic выбросит исключение ValueError.
Работа с вложенными структурами
Реальные API часто возвращают более сложные объекты. Например:
{
"location": {
"city": "New York",
"country": "USA"
},
"weather": {
"temperature": 22.5,
"unit": "Celsius"
}
}
Для обработки таких данных создайте вложенные модели:
class Location(BaseModel):
city: str
country: str
class Weather(BaseModel):
temperature: float
unit: str
class WeatherApiResponse(BaseModel):
location: Location
weather: Weather
Теперь вы можете работать с данным ответом:
async def get_weather():
raw_data = await fetch_weather()
try:
validated_data = WeatherApiResponse(**raw_data)
return validated_data
except ValueError as e:
print(f"Ошибка валидации вложенных данных: {e}")
raise
Работа с опциональными полями
Иногда внешние API могут пропустить некоторые ключи. Для таких ситуаций используем Optional:
class WeatherResponse(BaseModel):
city: str
temperature: float | None = None
unit: str
Теперь поле temperature не обязательно. Если его нет в данных, оно будет установлено в None.
Пример интеграции в FastAPI
Объединим всё, что мы рассмотрели, в реальный эндпоинт FastAPI:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/weather")
async def get_weather():
raw_data = await fetch_weather()
try:
validated_data = WeatherApiResponse(**raw_data)
return validated_data
except ValueError:
raise HTTPException(status_code=400, detail="Некорректные данные от API")
Теперь, если внешний API возвращает некорректные данные, наши пользователи получат понятную ошибку.
Практическая реализация
Когда данные не проходят проверку, Pydantic автоматически генерирует описание ошибок. Например:
from pydantic import ValidationError
try:
WeatherResponse(**{"city": "New York", "temperature": "hot", "unit": 123})
except ValidationError as e:
print(e.json())
Вывод:
[
{
"loc": ["temperature"],
"msg": "value is not a valid float",
"type": "type_error.float"
},
{
"loc": ["unit"],
"msg": "str type expected",
"type": "type_error.str"
}
]
Логирование ошибок
Логируйте все ошибки с помощью встроенной библиотеки logging для последующего анализа:
import logging
logging.basicConfig(level=logging.ERROR)
try:
WeatherResponse(**raw_data)
except ValidationError as e:
logging.error(f"Ошибка валидации: {e}")
raise
Советы по валидации данных
- Создавайте гибкие модели: используйте
Optionalдля необязательных полей и дефолтные значения. - Проверяйте ключи: всегда предполагайте, что ключи могут отсутствовать.
- Обрабатывайте вложенные структуры: если данные сложные, разбивайте их на отдельные модели.
- Логируйте ошибки: это помогает находить проблемы во внешних API.
Теперь вы знаете, как валидировать данные от внешних API в FastAPI. Эти знания помогут вам создавать надежные и устойчивые приложения, которые не рухнут при первой встрече с "неожиданной" погодой или странными данными.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ