Exception Handling in PyTrilinos

All C++ exceptions raised by Trilinos should be caught by PyTrilinos and converted to python exceptions. Fortunately, there is a relatively straightforward SWIG facility for doing this. Each SWIG interface file shall include an %exception directive prior to any Trilinos header file %include directives:

%include "exception.i"
%exception
{
  try
  {
    $action
  }
  catch(...)
  {
    SWIG_exception(SWIG_UnknownError, "Unknown C++ exception");
  }
}

It is possible to implement an %exception directive that includes a symbol name, prior to the first "{", that is specific to a function or method. By omitting this symbol name, we are applying this %exception to all functions and methods that get wrapped. Here, $action is a SWIG macro that is replaced by the generated code for calling the wrapped function or method. The catch(...) syntax ensures that every exception that might be thrown gets caught.

SWIG_exception is a C macro #define-ed at the top of the generated source file. SWIG_UnknownError is also a macro that evaluates to an integer. See the SWIG documentation for valid SWIG error macro names.

The directive given above is useful, but all exceptions will get converted to type UnknownError with a nearly meaningless error message. Realistically, we need to convert a wider range of exceptions to more meaningful python exception types, and produce more useful error messages. SWIG also provides a macro for treating most standard C++ exceptions, converting them to appropriate python exceptions and extracting their error message from their what() method. Simply change the %exception directive to:

%exception
{
  try
  {
    $action
  }
  SWIG_CATCH_STDEXCEPT
  catch(...)
  {
    SWIG_exception(SWIG_UnknownError, "Unknown C++ exception");
  }
}

and this will convert the vast majority of standard exceptions to appropriate python exceptions with useful error messages.

There are Trilnios packages that throw exceptions other than those found in the standard library. These can be caught anywhere prior to the catch(...) syntax, although in general, it is best to put them before the SWIG_CATCH_STDEXCEPT macro, especially if the package exceptions inherit from the standard exceptions. Here is the current Teuchos exception handler:

%exception
{
  try
  {
    $action
    if (PyErr_Occurred()) SWIG_fail;
  }
  catch(Teuchos::Exceptions::InvalidParameterType & e)
  {
    SWIG_exception(SWIG_TypeError, e.what());
  }
  catch(Teuchos::Exceptions::InvalidParameter & e)
  {
    PyErr_SetString(PyExc_KeyError, e.what());
    SWIG_fail;
  }
  SWIG_CATCH_STDEXCEPT
  catch(...)
  {
    SWIG_exception(SWIG_UnknownError, "Unknown C++ exception");
  }
}

A few notes: (1) After the $action macro, there is a call to PyErr_Occurred(). This is because the Teuchos wrappers %extend certain classes and those new methods can set python errors. Alternatively, you could raise C++ exceptions in all of these extensions, and then skip the PyErr_Occurred() check. (2) SWIG_fail is a C macro provided by SWIG that evaluates to goto fail, where fail is a label that exists within all wrapper functions. (3) The Teuchos::Exceptions::InvalidParameter exception is most closely related to the python KeyError exception, but SWIG does not have a corresponding SWIG error for this. Therefore, I use the PyErr_SetString() function and SWIG_fail macro.

Practical Considerations

Most PyTrilinos packages will need to %import "Teuchos.i" and/or %import "Epetra.i". Both of these interface files implement their own %exception directive, but both of them "turn off" exception handling by including a:

%exception;

at the end of the file. This is considered good practice and should be followed in all PyTrilinos SWIG interface files.

Nevertheless, experience shows that the following represents the best order for %include-s and %import-s when dealing with exceptions in PyTrilinos:

%include "exception.i"
%import "Teuchos.i"
%import "Epetra.i"
%exception
{
  ...
}

Putting the %include "exception.i" after the %import directives can result in undefined symbols when you compile the generated wrapper code.

Finally, every effort should be made to prevent users from getting an Unknown C++ exception error message. Study the package to determine as many of the possible exceptions that might be thrown as you can, and explicitly include them in the %exception directive. Whenever testing or use of PyTrilinos results in an Unknown C++ exception error message, track it down and then explicitly allow for it within the appropriate SWIG interface file. There is no excuse for a meaningless error message.