helplessKirk
helplessKirk

Reputation: 326

Cerberus - Field required only when dependency is met

Consider the following schema

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

I want the units field to be required only when the value of value_type field is either float or integer.

Here's the behaviour I aim to achieve

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

The above Schema returns the expected result only for the first 3 cases.

If I change the definition of units (by omitting the "required": True) to

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

then the validation of

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

returns True which is not what I want.

I took a look at oneof rules in the documentation but couldn't find a way to apply this only to the required property.

I want the value of required to be True only when the dependency is met.

How should I modify my schema to achieve this?

Upvotes: 4

Views: 1407

Answers (1)

funky-future
funky-future

Reputation: 3968

As your variations span more than one field, the *of rules aren't exactly suited, especially since these seem to be top-level fields in the document.

I would generally advise that there's still Python and not everything must be expressed with a schema, so you can simply define two valid schemas and test against these:

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

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

This is also better comprehendable than any schema you would end up with.

Alternatively, you could use the check_with rule. The example shows two different ways to submit an error, where the latter one is prefable when the errors are only presented to humans, as they allow custom messages for different situations while lacking structural information about the error:

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"})

Upvotes: 3

Related Questions