В этой лекции мы соберем все ранее изученные концепции и реализуем полноценный API на FastAPI с интеграцией внешнего сервиса. Мы организуем проект, создадим эндпоинты, подключимся к внешнему API, обработаем полученные данные и добавим валидацию, а также напишем простые тесты. Чтобы сделать проект интересным, мы будем работать с OpenWeatherMap API для предоставления данных о погоде.
Мы реализуем небольшой API-приложение, которое позволяет клиентам:
- Запрашивать текущую информацию о погоде по городу.
- Валидация данных (например, проверим, что город действительно указан).
- Обрабатывать ошибки в случае проблем с внешним API (например, неверный API-ключ или ошибки сети).
Такие знания пригодятся при создании реальных проектов, где требуется взаимодействие с другими сервисами: интеграция с платежными системами, соцсетями, аналитическими инструментами и т.д. Этот навык также часто проверяется на собеседованиях.
Создание структуры проекта
Сначала организуем каталог проекта. Самая базовая структура будет выглядеть так:
weather_app/
├── app/
│ ├── main.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── weather.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── schemas.py
│ ├── services/
│ ├── __init__.py
│ ├── weather_service.py
│ ├── settings.py
├── requirements.txt
- app/main.py — основной файл для запуска приложения.
- app/api/weather.py — здесь будем описывать маршруты.
- app/models/schemas.py — храним Pydantic-схемы.
- app/services/weather_service.py — реализуем логику интеграции с внешним API.
- app/settings.py — настройки проекта, например API-ключи.
Установка зависимостей
Начнем с установки библиотек. Нам понадобится FastAPI, httpx и Pydantic:
pip install fastapi uvicorn httpx
Обновите requirements.txt:
fastapi
httpx
uvicorn
Настройка основного файла приложения
Создадим минимальный файл main.py:
from fastapi import FastAPI
from app.api.weather import router as weather_router
app = FastAPI(
title="Weather API",
description="A simple API to fetch weather information from OpenWeatherMap",
version="1.0.0"
)
app.include_router(weather_router, prefix="/weather")
Здесь мы создаем экземпляр FastAPI и подключаем маршруты из модуля weather.
Реализация эндпоинта для получения погоды
Добавим файл app/api/weather.py:
from fastapi import APIRouter, HTTPException, Query
from app.models.schemas import WeatherResponse
from app.services.weather_service import get_weather_data
router = APIRouter()
@router.get("/", response_model=WeatherResponse)
async def get_weather(city: str = Query(..., description="City name to fetch weather data")):
"""
Fetch weather data for a given city.
"""
weather_data = await get_weather_data(city)
if not weather_data:
raise HTTPException(status_code=404, detail="Weather data not found")
return weather_data
- Маршрут: Создаем маршрут
GET /weather/, принимающий параметрcity. - Описание: Используем
Queryдля описания параметра города. - Ответ: Возвращаем JSON в виде, описанном в схеме
WeatherResponse.
Описание Pydantic-схемы
Теперь создадим файл app/models/schemas.py, где опишем схему ответа:
from pydantic import BaseModel
class WeatherResponse(BaseModel):
city: str
temperature: float
description: str
Этот класс задает структуру возвращаемых данных: город, температура и описание погоды.
Написание сервиса для работы с внешним API
Добавим файл app/services/weather_service.py:
import httpx
from app.settings import OPENWEATHERMAP_API_KEY
BASE_URL = "http://api.openweathermap.org/data/2.5/weather"
async def get_weather_data(city: str):
"""
Fetch weather data from OpenWeatherMap API.
"""
params = {
"q": city,
"appid": OPENWEATHERMAP_API_KEY,
"units": "metric" # Get temperature in Celsius
}
async with httpx.AsyncClient() as client:
response = await client.get(BASE_URL, params=params)
if response.status_code != 200:
return None
data = response.json()
return {
"city": data["name"],
"temperature": data["main"]["temp"],
"description": data["weather"][0]["description"]
}
- Функция отправляет асинхронный GET-запрос к OpenWeatherMap API.
- Парсит ответ и извлекает нужные данные: имя города, температуру и описание.
Настройка API-ключа
Создадим файл app/settings.py:
import os
OPENWEATHERMAP_API_KEY = os.getenv("OPENWEATHERMAP_API_KEY", "your_api_key_here")
Не забудьте заменить your_api_key_here на ваш реальный API-ключ. Или установите переменную окружения OPENWEATHERMAP_API_KEY.
Обработка ошибок
Если у сервиса OpenWeatherMap возникнет ошибка (например, неверный API-ключ или несуществующий город), мы возвращаем None. В маршруте это преобразуется в HTTP-ошибку 404 через HTTPException.
Можно улучшить обработку ошибок:
if response.status_code == 404:
return {"error": "City not found"}
elif response.status_code == 401:
return {"error": "Invalid API key"}
response.raise_for_status()
Тестирование эндпоинта
Простой клиент для проверки:
создайте файл test_weather.py:
import pytest
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
@pytest.mark.parametrize("city", ["London", "Paris", "Tokyo"])
def test_get_weather(city):
response = client.get(f"/weather/?city={city}")
assert response.status_code == 200
assert "temperature" in response.json()
- Тесты проверяют несколько городов.
- Убедитесь, что ответ содержит поле
temperature.
Запуск приложения
Запустите сервер с помощью команды:
uvicorn app.main:app --reload
Перейдите на http://127.0.0.1:8000/docs, чтобы протестировать эндпоинт через Swagger UI.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ