Skip to content

更大型的應用程式 - 多個檔案

🌐 AI 與人類共同完成的翻譯

此翻譯由人類指導的 AI 完成。🤝

可能會有對原意的誤解,或讀起來不自然等問題。🤖

你可以透過協助我們更好地引導 AI LLM來改進此翻譯。

英文版

如果你正在建置一個應用程式或 Web API,很少會把所有東西都放在單一檔案裡。

FastAPI 提供了一個方便的工具,讓你在維持彈性的同時,幫你組織應用程式的結構。

資訊

如果你來自 Flask,這相當於 Flask 的 Blueprints。

範例檔案結構

假設你有如下的檔案結構:

.
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│   │   ├── __init__.py
│   │   ├── items.py
│   │   └── users.py
│   └── internal
│       ├── __init__.py
│       └── admin.py

提示

有好幾個 __init__.py 檔案:每個目錄或子目錄各一個。

這讓我們可以把一個檔案中的程式碼匯入到另一個檔案。

例如,在 app/main.py 你可以有一行:

from app.routers import items
  • app 目錄包含所有內容。它有一個空的 app/__init__.py 檔案,所以它是一個「Python 套件」(「Python 模組」的集合):app
  • 它包含一個 app/main.py 檔案。因為它在一個 Python 套件中(有 __init__.py 檔案的目錄),它是該套件的一個「模組」:app.main
  • 還有一個 app/dependencies.py 檔案,就像 app/main.py 一樣,它是一個「模組」:app.dependencies
  • 有一個子目錄 app/routers/,裡面有另一個 __init__.py 檔案,所以它是一個「Python 子套件」:app.routers
  • 檔案 app/routers/items.py 在一個套件 app/routers/ 內,因此它是一個子模組:app.routers.items
  • 同樣地,app/routers/users.py 是另一個子模組:app.routers.users
  • 還有一個子目錄 app/internal/,裡面有另一個 __init__.py 檔案,所以它又是一個「Python 子套件」:app.internal
  • 檔案 app/internal/admin.py 是另一個子模組:app.internal.admin

同樣的檔案結構,附上註解:

.
├── 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

APIRouter

假設專門處理使用者的檔案是位於 /app/routers/users.py 的子模組。

你希望把與使用者相關的「路徑操作 (path operation)」從其他程式碼分離,讓結構更有條理。

但它仍然是同一個 FastAPI 應用程式 / Web API 的一部分(屬於同一個「Python 套件」)。

你可以使用 APIRouter 為該模組建立路徑操作。

匯入 APIRouter

你可以像對 FastAPI 類別那樣匯入並建立一個「實例」:

app/routers/users.py
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 類別一樣:

app/routers/users.py
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」類別。

所有相同的選項都支援。

同樣的 parametersresponsesdependenciestags 等全都可用。

提示

在這個範例中,變數名叫 router,但你可以用任何你想用的名稱。

我們稍後會把這個 APIRouter 加進主要的 FastAPI 應用程式中,但先來看看相依性與另一個 APIRouter

相依性

我們發現應用程式的多個地方會用到一些相依性。

所以把它們放進獨立的 dependencies 模組(app/dependencies.py)。

接下來我們會用一個簡單的相依性來讀取自訂的 X-Token 標頭:

app/dependencies.py
from typing import Annotated

from fastapi import Header, HTTPException


async def get_token_header(x_token: Annotated[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")
🤓 Other versions and variants
app/dependencies.py
from typing import Annotated

from fastapi import Header, HTTPException


async def get_token_header(x_token: Annotated[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

Prefer to use the Annotated version if possible.

app/dependencies.py
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")

提示

為了簡化範例,我們使用了一個虛構的標頭。

但在真實情況下,使用內建的安全工具會有更好的效果。

另一個帶有 APIRouter 的模組

假設你還有一個模組 app/routers/items.py,專門處理應用程式中的「items」。

你有以下路徑操作:

  • /items/
  • /items/{item_id}

其結構與 app/routers/users.py 相同。

但我們想要更聰明地簡化一些程式碼。

我們知道這個模組中的所有路徑操作都有相同的:

  • 路徑 prefix/items
  • tags:(只有一個標籤:items
  • 額外的 responses
  • dependencies:它們都需要我們先前建立的 X-Token 相依性

因此,我們可以不必把這些都加在每個路徑操作上,而是把它們加在 APIRouter 上。

app/routers/items.py
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):
    ...

...所以 prefix 末尾不能帶有 /

因此,此處的 prefix 是 /items

我們也可以加上一個 tags 清單,以及會套用在此 router 內所有路徑操作上的額外 responses

我們還可以加上一個 dependencies 清單,這些相依性會加入此 router 內所有的路徑操作,並在對它們的每個請求上執行 / 解決。

提示

請注意,就像在路徑操作裝飾器中的相依性一樣,不會把任何值傳遞給你的路徑操作函式(path operation function)。

最後的結果是這些 item 的路徑如下:

  • /items/
  • /items/{item_id}

...正如我們預期的。

  • 它們會被標記為只有一個字串 "items" 的標籤清單。
    • 這些「標籤」對自動互動式文件系統(使用 OpenAPI)特別有用。
  • 它們都會包含預先定義的 responses
  • 這些路徑操作都會在執行前評估 / 執行其 dependencies 清單。
    • 如果你也在特定的路徑操作中宣告了相依性,這些相依性也會被執行。
    • Router 的相依性會先執行,然後是裝飾器中的 dependencies,最後是一般參數相依性。
    • 你也可以加入帶有 scopesSecurity 相依性

提示

APIRouter 中設定 dependencies,例如可以用來對一整組路徑操作要求驗證。即使沒有在每個路徑操作個別加入相依性也沒關係。

檢查

prefixtagsresponsesdependencies 參數(就像許多其他情況一樣)只是 FastAPI 提供的功能,幫助你避免重複程式碼。

匯入相依性

這段程式碼在模組 app.routers.items(檔案 app/routers/items.py)中。

我們需要從模組 app.dependencies(檔案 app/dependencies.py)取得相依性函式。

因此我們用 .. 做相對匯入相依性:

app/routers/items.py
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"}

相對匯入如何運作

提示

如果你對匯入的運作方式十分了解,可以直接跳到下一節。

單一的點號 .,如下:

from .dependencies import get_token_header

代表:

  • 從此模組(檔案 app/routers/items.py)所在的相同套件(目錄 app/routers/)開始...
  • 找到模組 dependencies(想像的檔案 app/routers/dependencies.py)...
  • 並從中匯入函式 get_token_header

但那個檔案不存在,我們的相依性在 app/dependencies.py

回想一下我們的應用 / 檔案結構長這樣:


兩個點號 ..,如下:

from ..dependencies import get_token_header

代表:

  • 從此模組(檔案 app/routers/items.py)所在的相同套件(目錄 app/routers/)開始...
  • 前往其父套件(目錄 app/)...
  • 然後在那裡找到模組 dependencies(檔案 app/dependencies.py)...
  • 並從中匯入函式 get_token_header

這就正確了!🎉


同樣地,如果我們用三個點號 ...,如下:

from ...dependencies import get_token_header

就代表:

  • 從此模組(檔案 app/routers/items.py)所在的相同套件(目錄 app/routers/)開始...
  • 前往其父套件(目錄 app/)...
  • 再前往那個套件的父層(沒有更上層的套件了,app 已是最上層 😱)...
  • 然後在那裡找到模組 dependencies(檔案 app/dependencies.py)...
  • 並從中匯入函式 get_token_header

那會指向 app/ 之上的某個套件,該套件需有自己的 __init__.py 等等。但我們沒有。所以在這個例子中會丟出錯誤。🚨

不過現在你知道它的運作方式了,因此無論你的應用有多複雜,你都可以使用相對匯入。🤓

加上一些自訂的 tagsresponsesdependencies

我們沒有把 /items 的 prefix 以及 tags=["items"] 加在每個路徑操作上,因為我們已經把它們加在 APIRouter 上了。

但我們仍可以在特定的路徑操作上再加上更多的 tags,以及一些只屬於該路徑操作的額外 responses

app/routers/items.py
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"}

提示

這最後一個路徑操作會有組合後的標籤:["items", "custom"]

而且在文件中同時會有 404403 兩種回應。

主程式 FastAPI

現在,來看看 app/main.py 這個模組。

你會在這裡匯入並使用 FastAPI 類別。

這會是你的應用程式中把一切串起來的主檔案。

而隨著大多數的邏輯都放在各自的模組中,主檔案會相當簡潔。

匯入 FastAPI

照常匯入並建立 FastAPI 類別。

我們甚至可以宣告全域相依性,它們會與各 APIRouter 的相依性合併:

app/main.py
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!"}
🤓 Other versions and variants
app/main.py
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!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
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

現在我們匯入包含 APIRouter 的其他子模組:

app/main.py
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!"}
🤓 Other versions and variants
app/main.py
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!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
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.pyapp/routers/items.py 是同一個 Python 套件 app 的子模組,我們可以用單一的點號 . 來進行「相對匯入」。

匯入如何運作

這段:

from .routers import items, users

代表:

  • 從此模組(檔案 app/main.py)所在的相同套件(目錄 app/)開始...
  • 尋找子套件 routers(目錄 app/routers/)...
  • 並從中匯入子模組 items(檔案 app/routers/items.py)與 users(檔案 app/routers/users.py)...

模組 items 會有一個變數 routeritems.router)。這就是我們在 app/routers/items.py 建立的那個 APIRouter 物件。

接著我們對 users 模組做一樣的事。

我們也可以這樣匯入:

from app.routers import items, users

資訊

第一種是「相對匯入」:

from .routers import items, users

第二種是「絕對匯入」:

from app.routers import items, users

想了解更多關於 Python 套件與模組,請閱讀官方的模組說明文件

避免名稱衝突

我們直接匯入子模組 items,而不是只匯入它的變數 router

這是因為在子模組 users 中也有另一個名為 router 的變數。

如果我們像下面這樣一個接一個匯入:

from .routers.items import router
from .routers.users import router

來自 usersrouter 會覆蓋掉 itemsrouter,我們就無法同時使用兩者。

因此,為了能在同一個檔案中同時使用它們,我們直接匯入子模組:

app/main.py
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!"}
🤓 Other versions and variants
app/main.py
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!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
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!"}

usersitemsAPIRouter 納入

現在,把子模組 usersitemsrouter 納入:

app/main.py
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!"}
🤓 Other versions and variants
app/main.py
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!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
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!"}

資訊

users.router 是位於 app/routers/users.py 檔案內的 APIRouter

items.router 是位於 app/routers/items.py 檔案內的 APIRouter

透過 app.include_router(),我們可以把每個 APIRouter 加到主要的 FastAPI 應用程式。

它會把該 router 的所有路由都納入成為應用的一部分。

技術細節

實際上,它會在內部為 APIRouter 中宣告的每一個「路徑操作」建立一個對應的「路徑操作」。

所以在幕後,它實際運作起來就像是一個單一的應用。

檢查

把 router 納入時不需要擔心效能。

這只會在啟動時花費微秒等級,且只發生一次。

因此不會影響效能。⚡

以自訂的 prefixtagsresponsesdependencies 納入一個 APIRouter

現在,假設你的組織提供了一個 app/internal/admin.py 檔案給你。

它包含一個帶有一些管理員路徑操作的 APIRouter,並在組織內多個專案之間共用。

為了這個範例它會非常簡單。但假設因為它會與組織內的其他專案共用,我們不能直接修改它並把 prefixdependenciestags 等加在 APIRouter 上:

app/internal/admin.py
from fastapi import APIRouter

router = APIRouter()


@router.post("/")
async def update_admin():
    return {"message": "Admin getting schwifty"}

但當我們把這個 APIRouter 納入時,仍然希望設定自訂的 prefix,讓它所有的路徑操作都以 /admin 開頭;我們想用這個專案已經有的 dependencies 來保護它,並且要加入 tagsresponses

我們可以在不修改原始 APIRouter 的情況下,將這些參數傳給 app.include_router() 來達成:

app/main.py
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!"}
🤓 Other versions and variants
app/main.py
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!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
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 模組的每個路徑操作都會有:

  • 前綴 /admin
  • 標籤 admin
  • 相依性 get_token_header
  • 回應 418 🍵

但這只會影響我們應用中的那個 APIRouter,不會影響任何其他使用它的程式碼。

例如,其他專案可以用不同的驗證方式搭配相同的 APIRouter

加上一個路徑操作

我們也可以直接把路徑操作加到 FastAPI 應用中。

這裡我們就加一下... 只是為了示範可以這麼做 🤷:

app/main.py
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!"}
🤓 Other versions and variants
app/main.py
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!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
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() 加入的其他路徑操作正確地一起運作。

非常技術細節

注意:這是個非常技術性的細節,你大概可以直接略過。


APIRouter 不是被「掛載 (mount)」的,它們不會與應用的其他部分隔離開來。

這是因為我們要把它們的路徑操作包含進 OpenAPI 結構與使用者介面中。

由於無法將它們隔離並獨立「掛載」,所以這些路徑操作會被「複製」(重新建立),而不是直接包含進來。

檢查自動產生的 API 文件

現在,執行你的應用:

$ fastapi dev app/main.py

<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

然後開啟位於 http://127.0.0.1:8000/docs 的文件。

你會看到自動產生的 API 文件,包含來自所有子模組的路徑,使用正確的路徑(與前綴)與正確的標籤:

以不同的 prefix 多次納入同一個 router

你也可以用不同的前綴,對同一個 router 多次呼叫 .include_router()

例如,這對於在不同前綴下提供相同的 API 很有用,如 /api/v1/api/latest

這是進階用法,你可能不會需要,但若有需要它就在那裡。

在另一個 APIRouter 中納入一個 APIRouter

就像你可以在 FastAPI 應用中納入一個 APIRouter 一樣,你也可以在另一個 APIRouter 中納入一個 APIRouter,用法如下:

router.include_router(other_router)

請確保在把 router 納入 FastAPI 應用之前先這麼做,這樣 other_router 的路徑操作也會被包含進去。