跳转至

路径操作高级配置

OpenAPI 的 operationId

警告

如果您不是 OpenAPI 专家,请跳过本章。

operation_id 参数用于设置路径操作 中 OpenAPI 的 operationId

但路径操作的 operation_id 必须是唯一的。

from fastapi import FastAPI

app = FastAPI()


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

使用路径操作函数名定义 operationId

使用 API 的函数名定义 operationId,需要先遍历 API 函数名,再使用 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)

提示

手动调用 app.openapi() 时,应预先更新 operationId

警告

使用这种方式时,务必确保路径操作函数名是唯一的。

即使它们在不同的模块里(Python 文件)。

从 OpenAPI 中排除

把参数 include_in_schema 设置为 False,就会在 OpenAPI 概图中排除路径操作(同时也从 API 文档中排除该路径操作)。

from fastapi import FastAPI

app = FastAPI()


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

docstring 的高级描述

限制路径操作函数文档字符串在 OpenAPI 中显示的行数。

添加 \f(换页符),FastAPI 在换页符所在位置截断在 OpenAPI 中的输出内容。

换页符后的内容不会在文档中显示,但 Sphinx 等工具仍可以使用。

from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.post("/items/", response_model=Item, summary="Create an item")
async def create_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 Extra

在应用中声明路径操作时,FastAPI 会自动生成路径操作的元数据,并把它添加至 OpenAPI 概图。

技术细节

OpenAPI 规范中,它被称为操作对象

这些元数据包含路径操作的所有信息,并用于生成 API 文档。

包括 tagsparametersrequestBodyresponses 等。

这个专用于路径操作的 OpenAPI 概图通常由 FastAPI 自动生成,还可以扩展。

提示

这是一个低级扩展点。

如果只是要声明附加响应,使用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"}]

打开 API 文档,在指定的路径操作底部会显示扩展项。

查看 OpenAPI 的结果(API 里的 /openapi.json),就能看到扩展项是特定路径操作的组成部分。

{
    "openapi": "3.0.2",
    "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

本例中,没有声明任何 Pydantic 模型。实际上,甚至没有把请求体解析为 JSON,而是直接读取为 bytes,由 magic_data_reader() 函数进行解析。

总之,要为请求体声明预期的概图。

自定义 OpenAPI 内容类型

还是同样的技巧,使用 Pydantic 模型定义 JSON 概图,然后把它添加到路径操作的自定义 OpenAPI 概图里。

就算请求中数据的类型不是 JSON 也能这样操作。

例如,在这个应用中,我们不使用 FastAPI 的内置功能从 Pydantic 模型中提取 JSON Schema, 也不自动验证 JSON。实际上,请求的内容类型声明为 YAML,不是 JSON:

from typing import List

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.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.parse_obj(data)
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=e.errors())
    return item

尽管没有使用默认的内置功能,我们还是使用了 Pydantic 模型为要在 YAML 中接收的数据手动生成了 JSON Schema。

然后直接使用请求,以 bytes 格式提取请求体。这是指 FastAPI 甚至不会尝试把请求负载解析为 JSON。

在代码里直接解析 YAML 内容,然后再一次使用 Pydantic 模型验证 YAML 的内容:

from typing import List

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.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.parse_obj(data)
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=e.errors())
    return item

提示

此处复用了相同的 Pydantic 模型。

但同样,我们使用其他方式验证这个模型。