Skip to content

Commit 95e1cb5

Browse files
committed
Add default values handling. Fixes keleshev#12.
1 parent 65c4146 commit 95e1cb5

File tree

3 files changed

+97
-4
lines changed

3 files changed

+97
-4
lines changed

README.rst

+15
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,21 @@ You can mark a key as optional as follows:
219219
... Optional('occupation'): str}).validate({'name': 'Sam'})
220220
{'name': 'Sam'}
221221
222+
And if you want to give a default value for your optional key, pass a keyword
223+
argument ``default``, or use the ``Default`` class:
224+
225+
.. code:: python
226+
227+
>>> from schema import Optional, Default
228+
>>> Schema({'event': str,
229+
... Optional('date'): Use(int, default=2012),
230+
... }).validate({'event': 'First commit'})
231+
{'date': 2012, 'event': 'First commit'}
232+
>>> Schema({'account': str,
233+
... Optional('amount'): Default(int),
234+
... }).validate({'account': 'Piggy bank'})
235+
{'account': 'Piggy bank', 'amount': 0}
236+
222237
**schema** has classes ``And`` and ``Or`` that help validating several schemas
223238
for the same data:
224239

schema.py

+50-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,40 @@ def uniq(seq):
2727
return '\n'.join(a)
2828

2929

30+
def handle_default(init):
31+
32+
"""Add default handling to __init__ method; meant for decorators"""
33+
34+
def init2(self, *args, **kw):
35+
# get default from the ``default`` keyword argument
36+
if 'default' in kw:
37+
self.default = kw['default']
38+
del(kw['default'])
39+
# if auto_default is set, get default from first argument
40+
elif hasattr(self, 'auto_default') and self.auto_default:
41+
self.default = args[0]
42+
if hasattr(self.default, 'default'):
43+
self.default = self.default.default
44+
elif type(self.default) == type:
45+
self.default = 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+
3058
class And(object):
3159

60+
@handle_default
3261
def __init__(self, *args, **kw):
3362
self._args = args
63+
assert len(args)
3464
assert list(kw) in (['error'], [])
3565
self._error = kw.get('error')
3666

@@ -59,6 +89,7 @@ def validate(self, data):
5989

6090
class Use(object):
6191

92+
@handle_default
6293
def __init__(self, callable_, error=None):
6394
assert callable(callable_)
6495
self._callable = callable_
@@ -94,6 +125,7 @@ def priority(s):
94125

95126
class Schema(object):
96127

128+
@handle_default
97129
def __init__(self, schema, error=None):
98130
self._schema = schema
99131
self._error = error
@@ -138,12 +170,19 @@ def validate(self, data):
138170
x.autos, [e] + x.errors)
139171
else:
140172
raise SchemaError('key %r is required' % skey, e)
141-
coverage = set(k for k in coverage if type(k) is not Optional)
142173
required = set(k for k in s if type(k) is not Optional)
143-
if coverage != required:
174+
# missed keys
175+
if not required.issubset(coverage):
144176
raise SchemaError('missed keys %r' % (required - coverage), e)
177+
# wrong keys
145178
if len(new) != len(data):
146179
raise SchemaError('wrong keys %r in %r' % (new, data), e)
180+
# default for optional keys
181+
for k in set(s) - required - coverage:
182+
try:
183+
new[k.default] = s[k].default
184+
except AttributeError:
185+
pass
147186
return new
148187
if hasattr(s, 'validate'):
149188
try:
@@ -178,3 +217,12 @@ def validate(self, data):
178217
class Optional(Schema):
179218

180219
"""Marker for an optional part of Schema."""
220+
221+
auto_default = True
222+
223+
224+
class Default(Schema):
225+
226+
"""Wrapper automatically adding a default value if possible"""
227+
228+
auto_default = True

test_schema.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from pytest import raises
55

6-
from schema import Schema, Use, And, Or, Optional, SchemaError
6+
from schema import Schema, Use, And, Or, Optional, Default, SchemaError
77

88

99
try:
@@ -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():
@@ -138,6 +141,33 @@ def test_dict_optional_keys():
138141
{'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
139142

140143

144+
def test_dict_optional_keys_defaults():
145+
assert Schema({'a': 1, Optional('b'): Default(2)}).validate(
146+
{'a': 1}) == {'a': 1, 'b': 2}
147+
assert Schema({'a': 1, Optional('b'): Default(2)}).validate(
148+
{'a': 1}) == {'a': 1, 'b': 2}
149+
assert Schema({'a': 1, Optional('b'): Default(int)}).validate(
150+
{'a': 1}) == {'a': 1, 'b': 0}
151+
assert Schema({'a': 1, Optional('b'): Default(int, default=4)}).validate(
152+
{'a': 1}) == {'a': 1, 'b': 4}
153+
assert Schema({'a': 1, Optional('b'): Use(int, default='4')}).validate(
154+
{'a': 1}) == {'a': 1, 'b': 4}
155+
assert Schema({'a': 1, Optional('b'): Use(int, default=4)}).validate(
156+
{'a': 1}) == {'a': 1, 'b': 4}
157+
assert Schema({'a': 1, Optional('b'): Or(2, 4, default=4)}).validate(
158+
{'a': 1}) == {'a': 1, 'b': 4}
159+
assert Schema({'a': 1, Optional('b'): And(
160+
lambda x: x > 0,
161+
lambda x: x < 16,
162+
default=4)}).validate({'a': 1}) == {'a': 1, 'b': 4}
163+
assert Schema({'a': 1, Optional(int): Default(2)}).validate(
164+
{'a': 1}) == {'a': 1, 0: 2}
165+
assert Schema({'a': 1, Optional(int, default=4): Default(2)}).validate(
166+
{'a': 1}) == {'a': 1, 4: 2}
167+
with VE: Use(int, default='two')
168+
with VE: Default(lambda x: x < 42, default=1337)
169+
170+
141171
def test_complex():
142172
s = Schema({'<file>': And([Use(open)], lambda l: len(l)),
143173
'<path>': os.path.exists,

0 commit comments

Comments
 (0)