首页
/ FastAPI OpenAPI 模型:用于生成与校验 OpenAPI Schema 的 Pydantic 模型详解

FastAPI OpenAPI 模型:用于生成与校验 OpenAPI Schema 的 Pydantic 模型详解

2026-09-06 17:39:06作者:田桥桑Industrious

本文基于官方参考文档 docs/en/docs/reference/openapi/models.md 展开。该文档定义的页面内容为 fastapi.openapi.models 模块(页面通过 ::: fastapi.openapi.models 自动引用生成),其核心定位是:一组用于生成并校验 FastAPI 产出的 OpenAPI 文档的 Pydantic 模型。读完本文,你将掌握这些模型的完整类结构、字段别名机制($ref$schemanotin 等)、JSON Schema 2020-12 的覆盖方式、安全方案模型的分工,以及它们在 get_openapi() 调用链中"最后校验一道"的实际作用。

定位:生成与校验的"同一套合同"

官方文档对这一组模型只给了一句定义:

OpenAPI Pydantic models used to generate and validate the generated OpenAPI. (用于生成和校验所生成 OpenAPI 的 Pydantic 模型。)

这句话点出了双重视角:

  1. 生成视角get_openapi() 逐字段拼装出的 OpenAPI 字典,最终以 fastapi.openapi.models 中的模型结构为"目标形状";
  2. 校验视角:拼装完成后,FastAPI 会把整份字典回填到顶层 OpenAPI 模型上做一次完整验证。这一步的实现位于 fastapi/openapi/utils.py
# fastapi/openapi/utils.py (get_openapi 函数尾部)
return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True)

也就是说,OpenAPI(**output) 会用 Pydantic 对整份生成的 schema 做类型检查——任何与 OpenAPI 规范不符的字段(例如 info 缺失、paths 中出现非法类型)都会在这里以 ValidationError 形式暴露出来,而不是静默输出一份畸形文档。by_alias=True 则保证输出时 Python 属性名(如 schema_not_in_)被还原为 OpenAPI 规范中的原始键名(schemanotin)。

总体设计:BaseModelWithConfig、别名与容错

所有模型都继承自一个公共基类,见 fastapi/openapi/models.py

class BaseModelWithConfig(BaseModel):
    model_config = {"extra": "allow"}

extra = "allow" 是一个关键的工程决策:它允许模型接受 OpenAPI 规范中未显式建模的字段(即 Specification Extensions,如 x- 前缀的自定义字段)。源码中多处注释直接点明了这一点,例如 Operation.responsesComponents.callbacks 上标注的 "Using Any for Specification Extensions"。这保证了 FastAPI 生成的文档不会因为第三方工具注入的扩展字段而被校验拒绝。

第二组公共设计是字段别名。OpenAPI 规范中不少键名与 Python 关键字或命名习惯冲突,模型统一用"合法属性名 + alias"的方式承载:

模型字段 OpenAPI 键 所在位置
Reference.ref $ref models.py
Schema.schema_ / id / anchor / defs $schema / $id / $anchor / $defs models.py
Schema.not_ / if_ / else_ not / if / else models.py
Parameter.in_ in models.py
SecurityBase.type_ type models.py
ParameterBase.schema_ schema models.py
PathItem.ref $ref models.py

此外,模块顶部还有一段针对 email-validator 的防御性代码(models.py):当环境中未安装 email-validator 时,会回退到一个本地 EmailStr 兼容类,把邮件字段按普通 str 处理并打印 logger.warning 提示,同时保证 JSON Schema 仍输出 {"type": "string", "format": "email"}。这使"接触信息里的 email 字段"在最小依赖环境下依然可用。

元数据模型:Info、Contact、License、Server 与 Tag

OpenAPI 文档的"门面"信息由以下模型承载:

  • InfoL73-L80):必填 titleversion,可选 summarydescriptiontermsOfServicecontactlicense。FastAPI 应用构造参数 titleversiondescriptionterms_of_servicecontactlicense_info 等就是最终映射到这个模型上的,传递链路见 fastapi/applications.pyFastAPI.openapi_schema 属性对 get_openapi() 的调用。
  • ContactL61-L64):nameurlAnyUrl)、emailEmailStr),全部可选。
  • LicenseL67-L70):name 必填,identifier(SPDX 许可证标识符,OpenAPI 3.1 引入)、url 可选。
  • Server / ServerVariableL83-L92):Server.url 允许 AnyUrl | str,即既可以写完整 URL,也可以写带模板变量的相对形式(如 https://{env}.example.com),变量定义在 variables: dict[str, ServerVariable] 中,每个 ServerVariable 要求 default 必填、enum 若给出则至少 1 个元素(Field(min_length=1))、description 可选。
  • ExternalDocumentationL112-L114):urlAnyUrl)必填,description 可选。它同时出现在顶层 OpenAPITagOperationSchema 上,与 FastAPI(openapi_external_docs=...) 参数直接对应。
  • TagL413-L416):name 必填,descriptionexternalDocs 可选。

核心:Schema 模型对 JSON Schema 2020-12 的完整映射

SchemaL123-L204)是整份模块中最大的类,也是理解"为什么 FastAPI 生成的组件 schema 如此丰富"的关键。OpenAPI 3.1 直接内嵌了 JSON Schema 2020-12,源码中逐段以注释标注了所参照的词汇表,可分为五组:

  1. 核心词汇(Core Vocabulary)$schema$vocabulary$id$anchor$dynamicAnchor$ref$dynamicRef$defs$comment,全部以别名方式建模;
  2. 子 schema 应用词汇allOfanyOfoneOfnotif/then/elsedependentSchemasprefixItemsitemscontainspropertiespatternPropertiesadditionalPropertiespropertyNamesunevaluatedItemsunevaluatedProperties——注意所有这些字段都接受 SchemaOrBool 类型;
  3. 结构校验词汇type(受 SchemaType 字面量约束)、enumconst、数值约束(multipleOf/maximum/minimum 等,其中 multipleOfgt=0 校验)、字符串约束(maxLength/minLength ≥ 0、pattern)、数组约束(maxItems/minItems/uniqueItems/maxContains/minContains)、对象约束(maxProperties/minProperties/required/dependentRequired);
  4. 格式与内容词汇formatcontentEncodingcontentMediaTypecontentSchema
  5. 元数据与 OpenAPI 扩展titledescriptiondefaultdeprecatedreadOnlywriteOnlyexamples,以及仅属于 OpenAPI 而非纯 JSON Schema 的 discriminatorDiscriminator,含 propertyNamemapping)、xmlXMLname/namespace/prefix/attribute/wrapped)、externalDocs

几个值得注意的细节:

  • SchemaOrBool = Schema | boolL209):这是 JSON Schema 2020-12 的核心特性——schema 允许是布尔值(true 恒通过、false 恒拒绝)。因此 additionalProperties: bool | Schema 这类"紧凑写法"在模型层面是合法的。
  • type 的取值SchemaTypeL118-L120)限定为 "array" | "boolean" | "integer" | "null" | "number" | "object" | "string",且允许 list[SchemaType](JSON Schema 2020-12 的联合类型写法)。
  • example 已被标记弃用:该字段套用了 typing.deprecatedL198-L204),说明 OpenAPI 3.1 转向 examples 数组,但旧字段仍被支持。
  • 自引用模型Schema 内部大量引用自身("SchemaOrBool" 为前向引用),因此模块末尾显式调用了 Schema.model_rebuild()L433-L435),OperationEncoding 同理——这是使用递归 Pydantic 模型时的标准收尾动作。

路径与操作:PathItem、Operation、Parameter 与响应链

这一组模型描述了"每个端点长什么样":

  • PathItemL305-L318):$ref 别名 + summary/description,以及 get/put/post/delete/options/head/patch/trace 八个方法位(均为 Operation | None),外加路径级 serversparameters
  • OperationL289-L302):tagssummarydescriptionexternalDocsoperationIdparameterslist[Parameter | Reference])、requestBodyRequestBody | Reference)、responsesdict[str, Response | Any]Any 即为规范扩展留口)、callbacksdeprecatedsecuritylist[dict[str, list[str]]],即 security requirement 对象)、servers
  • ParameterBaseParameter / HeaderL243-L264):基类承载 descriptionrequireddeprecated、序列化规则(styleexplodeallowReserved)、schema(别名建模)、exampleexamples 与复杂场景的 contentParameter 额外要求 namein_ParameterInType 枚举:query/header/path/cookie);Header 则是不带 name/in 的参数基类,用于响应头与 Encoding.headers 等复用场景。
  • MediaTypeL236-L240):schemaSchema | Reference | None)、exampleexamplesdict[str, Example | Reference])、encodingdict[str, Encoding])。
  • EncodingL228-L233):contentTypeheadersstyleexplodeallowReserved
  • ExampleL212-L218):注意这是一个 TypedDicttotal=False)而非 Pydantic 模型,字段为 summary/description/value/externalValue。它被 fastapi/params.pyfastapi/param_functions.py 导入——这正是你在 Field(..., examples=[...]) 里传入示例字典时所使用的结构。
  • RequestBodyL267-L270):content: dict[str, MediaType] 必填,descriptionrequired 可选。
  • Response / LinkL273-L286):Response 要求 description 必填,可选 headerscontentlinksLink 支持 operationRefoperationIdparametersrequestBodydescriptionserver

安全方案模型:SecurityScheme 家族

安全方案模型与 fastapi.security 包中的安全类形成一一镜像关系——后者负责运行时鉴权,前者负责把方案描述写进 components.securitySchemes

模型 字段 对应安全类位置
SecurityBaseL328-L330 typeSecuritySchemeType 枚举:apiKey/http/oauth2/openIdConnect)、description fastapi/security/base.py
APIKeyL339-L342 继承默认 type=apiKey,加 inAPIKeyInquery/header/cookie)与 name fastapi/security/api_key.py
HTTPBaseL345-L347 默认 type=http,加 scheme fastapi/security/http.py
HTTPBearerL350-L352 scheme 固定为字面量 "bearer",可选 bearerFormat 同上
OAuthFlow* 家族(L355-L386 OAuthFlow 基类含 refreshUrlscopes;四个子类分别要求 authorizationUrl(implicit)、tokenUrl(password / clientCredentials)或两者(authorizationCode);OAuthFlows 聚合四者;OAuth2 要求 flows 必填 fastapi/security/oauth2.py
OpenIdConnectL389-L393 openIdConnectUrl 必填 fastapi/security/open_id_connect_url.py

顶层用 SecurityScheme = APIKey | HTTPBase | OAuth2 | OpenIdConnect | HTTPBearerL396)定义了联合类型。从源码结构看,fastapi.security.* 中的类在构造 OpenAPI 输出时会转译为上述模型实例(例如 fastapi/security/api_key.py 直接导入了 APIKey, APIKeyIn),从而保证运行时安全行为与文档描述出自同一套词汇。

Components 与顶层 OpenAPI 模型

ComponentsL399-L410)是复用注册表,包含 schemasresponsesparametersexamplesrequestBodiesheaderssecuritySchemeslinkscallbackspathItems 十类注册项,值多为"具体模型 | Reference"的联合——这正是 OpenAPI 文档中"定义一次、多处 $ref"模式的模型化表达。

顶层 OpenAPI 模型(L419-L430):

class OpenAPI(BaseModelWithConfig):
    openapi: str
    info: Info
    jsonSchemaDialect: str | None = None
    servers: list[Server] | None = None
    paths: dict[str, PathItem | Any] | None = None
    webhooks: dict[str, PathItem | Reference] | None = None
    components: Components | None = None
    security: list[dict[str, list[str]]] | None = None
    tags: list[Tag] | None = None
    externalDocs: ExternalDocumentation | None = None

其中 jsonSchemaDialect 是 OpenAPI 3.1 的新增字段(默认指向 JSON Schema 2020-12 的方言 URI),webhooks 对应 FastAPI 的 FastAPI(webhooks=...) 功能。paths 的值被放宽为 PathItem | Any,再次为规范扩展留出空间。

它们在请求链路中的实际位置

把上面的模型放回运行时链路,整体流程(均有源码依据)是:

  1. 客户端首次访问 /openapi.json(或文档页)时,FastAPI.openapi_schema 属性发现 schema 未缓存或路由版本变化,调用 get_openapi(...) 并传入 terms_of_servicecontactlicense_infoopenapi_external_docs 等应用级元数据(fastapi/applications.py);
  2. get_openapi() 收集路由字段、生成 definitions(即 components.schemas)、逐路由生成 pathssecuritySchemesfastapi/openapi/utils.py);
  3. 最终 OpenAPI(**output) 用本文描述的整套模型做一次完整校验与字段规整,再经 jsonable_encoder(..., by_alias=True, exclude_none=True)None 字段剔除、把属性名还原为规范键名后输出 JSON。

对开发者的实用启示

  • 想给生成的文档"注入"额外字段(如 x- 扩展)是安全的:BaseModelWithConfigextra="allow" 与多处 Any 留口保证扩展字段不会被校验丢弃或拒绝。
  • 自定义 OpenAPI 生成逻辑时,应以 fastapi.openapi.models 为目标形状:任何你手工构造或修改的 schema 字典,都可以先过一遍对应模型(如 Info(**info)Components(**components))来获得与 FastAPI 一致的校验语义。
  • 引用 examples记得 ExampleTypedDict:字段是可选的(total=False),且同样 extra="allow",但它的"校验"发生在消费方而非构造时。
  • 注意弃用信号Schema.example(单数)已被 typing.deprecated 标注,编写或解析 schema 时应以 examples 为准。
  • 依赖前提Contact.email 的严格校验依赖 email-validator;未安装时会降级为普通字符串并输出警告,不会导致应用不可用(fastapi/openapi/models.py)。

以上即 docs/en/docs/reference/openapi/models.md 所指向的 fastapi.openapi.models 模块的完整技术画像:它是 FastAPI 将"声明式路由"固化为标准 OpenAPI 3.1 文档时,生成端与校验端共用的那份合同。

登录后查看全文
热门项目推荐
相关项目推荐