From 381b61000ec8d1d42822b20af6a5d0e09c9bb2fa Mon Sep 17 00:00:00 2001 From: Tuukka Mustonen Date: Sat, 24 Sep 2016 14:26:49 +0300 Subject: [PATCH] Always evaluate {} as {} If you want a dict with any content, use 'dict', not Schema({}, extra=ALLOW_EXTRA). --- README.md | 19 +++++++++++++++++++ voluptuous/schema_builder.py | 2 +- voluptuous/tests/tests.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1eae118..71fe7fc 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,25 @@ 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 +>>> schema({'extra': 1}) +Traceback (most recent call last): + ... +MultipleInvalid: not a valid value +>>> 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: diff --git a/voluptuous/schema_builder.py b/voluptuous/schema_builder.py index 8f93863..fc78583 100644 --- a/voluptuous/schema_builder.py +++ b/voluptuous/schema_builder.py @@ -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) diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py index 86a43ce..debd09a 100644 --- a/voluptuous/tests/tests.py +++ b/voluptuous/tests/tests.py @@ -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):