以類別作為相依性¶
在更深入了解 相依性注入(Dependency Injection) 系統之前,我們先把前一個範例升級一下。
前一個範例中的 dict¶
在前一個範例中,我們從相依項("dependable")回傳了一個 dict:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: 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 function)的參數 commons 中取得的是一個 dict。
而我們知道,編輯器對 dict 無法提供太多輔助(例如自動完成),因為它無法預先知道其中的鍵與值的型別。
我們可以做得更好...
什麼算是相依性¶
到目前為止,你看到的相依性都是宣告成函式。
但那不是宣告相依性的唯一方式(雖然那大概是最常見的)。
關鍵在於,相依性應該要是「callable」。
在 Python 中,「callable」指的是任何可以像函式一樣被 Python「呼叫」的東西。
因此,如果你有一個物件 something(它可能不是函式),而你可以像這樣「呼叫」(執行)它:
something()
或是
something(some_argument, some_keyword_argument="foo")
那它就是一個「callable」。
以類別作為相依性¶
你可能已經注意到,建立一個 Python 類別的實例時,你用的語法也是一樣的。
例如:
class Cat:
def __init__(self, name: str):
self.name = name
fluffy = Cat(name="Mr Fluffy")
在這個例子中,fluffy 是 Cat 類別的一個實例。
而要建立 fluffy,你其實是在「呼叫」Cat。
所以,Python 類別本身也是一種 callable。
因此,在 FastAPI 中,你可以將 Python 類別作為相依性。
FastAPI 其實檢查的是它是否為「callable」(函式、類別或其他),以及它所定義的參數。
如果你在 FastAPI 中傳入一個「callable」作為相依性,FastAPI 會分析該「callable」的參數,並以與路徑操作函式參數相同的方式來處理它們,包括子相依性。
這也適用於完全沒有參數的 callable,就和沒有參數的路徑操作函式一樣。
接著,我們可以把上面的相依項(dependable)common_parameters 改成類別 CommonQueryParams:
from typing import Annotated
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: 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: Annotated[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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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: 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 Annotated
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: 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: Annotated[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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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: 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 Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: 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,型別為str。 - 一個查詢參數
skip,型別為int,預設為0。 - 一個查詢參數
limit,型別為int,預設為100。
兩種情況下,資料都會被轉換、驗證,並記錄到 OpenAPI schema 中等。
如何使用¶
現在你可以用這個類別來宣告你的相依性。
from typing import Annotated
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: 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: Annotated[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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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: 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:
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
Tip
如有可能,優先使用 Annotated 版本。
commons: CommonQueryParams = Depends(CommonQueryParams)
最後面的 CommonQueryParams,在:
... Depends(CommonQueryParams)
...才是 FastAPI 實際用來知道相依性是什麼的依據。
FastAPI 會從這個物件中提取宣告的參數,並且實際呼叫它。
在這個例子中,前面的 CommonQueryParams,於:
commons: Annotated[CommonQueryParams, ...
Tip
如有可能,優先使用 Annotated 版本。
commons: CommonQueryParams ...
...對 FastAPI 來說沒有任何特殊意義。FastAPI 不會用它來做資料轉換、驗證等(因為這部分由 Depends(CommonQueryParams) 處理)。
其實你可以只寫:
commons: Annotated[Any, Depends(CommonQueryParams)]
Tip
如有可能,優先使用 Annotated 版本。
commons = Depends(CommonQueryParams)
...像是:
from typing import Annotated, Any
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: 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: Annotated[Any, 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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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: 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:
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
Tip
如有可能,優先使用 Annotated 版本。
commons: CommonQueryParams = Depends(CommonQueryParams)
FastAPI 為這類情況提供了一個捷徑:當相依性「明確」是一個類別,且 FastAPI 會「呼叫」它來建立該類別的實例時。
對這些特定情況,你可以這樣做:
不要寫:
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
Tip
如有可能,優先使用 Annotated 版本。
commons: CommonQueryParams = Depends(CommonQueryParams)
...而是改為:
commons: Annotated[CommonQueryParams, Depends()]
Tip
如有可能,優先使用 Annotated 版本。
commons: CommonQueryParams = Depends()
你把相依性宣告為參數的型別,並使用不帶任何參數的 Depends(),而不用在 Depends(CommonQueryParams) 裡「再」寫一次整個類別。
整個範例就會變成:
from typing import Annotated
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: 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: Annotated[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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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: 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 會知道該怎麼做。
Tip
如果你覺得這樣比幫助更令人困惑,那就忽略它吧,你並不「需要」這個技巧。
這只是個捷徑。因為 FastAPI 在意幫你減少重複的程式碼。