Simpler and Safer: Shadow classes for calling Eiffel from Python

We solved the earlier problem of the complex and non-object-oriented Python/C API by shuffling Python/C API calls through a series of shadow classes, and so far the problem looks very similar--we have a series of simple functions which require an opaque pointer to an object, and need to wrap those into a form which seems more compatible with the target language.

The problem here is a little stranger. First, all the functions are lumped into a single module. When we were exposing the Python/C API to Eiffel, this was acceptable, because we were hand-coding a hierarchy of classes; each class could inherit from PYTHON_CLIENT and then only use a small subset of those functions, and since we were handcoding each class, it was fairly easy to decide what to keep and what to discard.

Second, when exposing a Python function to Eiffel, the method signature was easy--Python functions simply took a tuple of arguments and we left it to the Python side of things to determine if we were handing off too many arguments or too few. But when automatically generating Python stubs, it's important to know how many arguments to take so that the function prototype can properly match the C function that it is wrapping.

But where is this information located? The Eiffel source files certainly contain both sets of information--related functions are located together in a class, and the functions' signatures are located there as well. A more precise specification of the function signatures is located in the generated eiffel-glue.h header file. Using those two sources, we could indeed sit down and hand-generate a series of Python classes which do what we need. First, we must create a base class which encapsulates the object pointer, much as we did with the PYTHON_OBJECT class earlier--but since basic Eiffel objects have few methods, this class will be much easier, only containing the opaque pointer and functions for setting it:

Example 11-8. The eiffel_object base class

__doc__ = """

A base class for eiffel objects, this simply holds a pointer to the object in question.

"""

class eiffel_object :

    swig_pointer = "NULL"

    def __init__( self, new_pointer ) :
        "initializes with the given pointer"
        self.set_pointer( new_pointer )

    def set_pointer( self, new_pointer ):
        "sets the pointer--could be hazardous"
        self.swig_pointer = new_pointer


We can then inherit from this base class to implement the desired functionality. For example, to create a shadow class for our earlier CALLED_FROM_PYTHON class, we could write the following:

Example 11-9. The eiffel_CALLED_FROM_PYTHON class

import eiffel_object
import eiffel_glue

class eiffel_CALLED_FROM_PYTHON( eiffel_object.eiffel_object ) : 
        "Python shadow class for the Eiffel class CALLED_FROM_PYTHON"

        def print_message( self ) : 
                "returns void"
                return eiffel_glue.test_print_message( self.swig_pointer )

        def print_integer_addition( self, a1, a2 ) : 
                "returns void"
                return eiffel_glue.test_print_integer_addition( self.swig_pointer, a1, a2 )

We adopt a simple naming convention--prepend the Eiffel class name (left in upper case to distinguish it from regular Python classes) with the word "eiffel". For each exported feature, we simply pass on the arguments with the object's opaque pointer to the Eiffel object, sending those arguments to the exposed function in the eiffel_glue module. This actually works; once we get a pointer to an Eiffel-based CALLED_FROM_PYTHONobject, we can create a Python-based eiffel_CALLED_FROM_PYTHON object and use its features under Python exactly as if we were manipulating the object directly. The process of creating a Python shadow class works--albeit with much effort and the great possibility of human error.

We'll have none of that.