FastAPI教程 更大的應用 - 多個文件

2021-11-03 11:29 更新

如果你正在開發(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/__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 的子模塊。

你希望將與用戶相關的路徑操作與其他代碼分開,以使其井井有條。

但它仍然是同一 FastAPI 應用程序/web API 的一部分(它是同一「Python 包」的一部分)。

你可以使用 APIRouter 為該模塊創(chuàng)建路徑操作。

導入 APIRouter

你可以導入它并通過與 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}

使用 APIRouter 的路徑操作

然后你可以使用它來聲明路徑操作。

使用方式與 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

我們正在使用虛構的請求首部來簡化此示例。

但在實際情況下,使用集成的安全性實用工具會得到更好的效果。

其他使用 APIRouter 的模塊

假設你在位于 app/routers/items.py 的模塊中還有專門用于處理應用程序中「項目」的端點。

你具有以下路徑操作:

  • /items/
  • /items/{item_id}

這和 app/routers/users.py 的結構完全相同。

但是我們想變得更聰明并簡化一些代碼。

我們知道此模塊中的所有路徑操作都有相同的:

  • 路徑 prefix:/items。
  • tags:(僅有一個 items 標簽)。
  • 額外的 responses。
  • dependencies:它們都需要我們創(chuàng)建的 X-Token 依賴項。

因此,我們可以將其添加到 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)在為:

  • /items/
  • /items/{item_id}

...如我們所愿。

  • 它們將被標記為僅包含單個字符串 "items" 的標簽列表。這些「標簽」對于自動化交互式文檔系統(tǒng)(使用 OpenAPI)特別有用。
  • 所有的路徑操作都將包含預定義的 responses。
  • 所有的這些路徑操作都將在自身之前計算/執(zhí)行 dependencies 列表。如果你還在一個具體的路徑操作中聲明了依賴項,它們也會被執(zhí)行。路由器的依賴項最先執(zhí)行,然后是裝飾器中的 dependencies,再然后是普通的參數(shù)依賴項。你還可以添加具有 scopes 的 Security 依賴項。

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/routers/items.py 文件)所在的同一個包(app/routers/ 目錄)開始...
  • 找到 dependencies 模塊(一個位于 app/routers/dependencies.py 的虛構文件)...
  • 然后從中導入函數(shù) get_token_header。

但是該文件并不存在,我們的依賴項位于 app/dependencies.py 文件中。

請記住我們的程序/文件結構是怎樣的:

兩個點 ..,例如:

from ..dependencies import get_token_header

表示:

  • 從該模塊(app/routers/items.py 文件)所在的同一個包(app/routers/ 目錄)開始...
  • 跳轉到其父包(app/ 目錄)...
  • 在該父包中,找到 dependencies 模塊(位于 app/dependencies.py 的文件)...
  • 然后從中導入函數(shù) get_token_header。

正常工作了!????

同樣,如果我們使用了三個點 ...,例如:

from ...dependencies import get_token_header

那將意味著:

  • 從該模塊(app/routers/items.py 文件)所在的同一個包(app/routers/ 目錄)開始...
  • 跳轉到其父包(app/ 目錄)...
  • 然后跳轉到該包的父包(該父包并不存在,app 已經是最頂層的包 ????)...
  • 在該父包中,找到 dependencies 模塊(位于 app/ 更上一級目錄中的 dependencies.py 文件)...
  • 然后從中導入函數(shù) get_token_header。

這將引用 app/ 的往上一級,帶有其自己的 __init __.py 等文件的某個包。但是我們并沒有這個包。因此,這將在我們的示例中引發(fā)錯誤。????

但是現(xiàn)在你知道了它的工作原理,因此無論它們多么復雜,你都可以在自己的應用程序中使用相對導入。????

添加一些自定義的 tags、responses 和 dependencies

我們不打算在每個路徑操作中添加前綴 /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。

FastAPI 主體

現(xiàn)在,讓我們來看看位于 app/main.py 的模塊。

在這里你導入并使用 FastAPI 類。

這將是你的應用程序中將所有內容聯(lián)結在一起的主文件。

并且由于你的大部分邏輯現(xiàn)在都存在于其自己的特定模塊中,因此主文件的內容將非常簡單。

導入 FastAPI

你可以像平常一樣導入并創(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!"}

導入 APIRouter

現(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

表示:

  • 從該模塊(app/main.py 文件)所在的同一個包(app/ 目錄)開始...
  • 尋找 routers 子包(位于 app/routers/ 的目錄)...
  • 從該包中,導入子模塊 items (位于 app/routers/items.py 的文件) 以及 users (位于 app/routers/users.py 的文件)...

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!"}

包含 users 和 items 的 APIRouter

現(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ā)生。

因此,它不會影響性能。?

包含一個有自定義 prefix、tags、responses 和 dependencies 的 APIRouter

現(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 模塊的每個路徑操作都將具有:

  • /admin 前綴 。
  • admin 標簽。
  • get_token_header 依賴項。
  • 418 響應。 ????

但這只會影響我們應用中的 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)建),而不是直接包含。

查看自動化的 API 文檔

現(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 文檔,包括了來自所有子模塊的路徑:

多次使用不同的 prefix 包含同一個路由器

你也可以在同一路由器上使用不同的前綴來多次使用 .include_router()。

在有些場景這可能有用,例如以不同的前綴公開同一個的 API,比方說 /api/v1 和 /api/latest。

這是一個你可能并不真正需要的高級用法,但萬一你有需要了就能夠用上。

在另一個 APIRouter 中包含一個 APIRouter

與在 FastAPI 應用程序中包含 APIRouter 的方式相同,你也可以在另一個 APIRouter 中包含 APIRouter,通過:

router.include_router(other_router)

請確保在你將 router 包含到 FastAPI 應用程序之前進行此操作,以便 other_router 中的路徑操作也能被包含進來。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號