[英]FastAPI/Pydantic alias existing ORM field
I need to point Pydantic to a different attribute when serializing an ORM model. alias=
doesn't seem to work as expected.在序列化 ORM model 时,我需要将Pydantic指向不同的属性。alias alias=
似乎没有按预期工作。 In the example below I have an ORM object with both id
and uuid
attributes.在下面的示例中,我有一个 ORM object 具有id
和uuid
属性。 I want to serialize uuid
as id
.我想将uuid
序列化为id
。
The API response should be: API 响应应该是:
{
"id": "12345678-1234-5678-1234-567812345678",
"foo": "bar"
}
Full example:完整示例:
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
This raises这提高了
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)
The marshmallow equivalent of what I'm trying to achieve would be this:相当于我想要实现的棉花糖是这样的:
import marshmallow as ma
class ApiSchema(ma.Schema):
id = ma.fields.UUID(attribute='uuid')
foo = ma.fields.Str()
You misunderstand how aliases work.您误解了别名的工作原理。 An alias on a field takes priority (over the actual field name) when the fields are populated .当填充字段时,字段上的别名优先(高于实际字段名称)。 That means, during initialization, the class will look for the alias of a field in the data it is supposed to parse.这意味着,在初始化期间,class 将在它应该解析的数据中查找字段的别名。
The way you defined ApiSchema
, the field uuid
has the alias id
.您定义ApiSchema
的方式,字段uuid
具有别名id
。 Therefore, when you are parsing an instance of ORMModel
(happens in FastAPI behind the scenes via ApiSchema.from_orm
), the ApiSchema
class will look for an attribute named id
on that ORMModel
object to populate the uuid
field.因此,当您解析ORMModel
的实例时(通过ApiSchema.from_orm
在幕后发生在 FastAPI 中), ApiSchema
class 将在该ORMModel
object 上查找名为id
的属性以填充uuid
字段。
Since your ORMModel
actually has an attribute named id
(with the value 1
in your example), its value is taken to be assigned to the uuid
field of ApiSchema
.由于您的ORMModel
实际上有一个名为id
的属性(在您的示例中值为1
),因此将其值分配给ApiSchema
的uuid
字段。
Obviously, the integer 1
is not a UUID
object and can not be coerced into one, so you get that validation error telling you that the value it found for id
is not a valid UUID.显然, integer 1
不是UUID
object 并且不能被强制转换为一个,因此您会收到验证错误,告诉您它为id
找到的值不是有效的 UUID。
Here is the problem boiled down to the essentials:这是归结为要点的问题:
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))
The output of the first attempt:第一次尝试的output:
[
{
"loc": [
"id"
],
"msg": "field required",
"type": "value_error.missing"
}
]
The second:第二:
[
{
"loc": [
"id"
],
"msg": "value is not a valid uuid",
"type": "type_error.uuid"
}
]
I think you want it the other way around .我想你想要相反的方式。 I assume that your actual goal is to have a field named id
on your ApiSchema
model (and have that appear in your API endpoint) and alias it with uuid
, so that it takes the value of the ORMModel.uuid
attribute during initialization:我假设您的实际目标是在您的ApiSchema
model 上有一个名为id
的字段(并出现在您的 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))
The output: output:
{
"id": "12345678-1234-5678-1234-567812345678",
"foo": "bar"
}
To fix your FastAPI example, you would therefore probably do this:因此,要修复您的 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
Side note: Yes, the actual return type of endpoint
is ORMModel
.旁注:是的, endpoint
的实际返回类型是ORMModel
。 The wrapper returned by the decorator then takes that and turns it into an instance of ApiSchema
via from_orm
.装饰器返回的包装器然后接受它并通过from_orm
将它变成ApiSchema
的实例。
Forgot the last part to actually get the response you want.忘记了最后一部分以实际获得您想要的响应。 You need to set response_model_by_alias=False
in the route decorator (it is True
by default) for the response to actually use the regular field name instead of the alias.您需要在路由装饰器中设置response_model_by_alias=False
(默认情况下为True
),以便响应实际使用常规字段名称而不是别名。 I fixed the last code snipped accordingly.我相应地修复了最后一个代码。 Now the response will be:现在响应将是:
{"id":"12345678-1234-5678-1234-567812345678","foo":"bar"}
In the Pydantic BaseModel.json
method the by_alias
parameter has the value False
by default.在 Pydantic BaseModel.json
方法中, by_alias
参数默认值为False
。 FastAPI does this differently. FastAPI 的做法不同。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.