# 3. Case 1 - DBo Derived, Standalone — Coriolis 2 documentation

# 3. Case 1 - DBo Derived, Standalone

As example, we take `Library`. This a `DBo` derived class, but we choose not to export the parent classes. From Python, it will appear as a base class.

## 3.1 Class Associated Header File

Here is the typical content of a header file (for `PyLibrary`):

    #ifndef  PY_LIBRARY_H
    #define  PY_LIBRARY_H

    #include "hurricane/isobar/PyHurricane.h"
    #include "hurricane/Library.h"

    namespace  Isobar {
      using namespace Hurricane;

      extern "C" {

        typedef struct {
            PyObject_HEAD
            Library* _object;
        } PyLibrary;

        extern  PyTypeObject  PyTypeLibrary;
        extern  PyMethodDef   PyLibrary_Methods[];
        extern  PyObject*     PyLibrary_Link       ( Hurricane::Library* lib );
        extern  void          PyLibrary_LinkPyType ();

    #define IsPyLibrary(v) ( (v)->ob_type == &PyTypeLibrary )
    #define PYLIBRARY(v)   ( (PyLibrary*)(v) )
    #define PYLIBRARY_O(v) ( PYLIBRARY(v)->_object )

      }  // extern "C".
    }  // Isobar namespace.

    #endif  // PY_LIBRARY_H

The code is organized as follow:

1. It must have, as the first include `PyHurricane.h`, which provides the complete bunch of macros needed to build the module. Then the include of the C++ class we want to wrap (`Library.h`).

2. As Python is written in C, all the wrapper code has to be but inside an `extern "C"` namespace.

3. Definition of the wrapped struct, `PyLibrary`. It is standard Python here.

   Note

   For our set of macros to work, the name of the pointer to the C++ class must always be _object, and the various functions and macros defined here must take the name of the class (either in lowercase, camel case or capitals).

4. Declaration of the Python type `PyTypeLibrary` (standard).

5. Declaration of the Python type table of methods `PyLibrary_Methods` (standard).

6. Declaration of `PyLibrary_Link()`, helper to convert a C++ `Lybrary` into a `PyLibrary` (put in the support shared library).
7. Declaration of `PyLibrary_LinkPyType()`, this function setup the class-level function of the new Python type (here, `PyTypeLibrary`).
8. And, lastly, three macros to:
   - `IsPylibrary()`, know if a Python object is a `PyLibrary`
   - `PYLIBRARY()`, force cast (C style) of a `PyObject` into a `PyLibrary`.
   - `PYLIBRARY_O()`, extract the C++ object (`Library*`) from the Python object (`PyLibrary`).

## 3.2 Class Associated File

### 3.2.1 Head of the file

    #include "hurricane/isobar/PyLibrary.h"
    #include "hurricane/isobar/PyDataBase.h"
    #include "hurricane/isobar/PyCell.h"

    namespace Isobar {
      using namespace Hurricane;

      extern "C" {

    #define  METHOD_HEAD(function)   GENERIC_METHOD_HEAD(Library,lib,function)

As for the header, all the code must be put inside a `extern "C"` namespace.

A convenience macro `METHOD_HEAD()` must be defined, by refining `GENERIC_METHOD_HEAD()`. This macro will be used in the method wrappers below to cast the `_object` field of the Python object into the appropriate C++ class, this is done using a C-style cast. The parameters of that macro are:

1. The C++ encapsulated class (`Library`).
2. The name of the variable that will be used to store a pointer to the C++ working object.
3. The name of the C++ method which is to be wrapped.

### 3.2.2 The Python Module Part

First, we have to build all the wrappers to the C++ methods of the class. For common predicates, accessors, and mutators macros are supplied.

Wrapping of the `Library::getCell()` method:

    static PyObject* PyLibrary_getCell ( PyLibrary* self, PyObject* args )
    {
      Cell* cell = NULL;

      HTRY
        METHOD_HEAD( "Library.getCell()" )
        char* name = NULL;
        if (PyArg_ParseTuple(args,"s:Library.getCell", &name)) {
          cell = lib->getCell( Name(name) );
        } else {
          PyErr_SetString( ConstructorError
                         , "invalid number of parameters for Library::getCell." );
          return NULL;
        }
      HCATCH

      return PyCell_Link(cell);
    }

Key points about this method wrapper:

1. The `HTRY` / `HCATCH` macros provides an insulation from the C++ exceptions. If one is emitted, it will be catched and transformed in a Python one. This way, the Python program will be cleanly interrupted and the usual stack trace displayed.
2. The returned value of this method is of type `Cell*`, we have to transform it into a Python one. This is done with `PyCell_Link()`. This macro is supplied by the `PyCell.h` header and this is why it must be included.

Wrapping of the `Library::create()` method:

    static PyObject* PyLibrary_create( PyObject*, PyObject* args )
    {
      PyObject* arg0;
      PyObject* arg1;
      Library* library = NULL;

      HTRY
        __cs.init( "Library.create" );                          // Step (1).
        if (not PyArg_ParseTuple( args, "O&O&:Library.create"
                                , Converter, &arg0
                                , Converter, &arg1 )) {         // Step (2).
          PyErr_SetString( ConstructorError
                         , "invalid number of parameters for Library constructor." );
          return NULL;
        }
        if (__cs.getObjectIds() == ":db:string") {              // Step (3.a)
          DataBase* db = PYDATABASE_O(arg0);
          library = Library::create( db, Name(PyString_AsString(arg1)) );
        } else if (__cs.getObjectIds() == ":library:string") {  // Step (3.b)
          Library* masterLibrary = PYLIBRARY_O(arg0);
          library = Library::create( masterLibrary, Name(PyString_AsString(arg1)) );
        } else {
          PyErr_SetString( ConstructorError
                         , "invalid number of parameters for Library constructor." );
          return NULL;
        }
      HCATCH

      return PyLibrary_Link( library );
    }

Key point about this constructor:

1. We want the Python interface to mimic as closely as possible the C++ API. As such, Python object will be created using a static `.create()` method. So we do not use the usual Python allocation mechanism.
2. As it is a static method, there is no first argument.
3. Python do not allow function overload like C++. To emulate that behavior we use the `__cs` object (which is a global variable).
   1. Init/reset the `__cs` object: see step (1).
   2. Call `PyArg_ParseTuple()`, read every mandatory or optional argument as a Python object (`"O&"`) and use `Converter` on each one. `Converter` will determine the real type of the Python object given as argument by looking at the encapsulated C++ class. It then update the `__cs` object. Done in step (2)
   3. After the call to `PyArg_ParseTuple()`, the function `__cs.getObjectIds()` will return the signature of the various arguments. In our case, the valid signatures will be `":db:string"` (step (3.a)*a) and ``”:library:string”`` (*step (3.b)).
   4. Call the C++ method after extracting the C++ objects from the Python arguments. Note the use of the `PYLIBRARY_O()` and `PYDATABSE_O()` macros to perform the conversion.
4. Return the result, encapsulated through a call to `PyLibrary_Link()`.

Wrapping of the `Library::destroy()` method:

    DBoDestroyAttribute(PyLibrary_destroy, PyLibrary)

For C++ classes that are derived from `DBo`, the destroy method wrapper must be defined using the macro `DBoDestroyAttribute()`. This macro implements the bi-directional communication mechanism using `Hurricane::Property`. It must not be used for non `DBo` derived classes.

Defining the method table of the PyLibrary type:

    PyMethodDef PyLibrary_Methods[] =
      { { "create"    , (PyCFunction)PyLibrary_create , METH_VARARGS|METH_STATIC
                      , "Creates a new library." }
      , { "getCell"   , (PyCFunction)PyLibrary_getCell, METH_VARARGS
                      , "Get the cell of name <name>" }
      , { "destroy"   , (PyCFunction)PyLibrary_destroy, METH_NOARGS
                      , "Destroy associated hurricane object The python object remains." }
      , {NULL, NULL, 0, NULL}           /* sentinel */
      };

This is standard Python/C API. The name of the `PyMethodDef` table must be named from the class: `PyLibrary_Methods`.

### 3.2.3 Python Type Linking

Defining the `PyTypeLibrary` class methods and the type linking function.

Those are the functions for the Python object itself to work, not the wrapped method from the C++ class.

Note

At this point we do not define the `PyTypeLibrary` itself. Only it’s functions and a function to set them up once the type will be defined.

    DBoDeleteMethod(Library)
    PyTypeObjectLinkPyType(Library)

The macro `DBoDeleteMethod()` define the function to delete a `PyLibrary` Python object. Again, do not mistake it for the deletion of the C++ class (implemented by `DBoDestroyAttribute()`). Here again, `DBoDeleteMethod()` is specially tailored for `DBo` derived classes.

To define `PyLibrary_LinkPyType()`, use the `PyTypeObjectLinkPyType()` macro. This macro is specific for `DBo` derived classes that are seen as base classes under Python (i.e. we don’t bother exposing the base class under Python). `PyLibrary_LinkPyType()` setup the class functions in the `PyTypeLibrary` type object, it must be called in the Python module this class is part of (in this case: `PyHurricane.cpp`). This particular flavor of the macro will define and setup the following class functions:

- `PyTypeLibrary.tp_compare` (defined by the macro).
- `PyTypeLibrary.tp_repr` (defined by the macro).
- `PyTypeLibrary.tp_str` (defined by the macro).
- `PyTypeLibrary.tp_hash` (defined by the macro).
- `PyTypeLibrary.tp_methods` sets to the previously defined `PyLibrary_Methods` table.
- `PyTypeLibrary.tp_dealloc` is set to a function that must be named `PyLibrary_DeAlloc`, this is what `DBoDeleteMethod` does. It is not done by `PyTypeObjectLinkPyType`.

Defining the `PyTypeLibrary` type:

### 3.2.4 The Shared Library Part

This part will be put in a separate supporting shared library, allowing other Python module to link against it (and make use of its symbols).

    DBoLinkCreateMethod(Library)
    PyTypeObjectDefinitions(Library)

To define `PyTypeLibrary`, use the `PyTypeObjectDefinitions()` macro. This macro is specific for classes that, as exposed by Python, are neither derived classes nor base classes for others. That is, they are standalone from the inheritance point of view.

The `DBoLinkCreateMethod()` macro will define the `PyLibrary_Link()` function which is responsible for encapsulating a C++ `Library` object into a Python `PyLibrary` one.

## 3.3 Python Module (C++ namespace)

We use the Python module to replicate the C++ namespace. Thus, for the `Hurricane` namespace we create a Python `Hurricane` module which is defined in the `PyHurricane.cpp` file, then we add into that module dictionary all the Python types encapsulating the C++ classes of that namespace.

    DL_EXPORT(void) initHurricane ()
    {
      PyLibrary_LinkPyType();  // step 1.

      PYTYPE_READY( Library )  // step 2.

      __cs.addType( "library", &PyTypeLibrary, "<Library>", false ); // step 3.

      PyObject* module = Py_InitModule( "Hurricane", PyHurricane_Methods );
      if (module == NULL) {
        cerr << "[ERROR]\n"
             << "  Failed to initialize Hurricane module." << endl;
        return;
      }

      Py_INCREF( &PyTypeLibrary );                                        // step 4.
      PyModule_AddObject( module, "Library", (PyObject*)&PyTypeLibrary ); // step 4.
    }

The `initHurricane()` initialisation function shown above has been scrubbed of everything not relevant to the `PyLibrary` class. The integration of the `PyLibrary` class into the module needs four steps:

1. A call to PyLibrary_LinkPyType() to hook the Python type functions in the Python type object.

2. A call to the `PYTYPE_READY()` macro (standard Python).

3. Registering the type into the `__cs` object, with `addType()`. The arguments are self explanatory, save for the last which is a boolean to tell if this is a derived class or not.

4. Adding the type object (`PyTypeLibrary`) into the dictionnary of the module itself. This allow to mimic closely the C++ syntax:

       import Hurricane
       lib = Hurricane.Library.create( db, 'root' )