Skip to content

Commit

Permalink
Added support for sets and frozensets
Browse files Browse the repository at this point in the history
  • Loading branch information
svisser committed Jun 24, 2018
1 parent 44f5ace commit 4d742e4
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 0 deletions.
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,59 @@ True

```

### Sets and frozensets

Sets and frozensets are treated as a set of valid values. Each element
in the schema set is compared to each value in the input data:

```pycon
>>> schema = Schema({42})
>>> schema({42}) == {42}
True
>>> try:
... schema({43})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "invalid value in set"
True
>>> schema = Schema({int})
>>> schema({1, 2, 3}) == {1, 2, 3}
True
>>> schema = Schema({int, str})
>>> schema({1, 2, 'abc'}) == {1, 2, 'abc'}
True
>>> schema = Schema(frozenset([int]))
>>> try:
... schema({3})
... raise AssertionError('Invalid not raised')
... except Invalid as e:
... exc = e
>>> str(exc) == 'expected a frozenset'
True

```

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

```pycon
>>> schema = Schema(set())
>>> try:
... schema({1})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "invalid value in set"
True
>>> schema(set()) == set()
True
>>> schema = Schema(set)
>>> schema({1, 2}) == {1, 2}
True

```

### Validation functions

Validators are simple callables that raise an `Invalid` exception when
Expand Down
42 changes: 42 additions & 0 deletions voluptuous/schema_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ def _compile(self, schema):
return self._compile_list(schema)
elif isinstance(schema, tuple):
return self._compile_tuple(schema)
elif isinstance(schema, (frozenset, set)):
return self._compile_set(schema)
type_ = type(schema)
if inspect.isclass(schema):
type_ = schema
Expand Down Expand Up @@ -675,6 +677,46 @@ def _compile_list(self, schema):
"""
return self._compile_sequence(schema, list)

def _compile_set(self, schema):
"""Validate a set.
A set is an unordered collection of unique elements.
>>> validator = Schema({int})
>>> validator(set([42])) == set([42])
True
>>> with raises(er.Invalid, 'expected a set'):
... validator(42)
>>> with raises(er.MultipleInvalid, 'invalid value in set'):
... validator(set(['a']))
"""
type_ = type(schema)
type_name = type_.__name__

def validate_set(path, data):
if not isinstance(data, type_):
raise er.Invalid('expected a %s' % type_name, path)

_compiled = [self._compile(s) for s in schema]
errors = []
for value in data:
for validate in _compiled:
try:
validate(path, value)
break
except er.Invalid:
pass
else:
invalid = er.Invalid('invalid value in %s' % type_name, path)
errors.append(invalid)

if errors:
raise er.MultipleInvalid(errors)

return data

return validate_set

def extend(self, schema, required=None, extra=None):
"""Create a new `Schema` by merging this and the provided `schema`.
Expand Down
70 changes: 70 additions & 0 deletions voluptuous/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1141,3 +1141,73 @@ def test_SomeOf_on_bounds_assertion():

def test_comparing_voluptuous_object_to_str():
assert_true(Optional('Classification') < 'Name')


def test_set_of_integers():
schema = Schema({int})
with raises(Invalid, 'expected a set'):
schema(42)
with raises(Invalid, 'expected a set'):
schema(frozenset([42]))

schema(set())
schema(set([42]))
schema(set([42, 43, 44]))
try:
schema(set(['abc']))
except MultipleInvalid as e:
assert_equal(str(e), "invalid value in set")
else:
assert False, "Did not raise Invalid"


def test_frozenset_of_integers():
schema = Schema(frozenset([int]))
with raises(Invalid, 'expected a frozenset'):
schema(42)
with raises(Invalid, 'expected a frozenset'):
schema(set([42]))

schema(frozenset())
schema(frozenset([42]))
schema(frozenset([42, 43, 44]))
try:
schema(frozenset(['abc']))
except MultipleInvalid as e:
assert_equal(str(e), "invalid value in frozenset")
else:
assert False, "Did not raise Invalid"


def test_set_of_integers_and_strings():
schema = Schema({int, str})
with raises(Invalid, 'expected a set'):
schema(42)

schema(set())
schema(set([42]))
schema(set(['abc']))
schema(set([42, 'abc']))
try:
schema(set([None]))
except MultipleInvalid as e:
assert_equal(str(e), "invalid value in set")
else:
assert False, "Did not raise Invalid"


def test_frozenset_of_integers_and_strings():
schema = Schema(frozenset([int, str]))
with raises(Invalid, 'expected a frozenset'):
schema(42)

schema(frozenset())
schema(frozenset([42]))
schema(frozenset(['abc']))
schema(frozenset([42, 'abc']))
try:
schema(frozenset([None]))
except MultipleInvalid as e:
assert_equal(str(e), "invalid value in frozenset")
else:
assert False, "Did not raise Invalid"

0 comments on commit 4d742e4

Please sign in to comment.