Skip to content

Commit

Permalink
Always evaluate {} as {}
Browse files Browse the repository at this point in the history
If you want a dict with any content, use 'dict', not
Schema({}, extra=ALLOW_EXTRA).
  • Loading branch information
Tuukka Mustonen committed Sep 27, 2016
1 parent 79d7ed8 commit 55fe26d
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 3 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,28 @@ token `extra` as a key:

```

However, an empty dict (`{}`) is treated as is. If you want to specify a list that can
contain anything, specify it as `dict`:

```pycon
>>> schema = Schema({}, extra=ALLOW_EXTRA) # don't do this
>>> try:
... schema({'extra': 1})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "not a valid value"
True
>>> schema({})
{}
>>> schema = Schema(dict) # do this instead
>>> schema({})
{}
>>> schema({'extra': 1})
{'extra': 1}

```

#### Required dictionary keys

By default, keys in the schema are not required to be in the data:
Expand Down
12 changes: 9 additions & 3 deletions voluptuous/schema_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def _compile(self, schema):
return lambda _, v: v
if isinstance(schema, Object):
return self._compile_object(schema)
if isinstance(schema, collections.Mapping):
if isinstance(schema, collections.Mapping) and len(schema):
return self._compile_dict(schema)
elif isinstance(schema, list) and len(schema):
return self._compile_list(schema)
Expand Down Expand Up @@ -366,7 +366,7 @@ def _compile_dict(self, schema):
A dictionary schema will only validate a dictionary:
>>> validate = Schema({})
>>> validate = Schema({'prop': str})
>>> with raises(er.MultipleInvalid, 'expected a dictionary'):
... validate([])
Expand All @@ -381,7 +381,6 @@ def _compile_dict(self, schema):
>>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['two']"):
... validate({'two': 'three'})
Validation function, in this case the "int" type:
>>> validate = Schema({'one': 'two', 'three': 'four', int: str})
Expand All @@ -391,10 +390,17 @@ def _compile_dict(self, schema):
>>> validate({10: 'twenty'})
{10: 'twenty'}
An empty dictionary is matched as value:
>>> validate = Schema({})
>>> with raises(er.MultipleInvalid, 'not a valid value'):
... validate([])
By default, a "type" in the schema (in this case "int") will be used
purely to validate that the corresponding value is of that type. It
will not Coerce the value:
>>> validate = Schema({'one': 'two', 'three': 'four', int: str})
>>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['10']"):
... validate({'10': 'twenty'})
Expand Down
34 changes: 34 additions & 0 deletions voluptuous/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,40 @@ def test_empty_list_as_exact():
s([])


def test_empty_dict_as_exact():
# {} always evaluates as {}
s = Schema({})
assert_raises(Invalid, s, {'extra': 1})
s = Schema({}, extra=ALLOW_EXTRA) # this should not be used
assert_raises(Invalid, s, {'extra': 1})

# {...} evaluates as Schema({...})
s = Schema({'foo': int})
assert_raises(Invalid, s, {'foo': 1, 'extra': 1})
s = Schema({'foo': int}, extra=ALLOW_EXTRA)
s({'foo': 1, 'extra': 1})

# dict matches {} or {...}
s = Schema(dict)
s({'extra': 1})
s({})
s = Schema(dict, extra=PREVENT_EXTRA)
s({'extra': 1})
s({})

# nested {} evaluate as {}
s = Schema({
'inner': {}
}, extra=ALLOW_EXTRA)
assert_raises(Invalid, s, {'inner': {'extra': 1}})
s({})
s = Schema({
'inner': Schema({}, extra=ALLOW_EXTRA)
})
assert_raises(Invalid, s, {'inner': {'extra': 1}})
s({})


def test_schema_decorator_match_with_args():
@validate(int)
def fn(arg):
Expand Down

0 comments on commit 55fe26d

Please sign in to comment.