在所有路由中系统地排除字段
我发现最好使用一个具体但超级简单的示例来工作。假设您有以下模型:
from pydantic import BaseModel, Field
class Demo(BaseModel):
foo: str
bar: str = Field(return_in_api=False)
我们希望确保在响应中永远不会返回bar
,无论是当response_model
作为路由装饰器的参数明确提供时,还是仅将其设置为路由处理程序函数的返回注释时。(假设出于某种原因,我们不想使用内置的exclude
参数来排除字段。)
我发现最可靠的方法是子类化fastapi.routing.APIRoute
并钩入其__init__
方法。通过复制父类代码的一小部分,我们可以确保始终获得正确的响应模型。一旦我们拥有了它,只需要在调用父构造函数之前设置路由的response_model_exclude
参数即可。
这是我的建议:
from collections.abc import Callable
from typing import Any
from fastapi.responses import Response
from fastapi.dependencies.utils import get_typed_return_annotation, lenient_issubclass
from fastapi.routing import APIRoute, Default, DefaultPlaceholder
class CustomAPIRoute(APIRoute):
def __init__(
self,
path: str,
endpoint: Callable[..., Any],
*,
response_model: Any = Default(None),
**kwargs: Any,
) -> None:
if isinstance(response_model, DefaultPlaceholder):
return_annotation = get_typed_return_annotation(endpoint)
if lenient_issubclass(return_annotation, Response):
response_model = None
else:
response_model = return_annotation
if response_model is not None:
kwargs["response_model_exclude"] = {
name
for name, field in response_model.__fields__.items()
if field.field_info.extra.get("return_in_api") is False
}
super().__init__(path, endpoint, response_model=response_model, **kwargs)
我们现在可以在 Router 上设置自定义路由类 (documentation)。这样它就会被用于所有路由:
from fastapi import FastAPI
api = FastAPI()
api.router.route_class = CustomAPIRoute
@api.get("/demo1")
async def demo1() -> Demo:
return Demo(foo="a", bar="b")
@api.get("/demo2", response_model=Demo)
async def demo2() -> dict[str, Any]:
return {"foo": "x", "bar": "y"}
尝试使用
uvicorn
和
GET
获取端点
/demo1
和
/demo2
的简单API示例,分别返回响应
{"foo":"a"}
和
{"foo":"x"}
。
确保模式一致性
值得一提的是(除非我们采取额外措施),bar
字段仍将成为模式的一部分。这意味着,例如,两个端点的自动生成的OpenAPI文档将显示bar
作为预期响应的顶级属性。
这不是你的问题的一部分,所以我假设你已经意识到这一点并采取措施确保一致性。如果没有,或者对于其他人阅读此内容,您可以在基本模型的Config
上定义一个静态schema_extra
方法,在返回模式之前删除那些永远不会“向外部”显示的字段:
from typing import Any
from pydantic import BaseModel, Field
class CustomBaseModel(BaseModel):
class Config:
@staticmethod
def schema_extra(schema: dict[str, Any]) -> None:
properties = schema.get("properties", {})
to_delete = set()
for name, prop in properties.items():
if prop.get("return_in_api") is False:
to_delete.add(name)
for name in to_delete:
del properties[name]
class Demo(CustomBaseModel):
foo: str
bar: str = Field(return_in_api=False)
return_in_api=False
字段的简单模型,并提供一个返回该模型实例和所需输出的简单端点。您的详细说明应该在此之后给出。 - Daniil Fajnberg