【问题标题】:Python Dictionary Schema Validation with Dynamic "is in" Constraint具有动态“在”约束的 Python 字典模式验证
【发布时间】:2019-08-05 23:13:35
【问题描述】:

我正在寻找一种用于验证字典的解决方案,其中一个约束是 is in 约束,其中被认为有效的值来自正在验证的字典本身。

例如,想象下面的伪模式

{
    "notions" : [ string ],
    "category" : [ is in notions ]
}

为了完全清楚,我也口头表达了这个伪模式的约束。这些是我要验证的约束,是d要验证的字典:

  1. set(d.keys()) == {"notions", "categories"}
  2. isinstance(d["notions"], list)
  3. isinstance(notion, str) for notion in d["notions"]
  4. isinstance(d["category"], list)
  5. element is in d["notion"] for element in d["category"]

不要问,这个特定的数据结构是否有意义。它不是。我只是弥补,为我的问题创建一个最小的例子。我的实际字典模式要复杂得多,并且会对字典中的值进行多次引用。这就是为什么我想避免手动定义和验证约束,而是更喜欢基于模式的解决方案。

我查看了一些架构验证库,但我没有发现任何地方都包含此功能。是否有基于某些库的解决方案,也许有一些小的调整?我宁愿不要第二次发明轮子。

【问题讨论】:

  • 好吧,好吧,我们不需要谈论架构。但我不知道isinstance(notion, string) for notion in d["notions"](第 3 点)应该做什么。
  • @roganjosh 对不起,这应该是isinstance(notion, str) for notion in d["notions"],这应该意味着列表d["notions"] 的所有项目都是字符串。
  • 我有点希望typing module构建 字典时涵盖其中的一些内容,但不是全部。这是相当广泛的测试;我认为这是一个测试套件,而不是常规流程的一部分?
  • d.keys() == ["notions", "categories"] 可能会受到 Python element is in d["notion"] for element in d["category"] 只会不断增长。
  • @roganjosh:打字处理的是对象类型,而不是值。 str 是对象类型,all(v in d['notion'] for v in d['category']) 是值限制。

标签: python validation dictionary schema


【解决方案1】:

你的字典那么复杂,那么你做错了。考虑创建类并将该类的对象存储在字典中。这些类也可以包含其他类的其他对象。这样你就可以避免字典的嵌套。在类中创建函数以验证其数据。

【讨论】:

  • 字典嵌套?这是在哪里发生的?
  • “你做错了”是一个非常有力的声明,我认为它没有得到证实。 OP 只是进行了大量检查,以确保 dict 符合他们的预期。
  • 我的第一个方法实际上是创建一堆类来处理验证。完成后,我想:“一定有一个更简单的解决方案”(我的用例有 6 层嵌套。)我真的认为,为这个验证创建很多类不是很好的风格。每当架构发生变化,或者我想拥有其他架构时,我都需要编写一堆新类。这对我来说似乎不是很干燥。
  • 好吧,让我退后一步。在任何情况下,您都必须保留一些关于何时何地实施这些验证检查的文档。我仍然会在单独的文件上编写单独的类并记录它。至少这样我的代码更干净,更容易阅读。
  • 这是在验证从标准化的编程语言中性数据交换格式的键值对和值序列的反序列化时要解决的常见问题。像 JSON 或 YAML。您可以将这样的结构转换为实例,但您可能希望在执行此操作之前尽早验证数据。 OP 的要求是完全合理的。
【解决方案2】:

一般来说,模式验证器会尽量避免将数据拉入验证器。例如,JSON-schema standarddebating adding $data access in schemas,但还没有(还)实现这个想法(即使他们有 several use cases for it)。

一般的反对意见是,使验证模式依赖于它正在验证的数据,这使得保持验证上下文无关(这使得实现更容易,并使并行验证更容易)变得困难,并且它使得静态分析架构更难(因为架构在运行时随数据而变化)。

也就是说,Colander project 可以做你想做的事,因为它允许你在 Python 代码中简单地定义验证器。

例如:

import colander

class Foo(colander.MappingSchema):
    @colander.instantiate()
    class notions(colander.SequenceSchema):
        notion = colander.SchemaNode(colander.String())

    @colander.instantiate()
    class category(colander.SequenceSchema):
        cat = colander.SchemaNode(colander.String())

    def validator(self, node, cstruct):
        """Validate that all category values are listed in notions"""
        notions = set(cstruct['notions'])
        if not notions.issuperset(cstruct['category']):
            raise colander.Invalid(
                node['category'], 
                "All categories must be listed in notions"
            )

请注意,验证器是在定义 notionscategory 的级别定义的,因为验证器只能访问正在验证的数据的“本地”部分(所有子节点验证已经发生在)。如果您仅为category 定义了验证器,那么您将无法访问notions 列表,您可以指望notions 列表已经过验证。验证器引发Invalid 异常,第一个参数是category 模式节点,将责任直接归咎于该列表中的值。

Colander 架构在反序列化时进行验证;您可以将Schema.deserialize() 方法的输入视为未经验证的数据(滤锅序列化),并将输出视为应用程序就绪数据(appdata),经过验证和清理。这是因为如果漏掉,Colander 也会将默认值放在适当的位置,可以生成元组、集合、datetime 值等,并且还支持在使用模式处理数据时准备数据(清理 HTML 等)。

通过一些演示输入,上述模式验证并在成功时返回验证的结构:

>>> schema = Foo()
>>> schema.deserialize({'notions': [], 'category': []})
{'notions': [], 'category': []}
>>> schema.deserialize({'notions': ['foo', 'bar'], 'category': []})
{'notions': ['foo', 'bar'], 'category': []}
>>> schema.deserialize({'notions': ['foo', 'bar'], 'category': ['foo']})
{'notions': ['foo', 'bar'], 'category': ['foo']}
>>> schema.deserialize({'notions': ['foo', 'bar'], 'category': ['foo', 'spam']})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/.../site-packages/colander/__init__.py", line 2381, in deserialize
    self.validator(self, appstruct)
  File "<string>", line 17, in validator
colander.Invalid: {'category': 'All categories must be listed in notions'}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-06
    • 1970-01-01
    • 1970-01-01
    • 2014-12-19
    相关资源
    最近更新 更多