从字典生成 pydantic 模型

Generate pydantic model from a dict

是否有直接的方法从字典生成 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

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

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

您可以使用 MyModel.parse_obj(my_dict) 从字典生成模型。根据 documentation

this is very similar to the __init__ method of the model, except it takes a dict rather than keyword arguments.

我使用此方法在 运行 时间使用字典定义生成模型。这种方法也允许您定义嵌套模型。字段类型语法借鉴了 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)

虽然我喜欢@data_wiz 字典定义,但这是一个替代建议,它基于我的需要,可以即时采取简单的 JSON 响应,这些响应通常是 CamelCase 关键元素并能够处理这变成了 pythonic 风格 class.

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

以下代码片段可以从来自 JSON API 响应的实际数据字典生成模型,因为键是驼峰式,它将把它们转换为 pythonic snake 风格,但保留驼峰式作为别名.

这种花哨的别名可以轻松使用 JSON 转换为 Dict 而无需密钥转换,还可以直接导出 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

加法,可以用__init方法,

your_mode = YourMode(**your_dict)

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

生成一个 pydantic 模型

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

代码主要借自@data_wiz

辅助函数

from pydantic import create_model
# 
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()

与原始答案相比的优势:

  • 扩展默认值(对于嵌套结构)
  • 更简单的类型声明