Skip to content

Commit 22b2c08

Browse files
committed
Add default values handling. Fixes keleshev#12.
1 parent b81c544 commit 22b2c08

File tree

3 files changed

+101
-3
lines changed

3 files changed

+101
-3
lines changed

README.rst

+22
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,28 @@ You can mark a key as optional as follows:
225225
... Optional('occupation'): str}).validate({'name': 'Sam'})
226226
{'name': 'Sam'}
227227
228+
And if you want to give a default value, simply use the keyword argument
229+
``default``:
230+
231+
.. code:: python
232+
233+
>>> from schema import Optional, Use, Or
234+
>>> Schema({'event': str,
235+
... # use the default argument for schema classes
236+
... Optional('date'): Use(int, default=2012),
237+
... # wrap other objects in a Schema class to add a default value
238+
... Optional('comment'): Schema(str, default='Initial import'),
239+
... }).validate({'event': 'First commit'})
240+
{'comment': 'Initial import', 'date': 2012, 'event': 'First commit'}
241+
>>> # advanced use:
242+
... Schema({
243+
... # Optional key is a type, it gets instantiated as a default
244+
... Optional(int): Or(True, False, default=False),
245+
... # but it is possible to use the default attribute as well
246+
... Optional(int, default=42): Use(str, default='The answer'),
247+
... }).validate({})
248+
{0: False, 42: 'The answer'}
249+
228250
**schema** has classes ``And`` and ``Or`` that help validating several schemas
229251
for the same data:
230252

schema.py

+47-2
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,44 @@ def uniq(seq):
2323
return '\n'.join(a)
2424

2525

26+
def handle_default(init):
27+
28+
"""Add default handling to the __init__ method
29+
Meant to be used as a decorator"""
30+
31+
def init2(self, *args, **kw):
32+
# get default from the ``default`` keyword argument
33+
if 'default' in kw:
34+
self.default = kw['default']
35+
del(kw['default'])
36+
# if auto_default is set, get default from first argument
37+
elif hasattr(self, 'auto_default') and self.auto_default:
38+
self.default = args[0]
39+
if hasattr(self.default, 'default'):
40+
self.default = self.default.default
41+
elif issubclass(type(self.default), type):
42+
self.default = self.default()
43+
elif hasattr(self.default, 'validate'):
44+
# e.g. {Optional(Use(int)): ...}
45+
delattr(self, 'default')
46+
# normal init
47+
init(self, *args, **kw)
48+
# validate default
49+
if hasattr(self, 'default'):
50+
try:
51+
self.default = self.validate(self.default)
52+
except SchemaError:
53+
raise ValueError('%s does not validate its default: %s' % (
54+
self, self.default))
55+
return init2
56+
57+
2658
class And(object):
2759

60+
@handle_default
2861
def __init__(self, *args, **kw):
2962
self._args = args
63+
assert len(args)
3064
assert list(kw) in (['error'], [])
3165
self._error = kw.get('error')
3266

@@ -55,6 +89,7 @@ def validate(self, data):
5589

5690
class Use(object):
5791

92+
@handle_default
5893
def __init__(self, callable_, error=None):
5994
assert callable(callable_)
6095
self._callable = callable_
@@ -91,6 +126,7 @@ def priority(s):
91126

92127
class Schema(object):
93128

129+
@handle_default
94130
def __init__(self, schema, error=None):
95131
self._schema = schema
96132
self._error = error
@@ -136,15 +172,22 @@ def validate(self, data):
136172
if x is not None:
137173
raise SchemaError(['invalid value for key %r' % key] +
138174
x.autos, [e] + x.errors)
139-
coverage = set(k for k in coverage if type(k) is not Optional)
140175
required = set(k for k in s if type(k) is not Optional)
141-
if coverage != required:
176+
# missed keys
177+
if not required.issubset(coverage):
142178
raise SchemaError('missed keys %r' % (required - coverage), e)
179+
# wrong keys
143180
if len(new) != len(data):
144181
wrong_keys = set(data.keys()) - set(new.keys())
145182
s_wrong_keys = ', '.join('%r' % k for k in sorted(wrong_keys))
146183
raise SchemaError('wrong keys %s in %r' % (s_wrong_keys, data),
147184
e)
185+
# default for optional keys
186+
for k in set(s) - required - coverage:
187+
try:
188+
new[k.default] = s[k].default
189+
except AttributeError:
190+
pass
148191
return new
149192
if hasattr(s, 'validate'):
150193
try:
@@ -179,3 +222,5 @@ def validate(self, data):
179222
class Optional(Schema):
180223

181224
"""Marker for an optional part of Schema."""
225+
226+
auto_default = True

test_schema.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
basestring = str # Python 3 does not have basestring
1313

1414

15+
AE = raises(AssertionError)
16+
VE = raises(ValueError)
1517
SE = raises(SchemaError)
1618

1719

@@ -56,18 +58,19 @@ def test_validate_file():
5658

5759

5860
def test_and():
61+
with AE: And()
5962
assert And(int, lambda n: 0 < n < 5).validate(3) == 3
6063
with SE: And(int, lambda n: 0 < n < 5).validate(3.33)
6164
assert And(Use(int), lambda n: 0 < n < 5).validate(3.33) == 3
6265
with SE: And(Use(int), lambda n: 0 < n < 5).validate('3.33')
6366

6467

6568
def test_or():
69+
with AE: Or()
6670
assert Or(int, dict).validate(5) == 5
6771
assert Or(int, dict).validate({}) == {}
6872
with SE: Or(int, dict).validate('hai')
6973
assert Or(int).validate(4)
70-
with SE: Or().validate(2)
7174

7275

7376
def test_validate_list():
@@ -151,6 +154,34 @@ def test_dict_optional_keys():
151154
assert Schema({'a': 1, Optional('b'): 2}).validate({'a': 1}) == {'a': 1}
152155
assert Schema({'a': 1, Optional('b'): 2}).validate(
153156
{'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
157+
assert Schema({'a': 1, Optional(Use(int)): 2}).validate(
158+
{'a': 1, '4': 2}) == {'a': 1, 4: 2}
159+
160+
161+
def test_dict_optional_keys_defaults():
162+
assert Schema({'a': 1, Optional('b'): Schema(2, default=2)}).validate(
163+
{'a': 1}) == {'a': 1, 'b': 2}
164+
assert Schema({'a': 1, Optional('b'): Use(int, default='4')}).validate(
165+
{'a': 1}) == {'a': 1, 'b': 4}
166+
assert Schema({'a': 1, Optional('b'): Use(int, default=4)}).validate(
167+
{'a': 1}) == {'a': 1, 'b': 4}
168+
assert Schema({'a': 1, Optional('b'): Or(2, 4, default=4)}).validate(
169+
{'a': 1}) == {'a': 1, 'b': 4}
170+
assert Schema({'a': 1, Optional('b'): And(
171+
lambda x: x > 0,
172+
lambda x: x < 16,
173+
default=4)}).validate({'a': 1}) == {'a': 1, 'b': 4}
174+
assert Schema({'a': 1, Optional(int): Schema(2, default=2)}).validate(
175+
{'a': 1}) == {'a': 1, 0: 2}
176+
assert Schema({'a': 1,
177+
Optional(int, default=4): Schema(2, default=2)}).validate(
178+
{'a': 1}) == {'a': 1, 4: 2}
179+
with VE: Use(int, default='two')
180+
with VE: Schema(lambda x: x < 42, default=1337)
181+
assert Schema({'a': 1,
182+
Optional(Use(int, default=4)): Schema(int, default=2)
183+
}).validate(
184+
{'a': 1}) == {'a': 1, 4: 2}
154185

155186

156187
def test_complex():

0 commit comments

Comments
 (0)