Skip to content

Commit 93203b0

Browse files
committedMay 18, 2013
Reference values in error messages. Fixes keleshev#4.
1 parent 65c4146 commit 93203b0

File tree

3 files changed

+48
-23
lines changed

3 files changed

+48
-23
lines changed
 

‎README.rst

+6-3
Original file line numberDiff line numberDiff line change
@@ -243,19 +243,22 @@ User-friendly error reporting
243243

244244
You can pass a keyword argument ``error`` to any of validatable classes
245245
(such as ``Schema``, ``And``, ``Or``, ``Use``) to report this error instead of
246-
a built-in one.
246+
a built-in one. The format ``{value}`` will get replaced with the value failing
247+
the validation.
247248

248249
.. code:: python
249250
250-
>>> Schema(Use(int, error='Invalid year')).validate('XVII')
251+
>>> Schema(Use(int, error='Invalid year: {value}')).validate('XVII')
251252
Traceback (most recent call last):
252253
...
253-
SchemaError: Invalid year
254+
SchemaError: Invalid year: XVII
254255
255256
You can see all errors that occured by accessing exception's ``exc.autos``
256257
for auto-generated error messages, and ``exc.errors`` for errors
257258
which had ``error`` text passed to them.
258259

260+
You can use ``exc.value`` to directly access the faulty value.
261+
259262
You can exit with ``sys.exit(exc.code)`` if you want to show the messages
260263
to the user without traceback. ``error`` messages are given precedence in that
261264
case.

‎schema.py

+25-18
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ class SchemaError(Exception):
99

1010
"""Error during Schema validation."""
1111

12-
def __init__(self, autos, errors):
12+
def __init__(self, autos, errors, value):
1313
self.autos = autos if type(autos) is list else [autos]
1414
self.errors = errors if type(errors) is list else [errors]
15+
self.value = value
16+
self.errors = [None if e is None else e.format(value=self.value)
17+
for e in self.errors]
1518
Exception.__init__(self, self.code)
1619

1720
@property
@@ -47,14 +50,14 @@ def validate(self, data):
4750
class Or(And):
4851

4952
def validate(self, data):
50-
x = SchemaError([], [])
53+
x = SchemaError([], [], None)
5154
for s in [Schema(s, error=self._error) for s in self._args]:
5255
try:
5356
return s.validate(data)
5457
except SchemaError as _x:
5558
x = _x
5659
raise SchemaError(['%r did not validate %r' % (self, data)] + x.autos,
57-
[self._error] + x.errors)
60+
[self._error] + x.errors, data)
5861

5962

6063
class Use(object):
@@ -71,10 +74,11 @@ def validate(self, data):
7174
try:
7275
return self._callable(data)
7376
except SchemaError as x:
74-
raise SchemaError([None] + x.autos, [self._error] + x.errors)
77+
raise SchemaError([None] + x.autos, [self._error] + x.errors, data)
7578
except BaseException as x:
7679
f = self._callable.__name__
77-
raise SchemaError('%s(%r) raised %r' % (f, data, x), self._error)
80+
raise SchemaError('%s(%r) raised %r' % (f, data, x), self._error,
81+
data)
7882

7983

8084
def priority(s):
@@ -118,9 +122,9 @@ def validate(self, data):
118122
for skey in sorted(s, key=priority):
119123
svalue = s[skey]
120124
try:
121-
nkey = Schema(skey, error=e).validate(key)
125+
nkey = Schema(skey).validate(key) # error got forgotten
122126
try:
123-
nvalue = Schema(svalue, error=e).validate(value)
127+
nvalue = Schema(svalue).validate(value)
124128
except SchemaError as _x:
125129
x = _x
126130
raise
@@ -135,44 +139,47 @@ def validate(self, data):
135139
elif skey is not None:
136140
if x is not None:
137141
raise SchemaError(['key %r is required' % key] +
138-
x.autos, [e] + x.errors)
142+
x.autos, [e] + x.errors, data)
139143
else:
140-
raise SchemaError('key %r is required' % skey, e)
144+
raise SchemaError('key %r is required' % skey, e, data)
141145
coverage = set(k for k in coverage if type(k) is not Optional)
142146
required = set(k for k in s if type(k) is not Optional)
143147
if coverage != required:
144-
raise SchemaError('missed keys %r' % (required - coverage), e)
148+
raise SchemaError('missed keys %r' % (required - coverage), e,
149+
data)
145150
if len(new) != len(data):
146-
raise SchemaError('wrong keys %r in %r' % (new, data), e)
151+
raise SchemaError('wrong keys %r in %r' % (new, data), e, data)
147152
return new
148153
if hasattr(s, 'validate'):
149154
try:
150155
return s.validate(data)
151156
except SchemaError as x:
152-
raise SchemaError([None] + x.autos, [e] + x.errors)
157+
raise SchemaError([None] + x.autos, [e] + x.errors, data)
153158
except BaseException as x:
154159
raise SchemaError('%r.validate(%r) raised %r' % (s, data, x),
155-
self._error)
160+
self._error, data)
156161
if type(s) is type:
157162
if isinstance(data, s):
158163
return data
159164
else:
160-
raise SchemaError('%r should be instance of %r' % (data, s), e)
165+
raise SchemaError('%r should be instance of %r' % (data, s), e,
166+
data)
161167
if callable(s):
162168
f = s.__name__
163169
try:
164170
if s(data):
165171
return data
166172
except SchemaError as x:
167-
raise SchemaError([None] + x.autos, [e] + x.errors)
173+
raise SchemaError([None] + x.autos, [e] + x.errors, data)
168174
except BaseException as x:
169175
raise SchemaError('%s(%r) raised %r' % (f, data, x),
170-
self._error)
171-
raise SchemaError('%s(%r) should evaluate to True' % (f, data), e)
176+
self._error, data)
177+
raise SchemaError('%s(%r) should evaluate to True' % (f, data), e,
178+
data)
172179
if s == data:
173180
return data
174181
else:
175-
raise SchemaError('%r does not match %r' % (s, data), e)
182+
raise SchemaError('%r does not match %r' % (s, data), e, data)
176183

177184

178185
class Optional(Schema):

‎test_schema.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ def ve(_):
1919
raise ValueError()
2020

2121

22-
def se(_):
23-
raise SchemaError('first auto', 'first error')
22+
def se(data):
23+
raise SchemaError('first auto', 'first error', data)
2424

2525

2626
def test_schema():
@@ -164,6 +164,21 @@ def test_nice_errors():
164164
assert e.code == 'should be a number'
165165

166166

167+
def test_nice_errors_format():
168+
try:
169+
Schema(int, error='{value} should be integer').validate('x')
170+
except SchemaError as e:
171+
assert e.errors == ['x should be integer']
172+
try:
173+
Schema(Use(float), error='{value} should be a number').validate('x')
174+
except SchemaError as e:
175+
assert e.code == 'x should be a number'
176+
try:
177+
Schema({Optional('i'): Use(int, error='{value} should be a number')}).validate({'i': 'x'})
178+
except SchemaError as e:
179+
assert e.code == 'x should be a number'
180+
181+
167182
def test_use_error_handling():
168183
try:
169184
Use(ve).validate('x')

0 commit comments

Comments
 (0)
Please sign in to comment.