Search code examples
pythonvalidationyamlvoluptuous

Voluptuous : give error line in yaml file


I am using voluptuous a lot to validate yaml description files. Often the errors are cumbersome to decipher, especially for regular users.

I am looking for a way to make the error a bit more readable. One way is to identify which line in the YAML file is incrimined.

from voluptuous import Schema 
import yaml 
from io import StringIO

Validate = Schema({
    'name': str,
    'age': int,
})

data = """
name: John
age: oops
"""

data = Validate(yaml.load(StringIO(data)))

In the above example, I get this error:

MultipleInvalid: expected int for dictionary value @ data['age']

I would rather prefer an error like:

Error: validation failed on line 2, data.age should be an integer.

Is there an elegant way to achieve this?


Solution

  • With the help of flyx I found ruamel.yaml which provide the line and col of a parsed YAML file. So one can manage to get the wanted error with:

    from voluptuous import Schema 
    from ruamel.yaml import load, RoundTripLoader
    from io import StringIO
    
    Validate = Schema({
        'name': {
            'firstname': str,
            'lastname': str
        },
        'age': int,
    })
    
    data = """
    name: 
        firstname: John
        lastname: 12.0
    age: 42
    """
    
    class Validate:
        def __init__(self, stream):
            self._yaml = load(stream, Loader=RoundTripLoader)
            return self.validate()
    
        def validate(self):
            try:
                self.data = Criteria(self._yaml)
            except Invalid as e:
                node = self._yaml
                for key in e.path:
                    if (hasattr(node[key], '_yaml_line_col')):
                        node = node[key]
                    else:
                        break
                path = '/'.join(e.path)
                print(f"Error: validation failed on line {node._yaml_line_col.line}:{node._yaml_line_col.col} (/{path}): {e.error_message}")
            else:
                return self.data
            
    data = Validate(StringIO(data))
    

    With this I get this error message:

    Error: validation failed on line 2:4 (/name): extra keys not allowed