開発

FastAPIのDependsの使い方|依存性注入・yield・テストの差し替えをコードで解説

FastAPIのDependsの使い方|依存性注入・yield・テストの差し替えをコードで解説

FastAPIのDependsは、エンドポイントの引数に「この値はこの関数の戻り値で埋める」と宣言する仕組みです。認証済みのユーザー、DBの接続、ページングの値といった、多くのエンドポイントで同じ手順で作る値を1か所にまとめられます。この記事では、FastAPI 0.162.5で小さなメモAPIを作りながら、Annotatedでの書き方、サブ依存とクラス依存、ルーターやアプリ全体への適用、yieldを使った後処理、dependency_overridesによるテストの差し替えまでをコードで確認する構成です。Dependsに入れない方がよい処理の線引きも扱います。

まとめ:FastAPIのDependsで押さえる書き方と使い分けの結論

新しく書くコードはAnnotated[型, Depends(関数)]の形に揃えます。FastAPIは0.95.0でこの書き方に対応し、公式ドキュメントも推奨しています。Dependsには関数そのものを渡し、括弧を付けて呼びません。呼び出しはFastAPIの役目です。

使い分けの結論は次のとおりです。戻り値を使う前処理(認証済みユーザーの取得、DB接続)は引数のDependsに置きます。戻り値を使わない検査(APIキーの確認など)の配置先は、デコレーターやルーターのdependenciesです。後片付けが要る資源はyieldで書き、exceptで受けた例外は必ず送り直します。

テストではapp.dependency_overridesで認証とDBを差し替え、テストの終わりに必ず空に戻します。一方、業務ルールの判定や重い集計はDependsに入れず、サービス層の関数に置きます。依存関数は「リクエストから値を取り出して整える」ところで止めるのが、長く保守できる線です。

FastAPI 0.162系で依存関数を書いてDependsで注入する最小コード

まずは依存関数を1つ書いて、エンドポイントへ値が渡る流れを確かめます。

fastapi[standard]0.162.5を入れて検証環境を作る手順

2026年11月13日時点のPyPIで、FastAPIの最新は0.162.5(2026年11月11日公開)で、動作要件はPython 3.10以上です。この記事のコードはstr | Noneの型表記を使うため、3.10以上を前提にします。FastAPIを初めて入れる場合は、FastAPIの最短の起動手順とasync/defの使い分けを先に済ませてください。

# 仮想環境を作り、FastAPIとテスト用のpytestを入れる
python3 -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]==0.162.5" pytest

# main.py を置いたディレクトリで開発サーバーを起動する
fastapi dev main.py

fastapi[standard]には、開発サーバーのfastapi devと、テストで使うTestClientが依存するhttpxが含まれます。

Annotated[型, Depends(関数)]の書き方と関数を呼ばずに渡す理由

最小の形は次のとおりです。common_paramsが依存関数で、エンドポイントの引数paramsにその戻り値が入ります。

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


def common_params(q: str | None = None, limit: int = 20):
    return {"q": q, "limit": limit}


@app.get("/items")
def read_items(params: Annotated[dict, Depends(common_params)]):
    return params

Depends(common_params())と括弧を付けると、モジュールを読み込んだ時点で1回だけ実行され、その戻り値の辞書が渡されます。辞書は呼び出せないため、FastAPIはその依存を解決できずエラーになります。公式チュートリアルのDependenciesにあるとおり、関数は渡すだけで、呼ぶのはFastAPIです。

古いコードではparams: dict = Depends(common_params)のように既定値の位置に書く形も見かけます。動作は同じですが、公式はAnnotatedへの対応を0.95.0で追加し、使う前に0.95.1以上へ上げるよう案内しています。Annotatedの形なら同じ型をDbDepのような名前で再利用でき、引数の並び順の制約も受けません。

依存関数がクエリ・ヘッダーを受け取る仕組みとOpenAPIへの反映

依存関数の引数は、エンドポイントの引数と同じ規則で解釈されます。上のqとlimitはクエリパラメータになり、Header()を付ければヘッダー、Query(ge=1)を付ければ範囲検証つきのクエリになります。検証に通らなければ、エンドポイントの本体は実行されず、返るステータスコードは422です。

依存関数で宣言したパラメータは、OpenAPIの仕様書にもエンドポイントのパラメータとして載ります。/docsのSwagger UIで、依存側で宣言したヘッダーの入力欄が出るのはこのためです。表示の調整はFastAPIのSwagger UIの表示・設定と本番で隠す手順で扱っています。

サブ依存とクラス依存で認証・ページング処理を組み立てる実装手順

ここからは、トークンで利用者を判定し、自分のメモだけを件数上限つきで返すAPIを1ファイルで書きます。依存の連鎖、クラスによる設定値の受け渡し、yieldでの接続の開閉が1つのファイルに入っています。

# main.py
import sqlite3
from typing import Annotated

from fastapi import Depends, FastAPI, Header, HTTPException, Query, status

app = FastAPI()

# 検証用のトークン表。本番ではJWTの署名検証やDB照会に置き換える
TOKENS = {"token-alice": "alice", "token-bob": "bob"}


def get_db():
    # リクエストごとに接続を開き、後処理で必ず閉じる
    conn = sqlite3.connect("app.db", check_same_thread=False)
    conn.execute(
        "CREATE TABLE IF NOT EXISTS notes"
        "(id INTEGER PRIMARY KEY, owner TEXT, body TEXT)"
    )
    try:
        yield conn
    finally:
        conn.close()


def get_token(x_api_token: Annotated[str | None, Header()] = None):
    if x_api_token is None:
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, "token required")
    return x_api_token


def get_current_user(token: Annotated[str, Depends(get_token)]):
    user = TOKENS.get(token)
    if user is None:
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid token")
    return user


class Pagination:
    def __init__(self, max_limit: int):
        self.max_limit = max_limit

    def __call__(
        self,
        limit: Annotated[int, Query(ge=1)] = 20,
        offset: Annotated[int, Query(ge=0)] = 0,
    ):
        return {"limit": min(limit, self.max_limit), "offset": offset}


DbDep = Annotated[sqlite3.Connection, Depends(get_db)]
UserDep = Annotated[str, Depends(get_current_user)]
PageDep = Annotated[dict, Depends(Pagination(max_limit=50))]


@app.get("/notes")
def list_notes(db: DbDep, user: UserDep, page: PageDep):
    rows = db.execute(
        "SELECT id, body FROM notes WHERE owner = ? ORDER BY id LIMIT ? OFFSET ?",
        (user, page["limit"], page["offset"]),
    ).fetchall()
    return {"user": user, "page": page, "items": [{"id": r[0], "body": r[1]} for r in rows]}


@app.post("/notes", status_code=201)
def create_note(text: str, db: DbDep, user: UserDep):
    cur = db.execute("INSERT INTO notes(owner, body) VALUES (?, ?)", (user, text))
    db.commit()
    return {"id": cur.lastrowid}

起動したら、別の端末から次のように呼びます。ヘッダーのx-api-tokenは、引数名x_api_tokenのアンダースコアをハイフンに変えた名前です。

# メモを1件作る
curl -X POST -H "x-api-token: token-alice" "http://127.0.0.1:8000/notes?text=hello"

# limit=100 を指定しても、クラス依存の上限で50に切り詰められる
curl -H "x-api-token: token-alice" "http://127.0.0.1:8000/notes?limit=100"

# ヘッダーなしは get_token で止まり、401 {"detail":"token required"} になる
curl -i "http://127.0.0.1:8000/notes"

get_current_userへ依存をつなぐサブ依存の連鎖と実行順序

get_current_userは、自分の引数でget_tokenに依存しています。エンドポイントが宣言しているのはUserDepだけですが、FastAPIはget_token、get_current_user、エンドポイントの順に解決します。公式のSub-dependenciesのとおり、連鎖の深さに制限はありません。

どこかの段でHTTPExceptionを送出すると、そこで処理が止まり、後ろの段もエンドポイントの本体も実行されません。認証をエンドポイントの冒頭に毎回書く代わりに、型を1つ書き足すだけで守りが入るのがこの形の利点です。公式のGet Current Userでは、同じ連鎖の最初の段にOAuth2PasswordBearerを置いています。トークンの中身をJWTとして検証する部分は、JWTの構造と署名検証の仕組みを踏まえて書きます。

__call__付きクラスで上限値を変えられるページング依存を作る

Dependsに渡せるのは関数に限りません。呼び出せるものなら何でもよく、__call__を持つクラスのインスタンスも渡せます。Pagination(max_limit=50)はインスタンスを作った時点で上限を覚え、リクエストのたびに__call__がクエリを読みます。管理画面向けは200件、公開APIは50件のように、同じ処理を設定値違いで使い分けたいときの書き方です。

公式のAdvanced Dependenciesでは、この形を「パラメータ化された依存」として説明しています。クラスそのものをDepends(Pagination)と渡すと、__init__の引数がクエリとして扱われる別の動きになるので、取り違えに注意します。後者はClasses as Dependenciesの書き方で、戻り値の属性にエディタの補完が効く点が辞書を返す関数より有利です。

1リクエスト1回のキャッシュとuse_cache=Falseを使う場面

同じリクエストの中で同じ依存が何度出てきても、FastAPIは1回だけ呼び、その値を使い回します。list_notesとget_current_userの両方がget_dbに依存しても、開く接続は1本です。トランザクションを1つにまとめたいDB接続にとっては好都合な性質です。

呼ぶたびに新しい値が要る場合だけ、Depends(get_value, use_cache=False)と指定します。公式も「高度な場面」と位置づけていて、実務で使うのは、リクエスト内で時刻を2回取り直したい処理など限られた場面です。キャッシュはリクエストをまたがないので、設定ファイルの読み込みのように全体で1回にしたい処理はfunctools.lru_cacheを付けた関数を依存にします。

dependencies引数でルーター全体とアプリ全体に認証を掛ける方法

戻り値を使わない検査は、引数ではなくdependenciesのリストに置けます。エンドポイントの引数が検査用の変数で埋まるのを避けられます。

デコレーターのdependenciesは戻り値を捨てる点と使いどころ

@app.get("/admin/stats", dependencies=[Depends(verify_admin_key)])のように書くと、verify_admin_keyは実行されますが、戻り値はエンドポイントに渡りません。公式のDependencies in path operation decoratorsも、値を返しても使われないと明記しています。

向いているのは、APIキーの照合、メンテナンス中の503応答、IPの許可リストのように「通すか止めるか」だけを決める検査です。利用者の情報を本体で使うなら、引数のUserDepで受けます。両方に同じ依存を書いても、キャッシュにより呼び出しは1回で済みます。

APIRouterとアプリ全体で依存を掛ける範囲を分ける判断基準

同じ指定はAPIRouter(prefix="/admin", dependencies=[Depends(verify_admin_key)])でルーター単位に、FastAPI(dependencies=[Depends(verify_api_key)])でアプリ全体に掛けられます(Global Dependencies)。

アプリ全体に掛けるのは、ヘルスチェックや/docsを含めて例外なく通す検査に限ります。ロードバランサーのヘルスチェックまで認証で止めて、デプロイ後にインスタンスが外される事故はよくある失敗です。認証の要らないルートが1本でもあるなら、ルーター単位で掛けます。ルーターの分け方とprefixの設計は、同じFastAPIの記事群のAPIRouter編で扱う予定です。

yield依存でDBセッションを開閉する書き方と0.121のscope指定

サンプルのget_dbはreturnではなくyieldで値を渡しています。yieldより前が準備、後ろが後片付けです。

try・finallyでクローズするyield依存の基本形と例外の再送出

後片付けはfinallyに書きます。エンドポイントで例外が起きた場合も、接続のクローズは実行される仕組みです。公式のDependencies with yieldによると、既定では後処理はレスポンスをクライアントに送った後に動きます。

見落としやすいのはexceptの扱いです。except Exception:でロールバックしたあとraiseを書き忘れると、クライアントには500が返る一方で、サーバー側に例外のログが残りません。原因の追えない500が出続ける状態になります。ロールバックしたらraiseで送り直すか、意図したHTTPExceptionに変えて送出します。commitをどこに置くかはSQLAlchemyを使う場合に論点が増えるため、get_dbの依存性注入とcommitの置き場所を実測した解説を参照してください。

def get_db():
    conn = sqlite3.connect("app.db", check_same_thread=False)
    try:
        yield conn
    except Exception:
        conn.rollback()
        raise  # 送り直さないと500のログが残らない
    finally:
        conn.close()

0.106から0.121までで変わった後処理のタイミングと移行時の注意

後処理がいつ動くかは、版によって変わってきました。古い記事のコードを写すときに挙動が食い違う原因になるため、Advanced Dependenciesの技術的な詳細に載っている変遷を表にまとめます。

版 yield依存の後処理に関する変更
0.106.0 後処理をレスポンス送信の前に動かす形へ変更
0.110.0 握りつぶした例外は自動転送しない
0.118.0 後処理をレスポンス送信の後へ戻す
0.121.0 scope指定を追加・既定はrequest

0.121.0以降は、Depends(get_db, scope="function")と書くと、エンドポイントの関数が終わった直後、レスポンスを送る前に後処理が動きます。既定のscope="request"は送信後です。大きなファイルを返す処理の間ずっとDB接続を握っていたくない場合は"function"を選びます。逆にStreamingResponseの生成中にDBを読む処理では、送信前に接続が閉じると読めなくなるため、既定の"request"のままにします。

dependency_overridesでテスト時に認証とDBを差し替える手順

Dependsで組んだ一番の見返りはテストで得られます。本物のトークンもDBファイルも用意せずに、エンドポイントの処理だけを試せます。

pytestのフィクスチャでoverridesを設定して必ず戻す書き方

公式のTesting Dependencies with Overridesのとおり、app.dependency_overridesはただの辞書です。キーに元の依存関数、値に差し替える関数を入れます。

# test_main.py
import sqlite3

import pytest
from fastapi.testclient import TestClient

from main import app, get_current_user, get_db


@pytest.fixture
def client():
    conn = sqlite3.connect(":memory:", check_same_thread=False)
    conn.execute("CREATE TABLE notes(id INTEGER PRIMARY KEY, owner TEXT, body TEXT)")

    def override_db():
        yield conn

    app.dependency_overrides[get_db] = override_db
    app.dependency_overrides[get_current_user] = lambda: "tester"
    try:
        with TestClient(app) as c:
            yield c
    finally:
        app.dependency_overrides.clear()
        conn.close()


def test_create_and_list(client):
    assert client.post("/notes", params={"text": "hello"}).status_code == 201
    res = client.get("/notes", params={"limit": 100})
    assert res.json()["page"]["limit"] == 50
    assert res.json()["items"][0]["body"] == "hello"

get_current_userを差し替えると、その下のget_tokenも呼ばれなくなります。ヘッダーを付けずに通るのはこのためです。差し替えはfinallyで必ず消します。消し忘れると、後に続く認証のテストまで「tester」で通ってしまい、壊れた認証を見逃します。

差し替えが効かないときに疑うキーの指定とインポート経路の違い

差し替えが効かないとき、まず疑うのはキーです。辞書のキーは関数オブジェクトそのものなので、エンドポイントが使っている関数と同じものを指していなければ無視されます。別のモジュールで同名のget_dbを定義していないか、from app.deps import get_dbとfrom deps import get_dbのようにインポートの経路が二重になっていないかを確かめます。

クラス依存も同じ規則です。Depends(Pagination(max_limit=50))のようにその場で作ったインスタンスは、テストから同じオブジェクトを指せません。差し替えたい依存は、paginate_50 = Pagination(max_limit=50)のようにモジュールの変数に置いてから使います。

Dependsで書くべき処理とミドルウェアやサービス層に回す処理の線引き

Dependsは便利なぶん、何でも入れたくなります。ここでは入れない方がよい処理をはっきり書きます。

Dependsに入れると失敗するビジネスロジックと重い処理の例

「注文を確定する」「在庫を引き当てる」のような業務の判定を依存関数に入れるのは避けます。エンドポイントの引数を見ただけでは何が起きるか分からず、HTTPを経由しないバッチ処理から同じ処理を呼べなくなるからです。依存関数は、認証済みユーザー、DB接続、検証済みのクエリといった「リクエストから作る材料」を渡すところで止め、判定はサービス層の普通の関数に置きます。

もう1つの失敗は、async defの依存関数の中で同期のDBドライバやrequestsを呼ぶことです。その間はイベントループが止まり、同じワーカーの全リクエストが待たされます。同期の処理を呼ぶ依存はdefで書けば、FastAPIがスレッドプールで実行します。サンプルのget_dbがdefで、check_same_thread=Falseを付けているのはこのためです。依存とエンドポイントが別のスレッドで動くことがあるので、SQLiteの同一スレッド検査を外しています。

全リクエスト共通の処理をミドルウェアに置くべき条件と判断基準

リクエストIDの付与、処理時間の計測、CORS、圧縮のように、エンドポイントに関係なく全リクエストに掛けたい処理はミドルウェアに置きます。依存関数は該当するルートが見つかってから動くため、404の応答や静的ファイルには掛かりません。全部に効かせたい処理を依存で書くと、計測の数字から404が漏れます。

判断はこうです。エンドポイントの引数として値を受け取りたいならDepends、ルートと無関係に全通信を包みたいならミドルウェア、HTTP以外からも呼ぶならサービス層。この線を最初に決めておくと、エンドポイントが増えても依存の数は増えません。FlaskからFastAPIへ移るときに、before_requestの処理をどちらに移すかもこの基準で決まります(FlaskとFastAPIの違いを同じAPIで比較した記事)。既存のAPIを分割し直す設計や、認証の組み直しから相談したい場合は、API開発・システム連携の受託開発で受け付けています。

よくある質問

FastAPIのDependsについて、実装の途中で出てくることの多い質問に、2026年11月時点の版で答えます。

依存関数はasync defとdefのどちらで書けばよいですか?

どちらでも動きます。中でawaitする非同期のクライアント(httpxのAsyncClientや非同期のDBドライバ)を使うならasync def、同期のライブラリを呼ぶならdefです。defの依存はスレッドプールで実行されるため、イベントループを止めません。迷ったときの考え方はエンドポイント側と同じで、FastAPIとは?の記事のasync/defの使い分けがそのまま当てはまります。

DependsとSecurityは何が違いますか?

SecurityはDependsの仲間で、追加でscopesを受け取れます。OAuth2のスコープ(「読み取りのみ」「書き込み可」など)をエンドポイントごとに要求したいときに使い、要求したスコープはOpenAPIの仕様書にも載ります。スコープを使わない認証なら、この記事のようにDependsで対応可能です。

WebSocketのエンドポイントでもDependsは使えますか?

使えます。@app.websocketの引数でも、Depends、Query、Cookieによる宣言が可能です。ただしHTTPExceptionではなくWebSocketExceptionで接続を閉じる点が違います。認証をaccept()の前に済ませる書き方は、FastAPIのWebSocket実装の解説で扱っています。

依存関数でPydanticモデルを返すとリクエストボディと混同されませんか?

混同されません。引数の型がPydanticのモデルでも、Dependsで宣言していればFastAPIはリクエストボディとして読みません。逆に、依存関数の引数にPydanticのモデルを書くと、それはリクエストボディとして解釈されます。モデル定義の基本はPydanticのBaseModelの使い方を参照してください。

依存関数の中でRequestオブジェクトを受け取れますか?

受け取れます。依存関数の引数にrequest: Requestと書けば、エンドポイントと同じRequestが渡ります。クライアントのIPやパスを条件として判定する検査に使用可能です。ただし、ヘッダーの値をそのままログに出すと改行を混ぜたログの偽装につながるため、\rと\nを取り除いてから出力します。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.11.11 テックブログ Microsoft 2026年11月の月例更新:Outlookの悪用済みCVE-2026-124194と修正版の確認方法
  2. 2026.11.11 テックブログ JANISの不正アクセス:最大11,652名の医療機関情報と情報窃取プログラムの検知設定
  3. 2026.11.11 テックブログ SMBC日興証券の不正アクセス:持株会会員最大5.6万人の範囲と受託システムの分離設計
  4. 2026.01.21 コラム 税務調査の録音は禁止?判例・裁決と国税局マニュアルで見る、断られたときの対応
  5. 2026.11.09 テックブログ ステップゴルフ不正アクセス:口座情報の対象確認と偽連絡・引き落としの注意点

RELATED POSTS 関連記事

目次