コンテンツにスキップ

依存関係としてのクラス

依存性注入 システムを深く掘り下げる前に、先ほどの例をアップグレードしてみましょう。

前の例のdict

前の例では、依存関係("dependable")からdictを返していました:

from typing import Union

from fastapi import Depends, FastAPI

app = FastAPI()


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


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


@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons

しかし、path operation関数のパラメータcommonsdictが含まれています。

また、エディタはdictのキーと値の型を知ることができないため、多くのサポート(補完のような)を提供することができません。

もっとうまくやれるはずです...。

依存関係を作るもの

これまでは、依存関係が関数として宣言されているのを見てきました。

しかし、依存関係を定義する方法はそれだけではありません(その方が一般的かもしれませんが)。

重要なのは、依存関係が「呼び出し可能」なものであることです。

Pythonにおける「呼び出し可能」とは、Pythonが関数のように「呼び出す」ことができるものを指します。

そのため、somethingオブジェクト(関数ではないかもしれませんが)を持っていて、それを次のように「呼び出す」(実行する)ことができるとします:

something()

または

something(some_argument, some_keyword_argument="foo")

これを「呼び出し可能」なものと呼びます。

依存関係としてのクラス

Pythonのクラスのインスタンスを作成する際に、同じ構文を使用していることに気づくかもしれません。

例えば:

class Cat:
    def __init__(self, name: str):
        self.name = name


fluffy = Cat(name="Mr Fluffy")

この場合、fluffyCatクラスのインスタンスです。

そしてfluffyを作成するために、Catを「呼び出している」ことになります。

そのため、Pythonのクラスもまた「呼び出し可能」です。

そして、FastAPI では、Pythonのクラスを依存関係として使用することができます。

FastAPIが実際にチェックしているのは、それが「呼び出し可能」(関数、クラス、その他なんでも)であり、パラメータが定義されているかどうかということです。

FastAPI の依存関係として「呼び出し可能なもの」を渡すと、その「呼び出し可能なもの」のパラメータを解析し、サブ依存関係も含めて、path operation関数のパラメータと同じように処理します。

それは、パラメータが全くない呼び出し可能なものにも適用されます。パラメータのないpath operation関数と同じように。

そこで、上で紹介した依存関係のcommon_parametersCommonQueryParamsクラスに変更します:

from typing import Union

from fastapi import Depends, FastAPI

app = FastAPI()


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip : commons.skip + commons.limit]
    response.update({"items": items})
    return response

クラスのインスタンスを作成するために使用される__init__メソッドに注目してください:

from typing import Union

from fastapi import Depends, FastAPI

app = FastAPI()


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip : commons.skip + commons.limit]
    response.update({"items": items})
    return response

...以前のcommon_parametersと同じパラメータを持っています:

from typing import Union

from fastapi import Depends, FastAPI

app = FastAPI()


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


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


@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons

これらのパラメータは FastAPI が依存関係を「解決」するために使用するものです。

どちらの場合も以下を持っています:

  • オプショナルのqクエリパラメータ。
  • skipクエリパラメータ、デフォルトは0
  • limitクエリパラメータ、デフォルトは100

どちらの場合も、データは変換され、検証され、OpenAPIスキーマなどで文書化されます。

使用

これで、このクラスを使用して依存関係を宣言することができます。

from typing import Union

from fastapi import Depends, FastAPI

app = FastAPI()


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip : commons.skip + commons.limit]
    response.update({"items": items})
    return response

FastAPICommonQueryParamsクラスを呼び出します。これにより、そのクラスの「インスタンス」が作成され、インスタンスはパラメータcommonsとして関数に渡されます。

型注釈とDepends

上のコードではCommonQueryParamsを2回書いていることに注目してください:

commons: CommonQueryParams = Depends(CommonQueryParams)

以下にある最後のCommonQueryParams:

... = Depends(CommonQueryParams)

...は、FastAPI が依存関係を知るために実際に使用するものです。

そこからFastAPIが宣言されたパラメータを抽出し、それが実際にFastAPIが呼び出すものです。


この場合、以下にある最初のCommonQueryParams:

commons: CommonQueryParams ...

...は FastAPI に対して特別な意味をもちません。FastAPIはデータ変換や検証などには使用しません(それらのためには= Depends(CommonQueryParams)を使用しています)。

実際には以下のように書けばいいだけです:

commons = Depends(CommonQueryParams)

以下にあるように:

from typing import Union

from fastapi import Depends, FastAPI

app = FastAPI()


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons=Depends(CommonQueryParams)):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip : commons.skip + commons.limit]
    response.update({"items": items})
    return response

しかし、型を宣言することは推奨されています。そうすれば、エディタはcommonsのパラメータとして何が渡されるかを知ることができ、コードの補完や型チェックなどを行うのに役立ちます:

ショートカット

しかし、ここではCommonQueryParamsを2回書くというコードの繰り返しが発生していることがわかります:

commons: CommonQueryParams = Depends(CommonQueryParams)

依存関係が、クラス自体のインスタンスを作成するためにFastAPIが「呼び出す」特定のクラスである場合、FastAPI はこれらのケースのショートカットを提供しています。

それらの具体的なケースについては以下のようにします:

以下のように書く代わりに:

commons: CommonQueryParams = Depends(CommonQueryParams)

...以下のように書きます:

commons: CommonQueryParams = Depends()

パラメータの型として依存関係を宣言し、Depends()の中でパラメータを指定せず、Depends()をその関数のパラメータの「デフォルト」値(=のあとの値)として使用することで、Depends(CommonQueryParams)の中でクラス全体をもう一度書かなくてもよくなります。

同じ例では以下のようになります:

from typing import Union

from fastapi import Depends, FastAPI

app = FastAPI()


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip : commons.skip + commons.limit]
    response.update({"items": items})
    return response

...そして FastAPI は何をすべきか知っています。

豆知識

役に立つというよりも、混乱するようであれば無視してください。それをする必要はありません。

それは単なるショートカットです。なぜなら FastAPI はコードの繰り返しを最小限に抑えることに気を使っているからです。