1

我使用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.

有没有一种优雅的方式来实现这一目标?

4

2 回答 2

1

问题在于,在 的 API 边界上yaml.load,源的所有代表性信息都已丢失。Validate得到一个 Python dict 并且不知道它来自哪里,而且 dict 不包含此信息。

但是,您可以自己实现它。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 序列(我的代码假设每个中间节点都是 a MappingNode, aSequenceNode将在其列表中有单个节点value而不是键值元组)
  • MultipleInvalid为每个内部错误发出消息的句柄
  • expected int如果你真的想重写should be an integer(不知道你会怎么做)
  • 打印错误后中止
于 2022-02-07T14:55:18.220 回答
1

在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
于 2022-02-07T16:42:02.090 回答