2
0

DIって難しそうだけど、FastAPI のDependsは簡単に使えた話

Posted at

はじめに

FastAPIのDepend、なぜこれを使うのか、どんな時に使うのか、私なりにまとめてみました。
Dependsは、依存性注入(DI)するものとも言われてますが、DIの側面はあまり気にせず、見てみます。

シンプルな例

例えば、q,skip,limitのパラメータを処理common_parametersがあったとします。
エントリーポイントの関数、read_itemにパラメータを並べて、取得して、common_parametersを呼び出します。

main.py
from typing import Union

from fastapi import FastAPI, Depends

app = FastAPI()

def common_parameters(
    q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
    return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
    commands = common_parameters(q, skip, limit)
    return {"item_id": item_id, "commands": commands}

fastapi dev main.pyで実行してみます。

image.png

ちゃんとパラメータの読み取りができている事がわかります。

Dependsを使った場合

Dependsを使うと次のように書く事ができます。

main.py
from typing import Union

from fastapi import FastAPI, Depends

app = FastAPI()

def common_parameters(
    q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
    return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/{item_id}")
def read_item(item_id: int, commands: dict = Depends(common_parameters)):
    return {"item_id": item_id, "commands": commands}

何が嬉しいのか?

Dependsを使う場合と比較してみます。

関数内で直接呼び出す場合:

  • common_parameters関数に必要なパラメータをエントリーポイントの関数のパラメータに羅列する必要がある
  • read_items関数の内部でcommon_parameters関数が呼び出されてる事は、内部に隠れていている

Dependsを使う場合:

  • common_parameters関数に必要なパラメータは、common_parameters関数に記述するだけでよい
  • 関数の引数としてcommon_parametersが明示的に示されている。

単純なデフォルトの引数との違い

Dependsを使った表記は、デフォルト値を与える記述に見えます。Dependsを使わず、次のように書いた場合との違いはなんでしょうか?

@app.get("/items/")
async def read_items(commons: dict = common_parameters()):
    return commons

この記述の場合、common_parametersは関数の定義時に一度だけ呼び出されその時の返り値がcommandsに入ります。
APIが呼ばれた時の実行されるわけではなく、common_parametersにq,skipなどのパラメータを渡す事もできません。

Dependsは、read_items()が呼び出されるたびに、common_parametersを呼び出します。引数にはリクエストに含まれているパラメータが自動的に入ります。

※Depends自体がどんな実装になっているか気になりました

認証処理を行う例

エンドポイントとして定義された関数の引数に、Request型の値を定義すると、現在処理中のリクエストに関する情報を含むRequestオブジェクトが渡されます。
このリクエストオブジェクトを使って認証処理を行う例を作ってみます。

@app.get("/items/")
async def read_users_me(request: Request):
    authorization: str = request.headers.get("Authorization")
    scheme, _, token = authorization.partition(" ")
    user = decode_token(token)

    # userの値を使った処理が続く

Dependsを使った記述

認証に関する処理を、リクエスト関数本体から完全に切り出した記述にできました。

def get_current_user(request: Request):
    authorization: str = request.headers.get("Authorization")
    scheme, _, token = authorization.partition(" ")
    user = decode_token(token)
    return usesr

@app.get("/items/")
async def read_users_me(user: dict = Depends(get_current_user)):
    # userの値を使った処理が続く

まとめ

エンドポイントで行う処理は、リクエストの内容に応じて様々な処理を行う必要があり、複雑になりがちです。
Dependsを使う事で、この処理の一部を切り出して実行するようにでき、認証処理など共通した処理を記述するのに有効に利用する事ができます。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0