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

Improve custom error handling. #42

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
91 changes: 73 additions & 18 deletions schema.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
__version__ = '0.3.0'

error_messages = {
'did not validate': '{schema} {reason} {data}',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like these should be different subclasses of SchemaError. Then we could catch them selectively with try/except in calling code. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that I made this pull request over a year ago and it's obvious that this project is not actively maintained. I've moved on. schemtatics looks interesting....

'callable raised exception': '{schema}({data}) raised {details}',
'validate raised exception': '{schema}.validate({data}) raised {details}',
'invalid value for key': '{reason} {details}',
'missed keys': '{reason} {details}',
'wrong keys': '{reason} {details} in {data}',
'incorrect instance': '{data} should be instance of {schema}',
'should evaluate to True': '{schema}({data}) {reason}',
'does not match': '{schema} {reason} {data}'
}

def auto_error(schema, data, reason, details):
return format_error(error_messages[reason], schema, data, reason, details)

def format_error(error, schema, data, reason, details=None):
if error is None:
return
elif callable(error):
result = error(schema, data, reason, details)
assert isinstance(result, basestring), \
"error function must return a string"
return result
if callable(schema) and hasattr(schema, '__name__'):
schema = schema.__name__
else:
schema = repr(schema)
if not isinstance(details, basestring):
details = repr(details)
return error.format(schema=schema, data=repr(data), reason=reason,
details=details)

class SchemaError(Exception):

Expand Down Expand Up @@ -49,8 +80,11 @@ def validate(self, data):
return s.validate(data)
except SchemaError as _x:
x = _x
raise SchemaError(['%r did not validate %r' % (self, data)] + x.autos,
[self._error] + x.errors)
reason = 'did not validate'
raise SchemaError([format_error(auto_error, self, data, reason)] +
x.autos,
[format_error(self._error, self, data, reason)] +
x.errors)


class Use(object):
Expand All @@ -69,8 +103,10 @@ def validate(self, data):
except SchemaError as x:
raise SchemaError([None] + x.autos, [self._error] + x.errors)
except BaseException as x:
f = self._callable.__name__
raise SchemaError('%s(%r) raised %r' % (f, data, x), self._error)
f = self._callable
reason = 'callable raised exception'
raise SchemaError(format_error(auto_error, f, data, reason, x),
format_error(self._error, f, data, reason, x))


def priority(s):
Expand Down Expand Up @@ -134,46 +170,65 @@ def validate(self, data):
new[nkey] = nvalue
elif skey is not None:
if x is not None:
raise SchemaError(['invalid value for key %r' % key] +
x.autos, [e] + x.errors)
reason = 'invalid value for key'
details = '%r' % key
raise SchemaError([format_error(auto_error, s, data,
reason, details)] +
x.autos,
[format_error(e, s, data, reason,
details)] +
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:
raise SchemaError('missed keys %r' % (required - coverage), e)
reason = 'missed keys'
details = '%r' % (required - coverage)
raise SchemaError(format_error(auto_error, s, data, reason,
details),
format_error(e, s, data, reason, details))
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)
reason = 'wrong keys'
details = ', '.join('%r' % k for k in sorted(wrong_keys))
raise SchemaError(format_error(auto_error, s, data, reason,
details),
format_error(e, s, data, reason, details))
return new
if hasattr(s, 'validate'):
try:
return s.validate(data)
except SchemaError as x:
raise SchemaError([None] + x.autos, [e] + x.errors)
except BaseException as x:
raise SchemaError('%r.validate(%r) raised %r' % (s, data, x),
self._error)
reason = 'validate raised exception'
raise SchemaError(format_error(auto_error, s, data, reason, x),
format_error(self._error, s, data, reason, x))
if issubclass(type(s), type):
if isinstance(data, s):
return data
else:
raise SchemaError('%r should be instance of %r' % (data, s), e)
reason = 'incorrect instance'
raise SchemaError(format_error(auto_error, s, data, reason),
format_error(e, s, data, reason))
if callable(s):
f = s.__name__
try:
if s(data):
return data
except SchemaError as x:
raise SchemaError([None] + x.autos, [e] + x.errors)
except BaseException as x:
raise SchemaError('%s(%r) raised %r' % (f, data, x),
self._error)
raise SchemaError('%s(%r) should evaluate to True' % (f, data), e)
reason = 'callable raised exception'
raise SchemaError(format_error(auto_error, s, data, reason, x),
format_error(self._error, s, data, reason, x))
reason = 'should evaluate to True'
raise SchemaError(format_error(auto_error, s, data, reason),
format_error(e, s, data, reason))
if s == data:
return data
else:
raise SchemaError('%r does not match %r' % (s, data), e)
reason = 'does not match'
raise SchemaError(format_error(auto_error, s, data, reason),
format_error(e, s, data, reason))


class Optional(Schema):
Expand Down
14 changes: 13 additions & 1 deletion test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,19 @@ def test_nice_errors():
Schema({Optional('i'): Use(int, error='should be a number')}).validate({'i': 'x'})
except SchemaError as e:
assert e.code == 'should be a number'

try:
Schema(int, error='{data} should be integer').validate('x')
except SchemaError as e:
assert e.errors == ["'x' should be integer"]
try:
Schema(int, error='{data} {reason}').validate('x')
except SchemaError as e:
assert e.errors == ["'x' incorrect instance"]
try:
errfunc = lambda s, d, r, x: '%s should be integer' % d.upper()
Schema(int, error=errfunc).validate('x')
except SchemaError as e:
assert e.errors == ["X should be integer"]

def test_use_error_handling():
try:
Expand Down