JavaRush /Курси /Модуль 4: FastAPI /Обробка помилок при роботі з зовнішніми сервісами

Обробка помилок при роботі з зовнішніми сервісами

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

Усі ми знаємо: програма працює ідеально тільки на стадії демо для боса. У реальному житті трапляються таймаути, зникає інтернет-з'єднання, а зовнішні API повертають відповіді, які викликають сльози навіть у досвідчених розробників.

Помилки при взаємодії з зовнішніми сервісами можуть бути пов'язані з багатьма факторами:

  • Тимчасові збої сервера.
  • Некоректні дані від клієнта.
  • Таймаути при підключенні.
  • Помилки аутентифікації (наприклад, невірний API-ключ).
  • Невірні запити до API.
  • Зміни в документації API без повідомлення (так, таке трапляється).

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


Типові помилки при роботі з зовнішніми API

  1. Проблеми з підключенням

Можливо, сервер недоступний через мережеві проблеми або API тимчасово відключене на їхньому боці. Такі помилки зазвичай проявляються як ConnectionError, TimeoutError або їх аналоги в бібліотеці httpx.


import httpx

async def get_data_from_api():
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get("https://example.com/nonexistent")
            response.raise_for_status()  # Перевірка на HTTP-помилки
            return response.json()
    except httpx.RequestError as exc:
        print(f"Помилка під час підключення: {exc}")
        # Можна повернути дефолтне значення або повідомлення
        return {"error": "Сервер тимчасово недоступний"}

# Виклик функції
data = await get_data_from_api()
print(data)

Якщо сервер «ліг», обробка все одно поверне вам акуратну відповідь замість екрану з незрозумілою трассировкою.

  1. Помилки авторизації

Зовнішні API часто вимагають аутентифікацію, будь то через токени, ключі або OAuth. Якщо ви випадково відправите невірний токен або ключ, отримаєте помилку 401 Unauthorized або 403 Forbidden.

Розберемо цю ситуацію:


async def get_protected_data():
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                "https://api.example.com/protected-resource",
                headers={"Authorization": "Bearer INVALID_TOKEN"}
            )
            response.raise_for_status()
    except httpx.HTTPStatusError as exc:
        if exc.response.status_code == 401:
            print("Помилка авторизації. Перевірте ваш токен доступу!")
        elif exc.response.status_code == 403:
            print("Доступ заборонено. Ви не маєте прав для виконання цього запиту.")
        else:
            print(f"HTTP помилка: {exc}")
    return None
  1. Неправильний формат даних

API може несподівано повернути XML замість JSON (ні, це не жарт). Розбираємося з цим:


async def parse_response():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com/weird-format")
        try:
            data = response.json()  # Припускаємо JSON
        except ValueError:
            print("Помилка: Відповідь від сервера не є коректним JSON.")
            return None
        return data

data = await parse_response()

Якщо API несподівано вирішило «виправити» специфікацію, ваш код не впаде, а акуратно повідомить про помилку.


Загальна обробка виключень в httpx

Тут ми збираємо все докупи. Головне правило: ніколи не лишайте потенційні виключення необробленими!


async def fetch_data_with_error_handling():
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get("https://api.example.com/data")
            response.raise_for_status()  # Перевіряємо HTTP-помилки
            return response.json()
    except httpx.TimeoutException:
        print("Помилка: Запит перевищив встановлені часові рамки.")
    except httpx.HTTPStatusError as exc:
        print(f"HTTP помилка: Код {exc.response.status_code}, повідомлення: {exc.response.text}")
    except httpx.RequestError as exc:
        print(f"Помилка при запиті: {exc}")
    return None

Практичні поради щодо обробки помилок

  1. Використовуйте try-except.
    Будь-яка робота з зовнішнім API має бути оточена блоком try-except. Це ваш перший рівень захисту від ненормальних ситуацій.
  2. Логируйте ошибки.
    Замість того, щоб просто їх друкувати, використовуйте систему логування, таку як logging. Це допоможе в відладці та аналізі проблем.
    
    import logging
    
    logging.basicConfig(level=logging.ERROR)
    
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get("https://api.example.com")
    except httpx.RequestError as exc:
        logging.error(f"Помилка: {exc}")
    
  3. Повторні спроби.
    Використовуйте стратегію повторних спроб (retry) для тимчасових помилок. Наприклад, за допомогою циклу або бібліотеки tenacity.
    
    from tenacity import retry, wait_fixed, stop_after_attempt
    
    @retry(wait=wait_fixed(2), stop=stop_after_attempt(3))
    async def fetch_with_retry():
        async with httpx.AsyncClient() as client:
            response = await client.get("https://api.example.com")
            response.raise_for_status()
            return response.json()
    

    Цей код намагається виконати запит три рази з інтервалом 2 секунди між спробами.

  4. Показуйте інформативні повідомлення користувачам.
    Якщо помилка пов'язана з зовнішнім API, опишіть це користувачу зрозумілою мовою.
    
    async def user_friendly_api_call():
        try:
            async with httpx.AsyncClient() as client:
                response = await client.get("https://api.example.com/data")
                response.raise_for_status()
                return response.json()
        except httpx.RequestError:
            return {"error": "Зовнішній сервіс тимчасово недоступний. Спробуйте пізніше."}
    

Приклад: Повноцінна обробка помилок для API

Давайте напишемо приклад, який поєднує все, що ми пройшли.

Припустимо, ми інтегруємося з OpenWeatherMap API для отримання погодних даних.


import httpx
from tenacity import retry, wait_fixed, stop_after_attempt
import logging

logging.basicConfig(level=logging.ERROR)

API_KEY = "ваш_api_ключ"

@retry(wait=wait_fixed(2), stop=stop_after_attempt(3))
async def fetch_weather(city: str):
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                "https://api.openweathermap.org/data/2.5/weather",
                params={"q": city, "appid": API_KEY, "units": "metric"}
            )
            response.raise_for_status()
            return response.json()
    except httpx.HTTPStatusError as exc:
        logging.error(f"Помилка API: Код {exc.response.status_code} - {exc.response.text}")
        return {"error": "Помилка даних про погоду"}
    except httpx.RequestError as exc:
        logging.error(f"Помилка при запиті: {exc}")
        return {"error": "Мережева помилка. Спробуйте пізніше."}

async def main():
    city = "Moscow"
    weather = await fetch_weather(city)
    print(weather)

# Виклик функції
# asyncio.run(main())

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


Отже, обробка помилок при роботі з зовнішніми сервісами — це не просто «приємне доповнення»,
а невід'ємна частина якісного застосунку.

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