更正
首先,这一说法并不完全正确:
子类中的Config
完全覆盖从父类继承的Config
Config
本身就是is inherited.但个人Config
属性are overridden.
示例:
from pydantic import BaseModel, Extra
class Parent(BaseModel):
class Config:
extra = Extra.allow
validate_assignment = True
class Child(Parent):
class Config:
extra = Extra.forbid
print(Child.__config__.extra) # Extra.forbid
print(Child.__config__.validate_assignment) # True
这就是说,你面临的问题是你的Child.Config
中的schema_extra
属性是are overriding.换句话说,Child.Config.schema_extra
does replace Parent.Config.schema_extra
.
如果我们希望使用子模型来创建配置extensible的这个特定属性,而不是让它们覆盖它,我们就必须有一点创造性.
更新的解决方案
我建议定义一个在整个应用程序中使用的定制BaseModel
,挂钩到它的__init_subclass__
方法,并应用一个定制函数来迭代地从所有祖先模型"继承"schema_extra
配置属性(反之,MRO).
为了确保将任何schema_extra
function调用推迟到实际构造模式字典,我们将需要确保每个子类都将其schema_extra
配置值as defined by the user保存在单独的配置属性中.这不同于我的初始解决方案(见下文),我的初始解决方案不是最优的,因为它在类创建期间立即调用函数,因此无法访问稍后才创建的一些模式属性/键.
这似乎奏效了:
from collections.abc import Iterable
from functools import partial
from inspect import signature
from typing import Any, ClassVar, Union
from pydantic import BaseModel as PydanticBaseModel
from pydantic.config import BaseConfig as PydanticBaseConfig, SchemaExtraCallable
class BaseConfig(PydanticBaseConfig):
own_schema_extra: Union[dict[str, Any], SchemaExtraCallable] = {}
class BaseModel(PydanticBaseModel):
__config__: ClassVar[type[BaseConfig]] = BaseConfig
@classmethod
def __init_subclass__(cls, **kwargs: object) -> None:
cls.__config__.own_schema_extra = cls.__config__.schema_extra
cls.__config__.schema_extra = partial(
inherited_schema_extra,
base_classes=(
base for base in reversed(cls.__mro__)
if issubclass(base, BaseModel)
),
)
def inherited_schema_extra(
schema: dict[str, Any],
model: type[BaseModel],
*,
base_classes: Iterable[type[BaseModel]],
) -> None:
for base in base_classes:
base_schema_extra = base.__config__.own_schema_extra
if callable(base_schema_extra):
if len(signature(base_schema_extra).parameters) == 1:
base_schema_extra(schema)
else:
base_schema_extra(schema, model)
else:
schema.update(base_schema_extra)
注意,与BaseConfig
注释有关的所有事务主要是为了提高类型安全性,并在使用新的own_schema_extra
配置属性之前正确定义它.严格来说,这样做并不是必要的,但它使这成为一种更干净的解决方案.
Usage:
class Parent1(BaseModel):
class Config:
schema_extra = {
"version": "1",
"info": "Parent1 description",
}
class Parent2(BaseModel):
class Config:
schema_extra = {
"version": "2",
"info": "Parent2 description",
}
class Child(Parent2, Parent1):
class Config:
@staticmethod
def schema_extra(schema: dict[str, Any]) -> None:
schema["info"] = "Child Description"
schema["additional stuff"] = "Something else"
schema["title+version"] = f'{schema["title"]}v{schema["version"]}'
print(Child.schema_json(indent=4))
Output:
{
"title": "Child",
"type": "object",
"properties": {},
"version": "2",
"info": "Child Description",
"additional stuff": "Something else",
"title+version": "Childv2"
}
正如您在这里看到的,模式的多重继承得到了正确的支持(因为我们遵循MRO来构造它),并且对schema_extra
个函数的延迟调用允许我们访问其中一个函数中的title
键以构造title+version
值.
初始解决方案(有关警告,请参阅操作/备注)
我建议连接到__init_subclass__
并应用一个定制函数,以迭代方式从所有父模型"继承"schema_extra
配置属性(反之为MRO):
from __future__ import annotations
from inspect import signature
from pydantic import BaseModel as PydanticBaseModel
def inherit_model_schema_extra(model: type[BaseModel]) -> None:
schema_extra: dict[str, object] = {}
for parent in reversed(model.__mro__):
if not issubclass(parent, BaseModel):
continue
parent_schema_extra = parent.__config__.schema_extra
if callable(parent_schema_extra):
if len(signature(parent_schema_extra).parameters) == 1:
parent_schema_extra(schema_extra)
else:
parent_schema_extra(schema_extra, model)
else:
schema_extra.update(parent_schema_extra)
model.__config__.schema_extra = schema_extra
class BaseModel(PydanticBaseModel):
@classmethod
def __init_subclass__(cls, **kwargs: object) -> None:
inherit_model_schema_extra(cls)
这里处理schema_extra
的方式(即,作为可调用的或作为字典)基本上复制了当前稳定版本中的model_process_schema
函数的处理方式.(见line 591及以下)
Usage:
class Parent(BaseModel):
class Config:
schema_extra = {
"version": "00.00.00",
"info": "Parent description",
}
class Child(Parent):
class Config:
@staticmethod
def schema_extra(schema: dict[str, object]) -> None:
schema["info"] = "Child Description"
schema["additional stuff"] = "Something else"
print(Child.schema_json(indent=4))
Output:
{
"title": "Child",
"type": "object",
"properties": {},
"version": "00.00.00",
"info": "Child Description",
"additional stuff": "Something else"
}
正如您所看到的,Child
模式既有Parent
个额外部分,也有自己的额外部分,因此它自己的额外部分优先.