LSSTApplications
18.1.0
LSSTDataManagementBasePackage
|
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.
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:
In addition, you can catch this same error using either the LSST Exception base class:
or Python's built-in StandardError class (from which all LSST exceptions inherit):
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:
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:
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:
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:
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:
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.
In older versions of pex_exceptions, there were two Python classes that filled roles now covered by the Python lsst.pex.exceptions.Exception class:
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:
should be transformed to this to get the same effect:
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.