Dependency Injection (DI) в FastAPIDependency Injection (DI) в FastAPI

Dependency Injection (DI) — это концепция в программировании, которая позволяет управлять зависимостями между компонентами кода. Обычно это достигается за счёт внедрения необходимых ресурсов в функции или классы извне, что делает их более гибкими и легко тестируемыми. В традиционных программах, когда один объект создаёт или инициализирует другой, между ними создается жесткая связь. С использованием DI зависимости предоставляются извне, что упрощает взаимодействие между компонентами.

Примеры применения DI и его преимущества

Dependency Injection можно представить в виде услуги, которая поставляется извне. Например, класс, отвечающий за регистрацию пользователей, может нуждаться в доступе к базе данных для сохранения информации о новых пользователях и к сервису email для отправки приветственного письма. При использовании DI мы передаем объекты для работы с базой данных и отправки email в класс регистрации, не создавая их внутри. Это позволяет легко заменить или протестировать каждый компонент без изменения самого класса регистрации.

Преимущества DI:

  • Модульность: Код становится более независимым, так как компоненты легко заменяются.
  • Тестируемость: DI упрощает тестирование кода, поскольку позволяет подменять реальные зависимости на фиктивные (mock) или тестовые.
  • Гибкость: Упрощается добавление и изменение логики, например, мы можем легко заменить службу отправки email на другую, просто внедрив новый компонент.

Теперь, когда у нас есть базовое понимание DI, давайте рассмотрим, как эта концепция реализуется в FastAPI.

Dependency Injection в FastAPI

FastAPI — это фреймворк для создания веб-API, который предоставляет встроенную поддержку Dependency Injection. Одной из особенностей DI в FastAPI является автоматическая передача зависимостей с помощью функции Depends, которая упрощает инъекцию зависимостей. Dependency Injection в FastAPI поддерживает автоматическое внедрение объектов в функции, которые требуют внешних ресурсов, таких как:

  • Подключение к базе данных
  • Настройки конфигурации
  • Аутентификационные токены
  • Внешние API

FastAPI распознает зависимости, объявленные через Depends, и автоматически их создает и передает в нужные функции. Таким образом, DI помогает избежать дублирования кода и позволяет нам сосредоточиться на основной логике.

Пример базовой зависимости в FastAPI

Чтобы лучше понять работу DI в FastAPI, начнем с простого примера зависимости. Допустим, у нас есть функция, которая возвращает текущее время, и мы хотим использовать это время в различных эндпоинтах нашего API.

from fastapi import FastAPI, Depends
from datetime import datetime

app = FastAPI()

# Зависимость, возвращающая текущее время
def get_current_time():
    return datetime.now()

@app.get("/time")
def read_time(current_time: datetime = Depends(get_current_time)):
    return {"current_time": current_time}

В этом примере:

  1. Функция get_current_time возвращает текущее время.
  2. Эндпоинт /time использует Depends, чтобы получить значение времени из get_current_time.

Каждый раз, когда мы вызываем /time, FastAPI автоматически вызывает get_current_time, передает результат в read_time и возвращает значение пользователю.

Dependency Injection с базой данных в FastAPI

Теперь создадим более сложный пример с подключением к базе данных. Предположим, что мы хотим реализовать API, где пользователи могут запрашивать информацию из базы данных. Мы используем SQLAlchemy для управления базой данных и создаем отдельную функцию-зависимость для подключения к ней.

Шаг 1: Настройка подключения к базе данных

Сначала создаем базу данных и функцию для подключения:

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session

DATABASE_URL = "sqlite:///./test.db"

engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Функция-зависимость для создания и закрытия сессии базы данных
def get_db() -> Session:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

Функция get_db создает сессию базы данных, а затем yield передает ее в нужную функцию, при этом сессия автоматически закрывается после завершения запроса.

Шаг 2: Использование зависимости в эндпоинте

Теперь добавим эндпоинт, который будет использовать эту зависимость для выполнения запроса к базе данных. Предположим, у нас есть таблица пользователей, и мы хотим получить пользователя по его идентификатору:

# main.py
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from . import database  # импортируем модуль с функцией get_db

app = FastAPI()

# Модель пользователя
class User:
    id: int
    name: str

# Функция для получения пользователя из БД
def get_user(db: Session, user_id: int):
    user = db.query(User).filter(User.id == user_id).first()
    return user

# Эндпоинт с использованием зависимости get_db
@app.get("/users/{user_id}")
def read_user(user_id: int, db: Session = Depends(database.get_db)):
    user = get_user(db, user_id=user_id)
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user

Здесь db: Session = Depends(get_db) указывает, что функция get_db будет вызвана автоматически, а возвращаемое значение будет передано в db. Это освобождает нас от необходимости вручную создавать и закрывать сессию для каждого запроса, упрощая взаимодействие с базой данных.

Продвинутые примеры Dependency Injection

Помимо базы данных, Dependency Injection также полезен для таких компонентов, как конфигурационные настройки, авторизация и работа с внешними API.

1. Dependency Injection для аутентификации

Для веб-API часто требуется проверка подлинности пользователя. В следующем примере создадим зависимость, которая проверяет наличие токена и возвращает пользователя.

from fastapi import HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

security = HTTPBearer()

def get_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
    token = credentials.credentials
    # Логика для проверки и декодирования токена
    if token != "valid_token":  # простая проверка для примера
        raise HTTPException(status_code=401, detail="Invalid or expired token")
    return {"username": "test_user"}

Теперь добавим зависимость в эндпоинт:

@app.get("/protected")
def protected_route(user: dict = Depends(get_current_user)):
    return {"message": f"Hello, {user['username']}!"}

2. Dependency Injection для внешних API

Зависимости также могут быть полезны для обработки запросов к внешним сервисам. Например, если API интегрируется с внешним сервисом погоды, DI упростит обновление или замену этого сервиса.

import requests

def get_weather_data(city: str):
    response = requests.get(f"http://api.weather.com/v3/weather/{city}")
    return response.json()

Эту функцию можно использовать в любом эндпоинте:

@app.get("/weather/{city}")
def weather(city: str, weather_data=Depends(lambda: get_weather_data(city))):
    return weather_data

Тестирование и настройка DI для тестов

Одним из преимуществ DI является легкость замены зависимостей на тестовые объекты. В FastAPI тестирование можно проводить с помощью TestClient и фиктивных зависимостей.

Пример теста для зависимости базы данных:

from fastapi.testclient import TestClient
from .main import app

client = TestClient(app)

# Подменим get_db на фиктивное подключение
def override_get_db():
    # Создаем тестовую базу данных или используем фиктивные данные
    yield mock_db_session  # фиктивная сессия

app.dependency_overrides[get_db] = override_get_db

# Тестирование эндпоинта
def test_read_user():
    response = client.get("/users/1")
    assert response.status_code == 200

Заключение

Dependency Injection в FastAPI позволяет строить API, которые легко расширять и поддерживать. Использование DI улучшает тестируемость и модульность кода, позволяя передавать зависимости динамически. В FastAPI DI реализован с помощью Depends, что делает фреймворк удобным и гибким для создания любых веб-приложений, будь то простые API или сложные микросервисные архитектуры.

Поделится