JavaRush /Курси /Модуль 4: FastAPI /Відправлення асинхронних запитів до зовнішніх сервісів (G...

Відправлення асинхронних запитів до зовнішніх сервісів (GET)

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

Спочатку освіжимо в пам'яті, що таке асинхронне програмування.

У світі Python асинхронність досягається з використанням ключових слів async і await.

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

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

Асинхронний підхід особливо корисний при роботі з мережевими операціями, такими як HTTP-запити.

У FastAPI асинхронність — це фундаментальна частина. Якщо ми використовуємо асинхронні функції як эндпоінти та для взаємодії з зовнішніми API, ми зменшуємо блокування і пришвидшуємо обробку запитів.


Відправлення асинхронних GET-запитів з допомогою httpx

Бібліотека httpx підтримує виконання асинхронних HTTP-запитів, що робить її ідеальним вибором для роботи в асинхронних додатках, таких як FastAPI. Далі розберемо, як це працює.

Наводимо простий приклад асинхронного GET-запиту.

Припустимо, потрібно отримати список користувачів з публічного API. Ось як це робиться з використанням httpx:


import httpx

async def fetch_users():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://jsonplaceholder.typicode.com/users')
        # Перевіряємо статус відповіді
        if response.status_code == 200:
            # Повертаємо дані в форматі JSON
            return response.json()
        else:
            raise Exception(f"Помилка запиту: {response.status_code}")

Отже, як це працює:

  1. Ми використовуємо httpx.AsyncClient для виконання запитів.
  2. Метод await client.get() відправляє асинхронний GET-запит.
  3. Ми перевіряємо статус відповіді (200 означає успіх).
  4. Якщо все гаразд, ми повертаємо дані у форматі JSON.

Створюємо эндпоінт у FastAPI

Тепер завернемо наш асинхронний запит в эндпоінт FastAPI.

Припустимо, наш додаток має эндпоінт /users, який повертає список користувачів із зовнішнього API.

Ось як це зробити:


from fastapi import FastAPI, HTTPException
import httpx

app = FastAPI()

@app.get("/users")
async def get_users():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://jsonplaceholder.typicode.com/users')
        if response.status_code == 200:
            return response.json()
        else:
            raise HTTPException(status_code=response.status_code, detail="Помилка при запиті до зовнішнього API")

Тут ми створюємо эндпоінт, який:

  1. Відправляє GET-запит до зовнішнього API.
  2. Якщо запит успішний, повертає отримані дані користувачу.
  3. Якщо запит не пройшов, кидає виняток HTTPException, який FastAPI перетворює на зрозумілу HTTP-відповідь.

Обробка відповіді від зовнішнього API

Іноді дані, отримані від зовнішнього API, можуть бути «не надто чемними». Наприклад, вони можуть містити зайву інформацію. У таких випадках ми можемо попередньо обробити відповідь перед відправкою користувачу.

Припустимо, потрібно повернути лише імена користувачів із списку. Ось як це можна зробити:


@app.get("/users")
async def get_users():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://jsonplaceholder.typicode.com/users')
        if response.status_code == 200:
            users = response.json()
            # Повертаємо тільки імена користувачів
            return {"usernames": [user["name"] for user in users]}
        else:
            raise HTTPException(status_code=response.status_code, detail="Помилка при запиті до зовнішнього API")

Тепер эндпоінт поверне не повний об'єкт користувача, а лише їхні імена.


Обробка помилок

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

Для базової обробки помилок можна використовувати try-except. Ось приклад:


@app.get("/users")
async def get_users():
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get('https://jsonplaceholder.typicode.com/users')
            response.raise_for_status()  # Викидає помилку, якщо статус-код не 2xx
            return response.json()
    except httpx.RequestError as exc:
        raise HTTPException(status_code=500, detail=f"Помилка з'єднання: {exc}")
    except httpx.HTTPStatusError as exc:
        raise HTTPException(status_code=exc.response.status_code, detail=f"HTTP-помилка: {exc.response.content}")

Тут ми:

  1. Використовуємо response.raise_for_status() для перевірки успішності запиту.
  2. Перехоплюємо мережеві помилки (httpx.RequestError).
  3. Обробляємо помилки статусу HTTP (httpx.HTTPStatusError).

Практичний приклад: Інтеграція з зовнішнім API

Тепер створимо повноцінний міні-сервіс для отримання даних про погоду з зовнішнього API (наприклад, OpenWeatherMap).

Реєстрація і отримання API-ключа:

  1. Зайди на сайт OpenWeatherMap.
  2. Зареєструйся і створи API-ключ.

Ось приклад реалізації эндпоінта:


from fastapi import FastAPI, HTTPException
import httpx

app = FastAPI()
API_KEY = "ваш_api_ключ"

@app.get("/weather/{city}")
async def get_weather(city: str):
    url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}&units=metric"
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(url)
            response.raise_for_status()
            data = response.json()
            # Спрощуємо дані перед відправкою
            return {
                "city": data["name"],
                "temperature": data["main"]["temp"],
                "description": data["weather"][0]["description"]
            }
    except httpx.RequestError as exc:
        raise HTTPException(status_code=500, detail=f"Помилка з'єднання: {exc}")
    except httpx.HTTPStatusError as exc:
        raise HTTPException(status_code=exc.response.status_code, detail="Місто не знайдено!")

Перевірка роботи

  • Запусти додаток uvicorn main:app --reload.
  • Відкрий у браузері адресу http://127.0.0.1:8000/weather/London. Побачиш поточну погоду в Лондоні.

Розбір типових помилок

  1. Асинхронність не включена. Якщо забудеш додати async у функцію або await при виклику методу, отримаєш помилку "coroutine was never awaited".
  2. Неправильний API ключ. Якщо API-ключ відсутній або вказаний неправильно, сервіс поверне повідомлення про помилку. Переконайся, що передав ключ через параметр appid.
  3. Обробка виключень. Якщо твої запити падають (наприклад, через відсутній інтернет), без обробки помилок додаток "зламається". Додай try-except!

Тепер ти вмієш відправляти асинхронні GET-запити і обробляти дані, отримані з зовнішніх API. Ми вже на шляху до створення по-справжньому інтегрованих додатків! 🚀

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