我有一个 pydantic 模型,我想动态排除其中的字段。
我可以通过覆盖dict
模型上的函数来做到这一点,以便它可以采用我的自定义标志,例如:
class MyModel(BaseModel):
field: str
def dict(self, **kwargs):
if ('exclude_special_fields' in kwargs):
super().dict(exclude={"field": True}, **kwargs)
super().dict(**kwargs)
Run Code Online (Sandbox Code Playgroud)
.dict
但是,如果我的模型是调用它的另一个模型的子模型,则这不起作用:
class AnotherModel(BaseModel):
models: List[MyModel]
AnotherModel(models=[...]).dict(exclude_special_fields=True) # does not work
Run Code Online (Sandbox Code Playgroud)
这是因为当MyModel.dict()
调用时,它不会使用与父级相同的参数来调用。
我dict
也可以在父模型上编写一个覆盖,以指定任何子组件的排除(例如exclude={"models": {"__all__": {"field": True}}}
),但在我的现实世界示例中,我有许多使用这个子模型的父模型,并且我不想让为每一个写一个覆盖。
无论如何,我可以确保子模型知道何时排除字段吗?
额外的上下文
额外的上下文对于问题来说并不完全重要,但我想要这样做的原因是排除模型上的某些字段(如果它从 API 调用返回)。
查看源代码后,我看不出有任何方法可以通过提供的专门 kwarg 轻松实现这一点。该dict
函数是递归的,不支持任意参数。
现在,我能够将一些东西组合在一起,但是……这太糟糕了。这是使用 执行的pydantic==1.10.7
。
我在想我们可以在排除参数中应用一个特殊的标志值来提供一些东西来触发排除逻辑。这变得比我预期的更棘手,因为它需要了解对象的完整结构以及准确地如何索引排除的字段。列表上还发生了一些奇怪的标准化,这导致提供的值在函数调用过程中发生变化。
这是我能得到的最好的结果(警告未经过彻底测试)。我们创建一个字典,它在每次查找时返回自身,并__all__
作为排除字段公开。这将允许我们的密钥传递给每个模型,并传递给子对象进行评估。
EXCLUDED_SPECIAL_FIELDS = "exclude_special_fields"
class _ExclusionDict(dict):
def __init__(self):
super().__init__({"__all__": {EXCLUDED_SPECIAL_FIELDS: True}})
def get(self, key):
return self
ExcludeSpecial = _ExclusionDict()
class SpecialExclusionBaseModel(BaseModel):
_special_exclusions: set[str]
def dict(self, **kwargs):
exclusions = getattr(self.__class__, "_special_exclusions", None)
exclude = kwargs.get("exclude")
if exclusions and exclude and EXCLUDED_SPECIAL_FIELDS in exclude:
return {
k: v
for k, v in super().dict(**kwargs).items()
if k not in exclusions
}
return super().dict(**kwargs)
Run Code Online (Sandbox Code Playgroud)
有了这个基类,我们可以提供一个类字段,称为来指示当实例作为 kw 参数提供_special_exclusions
时我们想要排除哪些字段。ExcludeSpecial
exclude
在一些初步测试中,这似乎适用于嵌套层次结构,包括字典和列表。这里可能存在需要解决的错误,但希望这对其他人来说是一个很好的起点。
class MyModel(SpecialExclusionBaseModel):
_special_exclusions = {"field"}
field: str
class AnotherModel(BaseModel):
models: list[MyModel]
class AnotherAnotherModel(BaseModel):
models: dict[str, AnotherModel]
model = MyModel(field=1)
another = AnotherAnotherModel(models={"test": AnotherModel(models=[model])})
print(another.dict(exclude=ExcludeSpecial))
Run Code Online (Sandbox Code Playgroud)
{'models': {'test': {'models': [{}]}}}
Run Code Online (Sandbox Code Playgroud)