一尘不染

FastAPI/Pydantic 别名现有的 ORM 字段

py

在序列化 ORM 模型时,我需要将Pydantic指向不同的属性。alias=似乎没有按预期工作。在下面的示例中,我有一个具有iduuid属性的 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()

阅读 131

收藏
2023-02-01

共1个答案

一尘不染

您误解了别名的工作原理。当填充字段时,字段上的别名优先(高于实际字段名称)。这意味着,在初始化期间,该类将在它应该解析的数据中查找字段的别名。

按照您定义的方式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

旁注:是的,的实际返回类型endpointORMModel. 装饰器返回的包装器然后将其转换为ApiSchemavia的实例from_orm

PS

忘记了最后一部分以实际获得您想要的响应。您需要response_model_by_alias=False在路由装饰器(True默认情况下)中设置响应以实际使用常规字段名称而不是别名。我相应地修复了最后一个代码。现在响应将是:

{"id":"12345678-1234-5678-1234-567812345678","foo":"bar"}

在 PydanticBaseModel.json方法中,by_alias参数具有False默认值。FastAPI 的做法不同。

2023-02-01