pydantic:对具有别名的字段使用 property.getter 装饰器
pydantic: Using property.getter decorator for a field with an alias
一直向下滚动以获得 tl;dr,我提供了我认为很重要但与所问问题没有直接关系的上下文
一些上下文
我正在为 Web 应用程序制作一个 API,一些值是根据 pydantic BaseModel
中的其他值计算的.这些用于用户验证、数据序列化和数据库 (NoSQL) 文档的定义。
具体来说,我几乎所有的资源都继承自 OwnedResource
class,它定义了不相关的其他属性,例如 creation/last-update 日期:
object_key
-- 使用长度为 6 的 nanoid 和自定义字母 的对象的键
owner_key
-- 此键引用拥有该对象的用户 -- 一个长度为 10 的 nanoid。
_key
-- 这是我遇到一些问题的地方,我会解释原因。
所以 arangodb——我正在使用的数据库——强制使用 _key
作为 属性 的名称来识别资源。
由于在我的 webapp 中,所有资源仅由创建它们的用户访问,因此可以仅使用对象的键在 URL 中识别它们(例如 /subject/{object_key}
)。但是,由于 _key
必须是唯一的,我打算使用 f"{owner_key}/{object_key}"
构造此字段的值,以将每个用户的对象存储在数据库中,并可能允许将来跨用户资源共享.
目标是拥有最短的 每个用户 唯一标识符,因为完整 _key
的 owner_key
部分用于实际访问和操作根据存储在数据库中的文档始终相同:当前登录用户的 _key
.
我的尝试
当时我的想法是将 _key
字段定义为 class 中的 @property
装饰函数。但是,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子class的这个属性(见pydantic's documentation)
但是,这不起作用,如下例所示:
@router.post("/subjects/")
def create_a_subject(subject: InSubject):
print(subject.dict(by_alias=True))
with InSubject
定义 Subject
专有的属性,并且 Subject
是一个空的 class 继承自 InSubject
和 OwnedResource
:
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'}
如你所见,_key
或key
无处可见。
请询问详细信息和说明,我尽量使它易于理解,但我不确定这是否足够清楚。
tl;博士
一个无上下文且更通用的示例,没有深刻的上下文:
具有以下class:
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"
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 validator 并将 always
选项设置为 True 来序列化您的 _key
字段。
使用你的例子:
from typing import Optional
from pydantic import BaseModel, Field, validator
class SomeClass(BaseModel):
spam: str
eggs: Optional[str] = Field(alias="_eggs")
@validator("eggs", always=True)
def set_eggs(cls, v, values, **kwargs):
"""Set the eggs field based upon a spam value."""
return v or values.get("spam") + " bacon"
a = SomeClass(spam="I like")
my_dictionary = a.dict(by_alias=True)
print(my_dictionary)
> {'spam': 'I like', '_eggs': 'I like bacon'}
print(my_dictionary.get("_eggs"))
> "I like bacon"
因此,要序列化您的 _eggs
字段,您可以在其中插入序列化函数,而不是附加字符串,然后 return 它的输出。
一直向下滚动以获得 tl;dr,我提供了我认为很重要但与所问问题没有直接关系的上下文
一些上下文
我正在为 Web 应用程序制作一个 API,一些值是根据 pydantic BaseModel
中的其他值计算的.这些用于用户验证、数据序列化和数据库 (NoSQL) 文档的定义。
具体来说,我几乎所有的资源都继承自 OwnedResource
class,它定义了不相关的其他属性,例如 creation/last-update 日期:
object_key
-- 使用长度为 6 的 nanoid 和自定义字母 的对象的键
owner_key
-- 此键引用拥有该对象的用户 -- 一个长度为 10 的 nanoid。_key
-- 这是我遇到一些问题的地方,我会解释原因。
所以 arangodb——我正在使用的数据库——强制使用 _key
作为 属性 的名称来识别资源。
由于在我的 webapp 中,所有资源仅由创建它们的用户访问,因此可以仅使用对象的键在 URL 中识别它们(例如 /subject/{object_key}
)。但是,由于 _key
必须是唯一的,我打算使用 f"{owner_key}/{object_key}"
构造此字段的值,以将每个用户的对象存储在数据库中,并可能允许将来跨用户资源共享.
目标是拥有最短的 每个用户 唯一标识符,因为完整 _key
的 owner_key
部分用于实际访问和操作根据存储在数据库中的文档始终相同:当前登录用户的 _key
.
我的尝试
当时我的想法是将 _key
字段定义为 class 中的 @property
装饰函数。但是,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子class的这个属性(见pydantic's documentation)
但是,这不起作用,如下例所示:
@router.post("/subjects/")
def create_a_subject(subject: InSubject):
print(subject.dict(by_alias=True))
with InSubject
定义 Subject
专有的属性,并且 Subject
是一个空的 class 继承自 InSubject
和 OwnedResource
:
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'}
如你所见,_key
或key
无处可见。
请询问详细信息和说明,我尽量使它易于理解,但我不确定这是否足够清楚。
tl;博士
一个无上下文且更通用的示例,没有深刻的上下文:
具有以下class:
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"
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 validator 并将 always
选项设置为 True 来序列化您的 _key
字段。
使用你的例子:
from typing import Optional
from pydantic import BaseModel, Field, validator
class SomeClass(BaseModel):
spam: str
eggs: Optional[str] = Field(alias="_eggs")
@validator("eggs", always=True)
def set_eggs(cls, v, values, **kwargs):
"""Set the eggs field based upon a spam value."""
return v or values.get("spam") + " bacon"
a = SomeClass(spam="I like")
my_dictionary = a.dict(by_alias=True)
print(my_dictionary)
> {'spam': 'I like', '_eggs': 'I like bacon'}
print(my_dictionary.get("_eggs"))
> "I like bacon"
因此,要序列化您的 _eggs
字段,您可以在其中插入序列化函数,而不是附加字符串,然后 return 它的输出。