Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add default values handling. Fixes #12. #19

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,28 @@ You can mark a key as optional as follows:
... Optional('occupation'): str}).validate({'name': 'Sam'})
{'name': 'Sam'}

And if you want to give a default value, simply use the keyword argument
``default``:

.. code:: python

>>> from schema import Optional, Use, Or
>>> Schema({'event': str,
... # use the default argument for schema classes
... Optional('date'): Use(int, default=2012),
... # wrap other objects in a Schema class to add a default value
... Optional('comment'): Schema(str, default='Initial import'),
... }).validate({'event': 'First commit'})
{'comment': 'Initial import', 'date': 2012, 'event': 'First commit'}
>>> # advanced use:
... Schema({
... # Optional key is a type, it gets instantiated as a default
... Optional(int): Or(True, False, default=False),
... # but it is possible to use the default attribute as well
... Optional(int, default=42): Use(str, default='The answer'),
... }).validate({})
{0: False, 42: 'The answer'}

**schema** has classes ``And`` and ``Or`` that help validating several schemas
for the same data:

Expand Down
49 changes: 47 additions & 2 deletions schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,44 @@ def uniq(seq):
return '\n'.join(a)


def handle_default(init):

"""Add default handling to the __init__ method
Meant to be used as a decorator"""

def init2(self, *args, **kw):
# get default from the ``default`` keyword argument
if 'default' in kw:
self.default = kw['default']
del(kw['default'])
# if auto_default is set, get default from first argument
elif hasattr(self, 'auto_default') and self.auto_default:
self.default = args[0]
if hasattr(self.default, 'default'):
self.default = self.default.default
elif issubclass(type(self.default), type):
self.default = self.default()
elif hasattr(self.default, 'validate'):
# e.g. {Optional(Use(int)): ...}
delattr(self, 'default')
# normal init
init(self, *args, **kw)
# validate default
if hasattr(self, 'default'):
try:
self.default = self.validate(self.default)
except SchemaError:
raise ValueError('%s does not validate its default: %s' % (
self, self.default))
return init2


class And(object):

@handle_default
def __init__(self, *args, **kw):
self._args = args
assert len(args)
assert list(kw) in (['error'], [])
self._error = kw.get('error')

Expand Down Expand Up @@ -55,6 +89,7 @@ def validate(self, data):

class Use(object):

@handle_default
def __init__(self, callable_, error=None):
assert callable(callable_)
self._callable = callable_
Expand Down Expand Up @@ -91,6 +126,7 @@ def priority(s):

class Schema(object):

@handle_default
def __init__(self, schema, error=None):
self._schema = schema
self._error = error
Expand Down Expand Up @@ -136,15 +172,22 @@ def validate(self, data):
if x is not None:
raise SchemaError(['invalid value for key %r' % key] +
x.autos, [e] + x.errors)
coverage = set(k for k in coverage if type(k) is not Optional)
required = set(k for k in s if type(k) is not Optional)
if coverage != required:
# missed keys
if not required.issubset(coverage):
raise SchemaError('missed keys %r' % (required - coverage), e)
# wrong keys
if len(new) != len(data):
wrong_keys = set(data.keys()) - set(new.keys())
s_wrong_keys = ', '.join('%r' % k for k in sorted(wrong_keys))
raise SchemaError('wrong keys %s in %r' % (s_wrong_keys, data),
e)
# default for optional keys
for k in set(s) - required - coverage:
try:
new[k.default] = s[k].default
except AttributeError:
pass
return new
if hasattr(s, 'validate'):
try:
Expand Down Expand Up @@ -179,3 +222,5 @@ def validate(self, data):
class Optional(Schema):

"""Marker for an optional part of Schema."""

auto_default = True
34 changes: 33 additions & 1 deletion test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
basestring = str # Python 3 does not have basestring


AE = raises(AssertionError)
VE = raises(ValueError)
SE = raises(SchemaError)


Expand Down Expand Up @@ -56,18 +58,19 @@ def test_validate_file():


def test_and():
with AE: And()
assert And(int, lambda n: 0 < n < 5).validate(3) == 3
with SE: And(int, lambda n: 0 < n < 5).validate(3.33)
assert And(Use(int), lambda n: 0 < n < 5).validate(3.33) == 3
with SE: And(Use(int), lambda n: 0 < n < 5).validate('3.33')


def test_or():
with AE: Or()
assert Or(int, dict).validate(5) == 5
assert Or(int, dict).validate({}) == {}
with SE: Or(int, dict).validate('hai')
assert Or(int).validate(4)
with SE: Or().validate(2)


def test_validate_list():
Expand Down Expand Up @@ -151,6 +154,35 @@ def test_dict_optional_keys():
assert Schema({'a': 1, Optional('b'): 2}).validate({'a': 1}) == {'a': 1}
assert Schema({'a': 1, Optional('b'): 2}).validate(
{'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
assert Schema({'a': 1, Optional(Use(int)): 2}).validate(
{'a': 1, '4': 2}) == {'a': 1, 4: 2}


def test_dict_optional_keys_defaults():
assert Schema({'a': 1, Optional('b'): Schema(2, default=2)}).validate(
{'a': 1}) == {'a': 1, 'b': 2}
assert Schema({'a': 1, Optional('b'): Use(int, default='4')}).validate(
{'a': 1}) == {'a': 1, 'b': 4}
assert Schema({'a': 1, Optional('b'): Use(int, default=4)}).validate(
{'a': 1}) == {'a': 1, 'b': 4}
assert Schema({'a': 1, Optional('b'): Or(2, 4, default=4)}).validate(
{'a': 1}) == {'a': 1, 'b': 4}
assert Schema({'a': 1, Optional('b'): And(
lambda x: x > 0,
lambda x: x < 16,
default=4)}).validate({'a': 1}) == {'a': 1, 'b': 4}
assert Schema({'a': 1, Optional(int): Schema(2, default=2)}).validate(
{'a': 1}) == {'a': 1, 0: 2}
assert Schema({
'a': 1,
Optional(int, default=4): Schema(2, default=2)
}).validate({'a': 1}) == {'a': 1, 4: 2}
with VE: Use(int, default='two')
with VE: Schema(lambda x: x < 42, default=1337)
assert Schema({
'a': 1,
Optional(Use(int, default=4)): Schema(int, default=2)
}).validate({'a': 1}) == {'a': 1, 4: 2}


def test_complex():
Expand Down