例如,我有一个Parent型号的玩具示例:

from pydantic import BaseModel, Extra


class Parent(BaseModel):
    class Config:
        extra = Extra.ignore
        validate_assignment = True
        schema_extra = {
            "version": "00.00.00",
            "info": "Parent description",
            "name": "Parent Name",
        }

这里的目标是所有子模型继承相同的模式,并可能向该模式添加其他内容.

class Child(Parent):
    class Config:
        extra = Extra.ignore
        validate_assignment = True

        @staticmethod
        def schema_extra(
            schema: dict[str, object], model: type["Child"]
        ) -> None:
            schema['info'] = 'Child Description'
            schema['name'] = 'Child Name'
            schema['additional stuff'] = 'Something else'

上面的代码不起作用,因为子类中的Config完全覆盖了从父类继承的Config,因此在子类的模式中我们缺少version.

正如前面提到的,我希望所有继承的类都具有相同的基本架构布局和一些元信息,并可能更改或添加值.这有可能吗?

Edit个 来自@Daniil-Fajnberg的解决方案效果很好,但有一个警告.例如,具有:

class Child(Parent):
    class Config:
        @staticmethod
        def schema_extra(schema: dict[str, object]) -> None:
            schema["info"] = "Child Description"
            schema["additional stuff"] = "Something else"
            schema["name"] = f"{schema.get('title')}v{schema.get('version')}"

模式中name的结果条目将是例如:

‘name’:"Nonev00.00.00"

仅供参考:在我的设置中,我也使用schema_extra作为父对象的静态方法

推荐答案

更正

首先,这一说法并不完全正确:

子类中的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个额外部分,也有自己的额外部分,因此它自己的额外部分优先.

Python相关问答推荐

socket.gaierror:[Errno -2]名称或服务未知|Firebase x Raspberry Pi

用Python获取HTML Span类中的数据

如何将Matplotlib的fig.add_axes本地坐标与我的坐标关联起来?

如何终止带有队列的Python进程?+ 队列大小的错误?

使用pandas、matplotlib和Yearbox绘制时显示错误的年份

三个给定的坐标可以是矩形的点吗

通过优化空间在Python中的饼图中添加标签

使用FASTCGI在IIS上运行Django频道

Gekko:Spring-Mass系统的参数识别

如何标记Spacy中不包含特定符号的单词?

修复mypy错误-赋值中的类型不兼容(表达式具有类型xxx,变量具有类型yyy)

在Python中动态计算范围

关于Python异步编程的问题和使用await/await def关键字

从spaCy的句子中提取日期

通过ManyToMany字段与Through在Django Admin中过滤

如何在Pyplot表中舍入值

Python全局变量递归得到不同的结果

合并与拼接并举

OpenCV轮廓.很难找到给定图像的所需轮廓

在matplotlib中使用不同大小的标记顶部添加批注