FastAPIのdependencies.py入門:Dependsで共通処理を整理する方法

monitor displaying error text
Photo by Pixabay on Pexels.com

FastAPIでは、データベース接続や認証などの共通処理を依存関係として定義し、Dependsを通じて各エンドポイントから利用できます。

dependencies.pyは、その依存関係を整理するためによく使われるファイル名です。

ただし、FastAPIがこの名前のファイルを要求しているわけではありません。

小規模なうちは一つのファイルにまとめ、役割が増えたらデータベース、認証、設定などの単位に分けると、エンドポイントの責務を読み取りやすくできます。

FastAPIの特徴とAPI設計の基本を先に確認したい場合は、基礎解説も参照してください。

FastAPIの依存性注入とは

依存性注入とは、処理に必要な値や機能を関数の中で直接用意せず、外側から受け取る設計です。

FastAPIでは、エンドポイントが必要とする処理をDependsに渡すと、FastAPIがその処理を実行し、戻り値をエンドポイントの引数へ渡します。

この仕組みを使うと、同じ前処理を複数のエンドポイントへ重複して書かずに済みます。

用途 依存関係が担う処理 エンドポイントが受け取るもの
データベース セッションを作成し、処理後に閉じる 利用可能なセッション
認証 トークンを受け取り、利用者を確認する 確認済みの利用者情報
設定 環境ごとの設定を読み込む 設定オブジェクト
ヘッダー 必要なヘッダーを受け取り、値を確認する 確認済みの値

データベースセッションをリクエスト単位で管理する

データベースセッションは、作成する処理と閉じる処理を対にして管理する必要があります。

yieldを使う依存関係なら、エンドポイントへセッションを渡したあと、finallyで後始末を実行できます。

セッションを提供する依存関係

from app.database import SessionLocal

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

yieldまでの処理でセッションを準備し、yieldした値をエンドポイントへ渡します。

エンドポイントの処理が終わるとfinallyが実行されるため、利用側が同じ終了処理を繰り返し書く必要はありません。

ルーターから利用する例

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session

from app.dependencies import get_db
from app.models.user import User

router = APIRouter()

@router.get("/users/")
def read_users(db: Session = Depends(get_db)):
    return db.query(User).all()

Depends(get_db)には、get_db()の実行結果ではなく関数そのものを渡します。

セッションの作成と終了をルーターから切り離すことで、read_usersは利用者一覧を返す処理に集中できます。

認証と認可を分けて考える

認証は利用者が誰であるかを確認する処理で、認可は確認済みの利用者が対象の操作を実行できるかを判断する処理です。

この二つを依存関係として分けると、利用者の確認と権限の判断が混ざりにくくなります。

現在の利用者を取得する依存関係

from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def get_current_user(token: str = Depends(oauth2_scheme)):
    user = decode_and_validate_token(token)  # アプリケーション側で実装する
    if user is None:
        raise HTTPException(status_code=401, detail="Invalid credentials")
    return user

例のdecode_and_validate_tokenは、トークンの検証方法や利用者の取得方法に合わせて実装する部分です。

固定文字列との比較だけで本番の認証を済ませることはできないため、元の記事にあった簡略化した判定は概念を示す形へ改めました。

保護されたルートで利用する例

from fastapi import APIRouter, Depends

from app.dependencies import get_current_user

router = APIRouter()

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

トークンが無効なら依存関係の段階で処理が中断され、有効ならエンドポイントは利用者情報を受け取ります。

実運用の検証方法や権限設計は要件によって変わるため、FastAPIの認証と認可を含むセキュリティ設計もあわせて確認してください。

設定値を依存関係として渡す

データベースURLや秘密鍵などの設定値を一か所にまとめると、各エンドポイントが環境変数を個別に読む構成を避けられます。

次の例では、設定クラスと、そのインスタンスを返す依存関係を定義します。

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    api_key: str

    model_config = SettingsConfigDict(env_file=".env")

settings = Settings()

def get_settings():
    return settings

利用側はDepends(get_settings)を指定し、設定オブジェクトを受け取ります。

from fastapi import Depends

from app.config import Settings, get_settings

def get_secret_key(
    settings: Settings = Depends(get_settings),
):
    return settings.secret_key

設定ライブラリの記述はバージョンによって異なる場合があるため、実際のプロジェクトではインストール済みのPydanticとFastAPIに対応する公式ドキュメントを確認してください。

リクエストヘッダーを検証する

APIキーなどのリクエストヘッダーも、複数のエンドポイントで共通して確認するなら依存関係にできます。

元の記事の例はif x_api_keyで途切れていたため、設定値と比較し、不一致ならエラーを返す流れまで補いました。

from typing import Optional

from fastapi import Depends, Header, HTTPException

from app.config import Settings, get_settings

def get_api_key(
    x_api_key: Optional[str] = Header(default=None),
    settings: Settings = Depends(get_settings),
):
    if x_api_key != settings.api_key:
        raise HTTPException(status_code=401, detail="Invalid API key")
    return x_api_key

APIキーそのものはコードへ直接書かず、設定から取得します。

検証に成功した値をエンドポイントで使わない場合でも、Depends(get_api_key)を指定すれば、前処理として検証を実行できます。

dependencies.pyを分割する判断基準

dependencies.pyという一つのファイルにすべてを集めると、規模が大きくなるにつれて変更箇所を探しにくくなります。

データベース、認証、設定、ヘッダー検証の責務が独立してきた段階で、役割ごとのモジュールへ分けます。

  • dependencies/database.py:セッションの作成と終了
  • dependencies/auth.py:利用者の確認と権限判定
  • config.py:環境ごとの設定
  • dependencies/headers.py:共通ヘッダーの検証

分割の目的はファイル数を増やすことではなく、変更理由の異なる処理を切り分けることです。

ルーター、設定、サービスなどを含む全体の配置は、FastAPIのプロジェクト構成を段階的に整理する方法で詳しく解説しています。

実装前に確認したい項目

  • dependencies.pyをFastAPI固有の必須ファイルとして扱っていないか
  • yieldを使う依存関係で、終了処理をfinallyに置いているか
  • 認証と認可の責務を区別しているか
  • トークンやAPIキーを固定文字列だけで判定していないか
  • 秘密情報をソースコードへ直接書いていないか
  • エンドポイントが業務処理以外の準備や後始末で読みにくくなっていないか

依存関係は、共通処理を単に別ファイルへ移すための仕組みではありません。

準備、検証、後始末の境界を明確にし、エンドポイントへ必要な値だけを渡すために使うと、FastAPIのコードを追いやすく保てます。

投稿者 greeden

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)