如果你正在開發(fā)一個應用程序或 Web API,很少會將所有的內容都放在一個文件中。
FastAPI 提供了一個方便的工具,可以在保持所有靈活性的同時構建你的應用程序。
Info
如果你來自 Flask,那這將相當于 Flask 的 Blueprints。
假設你的文件結構如下:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
Tip
上面有幾個 __init__.py 文件:每個目錄或子目錄中都有一個。
這就是能將代碼從一個文件導入到另一個文件的原因。
例如,在 app/main.py 中,你可以有如下一行:
from app.routers import items
帶有注釋的同一文件結構:
.
├── app # 「app」是一個 Python 包
│ ├── __init__.py # 這個文件使「app」成為一個 Python 包
│ ├── main.py # 「main」模塊,例如 import app.main
│ ├── dependencies.py # 「dependencies」模塊,例如 import app.dependencies
│ └── routers # 「routers」是一個「Python 子包」
│ │ ├── __init__.py # 使「routers」成為一個「Python 子包」
│ │ ├── items.py # 「items」子模塊,例如 import app.routers.items
│ │ └── users.py # 「users」子模塊,例如 import app.routers.users
│ └── internal # 「internal」是一個「Python 子包」
│ ├── __init__.py # 使「internal」成為一個「Python 子包」
│ └── admin.py # 「admin」子模塊,例如 import app.internal.admin
假設專門用于處理用戶邏輯的文件是位于 /app/routers/users.py 的子模塊。
你希望將與用戶相關的路徑操作與其他代碼分開,以使其井井有條。
但它仍然是同一 FastAPI 應用程序/web API 的一部分(它是同一「Python 包」的一部分)。
你可以使用 APIRouter 為該模塊創(chuàng)建路徑操作。
你可以導入它并通過與 FastAPI 類相同的方式創(chuàng)建一個「實例」:
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 視為一個「迷你 FastAPI」類。
所有相同的選項都得到支持。
所有相同的 parameters、responses、dependencies、tags 等等。
Tip
在此示例中,該變量被命名為 router,但你可以根據(jù)你的想法自由命名。
我們將在主 FastAPI 應用中包含該 APIRouter,但首先,讓我們來看看依賴項和另一個 APIRouter。
我們了解到我們將需要一些在應用程序的好幾個地方所使用的依賴項。
因此,我們將它們放在它們自己的 dependencies 模塊(app/dependencies.py)中。
現(xiàn)在我們將使用一個簡單的依賴項來讀取一個自定義的 X-Token 請求首部:
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
我們正在使用虛構的請求首部來簡化此示例。
但在實際情況下,使用集成的安全性實用工具會得到更好的效果。
假設你在位于 app/routers/items.py 的模塊中還有專門用于處理應用程序中「項目」的端點。
你具有以下路徑操作:
這和 app/routers/users.py 的結構完全相同。
但是我們想變得更聰明并簡化一些代碼。
我們知道此模塊中的所有路徑操作都有相同的:
因此,我們可以將其添加到 APIRouter 中,而不是將其添加到每個路徑操作中。
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"}
由于每個路徑操作的路徑都必須以 / 開頭,例如:
@router.get("/{item_id}")
async def read_item(item_id: str):
...
...前綴不能以 / 作為結尾。
因此,本例中的前綴為 /items。
我們還可以添加一個 tags 列表和額外的 responses 列表,這些參數(shù)將應用于此路由器中包含的所有路徑操作。
我們可以添加一個 dependencies 列表,這些依賴項將被添加到路由器中的所有路徑操作中,并將針對向它們發(fā)起的每個請求執(zhí)行/解決。
Tip
請注意,和路徑操作裝飾器中的依賴項很類似,沒有值會被傳遞給你的路徑操作函數(shù)。
最終結果是項目相關的路徑現(xiàn)在為:
...如我們所愿。
Tip
在 APIRouter中具有 dependencies 可以用來,例如,對一整組的路徑操作要求身份認證。即使這些依賴項并沒有分別添加到每個路徑操作中。
Check
prefix、tags、responses 以及 dependencies 參數(shù)只是(和其他很多情況一樣)FastAPI 的一個用于幫助你避免代碼重復的功能。
這些代碼位于 app.routers.items 模塊,app/routers/items.py 文件中。
我們需要從 app.dependencies 模塊即 app/dependencies.py 文件中獲取依賴函數(shù)。
因此,我們通過 .. 對依賴項使用了相對導入:
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
如果你完全了解導入的工作原理,請從下面的下一部分繼續(xù)。
一個單點 .,例如:
from .dependencies import get_token_header
表示:
但是該文件并不存在,我們的依賴項位于 app/dependencies.py 文件中。
請記住我們的程序/文件結構是怎樣的:
兩個點 ..,例如:
from ..dependencies import get_token_header
表示:
正常工作了!????
同樣,如果我們使用了三個點 ...,例如:
from ...dependencies import get_token_header
那將意味著:
這將引用 app/ 的往上一級,帶有其自己的 __init __.py 等文件的某個包。但是我們并沒有這個包。因此,這將在我們的示例中引發(fā)錯誤。????
但是現(xiàn)在你知道了它的工作原理,因此無論它們多么復雜,你都可以在自己的應用程序中使用相對導入。????
我們不打算在每個路徑操作中添加前綴 /items 或 tags =["items"],因為我們將它們添加到了 APIRouter 中。
但是我們仍然可以添加更多將會應用于特定的路徑操作的 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
最后的這個路徑操作將包含標簽的組合:["items","custom"]。
并且在文檔中也會有兩個響應,一個用于 404,一個用于 403。
現(xiàn)在,讓我們來看看位于 app/main.py 的模塊。
在這里你導入并使用 FastAPI 類。
這將是你的應用程序中將所有內容聯(lián)結在一起的主文件。
并且由于你的大部分邏輯現(xiàn)在都存在于其自己的特定模塊中,因此主文件的內容將非常簡單。
你可以像平常一樣導入并創(chuàng)建一個 FastAPI 類。
我們甚至可以聲明全局依賴項,它會和每個 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!"}
現(xiàn)在,我們導入具有 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 一個部分的子模塊,因此我們可以使用單個點 . 通過「相對導入」來導入它們。
這段代碼:
from .routers import items, users
表示:
items 模塊將具有一個 router 變量(items.router)。這與我們在 app/routers/items.py 文件中創(chuàng)建的變量相同,它是一個 APIRouter 對象。
然后我們對 users 模塊進行相同的操作。
我們也可以像這樣導入它們:
from app.routers import items, users
Info
第一個版本是「相對導入」:
from .routers import items, users
第二個版本是「絕對導入」:
from app.routers import items, users
要了解有關 Python 包和模塊的更多信息,請查閱關于 Modules 的 Python 官方文檔。
我們將直接導入 items 子模塊,而不是僅導入其 router 變量。
這是因為我們在 users 子模塊中也有另一個名為 router 的變量。
如果我們一個接一個地導入,例如:
from .routers.items import router
from .routers.users import router
來自 users 的 router 將覆蓋來自 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!"}
現(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(),我們可以將每個 APIRouter 添加到主 FastAPI 應用程序中。
它將包含來自該路由器的所有路由作為其一部分。
技術細節(jié)
實際上,它將在內部為聲明在 APIRouter 中的每個路徑操作創(chuàng)建一個路徑操作。
所以,在幕后,它實際上會像所有的東西都是同一個應用程序一樣工作。
Check
包含路由器時,你不必擔心性能問題。
這將花費幾微秒時間,并且只會在啟動時發(fā)生。
因此,它不會影響性能。?
現(xiàn)在,假設你的組織為你提供了 app/internal/admin.py 文件。
它包含一個帶有一些由你的組織在多個項目之間共享的管理員路徑操作的 APIRouter。
對于此示例,它將非常簡單。但是假設由于它是與組織中的其他項目所共享的,因此我們無法對其進行修改,以及直接在 APIRouter 中添加 prefix、dependencies、tags 等:
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
但是我們仍然希望在包含 APIRouter 時設置一個自定義的 prefix,以便其所有路徑操作以 /admin 開頭,我們希望使用本項目已經有的 dependencies 保護它,并且我們希望它包含自定義的 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 將保持不變,因此我們仍然可以與組織中的其他項目共享相同的 app/internal/admin.py 文件。
結果是在我們的應用程序中,來自 admin 模塊的每個路徑操作都將具有:
但這只會影響我們應用中的 APIRouter,而不會影響使用它的任何其他代碼。
因此,舉例來說,其他項目能夠以不同的身份認證方法使用相同的 APIRouter。
我們還可以直接將路徑操作添加到 FastAPI 應用中。
這里我們這樣做了...只是為了表明我們可以做到????:
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() 添加的所有其他路徑操作一起正常運行。
特別的技術細節(jié)
注意:這是一個非常技術性的細節(jié),你也許可以直接跳過。
APIRouter 沒有被「掛載」,它們與應用程序的其余部分沒有隔離。
這是因為我們想要在 OpenAPI 模式和用戶界面中包含它們的路徑操作。
由于我們不能僅僅隔離它們并獨立于其余部分來「掛載」它們,因此路徑操作是被「克隆的」(重新創(chuàng)建),而不是直接包含。
現(xiàn)在,使用 app.main 模塊和 app 變量運行 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 的文檔。
你將看到使用了正確路徑(和前綴)和正確標簽的自動化 API 文檔,包括了來自所有子模塊的路徑:
你也可以在同一路由器上使用不同的前綴來多次使用 .include_router()。
在有些場景這可能有用,例如以不同的前綴公開同一個的 API,比方說 /api/v1 和 /api/latest。
這是一個你可能并不真正需要的高級用法,但萬一你有需要了就能夠用上。
與在 FastAPI 應用程序中包含 APIRouter 的方式相同,你也可以在另一個 APIRouter 中包含 APIRouter,通過:
router.include_router(other_router)
請確保在你將 router 包含到 FastAPI 應用程序之前進行此操作,以便 other_router 中的路徑操作也能被包含進來。
更多建議: