2

我想使用在我的架构中定义的默认值。我发现 python-jsonschema 常见问题解答中已经有一个示例:https ://python-jsonschema.readthedocs.io/en/stable/faq/

该示例扩展了property关键字的默认验证器并根据需要设置默认值。anyOf但是,一旦在同一架构中使用关键字,我就会遇到问题。

让我给你举个例子:

from jsonschema import Draft7Validator, validators


def extend_with_default(validator_class):
    validate_properties = validator_class.VALIDATORS["properties"]

    def set_defaults(validator, properties, instance, schema):
        for property, subschema in properties.items():
            if "default" in subschema:
                instance.setdefault(property, subschema["default"])

        for error in validate_properties(
            validator, properties, instance, schema,
        ):
            yield error

    return validators.extend(
        validator_class, {"properties": set_defaults},
    )


DefaultValidatingDraft7Validator = extend_with_default(Draft7Validator)

obj = {
    "my_list": [{"class_name": "some_class"}]
}
schema = {
    "properties": {
        "my_list": {
            "type": "array",
            "items": {
                "anyOf": [
                    {
                        "type": "object",
                        "properties": {
                            "class_name": {
                                "const": "some_class"
                            },
                            "some_property": {
                                "type": "number",
                                "default": 1
                            }
                        },
                        "required": ["class_name", "some_property"],
                        "additionalProperties": False
                    },
                    {
                        "type": "object",
                        "properties": {
                            "class_name": {
                                "const": "another_class"
                            },
                            "another_property": {
                                "type": "number",
                                "default": 1
                            }
                        },
                        "required": ["class_name", "another_property"],
                        "additionalProperties": False
                    }
                ]
            }
        }
    }
}

DefaultValidatingDraft7Validator(schema).validate(obj)
print(obj)

此示例实际上按预期工作。运行它提供以下输出:

{'my_list': [{'class_name': 'some_class', 'some_property': 1}]}

因此,该属性some_property已正确设置为默认值 1。但是,如果我们现在class_name将对象内的 更改为another_class,这适合anyOf列表中的第二个条目,我们会遇到以下问题:

obj = {
    "my_list": [{"class_name": "another_class"}]
}

=>

jsonschema.exceptions.ValidationError: {'class_name': 'another_class', 'some_property': 1, 'another_property': 1} is not valid under any of the given schemas

Failed validating 'anyOf' in schema['properties']['my_list']['items']:
    {
        "anyOf": [
            {
                "type": "object",
                "properties": {
                    "class_name": {
                        "const": "some_class"
                    },
                    "some_property": {
                        "type": "number",
                        "default": 1
                    }
                },
                "required": ["class_name", "some_property"],
                "additionalProperties": False
            },
            {
                "type": "object",
                "properties": {
                    "class_name": {
                        "const": "another_class"
                    },
                    "another_property": {
                        "type": "number",
                        "default": 1
                    }
                },
                "required": ["class_name", "another_property"],
                "additionalProperties": False
            }
        ]
    }

On instance['my_list'][0]:
    {'another_property': 1,
     'class_name': 'another_class',
     'some_property': 1}

在迭代anyOf列表时,给定的实例anyOf已经被第一个子模式更改。验证器调用每个子模式的anyOf所有相关验证器,结果第一个子模式的properties验证器将第一个子模式的默认值插入到当前实例中。当子模式的验证不成功时,也会发生这种情况anyOf。结果,不适合此示例的第一个子模式插入了属性'some_property': 1

obj = {'class_name': 'another_class', 'some_property': 1, 'another_property': 1}

所以现在anyOf到了通常适合对象的第二个子模式,另一个键已添加到实例中,导致验证失败以及additionalProperties不允许。结果,在其中没有找到有效的模式anyOf,我们得到了上述错误。

那么如何解决这个问题呢?我的方法是anyOf在迭代子模式列表时存储实例的值。如果子模式不匹配,则应还原此子模式所做的所有更改。不幸的是,直到现在,我都无法实现这种行为。

作为参考,这是我最近尝试的样子:

def extend_with_default(validator_class):
    validate_properties = validator_class.VALIDATORS["properties"]

    def set_defaults(validator, properties, instance, schema):
        for property, subschema in properties.items():
            if "default" in subschema:
                instance.setdefault(property, subschema["default"])

        for error in validate_properties(
            validator, properties, instance, schema,
        ):
            yield error

    def any_of(validator, subschemas, instance, schema):
        instance_copy = instance.copy()

        all_errors = []
        for index, subschema in enumerate(subschemas):
            errs = list(validator.descend(instance, subschema, schema_path=index))
            if not errs:
                break
            instance = instance_copy  # Make sure an instance that did not fit is not modified
            all_errors.extend(errs)
        else:
            yield ValidationError(
                "%r is not valid under any of the given schemas" % (instance,),
                context=all_errors,
            )

    return validators.extend(
        validator_class, {"properties": set_defaults, "anyOf": any_of},
    )

在内部这似乎有效,验证也有效。但由于某种原因,现在obj给出的内容{"my_list": [{"class_name": "another_class"}]}是:

{'my_list': [{'class_name': 'another_class', 'some_property': 1}]}

我不明白为什么。我猜字典在通过验证器时会发生变化,因为它们是可变的。因此,尝试重置实例可能不会在全局上下文中产生预期的效果。但是,我无法弄清楚如何解决这个问题。有人可以帮忙吗?

4

0 回答 0