简体   繁体   English

FastAPI/Pydantic 别名现有 ORM 字段

[英]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 具有iduuid属性。 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 ),因此将其值分配给ApiSchemauuid字段。

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的实例。

PS聚苯乙烯

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM