nik*_*las 6 python pydantic fastapi
from typing import Union
from pydantic import BaseModel, Field
class Category(BaseModel):
name: str = Field(alias="name")
class OrderItems(BaseModel):
name: str = Field(alias="name")
category: Category = Field(alias="category")
unit: Union[str, None] = Field(alias="unit")
quantity: int = Field(alias="quantity")
Run Code Online (Sandbox Code Playgroud)
当像这样实例化时:
OrderItems(**{'name': 'Test','category':{'name': 'Test Cat'}, 'unit': 'kg', 'quantity': 10})
Run Code Online (Sandbox Code Playgroud)
它返回这样的数据:
OrderItems(name='Test', category=Category(name='Test Cat'), unit='kg', quantity=10)
Run Code Online (Sandbox Code Playgroud)
但我想要这样的输出:
OrderItems(name='Test', category='Test Cat', unit='kg', quantity=10)
Run Code Online (Sandbox Code Playgroud)
我怎样才能实现这个目标?
您应该尽可能尝试按照您实际希望数据最终呈现的方式来定义模式,而不是您从其他地方接收数据的方式。
为了概括这个问题,我们假设您有以下模型:
from pydantic import BaseModel
class Foo(BaseModel):
x: bool
y: str
z: int
class _BarBase(BaseModel):
a: str
b: float
class Config:
orm_mode = True
class BarNested(_BarBase):
foo: Foo
class BarFlat(_BarBase):
foo_x: bool
foo_y: str
Run Code Online (Sandbox Code Playgroud)
问题:您希望能够使用BarFlat像foo一样的参数进行初始化BarNested,但数据最终会出现在平面模式中,其中字段foo_x和foo_y对应于模型上的x和(并且您对 不感兴趣)。yFooz
解决方案:定义一个自定义root_validator来pre=True检查foo数据中是否存在键/属性。如果是,它会根据Foo模型验证相应的对象,获取其x和值,然后使用它们通过和键y扩展给定的数据:foo_xfoo_y
from pydantic import BaseModel, root_validator
from pydantic.utils import GetterDict
...
class BarFlat(_BarBase):
foo_x: bool
foo_y: str
@root_validator(pre=True)
def flatten_foo(cls, values: GetterDict) -> GetterDict | dict[str, object]:
foo = values.get("foo")
if foo is None:
return values
# Assume `foo` must ba valid `Foo` data:
foo = Foo.validate(foo)
return {
"foo_x": foo.x,
"foo_y": foo.y,
} | dict(values)
Run Code Online (Sandbox Code Playgroud)
请注意,我们需要在根验证器中更加小心pre=True,因为值始终以 a 的形式传递GetterDict,这是一个不可变的类似映射的对象。因此,我们不能像分配字典那样简单地为其分配新值foo_x/ 。foo_y但没有什么可以阻止我们以常规旧数据的形式返回清理后的数据dict.
为了进行演示,我们可以向其添加一些测试数据:
test_dict = {"a": "spam", "b": 3.14, "foo": {"x": True, "y": ".", "z": 0}}
test_orm = BarNested(a="eggs", b=-1, foo=Foo(x=False, y="..", z=1))
test_flat = '{"a": "beans", "b": 0, "foo_x": true, "foo_y": ""}'
bar1 = BarFlat.parse_obj(test_dict)
bar2 = BarFlat.from_orm(test_orm)
bar3 = BarFlat.parse_raw(test_flat)
print(bar1.json(indent=4))
print(bar2.json(indent=4))
print(bar3.json(indent=4))
Run Code Online (Sandbox Code Playgroud)
输出:
{
"a": "spam",
"b": 3.14,
"foo_x": true,
"foo_y": "."
}
Run Code Online (Sandbox Code Playgroud)
{
"a": "eggs",
"b": -1.0,
"foo_x": false,
"foo_y": ".."
}
Run Code Online (Sandbox Code Playgroud)
{
"a": "beans",
"b": 0.0,
"foo_x": true,
"foo_y": ""
}
Run Code Online (Sandbox Code Playgroud)
第一个例子模拟了一种常见的情况,数据以嵌套字典的形式传递给我们。第二个例子是典型的数据库ORM对象情况,其中BarNested代表我们在数据库中找到的模式。第三个只是为了表明我们仍然可以在BarFlat 没有参数的情况下正确初始化foo。
需要注意的一个警告是,如果验证器foo在values. 如果您的模型配置了Extra.forbid该配置,将会导致错误。在这种情况下,您只需要额外的一行,将原始内容强制GetterDict为dict第一个,然后pop是密钥"foo",而不是get对其进行设置。
如果您需要嵌套Category模型进行数据库插入,但您想要一个“平面”订单模型,并且category在响应中只是一个字符串,那么您应该将其拆分为两个单独的模型。
然后,在响应模型中,您可以定义一个自定义验证器来处理当您尝试初始化它并提供或forpre=True的实例时的情况。Categorydictcategory
这是我的建议:
from pydantic import BaseModel, validator
class Category(BaseModel):
name: str
class OrderItemBase(BaseModel):
name: str
unit: str | None
quantity: int
class OrderItemCreate(OrderItemBase):
category: Category
class OrderItemResponse(OrderItemBase):
category: str
@validator("category", pre=True)
def handle_category_model(cls, v: object) -> object:
if isinstance(v, Category):
return v.name
if isinstance(v, dict) and "name" in v:
return v["name"]
return v
Run Code Online (Sandbox Code Playgroud)
这是一个演示:
if __name__ == "__main__":
insert_data = '{"name": "foo", "category": {"name": "bar"}, "quantity": 1}'
insert_obj = OrderItemCreate.parse_raw(insert_data)
print(insert_obj.json(indent=2))
... # insert into DB
response_obj = OrderItemResponse.parse_obj(insert_obj.dict())
print(response_obj.json(indent=2))
Run Code Online (Sandbox Code Playgroud)
这是输出:
{
"name": "foo",
"unit": null,
"quantity": 1,
"category": {
"name": "bar"
}
}
Run Code Online (Sandbox Code Playgroud)
{
"name": "foo",
"unit": null,
"quantity": 1,
"category": "bar"
}
Run Code Online (Sandbox Code Playgroud)
这种方法的好处之一是 JSON 架构与模型上的内容保持一致。如果您使用它,则FastAPI意味着 swagger 文档实际上将反映该端点的使用者收到的内容。您当然可以覆盖和自定义模式创建,但是......为什么呢?只要一开始就正确定义模型,就可以避免将来出现麻烦。
| 归档时间: |
|
| 查看次数: |
4018 次 |
| 最近记录: |