schema icon indicating copy to clipboard operation
schema copied to clipboard

Question - How do I define conditional rules?

Open fopguy41 opened this issue 2 years ago • 1 comments

Say I have a dictionary where the keys are conditional. How do I write a schema to check it?

For example, the following 2 are ok.

{
    'name': 'x',
    'x_val': 1
}

{
    'name': 'y',
    'y_val': 1
}

But the following 2 are not

{
    'name': 'x',
    'y_val': 1
}

{
    'name': 'y',
    'x_val': 1
}

The existence of what keys need to be in the dict is conditional on the value of name. I can't just say this is a dict with these keys. Name x has a certain set of keys (in this case, x_val), and name y has a different set of keys (y_val).

Of course, I can write my own lambda function that takes in such a dictionary and performs the checks. But I was wondering if there's some kind of out of the box solution for this type of validation.

thanks

fopguy41 avatar Sep 23 '22 00:09 fopguy41

I'm a new user, so there might be a neater way to take care of conditional rules, but here's an approach based on the Customized Validation section of the readme:

from schema import Schema, SchemaMissingKeyError, Or

class ConditionalSchema(Schema):
    def validate(self, data, _is_conditional_schema=True, **kwargs):
        data = super().validate(data, _is_conditional_schema=False, **kwargs)
        if _is_conditional_schema:
            if data["name"] == "x" and "x_val" not in data:
                raise SchemaMissingKeyError("x_val")
            elif data["name"] == "y" and "y_val" not in data:
                raise SchemaMissingKeyError("y_val")
        return data


sch = ConditionalSchema(
    {
        "name": Or("x", "y"),
        Or("y_val", "x_val", only_one=True): int,
    }
)
sch.validate({"name": "x", "x_val": 1})  # => {'name': 'x', 'x_val': 1}
sch.validate({"name": "y", "y_val": 1})  # => {'name': 'y', 'y_val': 1}
sch.validate({"name": "x", "y_val": 1})  # => schema.SchemaMissingKeyError: x_val
sch.validate({"name": "y", "x_val": 1})  # => schema.SchemaMissingKeyError: y_val

This approach is pretty flexible, but comes at the cost a level of indirection with respect to where the rules are defined.

Note that the passing through of kwargs is only necessary when nesting different Schema subclasses and could have been omitted from the above example.

Cheers!

LandingEllipse avatar Nov 24 '22 17:11 LandingEllipse