Skip to content

Commit

Permalink
add debugging doc, make sure all exceptions are documented somewhere …
Browse files Browse the repository at this point in the history
…and linked in the exception index
  • Loading branch information
mahmoud committed Jul 7, 2020
1 parent 7478496 commit 5f0c2a2
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 44 deletions.
43 changes: 8 additions & 35 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,53 +124,26 @@ just through redundancy.

.. autoclass:: glom.Ref

.. _exceptions:
Core Exceptions
---------------

Not all data is going to match specifications. Luckily, glom errors
are designed to be as readable and actionable as possible.

Exceptions
----------

glom introduces a few new exception types designed to maximize
readability and debuggability. Note that all these errors derive from
:exc:`GlomError`, and are only raised from :func:`glom()` calls, not
from spec construction or glom type registration. Those declarative
and setup operations raise :exc:`ValueError`, :exc:`TypeError`, and
other standard Python exceptions as appropriate.
All glom exceptions inherit from :exc:`GlomError`, described below,
along with other core exception types. For more details about handling
and debugging exceptions, see ":doc:`debugging`".

.. autoclass:: glom.PathAccessError

.. autoclass:: glom.CoalesceError

.. autoclass:: glom.UnregisteredTarget

.. autoclass:: glom.MatchError
:noindex:

.. autoclass:: glom.TypeMatchError
:noindex:

.. autoclass:: glom.CheckError
:noindex:

.. autoclass:: glom.PathAssignError
:noindex:

.. autoclass:: glom.PathDeleteError
:noindex:
.. auto:: glom.BadSpec

.. autoclass:: glom.GlomError

.. _debugging:


Debugging
---------

Even the most carefully-constructed specifications eventually need
debugging. If the error message isn't enough to fix your glom issues,
that's where **Inspect** comes in.

.. autoclass:: glom.Inspect

.. _setup-and-registration:

Expand Down
117 changes: 117 additions & 0 deletions docs/debugging.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
Exceptions and Debugging
========================

While glom works well when all goes as intended, it even shines when
data doesn't match expectations. glom's error messages and exception
hierarchy have been designed to maximize readability and
debuggability. Read on for a listing of glom's exceptions and how to
debug them.

.. contents:: Contents
:local:

.. _exceptions:

Exceptions
----------

glom introduces a several new exception types designed to maximize
readability and debuggability. Note that all these errors derive from
:exc:`GlomError`, and are only raised from :func:`glom()` calls, not
from spec construction or glom type registration. Those declarative
and setup operations raise :exc:`ValueError`, :exc:`TypeError`, and
other standard Python exceptions as appropriate.

Here is a short list of links to all public exception types in glom.

.. hlist::
:columns: 3

* :exc:`~glom.GlomError`
* :exc:`~glom.PathAccessError`
* :exc:`~glom.PathAssignError`
* :exc:`~glom.PathDeleteError`
* :exc:`~glom.CoalesceError`
* :exc:`~glom.FoldError`
* :exc:`~glom.MatchError`
* :exc:`~glom.TypeMatchError`
* :exc:`~glom.CheckError`
* :exc:`~glom.UnregisteredTarget`
* :exc:`~glom.BadSpec`


Reading a glom Exception
------------------------

glom errors are regular Python exceptions, but may look a little
different from other Python errors. Because glom is a data
manipulation library, glom errors include a data traceback,
interleaving spec and target data.

For example, let's raise an error by glomming up some data that doesn't exist:

.. code-block:: default
:linenos:
>>> target = {'planets': [{'name': 'earth', 'moons': 1}]}
>>> glom(target, ('planets', ['rings']))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/mahmoud/projects/glom/glom/core.py", line 1787, in glom
raise err
glom.core.PathAccessError: error raised while processing, details below.
Target-spec trace (most recent last):
- Target: {'planets': [{'name': 'earth', 'moons': 1}]}
- Spec: ('planets', ['rings'])
- Spec: 'planets'
- Target: [{'name': 'earth', 'moons': 1}]
- Spec: ['rings']
- Target: {'name': 'earth', 'moons': 1}
- Spec: 'rings'
glom.core.PathAccessError: could not access 'rings', part 0 of Path('rings'), got error: KeyError('rings')
Let's step through this output:


* Line **1**: We created a planet registry, similar to the one in the :doc:`tutorial`.
* Line **2-3**: We try to get a listing of ``rings`` of all the planets. Instead, we get a Python traceback.
* Line **7**: We see we have a :exc:`~glom.PathAccessError`.
* Line **8-9**: The "target-spec trace", our data stack, begins. It always starts with the target data as it was passed in.
* Line **10**: Next is the top-level spec, as passed in: ``('planets', ['rings'])``
* Line **11**: glom takes the first part of the spec from line 9, ``'planets'``, to get the next target.
* Line **12**: Because the spec on line 11 updated the current target, glom outputs it. When a spec is evaluated but the target value is unchanged, the target is skipped in the trace.
* Line **14-15**: We get to the last two lines, which include the culprit target and spec
* Line **16**: Finally, our familiar :exc:`~glom.PathAccessError` message,
with more details about the error, including the original ``KeyError('rings')``.

This view of glom evaluation answers many of the questions
a developer or user would ask upon encountering the error:

* What was the data?
* Which part of the spec failed?
* What was the original error?

The data trace does this by peeling away at the target and spec until
it hones in on the failure. Both targets and specs in traces are
truncated to terminal width to maximize readability.

.. note::

If for some reason you need the full Python stack instead of the
glom data traceback, pass ``glom_debug=True`` to the top-level glom
call.

.. _debugging:


Debugging
---------

Good error messages are great when the data has a problem, but what
about when a spec is incorrect?

Even the most carefully-constructed specifications eventually need
debugging. If the error message isn't enough to fix your glom issues,
that's where **Inspect** comes in.

.. autoclass:: glom.Inspect
7 changes: 6 additions & 1 deletion docs/grouping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ of data to a smaller set, otherwise known as "grouping" or
"reduction".

Combining iterables with Flatten and Merge
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
------------------------------------------

.. versionadded:: 19.1.0

Expand All @@ -26,3 +26,8 @@ specifier types and two convenience functions:
.. autoclass:: glom.Sum

.. autoclass:: glom.Fold

Exceptions
----------

.. autoclass:: glom.FoldError
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ There's much, much more to glom, check out the :doc:`tutorial` and :doc:`API ref
streaming
grouping
matching
debugging

.. toctree::
:maxdepth: 1
Expand Down
1 change: 0 additions & 1 deletion docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ declarative data transformations.
Go beyond basic with 10 minutes or less, and even further if you
can spare a half hour.


.. contents:: Contents
:local:

Expand Down
19 changes: 12 additions & 7 deletions glom/tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
That's more like it! We have a function that can give us our data, or
give us an error message we can read, understand, and act upon.
.. seealso::
For more on glom's error messages, see :doc:`debugging`.
Interactive Deep Get
--------------------
Expand Down Expand Up @@ -389,16 +393,17 @@
``primary_email.email`` would, outside of glom, result in an
AttributeError or TypeError like the one we described before the
Contact example. Inside of a glom ``Coalesce``, exceptions are caught
and we move on to the next spec. glom raises a ``CoalesceError`` when
no specs match, so we use ``default`` to tell it to return None
instead.
and we move on to the next spec. glom raises a
:class:`~glom.CoalesceError` when no specs match, so we use
``default`` to tell it to return None instead.
Some Contacts have nicknames or other names they prefer to go by, so
for ``pref_name``, we want to return the stored ``pref_name``, or fall
back to the normal name. Again, we use ``Coalesce``, but this time we
tell it not only to ignore the default ``GlomError`` exceptions, but
also ignore empty string values, and finally default to empty string
if all specs result in empty strings or :exc:`~glom.GlomError`.
back to the normal name. Again, we use :class:`~glom.Coalesce`, but
this time we tell it not only to ignore the default
:exc:`~glom.GlomError` exceptions, but also ignore empty string
values, and finally default to empty string if all specs result in
empty strings or :exc:`~glom.GlomError`.
And finally, for our last field, ``detail``, we want to conjure up a
bit of info that'll help jog the user's memory. We're going to include
Expand Down

0 comments on commit 5f0c2a2

Please sign in to comment.