依存関係としてのクラス¶
依存性注入 システムを深く掘り下げる前に、先ほどの例をアップグレードしてみましょう。
前の例の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関数のパラメータcommons
にdict
が含まれています。
また、エディタは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")
この場合、fluffy
はCat
クラスのインスタンスです。
そしてfluffy
を作成するために、Cat
を「呼び出している」ことになります。
そのため、Pythonのクラスもまた「呼び出し可能」です。
そして、FastAPI では、Pythonのクラスを依存関係として使用することができます。
FastAPIが実際にチェックしているのは、それが「呼び出し可能」(関数、クラス、その他なんでも)であり、パラメータが定義されているかどうかということです。
FastAPI の依存関係として「呼び出し可能なもの」を渡すと、その「呼び出し可能なもの」のパラメータを解析し、サブ依存関係も含めて、path operation関数のパラメータと同じように処理します。
それは、パラメータが全くない呼び出し可能なものにも適用されます。パラメータのないpath operation関数と同じように。
そこで、上で紹介した依存関係のcommon_parameters
を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(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
FastAPI はCommonQueryParams
クラスを呼び出します。これにより、そのクラスの「インスタンス」が作成され、インスタンスはパラメータ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 はコードの繰り返しを最小限に抑えることに気を使っているからです。