Rya*_*oss 8 python pydantic fastapi
我有一个用例,我想在构建模型的类中创建一些值。但是,当我在调用 API 时将类返回到 FastAPI 以便转换为 JSON 时,构造函数会再次运行,并且我可以获得与原始实例不同的值。
这是一个人为的例子来说明:
class SomeModel(BaseModel):
public_value: str
secret_value: Optional[str]
def __init__(self, **data):
super().__init__(**data)
# this could also be done with default_factory
self.secret_value = randint(1, 5)
def some_function() -> SomeModel:
something = SomeModel(public_value="hello")
print(something)
return something
@app.get("/test", response_model=SomeModel)
async def exec_test():
something = some_function()
print(something)
return something
Run Code Online (Sandbox Code Playgroud)
控制台输出为:
public_value='hello' secret_value=1
public_value='hello' secret_value=1
Run Code Online (Sandbox Code Playgroud)
但 Web API 中的 JSON 是:
{
"public_value": "hello",
"secret_value": 2
}
Run Code Online (Sandbox Code Playgroud)
当我单步执行代码时,我可以看到__init__被调用两次。
首先是在建设上something = SomeModel(public_value="hello")。
其次,令我意想不到的是,在调用的 API 处理程序exec_test中return something。
如果这是在类中设置某些内部数据的错误方法,请告诉我正确的使用方法。否则,这似乎是其中一个模块的意外行为。
当您使用 时,这应该是预期的行为response_model。文档中没有很清楚地解释,但在响应模型部分中,它说:
FastAPI 将使用它
response_model来:
- 将输出数据转换为其类型声明。
- 验证数据。
...
当您return something到达路由函数末尾时exec_test,FastAPI 会将其转换为另一个 SomeModel实例,进行验证,然后返回该经过验证的实例。因此,您得到的实例与最初返回的实例不同。
我以前也遇到过同样的问题:为什么response_model 似乎__init__两次出现在同一个对象上?,这导致了这个老问题:[BUG]使用response_model时双重验证pydantic模型。大多数回复都是“这是预料之中的”:
这是预期的事情,也是 的重点
ResponseModel,它确保您的数据处于正确的顺序。
它并不完全重复。在内部它会验证一次(如果您添加
response_model),但在端点函数上您将再次手动验证。我相信答案是:这是预期的结果。
我后来学到的解决方案是永远不要像那样改变模型__init__。我也认为alex_noname使用或 验证器提供的答案Field是避免此问题的最佳方法。
如果您确实需要该突变,以下是其他解决方法__init__:
只需跳过对路线功能的验证
在some_function实例化的地方,如果参数错误,SomeModel就会引发public_value={'a': 1})验证错误(例如,重复验证response_model可能是多余的。甚至在 FastAPI 上有一个PR 可以跳过验证response_model,但从未合并。
您只需删除response_model并将其替换为responses即可使用 OpenAPI 维护文档。
# @app.get("/test", response_model=SomeModel)
@app.get("/test", responses={200: {"model": SomeModel}})
async def exec_test():
something = some_function()
print(something)
return something
Run Code Online (Sandbox Code Playgroud)
让我们some_function返回模型的原始值,而不是模型本身的实例。基本上,将模型的实例化和验证移动/延迟到路由函数上。
def some_function() -> dict:
# something = SomeModel(public_value="hello")
something = {"public_value": "hello"}
print(something)
return something
@app.get("/test", response_model=SomeModel)
async def exec_test():
something = some_function()
print(something)
return something
Run Code Online (Sandbox Code Playgroud)
正如文档所说,FastAPI 会将返回值转换为response_model类型,从而实例化模型。在这里这样做意味着验证将很晚发生。此外,您还必须面对失去在任何地方使用 Pydantic 模型的便利性的问题。
与 #2 相关,有 2 个独立的模型,1 个用于内部使用,1 个用于放入response_model. 这类似于FastAPI 文档中的单独输入和输出模型示例:
class InternalModel(BaseModel):
public_value: str
class OutputModel(BaseModel):
public_value: str
secret_value: Optional[str]
def __init__(self, **data):
super().__init__(**data)
self.secret_value = randint(1, 5)
def some_function() -> InternalModel:
something = InternalModel(public_value="hello")
print(something)
return something
@app.get("/test", response_model=OutputModel)
async def exec_test():
something = some_function()
print(something)
return something
Run Code Online (Sandbox Code Playgroud)
由于您正在使用response_model路径操作,因此您的返回值将根据它进行验证。但由于您返回的是已验证的模型实例,因此这种情况会发生两次。如果您不使用可以__init__为模型的每个实例化生成不同值(无论输入值如何)的变异方法,则不会引人注意。
我认为最好的解决方案是使用该default_factory函数,因为在这种情况下,动态值 \xe2\x80\x8b\xe2\x80\x8b 仅在代码中实例化对象时生成,并且现成的返回值将在模型验证过程中使用。
class SomeModel(BaseModel):\n public_value: str\n secret_value: int = Field(default_factory=lambda: randint(1, 5))\nRun Code Online (Sandbox Code Playgroud)\n如果由于某种原因您不想使用上面的方法,那么您可以使用始终验证器,这将允许您检查值是否已传递或是否需要创建:
\nclass SomeModel(BaseModel):\n public_value: str\n secret_value: int = None\n\n @validator(\'secret_value\', pre=True, always=True)\n def secret_validator(cls, v):\n return randint(1, 5) if v is None else v\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
2792 次 |
| 最近记录: |