Простая библиотека проверки python, которая сообщает обо всех ошибках проверки вместо первого отказа?

Я пробовал voluptuous и schema, оба из которых простые и вежливые в проверке, но они оба делают отчет об ошибках на основе исключений, т.е. не работают при первой ошибке. Есть ли способ получить все ошибки проверки данных в "Чувствительности" или "Схема"?

Я нашел jsonschema, который, похоже, соответствует некоторым требованиям, но не имеет проверки для ключей объектов и проверки на основе специальных функций ( например, лямбда).

Требование:

def myMethod(input_dict):

   #input_dict should abide to this schema -> 
   # { 'id' : INT , 'name':'string 5-10 chars','hobbies': LIST OF STRINGS }
   # for incorrect input like
   # {'id': 'hello','name':'Dhruv','hobbies':[1,2,3] }
   # I should be able to return all errors like
   # ['id' is not integer,'hobbies' is not list of strings ]

Ответ 1

Я раньше использовал jsonschema, и он точно способен делать то, что вы хотите. Он также делает отчет об ошибках на основе исключений, если вы хотите, но вы также можете перебирать все ошибки проверки, найденные в документе, я написал краткую примерную программу, которая использует вашу схему (см. Json Schema V3 Spec) и распечатывает все найденные ошибки.

Изменить: я изменил script, так что теперь он использует специальный валидатор, который позволяет вам сворачивать свою собственную проверку, код должен быть понятным. Вы можете найти источник jsonschema для информации на extend и о том, как валидаторы расширены/закодированы.

#!/usr/bin/env python2

from jsonschema import Draft3Validator
from jsonschema.exceptions import ValidationError
from jsonschema.validators import extend
import json
import sys

schema = {
    "type": "object",
    "required": True,
    "additinalProperties": False,
    "properties": {
        "id": {
            "type": "integer",
            "required": True
        },
        "name": {
            "type": "string",
            "required": True,
            "minLength": 5,
            "maxLength": 10
        },
        "hobbies": {
            "type": "array",
            "customvalidator": "hobbies",
            "required": True,
            "items": {
                "type": "string"
            }
        }
    }
}


def hobbiesValidator(validator, value, instance, schema):
    if 'Foo' not in instance:
        yield ValidationError("You need to like Foo")

    for field in instance:
        if not validator.is_type(instance, "string"):
            yield ValidationError("A hobby needs to be a string")
        elif len(field) < 5:
            err = "I like only hobbies which are len() >= 5, {} doesn't"
            yield ValidationError(err.format(value))


def anotherHobbiesValidator(validator, value, instance, schema):
    pass


myCustomValidators = {
    'hobbies': hobbiesValidator,
    'anotherHobbies': anotherHobbiesValidator
}


def customValidatorDispatch(validator, value, instance, schema):
    if value not in myCustomValidators:
        err = '{} is unknown, we only know about: {}'
        yield ValidationError(err.format(value, ', '.join(myCustomValidators.keys())))
    else:
        errors = myCustomValidators[value](validator, value, instance, schema)
        for error in errors:
            yield error


def myMethod(input_dict):
    customValidator = extend(Draft3Validator, {'customvalidator': customValidatorDispatch}, 'MySchema')
    validator = customValidator(schema)

    errors = [e for e in validator.iter_errors(input_dict)]
    if len(errors):
        return errors

    # do further processing here
    return []

if __name__ == '__main__':
    data = None
    try:
        f = open(sys.argv[1], 'r')
        data = json.loads(f.read())
    except Exception, e:
        print "Failed to parse input: {}".format(e)
        sys.exit(-1)

    errors = myMethod(data)

    if not len(errors):
        print "Input is valid!"
    else:
        print "Input is not valid, errors:"
        for error in errors:
            print "Err: ", error

Неверный ввод:

$ cat invalid-input.json
{
    "id": "hello",
    "name": "Dhruv",
    "hobbies": [
        1, 2, 3
    ]
}

$ ./validate.py invalid-input.json
Input is not valid, errors:
Err:  1 is not of type 'string'

Failed validating 'type' in schema['properties']['hobbies']['items']:
    {'type': 'string'}

On instance['hobbies'][0]:
    1
Err:  2 is not of type 'string'

Failed validating 'type' in schema['properties']['hobbies']['items']:
    {'type': 'string'}

On instance['hobbies'][1]:
    2
Err:  3 is not of type 'string'

Failed validating 'type' in schema['properties']['hobbies']['items']:
    {'type': 'string'}

On instance['hobbies'][2]:
    3
Err:  You need to like Foo

Failed validating 'customvalidator' in schema['properties']['hobbies']:
    {'customvalidator': 'hobbies',
     'items': {'type': 'string'},
     'required': True,
     'type': 'array'}

On instance['hobbies']:
    [1, 2, 3]
Err:  A hobby needs to be a string

Failed validating 'customvalidator' in schema['properties']['hobbies']:
    {'customvalidator': 'hobbies',
     'items': {'type': 'string'},
     'required': True,
     'type': 'array'}

On instance['hobbies']:
     [1, 2, 3]
Err:  A hobby needs to be a string

Failed validating 'customvalidator' in schema['properties']['hobbies']:
    {'customvalidator': 'hobbies',
     'items': {'type': 'string'},
     'required': True,
     'type': 'array'}

On instance['hobbies']:
    [1, 2, 3]
Err:  A hobby needs to be a string

Failed validating 'customvalidator' in schema['properties']['hobbies']:
    {'customvalidator': 'hobbies',
     'items': {'type': 'string'},
     'required': True,
     'type': 'array'}

On instance['hobbies']:
    [1, 2, 3]
Err:  u'hello' is not of type 'integer'

Failed validating 'type' in schema['properties']['id']:
    {'required': True, 'type': 'integer'}

On instance['id']:
    u'hello'

Я думаю, что ваши требования теперь заполнены этим script.

Ответ 2

Фактически Voluptuous предоставляет эту функциональность, хотя это и не очевидно в документах, это просто вызов MultipleInvalid.errors. Функция возвращает список исключенных Invalid исключений из ваших валидаторов.

например.

try:

    schema({...})

except MultipleInvalid as e:
    # get the list of all `Invalid` exceptions caught
    print e.errors