首页 > 解决方案 > Cerberus - 仅当满足依赖性时才需要字段

问题描述

考虑以下架构

schema = {
    "value_type":{
        "type": "string", "required": True
    }, 
    "units": {
        "type": "string", 
         "dependencies": {"value_type": ["float", "integer"]},
         "required": True
    }
}

我希望当字段的值为or时才units需要该字段。value_typefloatinteger

这是我想要实现的行为

v = Validator(schema)
v.validate({"value_type": "float", "units": "mm"})  # 1. 
True
v.validate({"value_type": "boolean", "units": "mm"})  # 2.
False
v.validate({"value_type": "float"})  # 3.
False
v.validate({"value_type": "boolean"})  # 4.
True

上述 Schema 仅返回前 3 种情况的预期结果。

如果我将units(通过省略"required": True)的定义更改为

"units": {"type": "string", "dependencies": {"value_type": ["float", "integer"]}}

然后验证

v.validate({"value_type": "float"})  # 3.
True

返回True这不是我想要的。

我查看了文档oneof中的规则,但找不到仅将其应用于属性的方法。required

我希望 required 的值True仅在满足依赖项时才存在。

我应该如何修改我的架构来实现这一点?

标签: pythoncerberus

解决方案


由于您的变体跨越多个字段,因此*of规则并不完全适合,尤其是因为这些似乎是文档中的顶级字段。

我通常会建议仍然有 Python 并且并非所有内容都必须用模式表示,因此您可以简单地定义两个有效模式并针对这些模式进行测试:

schema1 = {...}
schema2 = {...}

if not any(validator(document, schema=x) for x in (schema1, schema2)):
    boom()

这也比你最终得到的任何模式都更容易理解。

或者,您可以使用该check_with规则。该示例显示了提交错误的两种不同方式,其中后一种方式在错误仅呈现给人类时是可预置的,因为它们允许针对不同情况自定义消息,但缺少有关错误的结构信息:

class MyValidator(Validator):
    def _check_with_units_required(self, field, value):
        if value in ("float", "integer"):
            if "units" not in self.document:
                self._error("units", errors.REQUIRED_FIELD, "check_with")
        else:
            if "units" in self.document:
                self._error(
                    "units", "The 'units' field must not be provided for value "
                             "types other than float or integer."
                )

schema = {
    "value_type": {
        "check_with": "units_required",
        "required": True,
        "type": "string"
    },
    "units": {
        "type": "string",
    }
}

validator = MyValidator(schema)

assert validator({"value_type": "float", "units": "mm"})
assert not validator({"value_type": "boolean", "units": "mm"})
assert not validator({"value_type": "float"})
assert validator({"value_type": "boolean"})

推荐阅读