使用外部类或字典创建 pydantic.BaseModel 定义
Posted
技术标签:
【中文标题】使用外部类或字典创建 pydantic.BaseModel 定义【英文标题】:Create a pydantic.BaseModel definition with external class or dictionary 【发布时间】:2021-10-09 13:27:43 【问题描述】:我有一个简单类的(动态)定义,如下所示:
class Simple:
val: int = 1
我打算用这个定义来构建一个pydantic.BaseModel
,所以可以从Simple
类中定义;基本上是这样做的,但是通过type
,在一个元类结构下,Simple
类是从中检索的。
from pydantic import BaseModel
class SimpleModel(Simple, BaseModel):
pass
# Actual ways tried:
SimpleModel = type('SimpleModel', (Simple, BaseModel), )
# or
SimpleModel = type('SimpleModel', (BaseModel, ), Simple.__annotations__)
但是,这种方法不会返回带有 Simple
类参数的模型类。
我知道BaseModel
已经在后台使用了一个相当复杂的元类,但是,我的预期实现也在一个元类下,我打算动态将Simple
类转移到来自 pydantic 的 BaseModel
。
您的建议将不胜感激。
【问题讨论】:
【参考方案1】:我首先设法让这个工作,将我的 Simple
类转换为来自 pydantic 的数据类,然后从中获取一个 pydantic 模型。
我不是 pydantic 方面的专家,所以不会介意您对这种方法的看法。
from pydantic.dataclasses import dataclass
SimpleModel = dataclass(Simple).__pydantic_model__
但我确实发现了麻烦(与@jsbueno 提供的答案相同),当直接使用BaseModel
为pathlib.Path
(例如)声明数据类型的注释时,提供的字符串值被强制转换为注释数据类型。但使用我或@jsbueno 的方法,数据类型保持原始(无强制)。
【讨论】:
【参考方案2】:您可以简单地调用type
,传递一个由SimpleModel
的__dict__
属性组成的字典——其中将包含您的fileds 默认值和__annotations__
属性,这些信息足以让Pydantic 完成它的工作。
我只是采取额外的步骤,在删除之前在普通的“SimpleModel”中默认创建的__weakref__
属性 - 以避免它指向错误的类。
from pydantic import BaseModel
class Simple:
val: int = 1
new_namespace = dict(Simple.__dict__) # copies the class dictproxy into a plain dictionary
del new_namespace["__weakref__"]
SimpleModel = type("SimpleModel", (BaseModel,), new_namespace)
我们有
In [58]: SimpleModel.schema()
Out[58]:
'title': 'Simple',
'type': 'object',
'properties': 'one_val': 'title': 'One Val',
'default': 1,
'type': 'integer'
这行得通 - 但由于 Pydantic 很复杂,为了使其更具前瞻性,使用 Pydantic 的元类提供的命名空间对象而不是普通字典可能会更好 - 正式的方法是使用
types
模型中的辅助函数:
import types
from pydantic import BaseModel
class Simple:
val: int = 1
SimpleModel = types.new_class(
"SimpleModel",
(BaseModel,),
exec_body=lambda ns:ns.update(
key: val for key, val in Simple.__dict__.items()
if not key.startswith("_")
)
)
new_type
调用计算适当的元类,并将正确的命名空间对象传递给exec_body
参数中的回调。在那里,我们只是用你的动态类上的 dict 的内容来填充它。
在这里,我选择更新命名空间并在一行中过滤所有“_”值,但您可以将传递给“exec_body”的函数定义为完整的多行函数,并更仔细地过滤您想要的内容。
【讨论】:
成功了,谢谢!到达那里的方式相当漫长,但它确实奏效了。我不介意这很长或很混乱,因为它是在元类中执行的。但是,我确实想出了另一种方法来实现这一目标。我将在这里添加我自己的答案,希望您对此发表意见吗? 上面的代码几乎没有任何“混乱”——我什至陈述了一个简短的版本,只需要一个中间变量和一个额外的行来删除__weakref__
成员,这可能会导致奇怪和不可调试的副作用。长答案是一个普通的函数调用,而不是调用type
,使用类似的代码来避免不需要的元素被复制。但是,是的,因为 Pydantic 附带了一个数据类装饰器,所以应该使用它 - 你的答案更加“面向未来”。
非常感谢您的意见。我将很快在单元测试中广泛介绍我的实现,因此我会将您的实现作为一种选择,以防某些事情决定停止工作。以上是关于使用外部类或字典创建 pydantic.BaseModel 定义的主要内容,如果未能解决你的问题,请参考以下文章
使用点符号字符串“a.b.c.d.e”检查嵌套字典,自动创建缺失的级别