pydantic:对具有别名的字段使用 property.getter 装饰器

Posted

技术标签:

【中文标题】pydantic:对具有别名的字段使用 property.getter 装饰器【英文标题】:pydantic: Using property.getter decorator for a field with an alias 【发布时间】:2020-11-25 14:42:17 【问题描述】:

一直向下滚动查看 tl;dr,我提供了我认为很重要但与所问问题没有直接关系的上下文

一点上下文

我正在为 web 应用程序制作 API,一些值是根据 pydantic BaseModel 中其他值的值计算的。这些用于用户验证、数据序列化和数据库 (NoSQL) 文档的定义。

具体来说,我几乎所有资源都继承自 OwnedResource 类,该类定义了其他不相关的属性,例如创建/上次更新日期:

object_key -- 对象的键使用长度为 6 的 nanoid 和自定义字母表 owner_key -- 该键引用拥有该对象的用户 -- 一个长度为 10 的 nanoid。 _key -- 这是我遇到一些问题的地方,我会解释原因。

因此,我正在使用的数据库 arangodb 将_key 强加为标识资源的属性的名称。

由于在我的 webapp 中,所有资源都只能由创建它们的用户访问,它们可以在 URL 中仅使用对象的键(例如 /subject/object_key)来标识。但是,由于_key 必须是唯一的,我打算使用f"owner_key/object_key" 构造该字段的值,以将每个用户的对象存储在数据库中,并可能允许将来跨用户共享资源。

目标是获得最短的每个用户唯一标识符,因为用于实际访问和操作存储在数据库中的文档的完整owner_key 部分始终是同:当前登录用户的_key

我的尝试

然后我的想法是在类中将_key 字段定义为@property-decorated 函数。但是,Pydantic 似乎没有将它们注册为模型字段。

此外,属性必须实际命名为key,并使用别名(与Field(... alias="_key"),如pydantic treats underscore-prefixed fields as internal and does not expose them。

这里是OwnedResource的定义:

class OwnedResource(BaseModel):
    """
    Base model for resources owned by users
    """

    object_key: ObjectBareKey = nanoid.generate(ID_CHARSET, OBJECT_KEY_LEN)
    owner_key: UserKey
    updated_at: Optional[datetime] = None
    created_at: datetime = datetime.now()

    @property
    def key(self) -> ObjectKey:
        return objectkey(self.owner_key)

    class Config:
        fields = "key": "_key" # [1]

[1] 由于 Field(..., alias="...") 不能使用,所以我使用 Config 子类的这个属性(见pydantic's documentation)

但是,这不起作用,如下例所示:

@router.post("/subjects/")
def create_a_subject(subject: InSubject):
    print(subject.dict(by_alias=True))

InSubject 定义了适合于Subject 的属性,而Subject 是一个继承自InSubjectOwnedResource 的空类:

class InSubject(BaseModel):
    name: str
    color: Color
    weight: Union[PositiveFloat, Literal[0]] = 1.0
    goal: Primantissa # This is just a float constrained in a [0, 1] range
    room: str

class Subject(InSubject, OwnedResource):
    pass

当我执行POST /subjects/ 时,控制台中会打印以下内容:

'name': 'string', 'color': Color('cyan', rgb=(0, 255, 255)), 'weight': 0, 'goal': 0.0, 'room': 'string'

如您所见,_keykey 无处可见。

请询问细节和澄清,我试图让这尽可能容易理解,但我不确定这是否足够清楚。

tl;博士

一个无上下文且更通用的示例,没有深入的上下文:

使用以下类:

from pydantic import BaseModel

class SomeClass(BaseModel):
    
    spam: str

    @property
    def eggs(self) -> str:
        return self.spam + " bacon"

    class Config:
        fields = "eggs": "_eggs"

我希望以下内容属实:

a = SomeClass(spam="I like")
d = a.dict(by_alias=True)
d.get("_eggs") == "I like bacon"

【问题讨论】:

【参考方案1】:

Pydantic 不支持序列化属性,GitHub 上有一个issue 请求此功能。

基于 ludwig-weiss 的 comment,他建议继承 BaseModel 并覆盖 dict 方法以包含属性。

class PropertyBaseModel(BaseModel):
    """
    Workaround for serializing properties with pydantic until
    https://github.com/samuelcolvin/pydantic/issues/935
    is solved
    """
    @classmethod
    def get_properties(cls):
        return [prop for prop in dir(cls) if isinstance(getattr(cls, prop), property) and prop not in ("__values__", "fields")]

    def dict(
        self,
        *,
        include: Union['AbstractSetIntStr', 'MappingIntStrAny'] = None,
        exclude: Union['AbstractSetIntStr', 'MappingIntStrAny'] = None,
        by_alias: bool = False,
        skip_defaults: bool = None,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
    ) -> 'DictStrAny':
        attribs = super().dict(
            include=include,
            exclude=exclude,
            by_alias=by_alias,
            skip_defaults=skip_defaults,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none
        )
        props = self.get_properties()
        # Include and exclude properties
        if include:
            props = [prop for prop in props if prop in include]
        if exclude:
            props = [prop for prop in props if prop not in exclude]

        # Update the attribute dict with the properties
        if props:
            attribs.update(prop: getattr(self, prop) for prop in props)

        return attribs

【讨论】:

以上是关于pydantic:对具有别名的字段使用 property.getter 装饰器的主要内容,如果未能解决你的问题,请参考以下文章

pydantic学习与使用-13.Field 定制字段使用别名alias

使用 Pydantic 将每个字段设为可选

如何给 Pydantic 列表字段一个默认值?

如何使用 pydantic/fastapi 接受具有数组\单个项目的单个路由对象?

Pydantic:根据其他字段的值在验证器中设置字段 None

如何在 pydantic 中设置最大字符串字段长度约束?