Voluptuous : 在 yaml 文件中给出错误行
Voluptuous : give error line in yaml file
我经常使用 voluptuous
来验证 yaml 描述文件。通常这些错误很难破译,尤其是对于普通用户。
我正在寻找一种使错误更易读的方法。一种方法是确定 YAML 文件中的哪一行是有罪的。
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)))
在上面的例子中,我得到这个错误:
MultipleInvalid: expected int for dictionary value @ data['age']
我更喜欢这样的错误:
Error: validation failed on line 2, data.age should be an integer.
有什么优雅的方法可以做到这一点吗?
问题是在 yaml.load
的 API 边界上,源的所有表示信息都已丢失。 Validate
得到了一个Python的字典,不知道它来自哪里,而且字典中也没有这个信息。
但是,您可以自己实施。 voluptuous' Invalid
错误带有一个 path
,这是要遵循的键列表。有了这个路径,您可以再次将 YAML 解析为节点(携带表示信息)并发现项目的位置:
import yaml
def line_from(path, yaml_input):
node = yaml.compose(yaml_input)
for item in path:
for entry in node.value:
if entry[0].value == item:
node = entry[1]
break
else: raise ValueError("unknown path element: " + item)
return node.start_mark.line
# demostrating this on more complex input than yours
data = """
spam:
egg:
sausage:
spam
"""
print(line_from(["spam", "egg", "sausage"], data))
# gives 4
有了这个,你就可以做
try:
data = Validate(yaml.load(StringIO(data)))
except Invalid as e:
line = line_from(e.path, data)
path = "data." + ".".join(e.path)
print(f"Error: validation failed on line {line} ({path}): {e.error_message}")
这个答案我会讲到这里,因为它向您展示了如何发现错误的来源。您可能需要将其扩展为:
- 处理 YAML 序列(我的代码假设每个中间节点都是
MappingNode
,SequenceNode
将在其 value
列表中有单个节点而不是 key-value元组)
- 处理
MultipleInvalid
为每个内部错误发出消息
- 如果你真的想重写
expected int
到 should be an integer
(不知道你会怎么做)
- 打印错误后中止
在 flyx 的帮助下,我发现 ruamel.yaml
提供了已解析 YAML 文件的行和列。因此,可以通过以下方式设法获得想要的错误:
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))
有了这个我得到了这个错误信息:
Error: validation failed on line 2:4 (/name): extra keys not allowed
我经常使用 voluptuous
来验证 yaml 描述文件。通常这些错误很难破译,尤其是对于普通用户。
我正在寻找一种使错误更易读的方法。一种方法是确定 YAML 文件中的哪一行是有罪的。
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)))
在上面的例子中,我得到这个错误:
MultipleInvalid: expected int for dictionary value @ data['age']
我更喜欢这样的错误:
Error: validation failed on line 2, data.age should be an integer.
有什么优雅的方法可以做到这一点吗?
问题是在 yaml.load
的 API 边界上,源的所有表示信息都已丢失。 Validate
得到了一个Python的字典,不知道它来自哪里,而且字典中也没有这个信息。
但是,您可以自己实施。 voluptuous' Invalid
错误带有一个 path
,这是要遵循的键列表。有了这个路径,您可以再次将 YAML 解析为节点(携带表示信息)并发现项目的位置:
import yaml
def line_from(path, yaml_input):
node = yaml.compose(yaml_input)
for item in path:
for entry in node.value:
if entry[0].value == item:
node = entry[1]
break
else: raise ValueError("unknown path element: " + item)
return node.start_mark.line
# demostrating this on more complex input than yours
data = """
spam:
egg:
sausage:
spam
"""
print(line_from(["spam", "egg", "sausage"], data))
# gives 4
有了这个,你就可以做
try:
data = Validate(yaml.load(StringIO(data)))
except Invalid as e:
line = line_from(e.path, data)
path = "data." + ".".join(e.path)
print(f"Error: validation failed on line {line} ({path}): {e.error_message}")
这个答案我会讲到这里,因为它向您展示了如何发现错误的来源。您可能需要将其扩展为:
- 处理 YAML 序列(我的代码假设每个中间节点都是
MappingNode
,SequenceNode
将在其value
列表中有单个节点而不是 key-value元组) - 处理
MultipleInvalid
为每个内部错误发出消息 - 如果你真的想重写
expected int
到should be an integer
(不知道你会怎么做) - 打印错误后中止
在 flyx 的帮助下,我发现 ruamel.yaml
提供了已解析 YAML 文件的行和列。因此,可以通过以下方式设法获得想要的错误:
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))
有了这个我得到了这个错误信息:
Error: validation failed on line 2:4 (/name): extra keys not allowed