4

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?

helplessKirk
  • 326
  • 1
  • 6
  • 16
  • Which version are you using? I can't replicate the behaviour of the fourth example with the current's `master` branch HEAD. I get this error: `{'units': ['required field']}`. – funky-future Feb 28 '19 at 22:40
  • @funky-future, version is 1.2. Apologies if it is confusing; The example shows the behaviour I want to achieve. I also get the same error when running this. – helplessKirk Mar 01 '19 at 08:15
  • so, what does "The above Schema returns the expected result only for the first 3 cases." mean? – funky-future Mar 01 '19 at 20:07
  • @funky-future, I mean that, the way that `schema` is defined, I get the results that I want (case 1: T, case 2: F, case 3: F) for the first three cases, but not for the fourth(in case 4, I want to get True but instead I get the error about the fields being required) – helplessKirk Mar 01 '19 at 23:17

1 Answers1

3

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"})
funky-future
  • 3,716
  • 1
  • 30
  • 43
  • Being able to express the relations without having to write "code" is kind of a requirement in my case, but I see your point about readability and comprehensibility. – helplessKirk Mar 04 '19 at 07:47
  • 1
    @helplessKirk Sure, there are often cases where everything should be lumped together in a schema. In this case I suggest to make use of the `check_with` rule, this way one can also use non-generic error messages, which are often more suited to point out what the problem is to users. – funky-future Mar 04 '19 at 16:47
  • Thanks for the suggestion for the `check_with` rule. Do you think you could add an example so I can accept the answer too? – helplessKirk Mar 07 '19 at 10:52
  • @helplessKirk i added that example. – funky-future Mar 30 '19 at 12:33
  • @funky-future, thanks! You have saved my day! Works like a charm. Was searching Cerberus docs for about 3 hours to find an answer for the same use case. – kapalkat Feb 10 '21 at 22:23