FastAPI结合Pydantic定义kebab-case请求头时,文档与实际行为不一致的解决方案咨询
我在FastAPI项目里用Pydantic定义通用请求头的时候,遇到了kebab-case字段的问题——请求里的kebab-case头没办法自动转换成对应的snake_case字段,而且生成的Swagger文档也和实际行为不一致。到底该怎么正确定义这个Pydantic头类,才能让文档和实际行为匹配呢?
下面是我整理的最小复现示例:
### main.py from typing import Annotated from fastapi import FastAPI, Header from pydantic import BaseModel, Field app = FastAPI() class CommonHeaders(BaseModel): simpleheader: str a_kebab_header: str | None = Field( default=None, title="a-kebab-header", alias="a-kebab-header", description="This is a header that should be specified as `a-kebab-header`", ) @app.get("/") def root_endpoint( headers: Annotated[CommonHeaders, Header()], ): result = {"headers received": headers} return result
运行项目后,打开Swagger文档,看起来是正常的:
Swagger文档里正确显示了kebab-case的请求头字段。
用“Try it out”生成的请求也是符合预期的:
curl -X 'GET' \ 'http://localhost:8000/' \ -H 'accept: application/json' \ -H 'simpleheader: foo' \ -H 'a-kebab-header: bar'
但返回结果里明显能看到kebab-case的头没被正确接收:
{ "headers received": { "simpleheader": "foo", "a-kebab-header": null } }
就算把请求里的头改成snake_case的a_kebab_header,也不管用。
后来我修改了头的定义,去掉了title和alias,结果文档和行为还是不一致:
class CommonHeaders(BaseModel): simpleheader: str a_kebab_header: str | None = Field( default=None, description="This is a header that should be specified as `a-kebab-header`", )
这时候Swagger文档里显示的是snake_case的字段:
Swagger文档里错误地显示了snake_case的请求头字段。
“Try it out”生成的请求也是snake_case的:
curl -X 'GET' \ 'http://localhost:8000/' \ -H 'accept: application/json' \ -H 'simpleheader: foo' \ -H 'a_kebab_header: bar'
但奇怪的是,这个请求也不生效,返回结果里a_kebab_header还是null:
{ "headers received": { "simpleheader": "foo", "a_kebab_header": null } }
不过最后我手动把请求里的头改成kebab-case的a-kebab-header,居然成功了:
curl -X 'GET' \ 'http://localhost:8000/' \ -H 'accept: application/json' \ -H 'simpleheader: foo' \ -H 'a-kebab-header: bar'
返回结果里终于拿到了正确的值:
{"headers received":{"simpleheader":"foo","a_kebab_header":"bar"}}
现在我就想知道,到底该怎么正确定义这个Pydantic头类,才能让Swagger文档和实际行为完全匹配? 文档和行为不一致的话,我肯定要被吐槽的。
最后补充一下:我试过不用Pydantic的写法,这种方式下文档和行为都正常(都是kebab-case),但这样就没法定义通用的头结构,每个接口都要重复写一遍,太麻烦了:
"""Alternative version without Pydantic.""" from typing import Annotated from fastapi import FastAPI, Header app = FastAPI() @app.get("/") def root_endpoint( simpleheader: Annotated[str, Header()], a_kebab_header: Annotated[ str | None, Header( title="a-kebab-header", description="This is a header that should be specified as `a-kebab-header`", ), ] = None, ): result = { "headers received": { "simpleheader": simpleheader, "a_kebab_header": a_kebab_header, } } return result
备注:内容来源于stack exchange,提问作者sql_knievel

