views:

408

answers:

3

I'm writing a SWIG wrapper around a custom C++ library which defines its own C++ exception types. The library's exception types are richer and more specific than standard exceptions. (For example, one class represents parse errors and has a collection of line numbers.) How do I propagate those exceptions back to Python while preserving the type of the exception?

A: 

From the swig documentation

%except(python) {
try {
$function
}
catch (RangeError) {
  PyErr_SetString(PyExc_IndexError,"index out-of-bounds");
 return NULL;
}
}
Glen
This is close. It preserves the type of the exception but how do I encapsulate custom exception types? Perhaps the SWIG examples cover this.
Barry
A: 

Is the swig exception documentation any help? It mentions defining different exception handlers..

Jon Cage
+3  A: 

I know this question is a few weeks old but I just found it as I was researching a solution for myself. So I'll take a stab at an answer, but I'll warn in advance it may not be an attractive solution since swig interface files can be more complicated than hand coding the wrapper. Also, as far as I can tell, the swig documentation never deals directly with user defined exceptions.

Let's say you want to throw the following exception from your c++ code module, mylibrary.cpp, along with a little error message to be caught in your python code:

throw MyException("Highly irregular condition...");  /* C++ code */

MyException is a user exception defined elsewhere.

Before you begin, make a note of how this exception should be caught in your python. For our purposes here, let's say you have python code such as the following:

import mylibrary
try:
    s = mylibrary.do_something('foobar')
except mylibrary.MyException, e:
    print(e)

I think this describes your problem.

My solution involves making four additions to your swig interface file (mylibrary.i) as follows:

Step 1: In the header directive (the usually unnamed %{...%} block) add a declaration for the pointer to the python aware exception, which we will call pMyException. Step 2 below will define this:

%{
#define SWIG_FILE_WITH_INIT  /* for eg */
extern char* do_something(char*);   /* or #include "mylibrary.h" etc  */
static PyObject* pMyException;  /* add this! */
%}

Step 2: Add an initialization directive (the "m" is particularly egregious, but that's what swig v1.3.40 currently needs at that point in its constructed wrapper file) - pMyException was declared in step 1 above:

%init %{
    pMyException = PyErr_NewException("_mylibrary.MyException", NULL, NULL);
    Py_INCREF(pMyException);
    PyModule_AddObject(m, "MyException", pMyException);
%}

Step 3: As mentioned in an earlier post, we need an exception directive - note "%except(python)" is deprecated. This will wrap the c+++ function "do_something" in a try-except block which catches the c++ exception and converts it to the python exception defined in step 2 above:

%exception do_something {
    try {
        $action
    } catch (MyException &e) {
        PyErr_SetString(pMyException, const_cast<char*>(e.what()));
        return NULL;
    }
}

/* The usual functions to be wrapped are listed here: */
extern char* do_something(char*);

Step 4: Because swig sets up a python wrapping (a 'shadow' module) around the .pyd dll, we also need to make sure our python code can 'see through' to the .pyd file. The following worked for me and it's preferable to editing the swig py wrapper code directly:

%pythoncode %{
    MyException = _mylibrary.MyException
%}

It's probably too late to be of much use to the original poster, but perhaps somebody else will find the suggestions above of some use. For small jobs you may prefer the cleanness of a hand-coded c++ extension wrapper to the confusion of a swig interface file.


Added:

In my interface file listing above, I omitted the standard module directive because I only wanted to describe the additions necessary to make exceptions work. But your module line should look like:

%module mylibrary

Also, your setup.py (if you are using distutils, which I recommend at least to get started) should have code similar to the following, otherwise step 4 will fail when _mylibrary is not recognized:

/* setup.py: */
from distutils.core import setup, Extension

mylibrary_module = Extension('_mylibrary', extra_compile_args = ['/EHsc'],
                    sources=['mylibrary_wrap.cpp', 'mylibrary.cpp'],)

setup(name="mylibrary",
        version="1.0",
        description='Testing user defined exceptions...',
        ext_modules=[mylibrary_module],
        py_modules = ["mylibrary"],)

Note the compile flag /EHsc, which I needed on Windows to compile exceptions. Your platform may not require that flag; google has the details.

I have tested the code using my own names which I have converted here to "mylibrary" and "MyException" to help in generalizing the solution. Hopefully the transcription errors are few or none. The main points are the %init and %pythoncode directives along with

static PyObject* pMyException;

in the header directive.

Hope that clarifies the solution.

SeanManef
This looks very promising -- I will try it out. It is not too late, btw, because for now we are just catching RuntimeError and examining the text of the error messages -- yuck.
Barry