Skip to content

路徑操作進階設定

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

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

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

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

英文版

OpenAPI operationId

Warning

如果你不是 OpenAPI 的「專家」,大概不需要這個。

你可以用參數 operation_id 為你的路徑操作(path operation)設定要使用的 OpenAPI operationId

你必須確保每個操作的 operationId 都是唯一的。

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/", operation_id="some_specific_id_you_define")
async def read_items():
    return [{"item_id": "Foo"}]
🤓 Other versions and variants
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/", operation_id="some_specific_id_you_define")
async def read_items():
    return [{"item_id": "Foo"}]

使用路徑操作函式(path operation function)的名稱作為 operationId

如果你想用 API 的函式名稱作為 operationId,你可以遍歷所有路徑,並使用各自的 APIRoute.name 覆寫每個路徑操作operation_id

應在加入所有路徑操作之後再這麼做。

from fastapi import FastAPI
from fastapi.routing import APIRoute

app = FastAPI()


@app.get("/items/")
async def read_items():
    return [{"item_id": "Foo"}]


def use_route_names_as_operation_ids(app: FastAPI) -> None:
    """
    Simplify operation IDs so that generated API clients have simpler function
    names.

    Should be called only after all routes have been added.
    """
    for route in app.routes:
        if isinstance(route, APIRoute):
            route.operation_id = route.name  # in this case, 'read_items'


use_route_names_as_operation_ids(app)
🤓 Other versions and variants
from fastapi import FastAPI
from fastapi.routing import APIRoute

app = FastAPI()


@app.get("/items/")
async def read_items():
    return [{"item_id": "Foo"}]


def use_route_names_as_operation_ids(app: FastAPI) -> None:
    """
    Simplify operation IDs so that generated API clients have simpler function
    names.

    Should be called only after all routes have been added.
    """
    for route in app.routes:
        if isinstance(route, APIRoute):
            route.operation_id = route.name  # in this case, 'read_items'


use_route_names_as_operation_ids(app)

Tip

如果你會手動呼叫 app.openapi(),請務必先更新所有 operationId 再呼叫。

Warning

如果你這樣做,必須確保每個路徑操作函式都有唯一的名稱,

即使它們位於不同的模組(Python 檔案)中。

從 OpenAPI 排除

若要從產生的 OpenAPI 結構(也就是自動文件系統)中排除某個路徑操作,使用參數 include_in_schema 並將其設為 False

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/", include_in_schema=False)
async def read_items():
    return [{"item_id": "Foo"}]
🤓 Other versions and variants
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/", include_in_schema=False)
async def read_items():
    return [{"item_id": "Foo"}]

從 docstring 提供進階描述

你可以限制 OpenAPI 從路徑操作函式的 docstring 中使用的內容行數。

加上一個 \f(跳頁字元,form feed)會讓 FastAPI 在此處截斷用於 OpenAPI 的輸出。

這個字元不會出現在文件中,但其他工具(例如 Sphinx)仍可使用其後的內容。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()


@app.post("/items/", summary="Create an item")
async def create_item(item: Item) -> Item:
    """
    Create an item with all the information:

    - **name**: each item must have a name
    - **description**: a long description
    - **price**: required
    - **tax**: if the item doesn't have tax, you can omit this
    - **tags**: a set of unique tag strings for this item
    \f
    :param item: User input.
    """
    return item

額外回應

你大概已看過如何為路徑操作宣告 response_modelstatus_code

這會定義該路徑操作主要回應的中繼資料。

你也可以宣告額外的回應及其模型、狀態碼等。

文件中有完整章節說明,請見 OpenAPI 中的額外回應

OpenAPI 額外資訊

當你在應用程式中宣告一個路徑操作時,FastAPI 會自動產生該路徑操作的相關中繼資料,並納入 OpenAPI 結構中。

技術細節

在 OpenAPI 規格中,這稱為 Operation 物件

它包含關於路徑操作的所有資訊,並用於產生自動文件。

其中包含 tagsparametersrequestBodyresponses 等。

這個針對單一路徑操作的 OpenAPI 結構通常由 FastAPI 自動產生,但你也可以擴充它。

Tip

這是一個較低階的擴充介面。

如果你只需要宣告額外回應,更方便的方式是使用 OpenAPI 中的額外回應

你可以使用參數 openapi_extra 來擴充某個路徑操作的 OpenAPI 結構。

OpenAPI 擴充

openapi_extra 可用來宣告例如 OpenAPI 擴充 的資料:

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/", openapi_extra={"x-aperture-labs-portal": "blue"})
async def read_items():
    return [{"item_id": "portal-gun"}]
🤓 Other versions and variants
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/", openapi_extra={"x-aperture-labs-portal": "blue"})
async def read_items():
    return [{"item_id": "portal-gun"}]

打開自動產生的 API 文件時,你的擴充會顯示在該路徑操作頁面的底部。

而在檢視產生出的 OpenAPI(位於你的 API 的 /openapi.json)時,也可以在相應路徑操作中看到你的擴充:

{
    "openapi": "3.1.0",
    "info": {
        "title": "FastAPI",
        "version": "0.1.0"
    },
    "paths": {
        "/items/": {
            "get": {
                "summary": "Read Items",
                "operationId": "read_items_items__get",
                "responses": {
                    "200": {
                        "description": "Successful Response",
                        "content": {
                            "application/json": {
                                "schema": {}
                            }
                        }
                    }
                },
                "x-aperture-labs-portal": "blue"
            }
        }
    }
}

自訂 OpenAPI 路徑操作結構

openapi_extra 中的字典會與自動產生的該路徑操作之 OpenAPI 結構進行深度合併。

因此你可以在自動產生的結構上加入額外資料。

例如,你可以選擇用自己的程式碼讀取並驗證請求,而不使用 FastAPI 與 Pydantic 的自動功能,但仍然希望在 OpenAPI 結構中定義該請求。

你可以透過 openapi_extra 辦到:

from fastapi import FastAPI, Request

app = FastAPI()


def magic_data_reader(raw_body: bytes):
    return {
        "size": len(raw_body),
        "content": {
            "name": "Maaaagic",
            "price": 42,
            "description": "Just kiddin', no magic here. ✨",
        },
    }


@app.post(
    "/items/",
    openapi_extra={
        "requestBody": {
            "content": {
                "application/json": {
                    "schema": {
                        "required": ["name", "price"],
                        "type": "object",
                        "properties": {
                            "name": {"type": "string"},
                            "price": {"type": "number"},
                            "description": {"type": "string"},
                        },
                    }
                }
            },
            "required": True,
        },
    },
)
async def create_item(request: Request):
    raw_body = await request.body()
    data = magic_data_reader(raw_body)
    return data
🤓 Other versions and variants
from fastapi import FastAPI, Request

app = FastAPI()


def magic_data_reader(raw_body: bytes):
    return {
        "size": len(raw_body),
        "content": {
            "name": "Maaaagic",
            "price": 42,
            "description": "Just kiddin', no magic here. ✨",
        },
    }


@app.post(
    "/items/",
    openapi_extra={
        "requestBody": {
            "content": {
                "application/json": {
                    "schema": {
                        "required": ["name", "price"],
                        "type": "object",
                        "properties": {
                            "name": {"type": "string"},
                            "price": {"type": "number"},
                            "description": {"type": "string"},
                        },
                    }
                }
            },
            "required": True,
        },
    },
)
async def create_item(request: Request):
    raw_body = await request.body()
    data = magic_data_reader(raw_body)
    return data

在這個範例中,我們沒有宣告任何 Pydantic 模型。事實上,請求本文甚至不會被 解析 為 JSON,而是直接以 bytes 讀取,並由函式 magic_data_reader() 以某種方式負責解析。

儘管如此,我們仍可宣告請求本文的預期結構。

自訂 OpenAPI Content-Type

用同樣的方法,你可以使用 Pydantic 模型來定義 JSON Schema,並把它包含到該路徑操作的自訂 OpenAPI 區段中。

即使請求中的資料型別不是 JSON 也可以這麼做。

例如,在這個應用中,我們不使用 FastAPI 內建的從 Pydantic 模型擷取 JSON Schema 的功能,也不使用 JSON 的自動驗證。實際上,我們將請求的 content type 宣告為 YAML,而非 JSON:

import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError

app = FastAPI()


class Item(BaseModel):
    name: str
    tags: list[str]


@app.post(
    "/items/",
    openapi_extra={
        "requestBody": {
            "content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
            "required": True,
        },
    },
)
async def create_item(request: Request):
    raw_body = await request.body()
    try:
        data = yaml.safe_load(raw_body)
    except yaml.YAMLError:
        raise HTTPException(status_code=422, detail="Invalid YAML")
    try:
        item = Item.model_validate(data)
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=e.errors(include_url=False))
    return item
🤓 Other versions and variants
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError

app = FastAPI()


class Item(BaseModel):
    name: str
    tags: list[str]


@app.post(
    "/items/",
    openapi_extra={
        "requestBody": {
            "content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
            "required": True,
        },
    },
)
async def create_item(request: Request):
    raw_body = await request.body()
    try:
        data = yaml.safe_load(raw_body)
    except yaml.YAMLError:
        raise HTTPException(status_code=422, detail="Invalid YAML")
    try:
        item = Item.model_validate(data)
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=e.errors(include_url=False))
    return item

儘管沒有使用預設的內建功能,我們仍透過 Pydantic 模型手動產生想以 YAML 接收之資料的 JSON Schema。

接著我們直接使用請求,並將本文擷取為 bytes。這表示 FastAPI 甚至不會嘗試把請求負載解析為 JSON。

然後在程式中直接解析該 YAML 內容,並再次使用相同的 Pydantic 模型來驗證該 YAML 內容:

import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError

app = FastAPI()


class Item(BaseModel):
    name: str
    tags: list[str]


@app.post(
    "/items/",
    openapi_extra={
        "requestBody": {
            "content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
            "required": True,
        },
    },
)
async def create_item(request: Request):
    raw_body = await request.body()
    try:
        data = yaml.safe_load(raw_body)
    except yaml.YAMLError:
        raise HTTPException(status_code=422, detail="Invalid YAML")
    try:
        item = Item.model_validate(data)
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=e.errors(include_url=False))
    return item
🤓 Other versions and variants
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError

app = FastAPI()


class Item(BaseModel):
    name: str
    tags: list[str]


@app.post(
    "/items/",
    openapi_extra={
        "requestBody": {
            "content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
            "required": True,
        },
    },
)
async def create_item(request: Request):
    raw_body = await request.body()
    try:
        data = yaml.safe_load(raw_body)
    except yaml.YAMLError:
        raise HTTPException(status_code=422, detail="Invalid YAML")
    try:
        item = Item.model_validate(data)
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=e.errors(include_url=False))
    return item

Tip

這裡我們重複使用同一個 Pydantic 模型。

不過也可以用其他方式進行驗證。