如何在Python中验证字典的结构(或模式)?

时间:2017-08-22 08:17:40

标签: python validation dictionary schema config

我有一个包含配置信息的字典:

my_conf = {
    'version': 1,

    'info': {
        'conf_one': 2.5,
        'conf_two': 'foo',
        'conf_three': False,
        'optional_conf': 'bar'
    }
}

我想检查字典是否遵循我需要的结构。

我正在寻找类似的东西:

conf_structure = {
    'version': int,

    'info': {
        'conf_one': float,
        'conf_two': str,
        'conf_three': bool
    }
}

is_ok = check_structure(conf_structure, my_conf)

是否有解决此问题或任何可以更轻松实施check_structure的库的解决方案?

9 个答案:

答案 0 :(得分:25)

您可以使用schemaPyPi Link

  

schema 是一个用于验证Python数据结构的库,例如从配置文件,表单,外部服务或命令行解析获得的数据结构,从JSON / YAML(或其他)转换为Python数据类型。

from schema import Schema, And, Use, Optional, SchemaError

def check(conf_schema, conf):
    try:
        conf_schema.validate(conf)
        return True
    except SchemaError:
        return False

conf_schema = Schema({
    'version': And(Use(int)),
    'info': {
        'conf_one': And(Use(float)),
        'conf_two': And(Use(str)),
        'conf_three': And(Use(bool)),
        Optional('optional_conf'): And(Use(str))
    }
})

conf = {
    'version': 1,
    'info': {
        'conf_one': 2.5,
        'conf_two': 'foo',
        'conf_three': False,
        'optional_conf': 'bar'
    }
}

print(check(conf_schema, conf))

答案 1 :(得分:5)

不使用库,您还可以定义一个简单的递归函数:

def check_structure(struct, conf):
    if isinstance(struct, dict) and isinstance(conf, dict):
        # struct is a dict of types or other dicts
        return all(k in conf and check_structure(struct[k], conf[k]) for k in struct)
    if isinstance(struct, list) and isinstance(conf, list):
        # struct is list in the form [type or dict]
        return all(check_structure(struct[0], c) for c in conf)
    elif isinstance(struct, type):
        # struct is the type of conf
        return isinstance(conf, struct)
    else:
        # struct is neither a dict, nor list, not type
        return False

这假设配置可以包含不在您结构中的键,如您的示例所示。

更新:新版本还支持列表,例如比如'foo': [{'bar': int}]

答案 2 :(得分:0)

@tobias_k打败了它(可能在时间和质量上都有)但这里是另一个递归函数,对于你(和我)来说可能更容易理解的任务:

def check_dict(my_dict, check_against):
    for k, v in check_against.items():
        if isinstance(v, dict):
            return check_dict(my_dict[k], v)
        else:
            if not isinstance(my_dict[k], v):
                return False
    return True

答案 3 :(得分:0)

您可以使用递归构建结构:

def get_type(value):
    if isinstance(value, dict):
        return {key: get_type(value[key]) for key in value}
    else:
        return str(type(value))

然后将所需结构与您的字典进行比较:

get_type(current_conf) == get_type(required_conf)

示例:

required_conf = {
    'version': 1,
    'info': {
        'conf_one': 2.5,
        'conf_two': 'foo',
        'conf_three': False,
        'optional_conf': 'bar'
    }
}

get_type(required_conf)

{'info': {'conf_two': "<type 'str'>", 'conf_one': "<type 'float'>", 'optional_conf': "<type 'str'>", 'conf_three': "<type 'bool'>"}, 'version': "<type 'int'>"}

答案 4 :(得分:0)

字典的性质,如果它们在python中使用而不是作为某些JSON导出,则不需要设置字典的顺序。相反,查找键会返回值(因此是字典)。

在任何一种情况下,这些函数都应该为您提供您所寻找的样本中存在的嵌套级别的内容。

#assuming identical order of keys is required

def check_structure(conf_structure,my_conf):
    if my_conf.keys() != conf_structure.keys():
        return False

    for key in my_conf.keys():
        if type(my_conf[key]) == dict:
            if my_conf[key].keys() != conf_structure[key].keys():
                return False

    return True

#assuming identical order of keys is not required

def check_structure(conf_structure,my_conf):
    if sorted(my_conf.keys()) != sorted(conf_structure.keys()):
        return False

    for key in my_conf.keys():
        if type(my_conf[key]) != dict:
            return False
        else:
            if sorted(my_conf[key].keys()) != sorted(conf_structure[key].keys()):
                return False

    return True

如果嵌套级别更高(即,它被配置为评估具有某些值作为字典的字典结构的相似性,而不是字典,其中某些值,后面的字典也是字典),显然需要更改此解决方案)。

答案 5 :(得分:0)

看起来dict-schema-validator包确实满足您的需求:

这是代表客户的简单架构:

{
  "_id":          "ObjectId",
  "created":      "date",
  "is_active":    "bool",
  "fullname":     "string",
  "age":          ["int", "null"],
  "contact": {
    "phone":      "string",
    "email":      "string"
  },
  "cards": [{
    "type":       "string",
    "expires":    "date"
  }]
}

验证:

from datetime import datetime
import json
from dict_schema_validator import validator


with open('models/customer.json', 'r') as j:
    schema = json.loads(j.read())

customer = {
    "_id":          123,
    "created":      datetime.now(),
    "is_active":    True,
    "fullname":     "Jorge York",
    "age":          32,
    "contact": {
        "phone":    "559-940-1435",
        "email":    "york@example.com",
        "skype":    "j.york123"
    },
    "cards": [
        {"type": "visa", "expires": "12/2029"},
        {"type": "visa"},
    ]
}

errors = validator.validate(schema, customer)
for err in errors:
    print(err['msg'])

输出:

[*] "_id" has wrong type. Expected: "ObjectId", found: "int"
[+] Extra field: "contact.skype" having type: "str"
[*] "cards[0].expires" has wrong type. Expected: "date", found: "str"
[-] Missing field: "cards[1].expires"

答案 6 :(得分:0)

您还可以使用dataclasses_json库。这是我通常的做法

from dataclasses import dataclass
from dataclasses_json import dataclass_json, Undefined
from dataclasses_json.undefined import UndefinedParameterError
from typing import Optional


#### define schema #######
@dataclass_json(undefined=Undefined.RAISE)
@dataclass
class Info:
  conf_one: float
  # conf_two: str
  conf_three: bool
  optional_conf: Optional[str]

@dataclass_json
@dataclass
class ConfStructure:
  version: int
  info: Info

####### test for compliance####
try:
  ConfStructure.from_dict(my_conf).to_dict()
except KeyError as e:
  print('theres a missing parameter')
except UndefinedParameterError as e:
  print('extra parameters')


答案 7 :(得分:0)

您可以使用 https://pypi.org/project/dictify/ 中的 dictify

在此处阅读文档 https://dictify.readthedocs.io/en/latest/index.html

这是可以做到的。

from dictify import Field, Model

class Info(Model):
    conf_one = Field(required=True).instance(float)
    conf_two = Field(required=True).instance(str)
    conf_three = Field(required=True).instance(bool)
    optional_conf = Field().instance(str)

class MyConf(Model):
    version = Field(required=True).instance(int)
    info = Field().model(Info)

my_conf = MyConf() # Invalid without required fields

# Valid
my_conf = MyConf({
    'version': 1,
    'info': {
        'conf_one': 2.5,
        'conf_two': 'foo',
        'conf_three': False,
        'optional_conf': 'bar'
    }
})

my_conf['info']['conf_one'] = 'hi' # Invalid, won't be assinged

答案 8 :(得分:0)

对未来的建议:使用 Pydantic!

Pydantic 在运行时强制执行类型提示,并在数据无效时提供用户友好的错误。定义数据应该如何在纯粹的、规范的 python 中;用 pydantic 验证它,就这么简单:

from pydantic import BaseModel


class Info(BaseModel):
    conf_one: float
    conf_two: str
    conf_three: bool

    class Config:
        extra = 'forbid'


class ConfStructure(BaseModel):
    version: int
    info: Info

如果验证失败,pydantic 将引发错误并详细说明错误:

my_conf_wrong = {
    'version': 1,

    'info': {
        'conf_one': 2.5,
        'conf_two': 'foo',
        'conf_three': False,
        'optional_conf': 'bar'
    }
}

my_conf_right = {
    'version': 10,

    'info': {
        'conf_one': 14.5,
        'conf_two': 'something',
        'conf_three': False
    }
}

model = ConfStructure(**my_conf_right)
print(model.dict())
# {'version': 10, 'info': {'conf_one': 14.5, 'conf_two': 'something', 'conf_three': False}}

res = ConfStructure(**my_conf_wrong)
# pydantic.error_wrappers.ValidationError: 1 validation error for ConfStructure
#     info -> optional_conf
# extra fields not permitted (type=value_error.extra)