Validation Errors

This is an exploration of the flatten_errors function given to check validation errors.

from configobj import ConfigObj, flatten_errors
from validate import Validator

Literal String

One of the things that I want to check is to see if the plugin was given the correct configuration.

configspec="""
[TEST]
value = LiteralString
""".splitlines()

config = """
[TEST]
value = NonLiteralString
""".splitlines()

config_spec = ConfigObj(configspec,
                        list_values=False,
                        _inspec=True)
configuration = ConfigObj(config, configspec=configspec)

validator = Validator()

outcome = configuration.validate(validator, preserve_errors=True)
print outcome
{'TEST': {'value': VdtUnknownCheckError('the check "LiteralString" is unknown.',)}}

So, it appears that you can’t just make up names in the configspec. What if we give it a single option? .. ‘

configspec="""
[TEST]
value = option('LiteralString')
""".splitlines()

config_spec = ConfigObj(configspec,
                        list_values=False,
                        _inspec=True)

configuration = ConfigObj(config, configspec=configspec)
outcome = configuration.validate(validator, preserve_errors=True)
print outcome
{'TEST': {'value': VdtValueError('the value "NonLiteralString" is unacceptable.',)}}

This seems to be what we want. But now how do you use flatten_errors? First, let’s see what outcome is when the value is correct. .. ‘

config = """
[TEST]
value = LiteralString
""".splitlines()

configuration = ConfigObj(config,
                          configspec=configspec)
outcome = configuration.validate(validator, preserve_errors=True)
print outcome
True

So, instead of a dictionary it returns True. Now let’s try and flatten the errors. .. ‘

def process_errors(config, outcome):
    """
    Uses `flatten_errors` to find bad values

    :param:

      - `config`: ConfigObj that created the outcome
      - `outcome`: returned value from config.validate
    """
    if outcome:
        for section_list, key, error in flatten_errors(config, outcome):
            if key is not None:
                print "The '{0}' key in section '{1}' failed validation (error='{2}')".format(key,
                                                                                ','.join(section_list),
                                                                                error)
            else:
                print "The '{0}' section was missing".format(','.join(section_list))
    return
config = """
[TEST]
value = BadValue
""".splitlines()

configuration = ConfigObj(config,
                          configspec=configspec)
outcome = configuration.validate(validator, preserve_errors=True)
process_errors(configuration, outcome)
The 'value' key in section 'TEST' failed validation (error='the value "BadValue" is unacceptable.')

Missing Value

Okay, so that was the bad-value. What about a missing value?

config_spec = """
[TEST]
option_1 = option('LiteralStringValue')
option_2 = integer
""".splitlines()
configspec = ConfigObj(config_spec,
                       list_values=False,
                       _inspec=True)

Now the configuration.

config = """
[TEST]
option_1 = LiteralStringValue
""".splitlines()

configuration = ConfigObj(config, configspec=configspec)
outcome = configuration.validate(validator, preserve_errors=True)
process_errors(configuration, outcome)
The 'option_2' key in section 'TEST' failed validation (error='False')

That didn’t work the way I thought it would. It looks like setting preserve_errors changes the behavior... I need to change process_errors. .. ‘

Process Errors 2

def process_errors_2(config, outcome):
    """
    Uses `flatten_errors` to find bad values

    :param:

      - `config`: ConfigObj that created the outcome
      - `outcome`: returned value from config.validate
    """
    if outcome:
        for sections, option, error in flatten_errors(config, outcome):
            section = ','.join(sections)
            if option is not None:

                if error:
                    print "Option '{0}' in section '{1}' failed validation (error='{2}')".format(option,
                                                                                                section,
                                                                                                error)
                else:
                    print "Option '{0}' in section '{1}' was missing.".format(option,
                                                                                section)
            else:
                print "The '{0}' section was missing".format(section)
    return

Now check again.

outcome = configuration.validate(validator, preserve_errors=True)
process_errors_2(configuration, outcome)
Option 'option_2' in section 'TEST' was missing.

All Bad Values

Now that we have a working process_errors_2 function, let’s see what happens if both the values are bad. .. ‘

config = """
[TEST]
option_1 = BadValue
option_2 = apple
""".splitlines()

configuration = ConfigObj(config,
                          configspec=configspec)
outcome = configuration.validate(validator, preserve_errors=True)
process_errors_2(configuration, outcome)
Option 'option_1' in section 'TEST' failed validation (error='the value "BadValue" is unacceptable.')
Option 'option_2' in section 'TEST' failed validation (error='the value "apple" is of the wrong type.')

Missing Section

Just for completeness, we’ll make sure that the process_errors_2 function handles missing sections correctly. .. ‘

config_spec = """
[TEST]
option_1 = integer

[[SUBTEST]]
option_2 = float
""".splitlines()

configspec = ConfigObj(config_spec,
                       list_values=False,
                       _inspec=True)
config = """
[TEST]
option_1 = 1
option_2 = 2
""".splitlines()

configuration = ConfigObj(config,
                          configspec=configspec)
outcome = configuration.validate(validator, preserve_errors=True)
process_errors_2(configuration, outcome)
The 'TEST,SUBTEST' section was missing

The output isn’t as intuitive as I would like, but I’m not sure it’s worth the effort to build up the brackets. .. ‘

Extras

Just to be safe, I’ll check to make sure that extra options don’t create errors.

config = """
[TEST]
option_1 = 2

[[SUBTEST]]
option_2 = 3.5
option_4 = 5
""".splitlines()
configuration = ConfigObj(config,
                          configspec=configspec)
outcome = configuration.validate(validator, preserve_errors=True)
process_errors_2(configuration, outcome)