¿Cómo validar la estructura (o esquema) del diccionario en Python?

Tengo un diccionario con información de configuración:

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

Quiero comprobar si el diccionario sigue la estructura que necesito.

Estoy buscando algo como esto:

 conf_structure = { 'version': int, 'info': { 'conf_one': float, 'conf_two': str, 'conf_three': bool } } is_ok = check_structure(conf_structure, my_conf) 

¿Hay alguna solución para este problema o alguna biblioteca que pueda facilitar la implementación de check_structure ?

Sin usar bibliotecas, también podría definir una función recursiva simple como esta:

 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 

Esto supone que la configuración puede tener claves que no están en su estructura, como en su ejemplo.


Actualización: la nueva versión también admite listas, por ejemplo, como 'foo': [{'bar': int}]

Puedes usar schema ( PyPi Link )

schema es una biblioteca para validar estructuras de datos de Python, como las que se obtienen de los archivos de configuración, formularios, servicios externos o análisis de línea de comandos, convertidos de JSON / YAML (o algo más) a los tipos de datos de 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)) 

@tobias_k me venció (probablemente tanto en tiempo como en calidad) pero aquí hay otra función recursiva para la tarea que podría ser un poco más fácil de seguir para ti (y para mí):

 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 

Puedes construir la estructura usando la recursividad:

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

Y luego compare la estructura requerida con su diccionario:

 get_type(current_conf) == get_type(required_conf) 

Ejemplo:

 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': "", 'conf_one': "", 'optional_conf': "", 'conf_three': ""}, 'version': ""} 

La naturaleza de los diccionarios, si se usan en Python y no se exportan como algunos JSON, es que no es necesario establecer el orden del diccionario. En su lugar, buscar claves devuelve valores (de ahí un diccionario).

En cualquier caso, estas funciones deben proporcionarle lo que está buscando para el nivel de anidamiento presente en las muestras que proporcionó.

 #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 

Esta solución obviamente tendría que cambiarse si el nivel de anidamiento fuera mayor (es decir, está configurado para evaluar la similitud en la estructura de los diccionarios que tienen algunos valores como diccionarios, pero no los diccionarios donde algunos de estos últimos diccionarios también son diccionarios).