从字典生成pydantic模型

51

有没有一种简单的方法可以从字典生成一个 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
7个回答

92
你可以使用MyModel.parse_obj(my_dict)从字典生成一个模型。根据文档所述 – 这非常类似于模型的__init__方法,只是它接受字典而不是关键字参数。

1
那不是问题的关键。 - Alex K
1
那不是问题。 - undefined
1
@AlexK 这个答案被接受了,所以看起来是一个足够好的回答。 - Fynn
7
这在最近的版本中已被弃用。 建议使用MyModel.model_validate()代替。 - Andrey E
1
当你有嵌套类时,如何处理这个问题呢?model_validate()对我来说行不通! - undefined

44

你也可以使用它的__init__方法:

your_mode = YourMode(**your_dict)

这应该是被接受的答案。https://fastapi.tiangolo.com/tutorial/extra-models/#unwrapping-a-dict-and-extra-keywords 正在执行以下操作:UserInDB(**user_in.dict(), hashed_password=hashed_password)。他们所谓的“解包”字典,然后添加任何关键字参数。 - John
1
这种方法是可行的,但会使向后兼容变得更加困难。如果你在 JSON 中添加了一个新属性,但旧版本的模型尝试使用 **kwargs 加载它时,就会出现错误。如果你改用 Model.parse_obj,那么多余的参数将被简单地忽略掉。 - Snorfalorpagus
2
这是关于从零开始创建模型,而不是关于填充模型的内容。 - Abdur-Rahmaan Janhangeer

6

我使用这种方法通过一个字典定义在运行时生成模型。这种方法允许您定义嵌套的模型,字段类型的语法参考了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)


这是最好的答案,但我还添加了很多我定制的内容,请也看一下下面的答案。 - Farhan Hai Khan

6

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

或者您可以使用datamodel-code-generator(独立包)从模式定义中生成模型。


datamodel-codegen --input file.json --output model.py --input-file-type json数据模型代码生成器 --输入 file.json --输出 model.py --输入文件类型 json - Hans Daigle

4
如果你有一份示例 JSON 并且想要生成一个 pydantic 模型用于验证和使用,那么可以尝试使用这个网站 - https://jsontopydantic.com/ ,它可以从示例 JSON 中生成一个 pydantic 模型。

1

这是一个使用Python字典生成数据模型的定制代码。

代码主要借鉴自@data_wiz

辅助函数

from pydantic import create_model
# https://dev59.com/kVIG5IYBdhLWcg3w62QZ
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()

相较于原答案的优点:

  • 扩展默认值(适用于嵌套结构)
  • 更容易的类型声明

1
虽然我喜欢@data_wiz的词典定义,但以下是基于我的需求提出的另一种建议:能够快速处理常见的CamelCase键元素并将其转换为Python格式的类。JSON响应,并且能够以Pythonic样式处理它。
使用标准函数JSON可以轻松地转换为Dict,但是!我想以Pythonic样式进行操作。我还想能够具有一些类型覆盖,将字符串转换为Pythonic类型。我还希望指示一些可选元素。这就是我开始喜欢Pydantic的地方。
以下代码片段可以从JSON API响应的实际数据Dict生成模型,由于键是camelcase,因此它将将它们转换为Pythonic蛇形样式,但保留CamelCase作为别名。
这种pydantic别名使得可以轻松地消耗转换为Dict而不需要键转换的JSON,并直接导出JSON格式的输出。请注意动态模型DynamicModel.__config__.allow_population_by_field_name = True的配置,这允许从Alias或Pythonic字段名称创建dynamicModel。
此代码目前尚未完全功能齐全,无法处理列表,但对于简单情况,它已经很好地为我工作了。使用示例在pydanticModelGenerator的docstring中。
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接