16

是否有一种直接的方法可以从字典中生成 Pydantic 模型?

这是我拥有的数据示例。

{
    'id': '424c015f-7170-4ac5-8f59-096b83fe5f5806082020',
    'contacts': [{
        'displayName': 'Norma Fisher',
        'id': '544aa395-0e63-4f9a-8cd4-767b3040146d'
    }],
    'startTime': '2020-06-08T09:38:00+00:00'
}

期待一个类似于...的模型

class NewModel(BaseModel):
    id: str
    contacts: list
    startTime: str
4

7 回答 7

28

您可以使用MyModel.parse_obj(my_dict)从字典生成模型。根据文档——</p>

这与模型的方法非常相似__init__,除了它需要一个字典而不是关键字参数。

于 2020-10-23T19:00:41.640 回答
11

此外,您可以使用 __init 方法,

your_mode = YourMode(**your_dict)
于 2021-01-14T02:30:22.423 回答
5

没有确切的方法,但是create_model()如果您知道字段类型,则可以使用它来创建模型。

或者有datamodel-code-generator(单独的包),它允许您从模式定义生成模型。

于 2020-06-08T18:52:29.840 回答
4

我使用此方法在运行时使用字典定义生成模型。这种方法也允许您定义嵌套模型。字段类型语法借鉴了 create_model 方法。

from pydantic import create_model
m = {
    "a":(int,...),
    "b":{
        "c":(str,"hi"),
        "d":{
            "e":(bool,True),
            "f":(float,0.5)
        }
    }
}

def dict_model(name:str,dict_def:dict):
    fields = {}
    for field_name,value in dict_def.items():
        if isinstance(value,tuple):
            fields[field_name]=value
        elif isinstance(value,dict):
            fields[field_name]=(dict_model(f'{name}_{field_name}',value),...)
        else:
            raise ValueError(f"Field {field_name}:{value} has invalid syntax")
    return create_model(name,**fields)

model = dict_model("some_name",m)

于 2020-12-21T23:16:43.567 回答
2

如果您有一个示例 json 并且想要生成一个 pydantic 模型进行验证并使用它,那么您可以尝试这个网站 - https://jsontopydantic.com/ 它可以从示例 json 生成一个 pydantic 模型

于 2022-01-20T06:25:40.097 回答
0

虽然我喜欢@data_wiz 字典定义,但这里有一个替代建议,它基于我需要即时获取简单的 JSON 响应,这些响应通常是 CamelCase 关键元素,并且能够将其处理成 pythonic 样式的类。

但是,使用标准函数 JSON 可以轻松转换为 Dict!我想以 Python 风格来解决这个问题,我还希望能够有一些类型覆盖,将字符串转换为 Python 类型,我还想指出可选的元素。这就是我开始喜欢 Pydantic 的地方。

以下代码片段可以从 JSON API 响应的实际数据 Dict 生成模型,因为键是驼峰式,它会将它们转换为 pythonic 蛇样式,但保留驼峰式作为别名。

这种 pydantic 别名可以轻松使用转换为 Dict 的 JSON,而无需进行密钥转换,也可以直接导出 JSON 格式的输出。注意观察动态模型的配置,DynamicModel.__config__.allow_population_by_field_name = True这允许从别名或 Pythonic 字段名称创建动态模型。

此代码功能不完整,目前无法处理列表,但对于简单的情况,它对我来说效果很好。使用示例在 pydanticModelGenerator 的文档字符串中

from inflection import underscore
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field, create_model


class ModelDef(BaseModel):
    """Assistance Class for Pydantic Dynamic Model Generation"""

    field: str
    field_alias: str
    field_type: Any


class pydanticModelGenerator:
    """
    Takes source_data:Dict ( a single instance example of something like a JSON node) and self generates a pythonic data model with Alias to original source field names. This makes it easy to popuate or export to other systems yet handle the data in a pythonic way.
    Being a pydantic datamodel all the richness of pydantic data validation is available and these models can easily be used in FastAPI and or a ORM

    It does not process full JSON data structures but takes simple JSON document with basic elements

    Provide a model_name, an example of JSON data and a dict of type overrides

    Example:

    source_data = {'Name': '48 Rainbow Rd',
        'GroupAddressStyle': 'ThreeLevel',
        'LastModified': '2020-12-21T07:02:51.2400232Z',
        'ProjectStart': '2020-12-03T07:36:03.324856Z',
        'Comment': '',
        'CompletionStatus': 'Editing',
        'LastUsedPuid': '955',
        'Guid': '0c85957b-c2ae-4985-9752-b300ab385b36'}

    source_overrides = {'Guid':{'type':uuid.UUID},
            'LastModified':{'type':datetime },
            'ProjectStart':{'type':datetime },
            }
    source_optionals = {"Comment":True}

    #create Model
    model_Project=pydanticModelGenerator(
        model_name="Project",
        source_data=source_data,
        overrides=source_overrides,
        optionals=source_optionals).generate_model()

    #create instance using DynamicModel
    project_instance=model_Project(**project_info)

    """

    def __init__(
        self,
        model_name: str = None,
        source_data: str = None,
        overrides: Dict = {},
        optionals: Dict = {},
    ):
        def field_type_generator(k, overrides, optionals):
            pass
            field_type = str if not overrides.get(k) else overrides[k]["type"]
            return field_type if not optionals.get(k) else Optional[field_type]

        self._model_name = model_name
        self._json_data = source_data
        self._model_def = [
            ModelDef(
                field=underscore(k),
                field_alias=k,
                field_type=field_type_generator(k, overrides, optionals),
            )
            for k in source_data.keys()
        ]

    def generate_model(self):
        """
        Creates a pydantic BaseModel
        from the json and overrides provided at initialization
        """
        fields = {
            d.field: (d.field_type, Field(alias=d.field_alias)) for d in self._model_def
        }
        DynamicModel = create_model(self._model_name, **fields)
        DynamicModel.__config__.allow_population_by_field_name = True
        return DynamicModel
于 2021-01-06T09:31:02.750 回答
0

这是使用 python dicts 生成数据模型的自定义代码。

代码大部分是从@data_wiz 借来的

辅助函数

from pydantic import create_model
# https://stackoverflow.com/questions/62267544/generate-pydantic-model-from-a-dict
from copy import deepcopy

def get_default_values(input_schema_copy):
    """Get the default values from the structured schema dictionary. Recursive Traversal of the Schema is performed here.

    Args:
        input_schema_copy (dict): The input structured dictionary schema. Preferred deepcopy of the input schema to avoid inplace changes for the same.

    Returns:
        default_values (dict): The default values of the input schema.

    """    
    for k, v in input_schema_copy.items():
        if isinstance(v, dict):
            input_schema_copy[k] = get_default_values(v)
        else:
            input_schema_copy[k] = v[1]
    return input_schema_copy

def get_defaults(input_schema):
    """Wrapper around get_default_values to get the default values of the input schema using a deepcopy of the same to avoid arbitrary value changes.

    Args:
        input_schema (dict): The input structured dictionary schema.

    Returns:
        default_values (dict): The default values of the input schema.
    """    
    input_schema_copy = deepcopy(input_schema)
    return get_default_values(input_schema_copy)

def are_any_defaults_empty(default_values):
    """Check if any of the default values are empty (Ellipsis - ...)?

    Args:
        default_values (dict): The default values of the input schema.

    Returns:
        Bool: True if any of the default values are empty (Ellipsis - ...), False otherwise.
    """    
    for _, v in default_values.items():
        if isinstance(v, dict):
            are_any_defaults_empty(v)
        else:
            if v is Ellipsis: # ... symbol
                return True
    
    return False

def correct_schema_structure(input_schema_copy):
    for k, v in input_schema_copy.items():
        if isinstance(v, dict):
            input_schema_copy[k] = correct_schema_structure(v)
        elif type(v) == type:
            input_schema_copy[k] = (v,...)
        elif not hasattr(v, '__iter__') or isinstance(v, str):
            input_schema_copy[k] = (type(v),v)
    return input_schema_copy


def dict_model(dict_def:dict, name :str = "Demo_Pydantic_Nested_Model"):
    """Helper function to create the Pydantic Model from the dictionary.

    Args:
        name (str): The Model Name that you wish to give to the Pydantic Model.
        dict_def (dict): The Schema Definition using a Dictionary.

    Raises:
        ValueError: When the Schema Definition is not a Tuple/Dictionary.

    Returns:
        pydantic.Model: A Pydantic Model.
    """    
    fields = {}
    for field_name,value in dict_def.items():
        if isinstance(value,tuple):
            fields[field_name]=value
        elif isinstance(value,dict):
            # assign defaults to nested structures here (if present)
            default_value = get_defaults(value)
            default_value = Ellipsis if are_any_defaults_empty(default_value) else default_value
            fields[field_name]=(dict_model(value, f'{name}_{field_name}'),default_value)
        else:
            raise ValueError(f"Field {field_name}:{value} has invalid syntax")
    print(fields) # helpful for debugging
    return create_model(name,**fields)

架构校正

input_schema = {
    "a":(int,...),
    "b":{
        "c":(str,"hi"),
        "d":{
            "e":(bool,True),
            "f":(float,0.5)
        },
    },
    "g":"hello",
    "h" : 123,
    "i" : str,
    "k" : int
}

input_schema_corrected = correct_schema_structure(input_schema)
input_schema_corrected

输出 :

{'a': (int, Ellipsis),
 'b': {'c': (str, 'hi'), 'd': {'e': (bool, True), 'f': (float, 0.5)}},
 'g': (str, 'hello'),
 'h': (int, 123),
 'i': (str, Ellipsis),
 'k': (int, Ellipsis)}

实际模型创建

model = dict_model(dict_def= input_schema, name= "Demo_Pydantic_Nested_Model")

检查模型架构

model.schema()
{'title': 'Demo_Pydantic_Nested_Model',
 'type': 'object',
 'properties': {'a': {'title': 'A', 'type': 'integer'},
  'b': {'title': 'B',
   'default': {'c': 'hi', 'd': {'e': True, 'f': 0.5}},
   'allOf': [{'$ref': '#/definitions/Demo_Pydantic_Nested_Model_b'}]},
  'g': {'title': 'G', 'default': 'hello', 'type': 'string'},
  'h': {'title': 'H', 'default': 123, 'type': 'integer'},
  'i': {'title': 'I', 'type': 'string'},
  'k': {'title': 'K', 'type': 'integer'}},
 'required': ['a', 'i', 'k'],
 'definitions': {'Demo_Pydantic_Nested_Model_b_d': {'title': 'Demo_Pydantic_Nested_Model_b_d',
   'type': 'object',
   'properties': {'e': {'title': 'E', 'default': True, 'type': 'boolean'},
    'f': {'title': 'F', 'default': 0.5, 'type': 'number'}}},
  'Demo_Pydantic_Nested_Model_b': {'title': 'Demo_Pydantic_Nested_Model_b',
   'type': 'object',
   'properties': {'c': {'title': 'C', 'default': 'hi', 'type': 'string'},
    'd': {'title': 'D',
     'default': {'e': True, 'f': 0.5},
     'allOf': [{'$ref': '#/definitions/Demo_Pydantic_Nested_Model_b_d'}]}}}}}

测试数据验证

test_dict = { "a" : 0, "i" : "hello", "k" : 123}

model(**test_dict).dict()

与原始答案相比的优势:

  • 扩展默认值(用于嵌套结构)
  • 更简单的类型声明
于 2022-02-22T19:38:48.020 回答