Skip to content

Commit 55fe26d

Browse files
author
Tuukka Mustonen
committed
Always evaluate {} as {}
If you want a dict with any content, use 'dict', not Schema({}, extra=ALLOW_EXTRA).
1 parent 79d7ed8 commit 55fe26d

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,28 @@ token `extra` as a key:
360360

361361
```
362362

363+
However, an empty dict (`{}`) is treated as is. If you want to specify a list that can
364+
contain anything, specify it as `dict`:
365+
366+
```pycon
367+
>>> schema = Schema({}, extra=ALLOW_EXTRA) # don't do this
368+
>>> try:
369+
... schema({'extra': 1})
370+
... raise AssertionError('MultipleInvalid not raised')
371+
... except MultipleInvalid as e:
372+
... exc = e
373+
>>> str(exc) == "not a valid value"
374+
True
375+
>>> schema({})
376+
{}
377+
>>> schema = Schema(dict) # do this instead
378+
>>> schema({})
379+
{}
380+
>>> schema({'extra': 1})
381+
{'extra': 1}
382+
383+
```
384+
363385
#### Required dictionary keys
364386

365387
By default, keys in the schema are not required to be in the data:

voluptuous/schema_builder.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def _compile(self, schema):
201201
return lambda _, v: v
202202
if isinstance(schema, Object):
203203
return self._compile_object(schema)
204-
if isinstance(schema, collections.Mapping):
204+
if isinstance(schema, collections.Mapping) and len(schema):
205205
return self._compile_dict(schema)
206206
elif isinstance(schema, list) and len(schema):
207207
return self._compile_list(schema)
@@ -366,7 +366,7 @@ def _compile_dict(self, schema):
366366
367367
A dictionary schema will only validate a dictionary:
368368
369-
>>> validate = Schema({})
369+
>>> validate = Schema({'prop': str})
370370
>>> with raises(er.MultipleInvalid, 'expected a dictionary'):
371371
... validate([])
372372
@@ -381,7 +381,6 @@ def _compile_dict(self, schema):
381381
>>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['two']"):
382382
... validate({'two': 'three'})
383383
384-
385384
Validation function, in this case the "int" type:
386385
387386
>>> validate = Schema({'one': 'two', 'three': 'four', int: str})
@@ -391,10 +390,17 @@ def _compile_dict(self, schema):
391390
>>> validate({10: 'twenty'})
392391
{10: 'twenty'}
393392
393+
An empty dictionary is matched as value:
394+
395+
>>> validate = Schema({})
396+
>>> with raises(er.MultipleInvalid, 'not a valid value'):
397+
... validate([])
398+
394399
By default, a "type" in the schema (in this case "int") will be used
395400
purely to validate that the corresponding value is of that type. It
396401
will not Coerce the value:
397402
403+
>>> validate = Schema({'one': 'two', 'three': 'four', int: str})
398404
>>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['10']"):
399405
... validate({'10': 'twenty'})
400406

voluptuous/tests/tests.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,40 @@ def test_empty_list_as_exact():
479479
s([])
480480

481481

482+
def test_empty_dict_as_exact():
483+
# {} always evaluates as {}
484+
s = Schema({})
485+
assert_raises(Invalid, s, {'extra': 1})
486+
s = Schema({}, extra=ALLOW_EXTRA) # this should not be used
487+
assert_raises(Invalid, s, {'extra': 1})
488+
489+
# {...} evaluates as Schema({...})
490+
s = Schema({'foo': int})
491+
assert_raises(Invalid, s, {'foo': 1, 'extra': 1})
492+
s = Schema({'foo': int}, extra=ALLOW_EXTRA)
493+
s({'foo': 1, 'extra': 1})
494+
495+
# dict matches {} or {...}
496+
s = Schema(dict)
497+
s({'extra': 1})
498+
s({})
499+
s = Schema(dict, extra=PREVENT_EXTRA)
500+
s({'extra': 1})
501+
s({})
502+
503+
# nested {} evaluate as {}
504+
s = Schema({
505+
'inner': {}
506+
}, extra=ALLOW_EXTRA)
507+
assert_raises(Invalid, s, {'inner': {'extra': 1}})
508+
s({})
509+
s = Schema({
510+
'inner': Schema({}, extra=ALLOW_EXTRA)
511+
})
512+
assert_raises(Invalid, s, {'inner': {'extra': 1}})
513+
s({})
514+
515+
482516
def test_schema_decorator_match_with_args():
483517
@validate(int)
484518
def fn(arg):

0 commit comments

Comments
 (0)