lsst.pex.exceptions
8.0.0.0-23-g68b96ed
|
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 SWIG, 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 Swig 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: someSwiggedFunction() # assume this throws NotFoundError except lsst.pex.exceptions.NotFoundError as err: pass
In addition, you can catch this same error using either the LSST Exception base class:
try: someSwiggedFunction() # 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)
or Python's built-in StandardError class (from which all LSST exceptions inherit):
try: someSwiggedFunction() # 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: someSwiggedFunction() # 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 (this feature could potentially added if be desired, but currently we see no need for it).
Generally, raising a pure-Python exception is preferred over raising a wrapped C++ exception. When some implementations of an interface are in Swigged 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'
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
When creating Swig wrappers for a C++ library that throws LSST exceptions, include the following in the Swig interface file:
%{ #include "lsst/pex/exceptions.h" %} %import "lsst/pex/exceptions/exceptionsLib.i"
as well as either:
%include "lsst/pex/exceptions/handler.i"
or (if depending on utils as well as pex_exceptions):
%include "lsst/utils/p_lsstSwig.i"
Then, finally:
%lsst_exceptions()
Note that this procedure has not changed from previous versions of pex_exceptions, though the actual exception handling code is now in pex_exceptions and included by utils' p_lsstSwig.i, instead of being defined directly there.
For C++ Developers: Wrapping New C++ Exceptions
When creating Swig wrappers for a C++ library that defines a new C++ exception, use the %declareException (see declareException) Swig macro, which is defined in exceptionsLib.i. This will rename the Swig-generated wrapper class by prefixing it with a "_Cpp", then generate a special wrapper class with the original name. Like the standard exception wrappers, the wrappers for custom exceptions will automatically forward attribute requests to the Swig-generated wrapper, so there should be no need to add custom methods to the wrapper.
See tests/testLib.i for a complete example.