LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
lsst::pex::exceptions; LSST Exceptions

Introduction

LSST C++ exceptions are designed to automatically provide information about where the exception was thrown from. Exception subclasses can be defined to more precisely delineate their causes. Context information can be provided through a simple message or, in rare cases, additional instance variables within exception subclasses; caught and rethrown exceptions can have additional context information appended.

Python Interface

For Python Users: Catching C++ Exceptions

Python wrappers for the C++ exception objects are generated using pybind11, with an additional custom wrapper layer on top. This additional layer allows the wrapped exceptions to inherit from Python's built-in Exception class, which is necessary for them to be raised or caught in Python. These custom wrappers have the same names as their C++ counterparts. The immediate pybind11 wrappers should not be used by users, and as such are generally renamed or not imported into a package namespace to hide them.

This means that to catch a C++ exception in Python (we'll use pex::exceptions::NotFoundError), you can simply use:

try:
someWrappedFunction() # assume this throws NotFoundError
pass
Reports attempts to access elements using an invalid key.
Definition: Runtime.h:151
A base class for image defects.

In addition, you can catch this same error using either the LSST Exception base class:

try:
someWrappedFunction() # assume this throws NotFoundError
except lsst.pex.exceptions.Exception as err:
# Note that 'err' is still the most-derived exception type:
assert isinstance(err, lsst.pex.exceptions.NotFoundError)
Provides consistent interface for LSST exceptions.
Definition: Exception.h:107

or Python's built-in StandardError class (from which all LSST exceptions inherit):

try:
someWrappedFunction() # assume this throws NotFoundError
except StandardError as err:
# Once again, 'err' is still the most-derived exception type:
assert isinstance(err, lsst.pex.exceptions.NotFoundError)

In addition, we've multiply-inherited certain LSST exceptions from obvious Python counterparts:

This means that there's one more way to catch our NotFoundError:

try:
someWrappedFunction() # assume this throws NotFoundError
except LookupError as err:
# Once again, 'err' is still the most-derived exception type:
assert isinstance(err, lsst.pex.exceptions.NotFoundError)

When working out what exception specifiers will match a given exception, it's also worth keeping in mind that many LSST exceptions inherit from lsst::pex::exceptions::RuntimeError, and hence inherit indirectly from Python's RuntimeError.

For Python Users: Raising C++ Exceptions

LSST Exceptions can also be raised just like any other Python exception. The resulting exception object cannot be passed back to C++ functions, however, unlike other wrapped C++ objects (if this is needed, you can instead pass "err.cpp" instead of "err").

Generally, raising a pure-Python exception is preferred over raising a wrapped C++ exception. When some implementations of an interface are in wrapped C++ and others are in pure-Python, however, it may be better to raise wrapped C++ exceptions even in the pure-Python implementation, so calling code that wants to catch exceptions is better insulated from the choice of implementation language.

One can also define custom Python exceptions that inherit from wrapped LSST C++ exceptions, in the same way one would inherit from any other exception:

class MyCustomException(lsst.pex.exceptions.NotFoundError):
pass

Printing Exceptions and Tracebacks

The C++ and Python stringification methods of LSST exceptions have been carefully tuned to allow the partial C++ traceback to look broadly like a continuation of the Python traceback provided by Python itself. For example, if we call the failException1() function that's part of the test suite for pex_exceptions, the traceback looks like this:

>>> import testLib
>>> testLib.failException1("my message")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "testLib.py", line 637, in failException1
return _testLib.failException1(*args)
lsst.pex.exceptions.Exception:
File "tests/testLib.i", line 64, in void fail1(const string&) [with T = lsst::pex::exceptions::Exception, std::string = std::basic_string<char>]
my message {0}
lsst::pex::exceptions::Exception: 'my message'
def line(points, frame=None, origin=afwImage.PARENT, symbs=False, ctype=None, size=0.5)
Definition: ds9.py:104
std::vector< Tracepoint > Traceback
Definition: Exception.h:95
STL namespace.

This is done by having the str() method of the Python exception wrapper classes return a newline followed by the C++ traceback (so this takes over in the above just after "lsst.pex.exceptions.Exception: "). Unfortunately, custom exception traceback formatting (such as that provided by IPython) will not be applied to the C++ traceback. This appears to be impossible to support.

When a C++ exception is raised in Python, str() will just return the string associated with exception, generating a traceback like this:

>>> import lsst.pex.exceptions
>>> raise lsst.pex.exceptions.NotFoundError("my message")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
lsst.pex.exceptions.wrappers.NotFoundError: my message

This is because C++ exceptions raised in Python do not carry any traceback information - Python handles traceback information separately, and hence there's no need to duplicate it in the Python object.

Both of the str() methods delegate to the C++ addToStream() method, which is also used to implement stream (<<) output; these return the same as str().

In both cases, repr() is defined to return the name of the exception class with the message following in parenthesis, as is standard in Python:

NotFoundError('my message')

For C++ Developers: Invoking Exception Translation

Make sure lsst.pex.exceptions is imported before the exception is raised, as this will register the automatic translators from C++ to Python exceptions.

For C++ Developers: Wrapping New C++ Exceptions

When creating pybind11 wrappers for a C++ library that defines a new C++ exception, use the declareException (see lsst::pex::exceptions::python::declareException) function template, which is defined in include/lsst/pex/exceptions/python/Exception.h. This will generate a new custom exception type and will enable automatic translation of this exception type from C++ to Python. It returns a standard pybind11::class_ object for the exception that you can add custom constructors and members to as needed.

See tests/testLib.cc for a complete example.

Transition from LsstCppException and LsstException

In older versions of pex_exceptions, there were two Python classes that filled roles now covered by the Python lsst.pex.exceptions.Exception class:

  • LsstCppException provided a pure-Python wrapper that inherited from Python's built-in Exception class.
  • LsstException was the name given to the Swig-wrapped C++ lsst::pex::exceptions::Exception class. Derived classes were not renamed.

Older code that uses catches LsstCppException and then checks the type of the LsstException subclass it holds should be converted to catch the derived class exception directly. For instance, older code that looks like this:

try:
...
except lsst.pex.exceptions.LsstCppException as err:
if isinstance(err.args[0], lsst.pex.exceptions.RuntimeError):
...
Reports errors that are due to events beyond the control of the program.
Definition: Runtime.h:104
T raise(T... args)

should be transformed to this to get the same effect:

try:
...
...

Note that there is no need to re-raise here; if an exception other than RuntimeError is thrown in this example, the except block will not match and the exception will propagate up normally.