跳转至

路径操作高级配置

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_model 和 status_code。

这种方式定义了路径操作主响应的元数据。

您还可以使用模型、状态码等声明附加响应。

高级用户指南有一章专门介绍相关内容,详见 OpenAPI 中的附加响应。

OpenAPI Extra

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

技术细节

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

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

包括 tags、parameters、requestBody、responses 等。

这个专用于路径操作的 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 模型。

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