如果你正在開發(fā)一個(gè)應(yīng)用程序或 Web API,很少會(huì)將所有的內(nèi)容都放在一個(gè)文件中。
FastAPI 提供了一個(gè)方便的工具,可以在保持所有靈活性的同時(shí)構(gòu)建你的應(yīng)用程序。
Info
如果你來自 Flask,那這將相當(dāng)于 Flask 的 Blueprints。
假設(shè)你的文件結(jié)構(gòu)如下:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
Tip
上面有幾個(gè) __init__.py 文件:每個(gè)目錄或子目錄中都有一個(gè)。
這就是能將代碼從一個(gè)文件導(dǎo)入到另一個(gè)文件的原因。
例如,在 app/main.py 中,你可以有如下一行:
from app.routers import items
帶有注釋的同一文件結(jié)構(gòu):
.
├── app # 「app」是一個(gè) Python 包
│ ├── __init__.py # 這個(gè)文件使「app」成為一個(gè) Python 包
│ ├── main.py # 「main」模塊,例如 import app.main
│ ├── dependencies.py # 「dependencies」模塊,例如 import app.dependencies
│ └── routers # 「routers」是一個(gè)「Python 子包」
│ │ ├── __init__.py # 使「routers」成為一個(gè)「Python 子包」
│ │ ├── items.py # 「items」子模塊,例如 import app.routers.items
│ │ └── users.py # 「users」子模塊,例如 import app.routers.users
│ └── internal # 「internal」是一個(gè)「Python 子包」
│ ├── __init__.py # 使「internal」成為一個(gè)「Python 子包」
│ └── admin.py # 「admin」子模塊,例如 import app.internal.admin
假設(shè)專門用于處理用戶邏輯的文件是位于 /app/routers/users.py 的子模塊。
你希望將與用戶相關(guān)的路徑操作與其他代碼分開,以使其井井有條。
但它仍然是同一 FastAPI 應(yīng)用程序/web API 的一部分(它是同一「Python 包」的一部分)。
你可以使用 APIRouter 為該模塊創(chuàng)建路徑操作。
你可以導(dǎo)入它并通過與 FastAPI 類相同的方式創(chuàng)建一個(gè)「實(shí)例」:
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
然后你可以使用它來聲明路徑操作。
使用方式與 FastAPI 類相同:
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
你可以將 APIRouter 視為一個(gè)「迷你 FastAPI」類。
所有相同的選項(xiàng)都得到支持。
所有相同的 parameters、responses、dependencies、tags 等等。
Tip
在此示例中,該變量被命名為 router,但你可以根據(jù)你的想法自由命名。
我們將在主 FastAPI 應(yīng)用中包含該 APIRouter,但首先,讓我們來看看依賴項(xiàng)和另一個(gè) APIRouter。
我們了解到我們將需要一些在應(yīng)用程序的好幾個(gè)地方所使用的依賴項(xiàng)。
因此,我們將它們放在它們自己的 dependencies 模塊(app/dependencies.py)中。
現(xiàn)在我們將使用一個(gè)簡(jiǎn)單的依賴項(xiàng)來讀取一個(gè)自定義的 X-Token 請(qǐng)求首部:
from fastapi import Header, HTTPException
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def get_query_token(token: str):
if token != "jessica":
raise HTTPException(status_code=400, detail="No Jessica token provided")
Tip
我們正在使用虛構(gòu)的請(qǐng)求首部來簡(jiǎn)化此示例。
但在實(shí)際情況下,使用集成的安全性實(shí)用工具會(huì)得到更好的效果。
假設(shè)你在位于 app/routers/items.py 的模塊中還有專門用于處理應(yīng)用程序中「項(xiàng)目」的端點(diǎn)。
你具有以下路徑操作:
這和 app/routers/users.py 的結(jié)構(gòu)完全相同。
但是我們想變得更聰明并簡(jiǎn)化一些代碼。
我們知道此模塊中的所有路徑操作都有相同的:
因此,我們可以將其添加到 APIRouter 中,而不是將其添加到每個(gè)路徑操作中。
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
由于每個(gè)路徑操作的路徑都必須以 / 開頭,例如:
@router.get("/{item_id}")
async def read_item(item_id: str):
...
...前綴不能以 / 作為結(jié)尾。
因此,本例中的前綴為 /items。
我們還可以添加一個(gè) tags 列表和額外的 responses 列表,這些參數(shù)將應(yīng)用于此路由器中包含的所有路徑操作。
我們可以添加一個(gè) dependencies 列表,這些依賴項(xiàng)將被添加到路由器中的所有路徑操作中,并將針對(duì)向它們發(fā)起的每個(gè)請(qǐng)求執(zhí)行/解決。
Tip
請(qǐng)注意,和路徑操作裝飾器中的依賴項(xiàng)很類似,沒有值會(huì)被傳遞給你的路徑操作函數(shù)。
最終結(jié)果是項(xiàng)目相關(guān)的路徑現(xiàn)在為:
...如我們所愿。
Tip
在 APIRouter中具有 dependencies 可以用來,例如,對(duì)一整組的路徑操作要求身份認(rèn)證。即使這些依賴項(xiàng)并沒有分別添加到每個(gè)路徑操作中。
Check
prefix、tags、responses 以及 dependencies 參數(shù)只是(和其他很多情況一樣)FastAPI 的一個(gè)用于幫助你避免代碼重復(fù)的功能。
這些代碼位于 app.routers.items 模塊,app/routers/items.py 文件中。
我們需要從 app.dependencies 模塊即 app/dependencies.py 文件中獲取依賴函數(shù)。
因此,我們通過 .. 對(duì)依賴項(xiàng)使用了相對(duì)導(dǎo)入:
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Tip
如果你完全了解導(dǎo)入的工作原理,請(qǐng)從下面的下一部分繼續(xù)。
一個(gè)單點(diǎn) .,例如:
from .dependencies import get_token_header
表示:
但是該文件并不存在,我們的依賴項(xiàng)位于 app/dependencies.py 文件中。
請(qǐng)記住我們的程序/文件結(jié)構(gòu)是怎樣的:
兩個(gè)點(diǎn) ..,例如:
from ..dependencies import get_token_header
表示:
正常工作了!????
同樣,如果我們使用了三個(gè)點(diǎn) ...,例如:
from ...dependencies import get_token_header
那將意味著:
這將引用 app/ 的往上一級(jí),帶有其自己的 __init __.py 等文件的某個(gè)包。但是我們并沒有這個(gè)包。因此,這將在我們的示例中引發(fā)錯(cuò)誤。????
但是現(xiàn)在你知道了它的工作原理,因此無論它們多么復(fù)雜,你都可以在自己的應(yīng)用程序中使用相對(duì)導(dǎo)入。????
我們不打算在每個(gè)路徑操作中添加前綴 /items 或 tags =["items"],因?yàn)槲覀儗⑺鼈兲砑拥搅?nbsp;APIRouter 中。
但是我們?nèi)匀豢梢蕴砑痈鄬?huì)應(yīng)用于特定的路徑操作的 tags,以及一些特定于該路徑操作的額外 responses:
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Tip
最后的這個(gè)路徑操作將包含標(biāo)簽的組合:["items","custom"]。
并且在文檔中也會(huì)有兩個(gè)響應(yīng),一個(gè)用于 404,一個(gè)用于 403。
現(xiàn)在,讓我們來看看位于 app/main.py 的模塊。
在這里你導(dǎo)入并使用 FastAPI 類。
這將是你的應(yīng)用程序中將所有內(nèi)容聯(lián)結(jié)在一起的主文件。
并且由于你的大部分邏輯現(xiàn)在都存在于其自己的特定模塊中,因此主文件的內(nèi)容將非常簡(jiǎn)單。
你可以像平常一樣導(dǎo)入并創(chuàng)建一個(gè) FastAPI 類。
我們甚至可以聲明全局依賴項(xiàng),它會(huì)和每個(gè) APIRouter 的依賴項(xiàng)組合在一起:
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
現(xiàn)在,我們導(dǎo)入具有 APIRouter 的其他子模塊:
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
由于文件 app/routers/users.py 和 app/routers/items.py 是同一 Python 包 app 一個(gè)部分的子模塊,因此我們可以使用單個(gè)點(diǎn) . 通過「相對(duì)導(dǎo)入」來導(dǎo)入它們。
這段代碼:
from .routers import items, users
表示:
items 模塊將具有一個(gè) router 變量(items.router)。這與我們?cè)?nbsp;app/routers/items.py 文件中創(chuàng)建的變量相同,它是一個(gè) APIRouter 對(duì)象。
然后我們對(duì) users 模塊進(jìn)行相同的操作。
我們也可以像這樣導(dǎo)入它們:
from app.routers import items, users
Info
第一個(gè)版本是「相對(duì)導(dǎo)入」:
from .routers import items, users
第二個(gè)版本是「絕對(duì)導(dǎo)入」:
from app.routers import items, users
要了解有關(guān) Python 包和模塊的更多信息,請(qǐng)查閱關(guān)于 Modules 的 Python 官方文檔。
我們將直接導(dǎo)入 items 子模塊,而不是僅導(dǎo)入其 router 變量。
這是因?yàn)槲覀冊(cè)?nbsp;users 子模塊中也有另一個(gè)名為 router 的變量。
如果我們一個(gè)接一個(gè)地導(dǎo)入,例如:
from .routers.items import router
from .routers.users import router
來自 users 的 router 將覆蓋來自 items 中的 router,我們將無法同時(shí)使用它們。
因此,為了能夠在同一個(gè)文件中使用它們,我們直接導(dǎo)入子模塊:
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
現(xiàn)在,讓我們來包含來自 users 和 items 子模塊的 router。
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
Info
users.router 包含了 app/routers/users.py 文件中的 APIRouter。
items.router 包含了 app/routers/items.py 文件中的 APIRouter。
使用 app.include_router(),我們可以將每個(gè) APIRouter 添加到主 FastAPI 應(yīng)用程序中。
它將包含來自該路由器的所有路由作為其一部分。
技術(shù)細(xì)節(jié)
實(shí)際上,它將在內(nèi)部為聲明在 APIRouter 中的每個(gè)路徑操作創(chuàng)建一個(gè)路徑操作。
所以,在幕后,它實(shí)際上會(huì)像所有的東西都是同一個(gè)應(yīng)用程序一樣工作。
Check
包含路由器時(shí),你不必?fù)?dān)心性能問題。
這將花費(fèi)幾微秒時(shí)間,并且只會(huì)在啟動(dòng)時(shí)發(fā)生。
因此,它不會(huì)影響性能。?
現(xiàn)在,假設(shè)你的組織為你提供了 app/internal/admin.py 文件。
它包含一個(gè)帶有一些由你的組織在多個(gè)項(xiàng)目之間共享的管理員路徑操作的 APIRouter。
對(duì)于此示例,它將非常簡(jiǎn)單。但是假設(shè)由于它是與組織中的其他項(xiàng)目所共享的,因此我們無法對(duì)其進(jìn)行修改,以及直接在 APIRouter 中添加 prefix、dependencies、tags 等:
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
但是我們?nèi)匀幌M诎?nbsp;APIRouter 時(shí)設(shè)置一個(gè)自定義的 prefix,以便其所有路徑操作以 /admin 開頭,我們希望使用本項(xiàng)目已經(jīng)有的 dependencies 保護(hù)它,并且我們希望它包含自定義的 tags 和 responses。
我們可以通過將這些參數(shù)傳遞給 app.include_router() 來完成所有的聲明,而不必修改原始的 APIRouter:
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
這樣,原始的 APIRouter 將保持不變,因此我們?nèi)匀豢梢耘c組織中的其他項(xiàng)目共享相同的 app/internal/admin.py 文件。
結(jié)果是在我們的應(yīng)用程序中,來自 admin 模塊的每個(gè)路徑操作都將具有:
但這只會(huì)影響我們應(yīng)用中的 APIRouter,而不會(huì)影響使用它的任何其他代碼。
因此,舉例來說,其他項(xiàng)目能夠以不同的身份認(rèn)證方法使用相同的 APIRouter。
我們還可以直接將路徑操作添加到 FastAPI 應(yīng)用中。
這里我們這樣做了...只是為了表明我們可以做到????:
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
它將與通過 app.include_router() 添加的所有其他路徑操作一起正常運(yùn)行。
特別的技術(shù)細(xì)節(jié)
注意:這是一個(gè)非常技術(shù)性的細(xì)節(jié),你也許可以直接跳過。
APIRouter 沒有被「掛載」,它們與應(yīng)用程序的其余部分沒有隔離。
這是因?yàn)槲覀兿胍?OpenAPI 模式和用戶界面中包含它們的路徑操作。
由于我們不能僅僅隔離它們并獨(dú)立于其余部分來「掛載」它們,因此路徑操作是被「克隆的」(重新創(chuàng)建),而不是直接包含。
現(xiàn)在,使用 app.main 模塊和 app 變量運(yùn)行 uvicorn:
uvicorn app.main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
然后打開位于 http://127.0.0.1:8000/docs 的文檔。
你將看到使用了正確路徑(和前綴)和正確標(biāo)簽的自動(dòng)化 API 文檔,包括了來自所有子模塊的路徑:
你也可以在同一路由器上使用不同的前綴來多次使用 .include_router()。
在有些場(chǎng)景這可能有用,例如以不同的前綴公開同一個(gè)的 API,比方說 /api/v1 和 /api/latest。
這是一個(gè)你可能并不真正需要的高級(jí)用法,但萬一你有需要了就能夠用上。
與在 FastAPI 應(yīng)用程序中包含 APIRouter 的方式相同,你也可以在另一個(gè) APIRouter 中包含 APIRouter,通過:
router.include_router(other_router)
請(qǐng)確保在你將 router 包含到 FastAPI 應(yīng)用程序之前進(jìn)行此操作,以便 other_router 中的路徑操作也能被包含進(jìn)來。
更多建議: