keleshev / schema

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

Formatting error when supplying raw dict as schema to `Or` #240

Closed sritchie closed 3 years ago

sritchie commented 4 years ago

I'm not sure if this is valid Schema usage, but I wanted to drop my reproduction here just in case.

I have a schema that looks like this:

  import schema as s

  test1 = s.Or(str, {
      s.Optional("gpu"): str,
      s.Optional("cpu"): str
  },
               error="Got '{}'")

  test1.validate({"cpu": "face", "gpu": "cake"})

Trying to run that code throws this error:

self = Schema(<class 'dict'>), data = {'cpu': 'face', 'gpu': 'cake'}

    def validate(self, data):
      Schema = self.__class__
      s = self._schema
>     e = self._error.format(data) if self._error else None
E     KeyError: "'cpu'"

env/lib/python3.6/site-packages/schema.py:372: KeyError

When I trace, it looks like what's happening is that self._error on the Or schema is getting set to the FILLED schema, on this line:

https://github.com/keleshev/schema/blob/master/schema.py#L345

ie, when I insert print(self._error) there, I see:

Got '{}'
Got '{'cpu': 'face', 'gpu': 'cake'}'

Which creates this case:

>>> "Got '{'cpu': 'face', 'gpu': 'cake'}'".format({'cpu': 'face', 'gpu': 'cake'})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: "'cpu'"

The issue's solved if I wrap the dictionary in s.Schema... but it might be worth having Or, And etc do the wrapping for us if it sees a raw dict to prevent this issue.

Hope this helps!

sritchie commented 4 years ago

Actually, it looks like the issue goes deeper. This occurs with any unwrapped data structure, like this:

AptPackages = s.Or(
    [str], s{
        s.Optional("gpu", default=list): [str],
        s.Optional("cpu", default=list): [str]
    },
    error=""""apt_packages" entry must be a dictionary or list, not '{}'""")

The error is triggered if any of the dicts or lists are unwrapped.

This succeeds:

AptPackages = s.Or(
    s.Schema([str]),
    s.Schema({
        s.Optional("gpu", default=list): s.Schema([str]),
        s.Optional("cpu", default=list): s.Schema([str])
    }),
    error=""""apt_packages" entry must be a dictionary or list, not '{}'""")