【问题标题】:type-hinting attributes of one class from attribute type of another class一个类的类型提示属性来自另一个类的属性类型
【发布时间】:2022-12-19 11:33:56
【问题描述】:

我有两个相关的 pydantic 模型类:

class TargetBase(BaseModel):
    url: str = Field(
        default="google.com", 
        title="Target URL",
        )
    port: int = Field(
        default=443, 
        gt=0,
        title="Target URL port number",
        )

这两个属性都是必需的。

下一个模型指定一个 PATCH-structure,它应该接受带有任何具有 null 或 None 值的属性的请求,例如:

class TargetPatch(BaseModel):
    url: str | None = "google.com"
    port: int | None = 443

我不希望为TargetPatch-class 中的所有属性重新定义Field() 值,因为这可能导致Field() 规范在以后的修改中相互转移。

我试过类似的东西:

class TargetPatch(BaseModel):
    url: str | None = TargetBase.url
    port: int | None = TargetBase.port

但是 python 告诉我 TargetBase 没有 url 属性。

我不得不为 TargetPatch 类属性重新定义 Field()-值:

class TargetPatch(BaseModel):
    url: str | None = Field(        # None is the extra requirement in this class
        default=None, # "google.com",  [Edited]
        title="Target URL",
        )
    port: int | None = Field(       # None is the extra requirement in this class
        default=None, # 443,  [Edited]
        gt=0,
        title="Target URL port number",
        )

我怎么能够同步Field()-两个类属性的值并使TargetPatch-类属性接受None值?

我可以弄清楚,这个问题需要从另一个类访问类属性的默认值。可能吗?

【问题讨论】:

  • 如果在 PATCH 请求中遗漏了 PATCH 请求中的默认值,是否意味着它们将获得默认值?所以如果你只想更新port,你突然得到google.com作为url的默认值? (不确定这是否是实际行为,因为这取决于您的观点,但如果明确给出 None 可能会更好?(然后仅在从请求中提取定义的字段时获取它们)。所以 - 虽然实际上不是回答你的问题;补丁请求的默认值似乎有点不合常规?
  • 感谢您指出,我已经更正了我的帖子。

标签: python python-3.x fastapi pydantic


【解决方案1】:

Field() 似乎创建了 pydantic.fields.FieldInfo 对象。我认为您可以使用copy.deepcopy() 创建深层副本并仅更新副本中的必要属性。如果你想访问一个字段的FieldInfo,你可以使用__fields__[<name>].field_info

检查以下示例。我使用了 python 3.9.7(这是我手边的那个,也是我使用 typing.Optional 的原因)和 pydantic 1.10.2

import copy
from typing import Any, Optional

from pydantic import BaseModel, Field
from pydantic.fields import FieldInfo


class TargetBase(BaseModel):
    url: str = Field(default="google.com", title="Target URL")
    port: int = Field(default=443, gt=0, title="Target URL port number")


def _copy_field_info(field_info: FieldInfo, **kwargs: Any) -> FieldInfo:
    print("FieldInfo to copy:", id(field_info), repr(field_info))
    field_info_copy = copy.deepcopy(field_info)
    for name, value in kwargs.items():
        setattr(field_info_copy, name, value)
    field_info_copy._validate()  # Field() does this internally
    print("Copied FieldInfo:", id(field_info_copy), repr(field_info_copy))
    return field_info_copy


class TargetPatch(BaseModel):
    url: Optional[str] = _copy_field_info(
        TargetBase.__fields__["url"].field_info,
        default=None
    )
    port: Optional[int] = _copy_field_info(
        TargetBase.__fields__["port"].field_info,
        default=None
    )


print(repr(TargetBase()))
print(repr(TargetPatch()))

输出:

FieldInfo to copy: 2265963690048 FieldInfo(default='google.com', title='Target URL', extra={})
Copied FieldInfo: 2265968680000 FieldInfo(title='Target URL', extra={})
FieldInfo to copy: 2265963689792 FieldInfo(default=443, title='Target URL port number', gt=0, extra={})
Copied FieldInfo: 2265968680256 FieldInfo(title='Target URL port number', gt=0, extra={})
TargetBase(url='google.com', port=443)
TargetPatch(url=None, port=None)

【讨论】:

    猜你喜欢
    • 2021-12-14
    • 1970-01-01
    • 2021-04-02
    • 2018-11-23
    • 1970-01-01
    • 2019-02-20
    • 2018-08-19
    相关资源
    最近更新 更多