diff --git a/CHANGES.txt b/CHANGES.txt index 66f179c..7d07728 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,9 @@ Changes Next release ------------ +- Add a ``__str__`` method to the ``colander.Invalid`` exception that + prints an error summary. + - Various error message improvements. - Add ``colander.Length`` validator class. diff --git a/colander/__init__.py b/colander/__init__.py index 5a23ef4..e8cc4e4 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1,4 +1,5 @@ import itertools +import pprint class _missing(object): pass @@ -66,6 +67,11 @@ class Invalid(Exception): errors['.'.join(keyparts)] = '; '.join(msgs) return errors + def __str__(self): + """ Return a pretty-formatted string representation of the + result of an execution of this exception's ``asdict`` method""" + return pprint.pformat(self.asdict()) + class All(object): """ Composite validator which succeeds if none of its subvalidators raises an :class:`colander.Invalid` exception""" diff --git a/colander/tests.py b/colander/tests.py index 6a2244a..168254d 100644 --- a/colander/tests.py +++ b/colander/tests.py @@ -81,7 +81,25 @@ class TestInvalid(unittest.TestCase): self.assertEqual(d, {'node1.node2.3': 'exc1; exc2; exc3', 'node1.node4': 'exc1; exc4'}) - + def test___str__(self): + from colander import Positional + node1 = DummySchemaNode(None, 'node1') + node2 = DummySchemaNode(Positional(), 'node2') + node3 = DummySchemaNode(Positional(), 'node3') + node4 = DummySchemaNode(Positional(), 'node4') + exc1 = self._makeOne(node1, 'exc1', pos=1) + exc2 = self._makeOne(node2, 'exc2', pos=2) + exc3 = self._makeOne(node3, 'exc3', pos=3) + exc4 = self._makeOne(node4, 'exc4', pos=4) + exc1.add(exc2) + exc2.add(exc3) + exc1.add(exc4) + result = str(exc1) + self.assertEqual( + result, + "{'node1.node2.3': 'exc1; exc2; exc3', 'node1.node4': 'exc1; exc4'}" + ) + class TestAll(unittest.TestCase): def _makeOne(self, validators): from colander import All diff --git a/docs/index.rst b/docs/index.rst index 0dadfe1..40c12cf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -282,23 +282,20 @@ or a validation error? {'location':'work', 'number':'555-8989'},], } schema = Person() - try: - schema.deserialize(data) - except colander.Invalid, e: - print e.asdict() + schema.deserialize(data) The ``deserialize`` method will raise an exception, and the ``except`` -clause above will be invoked, causing ``e.asdict()`` to be printed. -This wil print: +clause above will be invoked, causing an error messaage to be printed. +It will print something like: .. code-block:: python :linenos: - {'age':'-1 is less than minimum value 0', - 'friends.1.0':'"t" is not a number', - 'phones.0.location:'"bar" is not one of ["home", "work"]'} + Invalid: {'age':'-1 is less than minimum value 0', + 'friends.1.0':'"t" is not a number', + 'phones.0.location:'"bar" is not one of "home", "work"'} -The above error dictionary is telling us that: +The above error is telling us that: - The top-level age variable failed validation. @@ -308,6 +305,37 @@ The above error dictionary is telling us that: - The zeroth phone number has a bad location: it should be one of "home" or "work". +We can optionally catch the exception raised and obtain the raw error +dictionary: + +.. code-block:: python + :linenos: + + import colander + + data = { + 'name':'keith', + 'age':'-1', + 'friends':[('1', 'jim'),('t', 'bob'), ('3', 'joe'), ('4', 'fred')], + 'phones':[{'location':'bar', 'number':'555-1212'}, + {'location':'work', 'number':'555-8989'},], + } + schema = Person() + try: + schema.deserialize(data) + except colander.Invalid, e: + errors = e.asdict() + print errors + +This will print something like: + +.. code-block:: python + :linenos: + + {'age':'-1 is less than minimum value 0', + 'friends.1.0':'"t" is not a number', + 'phones.0.location:'"bar" is not one of "home", "work"'} + Defining A Schema Imperatively ------------------------------