Skip to content

Commit

Permalink
Python3 support, thanks to Steve Leonard (@xsleonard).
Browse files Browse the repository at this point in the history
  • Loading branch information
alecthomas committed May 28, 2013
1 parent 973e435 commit 34cd541
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 189 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Expand Up @@ -3,7 +3,8 @@ python:
- "2.6"
- "2.7"
# Not quite ready for prime time...
# - "3.2"
- "3.2"
- "3.3"
- "pypy"
# command to install dependencies
#install: "pip install -r requirements.txt --use-mirrors"
Expand Down
161 changes: 103 additions & 58 deletions README.rst
Expand Up @@ -51,46 +51,65 @@ and goes a little further for completeness.

"q" is required::

>>> schema({})
Traceback (most recent call last):
...
MultipleInvalid: required key not provided @ data['q']
>>> from voluptuous import MultipleInvalid
>>> try:
... schema({})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data['q']"
True

...must be a string::

>>> schema({'q': 123})
Traceback (most recent call last):
...
MultipleInvalid: expected str for dictionary value @ data['q']
>>> try:
... schema({'q': 123})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "expected str for dictionary value @ data['q']"
True

...and must be at least one character in length::

>>> schema({'q': ''})
Traceback (most recent call last):
...
MultipleInvalid: length of value must be at least 1 for dictionary value @ data['q']
>>> try:
... schema({'q': ''})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "length of value must be at least 1 for dictionary value @ data['q']"
True
>>> schema({'q': '#topic'})
{'q': '#topic'}

"per_page" is a positive integer no greater than 20::

>>> schema({'q': '#topic', 'per_page': 900})
Traceback (most recent call last):
...
MultipleInvalid: value must be at most 20 for dictionary value @ data['per_page']
>>> schema({'q': '#topic', 'per_page': -10})
Traceback (most recent call last):
...
MultipleInvalid: value must be at least 1 for dictionary value @ data['per_page']
>>> try:
... schema({'q': '#topic', 'per_page': 900})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "value must be at most 20 for dictionary value @ data['per_page']"
True
>>> try:
... schema({'q': '#topic', 'per_page': -10})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
True

"page" is an integer >= 0::

>>> schema({'q': '#topic', 'page': 'one'})
Traceback (most recent call last):
...
MultipleInvalid: expected int for dictionary value @ data['page']
>>> schema({'q': '#topic', 'page': 1})
{'q': '#topic', 'page': 1}
>>> try:
... schema({'q': '#topic', 'per_page': 'one'})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "expected int for dictionary value @ data['per_page']"
True
>>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1}
True

Defining schemas
----------------
Expand All @@ -117,10 +136,14 @@ instance of the type::
>>> schema = Schema(int)
>>> schema(1)
1
>>> schema('one')
Traceback (most recent call last):
...
MultipleInvalid: expected int
>>> try:
... schema('one')
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "expected int"
True


Lists
~~~~~
Expand Down Expand Up @@ -153,10 +176,13 @@ this property. Here's an example of a date validator::
>>> schema = Schema(Date())
>>> schema('2013-03-03')
datetime.datetime(2013, 3, 3, 0, 0)
>>> schema('2013-03')
Traceback (most recent call last):
...
MultipleInvalid: not a valid value
>>> try:
... schema('2013-03')
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "not a valid value"
True

In addition to simply determining if a value is valid, validators may mutate
the value into a valid form. An example of this is the ``Coerce(type)``
Expand Down Expand Up @@ -197,10 +223,13 @@ By default any additional keys in the data, not in the schema will trigger
exceptions::

>>> schema = Schema({2: 3})
>>> schema({1: 2, 2: 3})
Traceback (most recent call last):
...
MultipleInvalid: extra keys not allowed @ data[1]
>>> try:
... schema({1: 2, 2: 3})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "extra keys not allowed @ data[1]"
True

This behaviour can be altered on a per-schema basis with ``Schema(..., extra=True)``::

Expand All @@ -227,18 +256,24 @@ By default, keys in the schema are not required to be in the data::
Similarly to how extra_ keys work, this behaviour can be overridden per-schema::

>>> schema = Schema({1: 2, 3: 4}, required=True)
>>> schema({3: 4})
Traceback (most recent call last):
...
MultipleInvalid: required key not provided @ data[1]
>>> try:
... schema({3: 4})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data[1]"
True

And per-key, with the marker token ``Required(key)``::

>>> schema = Schema({Required(1): 2, 3: 4})
>>> schema({3: 4})
Traceback (most recent call last):
...
MultipleInvalid: required key not provided @ data[1]
>>> try:
... schema({3: 4})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data[1]"
True
>>> schema({1: 2})
{1: 2}

Expand All @@ -249,16 +284,23 @@ using the marker token ``Optional(key)``::

>>> from voluptuous import Optional
>>> schema = Schema({1: 2, Optional(3): 4}, required=True)
>>> schema({})
Traceback (most recent call last):
...
MultipleInvalid: required key not provided @ data[1]
>>> try:
... schema({})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data[1]"
True
>>> schema({1: 2})
{1: 2}
>>> schema({1: 2, 4: 5})
Traceback (most recent call last):
...
MultipleInvalid: extra keys not allowed @ data[4]
>>> try:
... schema({1: 2, 4: 5})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "extra keys not allowed @ data[4]"
True

>>> schema({1: 2, 3: 4})
{1: 2, 3: 4}

Expand Down Expand Up @@ -304,10 +346,13 @@ but the literal ``6`` will not match any of the elements of that list. This
error will be reported back to the user immediately. No backtracking is
attempted::

>>> schema([[6]])
Traceback (most recent call last):
...
MultipleInvalid: invalid list value @ data[0][0]
>>> try:
... schema([[6]])
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "invalid list value @ data[0][0]"
True

If we pass the data ``[6]``, the ``6`` is not a list type and so will not
recurse into the first element of the schema. Matching will continue on to the
Expand Down
96 changes: 60 additions & 36 deletions tests.rst
Expand Up @@ -10,25 +10,39 @@ Error reporting should be accurate::

It should show the exact index and container type, in this case a list value::

>>> schema(['one', 'two'])
Traceback (most recent call last):
...
MultipleInvalid: invalid list value @ data[1]
>>> try:
... schema(['one', 'two'])
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == 'invalid list value @ data[1]'
True

It should also be accurate for nested values::

>>> schema([{'two': 'nine'}])
Traceback (most recent call last):
...
MultipleInvalid: not a valid value for dictionary value @ data[0]['two']
>>> schema([{'four': ['nine']}])
Traceback (most recent call last):
...
MultipleInvalid: invalid list value @ data[0]['four'][0]
>>> schema([{'six': {'seven': 'nine'}}])
Traceback (most recent call last):
...
MultipleInvalid: not a valid value for dictionary value @ data[0]['six']['seven']
>>> try:
... schema([{'two': 'nine'}])
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "not a valid value for dictionary value @ data[0]['two']"
True

>>> try:
... schema([{'four': ['nine']}])
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "invalid list value @ data[0]['four'][0]"
True

>>> try:
... schema([{'six': {'seven': 'nine'}}])
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "not a valid value for dictionary value @ data[0]['six']['seven']"
True

Errors should be reported depth-first::

Expand All @@ -44,19 +58,21 @@ Errors should be reported depth-first::
Voluptuous supports validation when extra fields are present in the data::

>>> schema = Schema({'one': 1, Extra: object})
>>> schema({'two': 'two', 'one': 1})
{'two': 'two', 'one': 1}
>>> schema({'two': 'two', 'one': 1}) == {'two': 'two', 'one': 1}
True
>>> schema = Schema({'one': 1})
>>> schema({'two': 2})
Traceback (most recent call last):
...
MultipleInvalid: extra keys not allowed @ data['two']

>>> try:
... schema({'two': 2})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "extra keys not allowed @ data['two']"
True

dict, list, and tuple should be available as type validators::

>>> Schema(dict)({'a': 1, 'b': 2})
{'a': 1, 'b': 2}
>>> Schema(dict)({'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
True
>>> Schema(list)([1,2,3])
[1, 2, 3]
>>> Schema(tuple)((1,2,3))
Expand All @@ -70,8 +86,8 @@ subclasses of dict or list::
... pass
>>>
>>> d = Schema(dict)(Dict(a=1, b=2))
>>> d
{'a': 1, 'b': 2}
>>> d == {'a': 1, 'b': 2}
True
>>> type(d) is Dict
True
>>> class List(list):
Expand Down Expand Up @@ -131,10 +147,14 @@ Schemas built with All() should give the same error as the original validator (I
... }])
... })

>>> schema({'items': [{}]})
Traceback (most recent call last):
...
MultipleInvalid: required key not provided @ data['items'][0]['foo']
>>> try:
... schema({'items': [{}]})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data['items'][0]['foo']"
True


Validator should return same instance of the same type for object::

Expand All @@ -157,7 +177,7 @@ check object type::
>>> named = NamedTuple(q='one')
>>> schema(named) == named
True
>>> schema(named)
>>> schema(named)
NamedTuple(q='one')

If `cls` argument passed to object validator we should check object type::
Expand Down Expand Up @@ -189,10 +209,14 @@ Ensure that objects with `__slots__` supported properly::
...
>>> structure = DictStructure(q='one')
>>> structure.page = 1
>>> schema(structure)
Traceback (most recent call last):
...
MultipleInvalid: extra keys not allowed @ data['page']
>>> try:
... schema(structure)
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "extra keys not allowed @ data['page']"
True

>>> schema = Schema(Object({'q': 'one', Extra: object}))
>>> schema(structure)
DictStructure(q='one', page=1)
Expand Down

0 comments on commit 34cd541

Please sign in to comment.