How to forward a custom exception to Python

Lukáš Hrázký <[email protected]>
Newsgroups gmane.comp.programming.swig
Message-ID <[email protected]>
Hello,

I'm having trouble defining a custom exception in a library and using
it in Python bindings for the library generated by SWIG. I need to
catch this exception in Python (as a user of the library) and handle it
in a special way.

(As a bit of a side note) I've found little to no information on how to
do this in SWIG documentation, this is what I've gathered from various
Stack Overflow posts and put together over hours of trial and error.

I've got these files:

// mylib/error.hpp
#include <stdexcept>

class Error : public std::runtime_error {
public: 
    Error(const std::string & what) : runtime_error(what) {}
};


// bindings/error.i
%module error

%{
    #include "mylib/error.hpp"
    #define SWIG_FILE_WITH_INIT
    PyObject* m_Error;
%}

%init %{
    m_Error = PyErr_NewException("mylib._error.Error", NULL, NULL);
    Py_INCREF(m_Error);
    PyModule_AddObject(m, "Error", m_Error);
%}

%pythoncode %{
    Error = _error.Error
%}


// catch_error.i
%include <exception.i>

%{
    #include "mylib/error.hpp"
    extern PyObject* m_Error;
%}

%exception {
    try {
        $action
    } catch (const Error & e) {
        PyErr_SetString(m_Error, const_cast<char*>(e.what()));
        SWIG_fail;
    }
}


And then I have multiple other modules in the bindings, in which i do:
%include "catch_error.i"

Which basically adds a common try-catch for any wrapped function to
catch the C++ exceptions and rethrow them as Python exceptions in a
uniform way.

Now the problem with this is the m_Error global variable. The variable
is allocated in the _error.so module generated by the bindings, but the
other modules need to access it as well to set the error text.
Therefore, they need the m_Error symbol loaded. This is where it gets
tough:

* If I link the other modules against _error.so, and _error.so is in
  LD_LIBRARY_PATH, it works. (this is not normally the case, so not a
  good solution)

* If I link the other modules against _error.so and make sure the
  mylib.error Python module is imported first, it works. (this is what
  I have now, but the solution is a borderline hack and looks very
  fragile)

* If I don't link against _error.so and use:
  import sys, os
  sys.setdlopenflags(os.RTLD_NOW | os.RTLD_GLOBAL)

  Before importing any of my modules, it works (found this in [1])
  (also looks like a hack and messing with Python internals)

Otherwise I get "undefined symbol: m_Error" when importing the python
modules.
  
How can I solve this? It seems to me like having a custom exception
propagated to Python is a rather common use case and the "right" way to
do error handling. I find it hard to believe I'm the first one to run
into this. This could obviously work if I only had a single Python
module with all the SWIG bindings. But I don't really want that and
SWIG documentation also recommends multiple modules.

I'm doing this for libdnf Python bindings, if interested, you can find
my pull request for it at [2].

Thanks in advance,
Lukas

[1] https://stackoverflow.com/questions/7195656/linking-scope-of-loaded-symbols-when-using-swig-to-wrap-c-for-python/7216672
[2] https://github.com/rpm-software-management/libdnf/pull/866
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.