在序列化 ORM 模型时,我需要将Pydantic指向不同的属性。alias=
似乎没有按预期工作。在下面的示例中,我有一个具有id
和uuid
属性的 ORM 对象。我想序列uuid
化为id
.
API 响应应该是:
{
"id": "12345678-1234-5678-1234-567812345678",
"foo": "bar"
}
完整示例:
from uuid import UUID
from fastapi import FastAPI
from pydantic import BaseModel, Field
from dataclasses import dataclass
class ApiSchema(BaseModel):
class Config:
orm_mode = True
uuid: UUID = Field(alias='id')
foo: str | None = None
@dataclass
class ORMModel:
id: int
uuid: UUID
foo: str = 'bar'
app = FastAPI()
@app.get("/")
def endpoint() -> ApiSchema:
t = ORMModel(id=1, uuid=UUID('12345678123456781234567812345678'), foo='bar')
return t
这提高了
File fastapi/routing.py", line 141, in serialize_response
raise ValidationError(errors, field.type_)
pydantic.error_wrappers.ValidationError: 1 validation error for ApiSchema
response -> id
value is not a valid uuid (type=type_error.uuid)
相当于我想要实现的是这样的:
import marshmallow as ma
class ApiSchema(ma.Schema):
id = ma.fields.UUID(attribute='uuid')
foo = ma.fields.Str()
您误解了别名的工作原理。当填充字段时,字段上的别名优先(高于实际字段名称)。这意味着,在初始化期间,该类将在它应该解析的数据中查找字段的别名。
按照您定义的方式ApiSchema
,该字段uuid
具有别名id
。因此,当您解析一个实例时ORMModel
(通过 发生在幕后的 FastAPI 中ApiSchema.from_orm
),ApiSchema
该类将查找id
在该ORMModel
对象上命名的属性以填充该uuid
字段。
由于您ORMModel
实际上有一个名为的属性id
(在您的示例中具有值)1
,因此将其值分配给uuid
.ApiSchema
显然,整数1
不是UUID
对象,不能强制转换为一个对象,因此您会收到验证错误,告诉您它找到的值id
不是有效的 UUID。
这是归结为要点的问题:
from uuid import UUID
from pydantic import BaseModel, Field, ValidationError
class ApiSchema(BaseModel):
uuid: UUID = Field(alias='id')
foo: str | None = None
try:
ApiSchema.parse_obj({"uuid": "this is ignored", "foo": "bar"})
except ValidationError as exc:
print(exc.json(indent=2))
try:
ApiSchema.parse_obj({"id": 1, "foo": "bar"})
except ValidationError as exc:
print(exc.json(indent=2))
第一次尝试的输出:
[
{
"loc": [
"id"
],
"msg": "field required",
"type": "value_error.missing"
}
]
第二:
[
{
"loc": [
"id"
],
"msg": "value is not a valid uuid",
"type": "type_error.uuid"
}
]
我想你想要相反的方式。我假设您的实际目标是id
在您的ApiSchema
模型上命名一个字段(并出现在您的 API 端点中)并将其别名为uuid
,以便它ORMModel.uuid
在初始化期间采用属性的值:
from uuid import UUID
from pydantic import BaseModel, Field
class ApiSchema(BaseModel):
id: UUID = Field(alias="uuid")
foo: str | None = None
obj = ApiSchema.parse_obj(
{
"id": "this is ignored",
"uuid": UUID("12345678123456781234567812345678"),
"foo": "bar",
}
)
print(obj.json(indent=2))
输出:
{
"id": "12345678-1234-5678-1234-567812345678",
"foo": "bar"
}
因此,要修复您的 FastAPI 示例,您可能会这样做:
from dataclasses import dataclass
from uuid import UUID
from fastapi import FastAPI
from pydantic import BaseModel, Field
class ApiSchema(BaseModel):
id: UUID = Field(alias="uuid")
foo: str | None = None
class Config:
orm_mode = True
@dataclass
class ORMModel:
id: int
uuid: UUID
foo: str = "bar"
app = FastAPI()
@app.get("/", response_model=ApiSchema, response_model_by_alias=False)
def endpoint() -> ORMModel:
t = ORMModel(id=1, uuid=UUID("12345678123456781234567812345678"), foo="bar")
return t
旁注:是的,的实际返回类型endpoint
是ORMModel
. 装饰器返回的包装器然后将其转换为ApiSchema
via的实例from_orm
。
忘记了最后一部分以实际获得您想要的响应。您需要response_model_by_alias=False
在路由装饰器(True
默认情况下)中设置响应以实际使用常规字段名称而不是别名。我相应地修复了最后一个代码。现在响应将是:
{"id":"12345678-1234-5678-1234-567812345678","foo":"bar"}
在 PydanticBaseModel.json
方法中,by_alias
参数具有False
默认值。FastAPI 的做法不同。