keleshev / schema

Schema validation just got Pythonic
MIT License
2.86k stars 214 forks source link

Unexpected behavior: Can not double validate keys #289

Open MrChadMWood opened 1 year ago

MrChadMWood commented 1 year ago
from schema import Schema, And, Or, Use, Optional, SchemaError, Forbidden

x = Schema({
    Or('request', 'requests', only_one=True) : dict,
    Optional('requests'): {Use(int): dict}
})

x.validate({
    'requests': {1:{}}
})

The expected result here is that a user can supply a dict using the key 'request' or 'requests'. If 'requests' is used, then the nested dict keys should be numbered. I assumed that the dict would validate Or('request', 'requests', only_one=True) : dict, and then see the optional key 'requests' was supplied. It should also validate Optional('requests'): {Use(int): dict}. But the output seems to only allow one validation per key,

See output:

---------------------------------------------------------------------------
SchemaMissingKeyError                     Traceback (most recent call last)
Input In [298], in <cell line: 1>()
----> 1 x.validate({
      2     'requests': {1:{}}
      3 })

File ~\AppData\Local\Programs\Python\Python39\lib\site-packages\schema.py:420, in Schema.validate(self, data, **kwargs)
    418     message = "Missing key%s: %s" % (_plural_s(missing_keys), s_missing_keys)
    419     message = self._prepend_schema_name(message)
--> 420     raise SchemaMissingKeyError(message, e.format(data) if e else None)
    421 if not self._ignore_extra_keys and (len(new) != len(data)):
    422     wrong_keys = set(data.keys()) - set(new.keys())

SchemaMissingKeyError: Missing key: Or('request', 'requests')
MrChadMWood commented 1 year ago

Perhaps my expectation is impossible because of the need to support other functionalities. May I suggest adding some kind of "If Then" functionality?

Like this:

x = Schema({
    Or('request', 'requests', only_one=True) : dict,
    If('requests'): {Use(int): dict}
})

In this example, If would inherit the Optional() property of not being required. Though, maybe it's also worth enforcing that any If keys supplied should correspond with one of the keys supplied to an Or(). This way, users cant rely on If() instead of Optional(), thereby preserving consistency.

MrChadMWood commented 1 year ago

One potential workaround:

x = Schema({
    Optional(Or('request', 'requests', only_one=True)) : dict,
    Optional('requests'): {Use(int): dict}
})

Though, this will validate an empty dict. Perhaps this is still the best solution, as it shouldn't be too hard to include a

# Untested
if len(input_data) == 0:
    raise SchemaError('blah blah blah')

...or something of the sort.

Edit: Damn it... nevermind.

x = Schema({
    Optional(Or('request', 'requests', only_one=True)) : dict,
    Optional('requests'): {Use(int): dict}
})

x.validate({'request':{},'requests':{1:{}}})

# {'request': {}, 'requests': {1: {}}}
MrChadMWood commented 1 year ago

Well, I would have though this would work too. But no.

x = Schema({
    Optional(Or('request', 'requests', only_one=True)) : dict,
    Optional('requests'): {Use(int): dict},
    Forbidden(And('request', 'requests')): object
})

x.validate({'request':{},'requests':{1:{}}})

# {'request': {}, 'requests': {1: {}}}