Reputation: 338
Here are examples of functions in my C API:
int do_something(struct object *with_this)
struct object *get_something(struct object *from_this)
do_something
will return 0
for success or -1
and set errno
for failure. get_something
will return a valid pointer on success or NULL
and set errno
for failure.
I'm using to SWIG 2.0 to generate python bindings. For the do_something
binding I'd like it to return an exception based on errno
if it fails and return the Python None
object if it succeeds. For the get_something
binding I'd like it to return an exception based on errno
if it fails and the opaque object if it succeeds.
The question is how do I get SWIG to do all of this magic for me?
Currently I'm using SWIG 2.0.
I can use %exception
and define different exceptions per symbol, but I'd like it to be based on return type. I'll have a lot of the API functions and I don't want to list them out. So I can do this for each symbol:
%exception do_something {
$action
if (result < 0) {
return PyErr_SetFromErrno(PyExc_RuntimeError);
}
}
%exception get_something {
$action
if (result == NULL) {
return PyErr_SetFromErrno(PyExc_RuntimeError);
}
}
It would be a lot better if I could do something like this (like you can do with %typemap
):
%exception int {
$action
if (result < 0) {
return PyErr_SetFromErrno(PyExc_RuntimeError);
}
}
%exception struct object * {
$action
if (result == NULL) {
return PyErr_SetFromErrno(PyExc_RuntimeError);
}
}
Can I do something like this? If I had something like this, then that would take care of the exceptions. And I believe my get_something
binding would be perfect.
My do_something
binding would still need something like this to get it to return the Python None object if I use %typemap
:
%typemap(out) int {
$result = Py_BuildValue("");
}
Does the magic I'm looking for exist or am I going about this all wrong? Is there a better way to do this?
Upvotes: 1
Views: 365
Reputation: 88711
To help answer this question I took your functions and created the following test.h file that included enough real code to actually illustrate the problem:
struct object { int bar; };
static int do_something(struct object *with_this) {
errno = EACCES;
return -1;
}
static struct object *get_something(struct object *from_this) {
errno = EAGAIN;
return NULL;
}
I'm fairly sure that %typemap(out)
is the place to write this code in, not %exception
, because the behaviour you want is determined by the return type, not the name of the function called. The fact that you also care about the value of the return further backs this up. You can to some degree avoid repetition using $typemap
to reference other typemaps within yours.
So I wrote a little SWIG interface to illustrate this:
%module test
%{
#include <errno.h>
#include "test.h"
%}
%typemap(out) int do_something %{
if ($1 < 0) {
PyErr_SetFromErrno(PyExc_RuntimeError);
SWIG_fail;
}
$result = Py_None; // Return only controls exception
Py_INCREF($result);
%}
%typemap(out) struct object *get_something %{
if (!$1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
SWIG_fail;
}
// Return passed through unless NULL
$typemap(out,$1_ltype);
%}
%include "test.h"
Which worked when tested:
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.do_something(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: (13, 'Permission denied')
>>> test.get_something(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: (11, 'Resource temporarily unavailable')
>>>
This only worked though because I explicitly named do_something
when matching the typemap, so the fallback without the named function is fine. If you just try to remove that restriction you'll get an error about recursive typemaps. You can work around that using %apply
, e.g.
%typemap(out) struct object * MY_NULL_OR_ERRNO %{
if (!$1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
SWIG_fail;
}
$typemap(out,$1_ltype);
%}
%apply struct object * MY_NULL_OR_ERRNO { struct object *get_something, struct object *some_other_function };
If that's too painful you can of course just write the typemap by hand:
%typemap(out) struct object * %{
if (!$1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
SWIG_fail;
}
$result = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, $owner);
%}
Which seems the simplest option if you really want that everywhere a struct object*
gets returned.
There are other possible solutions using %pythoncode
or %pythonappend
, but neither of them are really improvements over the above in my view.
Upvotes: 2