JavaRush /Курси /Модуль 4: FastAPI /Валідація даних, отриманих від зовнішніх API

Валідація даних, отриманих від зовнішніх API

Модуль 4: FastAPI
Рівень 16 , Лекція 5
Відкрита

Однак взаємодія з зовнішніми API — це не тільки запити, але й робота з відповідями. Сервіси можуть повертати дані некоректного формату, пропускати ключі або навіть відповідати чимось не тим (наприклад, помилкою). Сьогодні обговоримо, як валідовувати ці дані в FastAPI за допомогою бібліотеки Pydantic. Так, це той самий Pydantic, який ми вже мельком бачили в контексті створення моделей для вводу даних.

Уявімо, що ви замовили піцу онлайн. Очікуєте побачити в коробці апетитний шматок тіста з сиром і начинкою, але натомість принесли суп. У програмуванні така несподіванка (ну, майже) може статись, якщо зовнішнє API раптом поверне дані не того формату, який ви очікували.

Ось кілька причин, чому валідація даних важлива:

  1. Гарантія структури даних: без валідації ви не можете бути впевненими, що дані в потрібному форматі.
  2. Обробка помилок: якщо дані не відповідають очікуванням, ви можете заздалегідь попередити додаток і не допустити падіння.
  3. Безпека: валідація запобігає використанню некоректних даних, які можуть призвести до вразливостей.

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

Поради з валідації даних

  1. Створюйте гнучкі моделі: використовуйте Optional для необов'язкових полів і значення за замовчуванням.
  2. Перевіряйте ключі: завжди припускайте, що ключі можуть бути відсутні.
  3. Опрацьовуйте вкладені структури: якщо дані складні, розбивайте їх на окремі моделі.
  4. Логуйте помилки: це допомагає знаходити проблеми у зовнішніх API.

Тепер ви знаєте, як валідовувати дані від зовнішніх API в FastAPI. Ці знання допоможуть створювати надійні й стійкі додатки, які не впадуть при першому зіткненні з "неочікуваною" погодою або дивними даними.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ