[[SIGVerse with Python]]

The aim of this tutorial is to show how to use exception handling to understand errors that occur in the Python code that boost::python handles. We use Python C API to read the information about the error, because the error message shown by boost::python itself is not so helpful. 

It will become more clear by the using the following example, in which shall import a fake python module called as "some_module" (it does not exist, we just want to see the information related with error message).

The function for exception handling was taken from the [[blog:http://thejosephturner.com/blog/page/1]] written by Joseph Turner.

Please use the following code and name it as "ControllerSample.cpp".

#highlight(c){{

 #include "ControllerEvent.h"
#include "Controller.h"
#include "Logger.h"
#include <iostream>
#include <boost/python.hpp>
#include <Python.h>
#include <dlfcn.h>

namespace py = boost::python; // create namespace variable for boost::python
using namespace std;

class MyController : public Controller { 
public: 
  void onInit(InitEvent &evt); 
  double onAction(ActionEvent&); 
  void onRecvMsg(RecvMsgEvent &evt);
  void onCollision(CollisionEvent &evt);
}; 
 
void MyController::onInit(InitEvent &evt) { 

  SimObj *my = getObj(myname());

  dlopen("libpython2.7.so", RTLD_LAZY | RTLD_GLOBAL);

  Py_Initialize();  //initialization of the python interpreter

}
double MyController::onAction(ActionEvent &evt) { 

double x=2.0;

    Py_Initialize();

        py::object main_module = py::import("__main__");
        // load the dictionary object out of the main module
        py::object main_namespace = main_module.attr("__dict__");
        // run simple code within the main namespace using the boost::python::exec

        // importing a fake module
        py::exec("import some_module", main_namespace);
   
   

  return 1.0;     
} 
 
void MyController::onRecvMsg(RecvMsgEvent &evt) { 
} 

void MyController::onCollision(CollisionEvent &evt) {
}
 
extern "C" Controller * createController() { 
  return new MyController; 
} 

}}

Please set the following environment variables to set the path to boost library.

          export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/pool/lib

Now, execute "make" it will create a ControllerSample.so file.

         make
 

Please copy the following xml file and name it as "WorldSample.xml"

#highlight(xml){{

<?xml version="1.0" encoding="utf8"?>
<world name="myworld1">
  <gravity x="0.0" y="-980.7" z="0.0"/>
  <instanciate class="seToy_D.xml">
    <set-attr-value name="name" value="toy_D"/>
    <set-attr-value name="language" value="c++"/>
    <set-attr-value name="implementation"
                    value="./ControllerSample.so"/>
    <set-attr-value name="dynamics" value="true"/>
    <set-attr-value name="x" value="0.0"/>
    <set-attr-value name="y" value="60.0"/>
    <set-attr-value name="z" value="0.0"/>
    <set-attr-value name="mass" value="1.0"/>
    <set-attr-value name="collision" value="true"/>
  </instanciate>
</world>
}}

Please copy the following Makefile file and name it as "Makefile". We shall be using the same Makefile in subsequent examples.


#highlight(c){{

#sigverse header
SIG_SRC  = $(SIGVERSE_PATH)/include/sigverse
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/raghav/pool/lib

CC          = gcc
CPP     = g++
AS          = as
LD          = g++
AR          = ar
RANLIB     = ranlib
OBJCOPY     = objcopy

# external libraries and headers

# change the next line to point to the location of your boost installation
EXTERN_DIR = /home/raghav/pool
EXTERN_LIBDIR = $(EXTERN_DIR)/lib
EXTERN_INCDIR = $(EXTERN_DIR)/include
EXTERN_BINDIR = $(EXTERN_DIR)/bin

BOOST_PYTHON_LIB = $(EXTERN_LIBDIR)/libboost_python.a

INCDIRS = .
INCDIRS += $(EXTERN_INCDIR)
# you may also need to change this directory if you want to pin to a specific
#   python version
INCDIRS += /usr/include/python2.7
INCDIRS += /usr/lib/python2.7/dist-packages/numpy/core/include

INCDIR = $(foreach dir, $(INCDIRS), -I$(dir))

%.o: %.cpp
     $(CPP) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -c $< -o $@

#specifying of object file
OBJS =  ControllerSample.so


ControllerSample.so: ControllerSample.cpp
     g++ -DCONTROLLER -DNDEBUG -DUSE_ODE -DdDOUBLE  -I$(SIG_SRC) -I$(SIG_SRC)/comm/controller -I$(INCDIR) -fPIC -shared -o $@ $^ $(LDFLAGS) -L$(EXTERN_LIBDIR) -lboost_python -lpython2.7

#$^ , $+     The names of all the prerequisites, with spaces between them. The value of $^ omits duplicate prerequisites,
# while $+ retains them and preserves their order



clean:
     rm -rf *.so *.o $(HWOBJS) hello_world



}}



Also set the PYTHONPATH

         export PYTHONPATH=$PYTHONPATH:/path_to_your_workspace


Now, please load the world file into Sigverse and push "Start" Button.

        ./sigverse.sh -w ./WorldSample -p write_your_port_mumber

It will output the following error …which is not-so useful

            terminate called after throwing an instance of 'boost::python::error_already_set'


In short, any errors that occur in the Python code that boost::python handles will cause the library to raise this exception; unfortunately, the exception does not encapsulate any of the information about the error itself. To extract information about the error, we’re going to have to resort to using the Python C API and some Python itself. First, catch the error:

#highlight(c){{

    try{

        py::object main_module = py::import("__main__");
        // load the dictionary object out of the main module
        py::object main_namespace = main_module.attr("__dict__");
        // run simple code within the main namespace using the boost::python::exec

        // of course, you can also import functionality from the standard library
        py::exec("import some_module", main_namespace);
   
   
    }
        catch(boost::python::error_already_set const &){
        // Parse and output the exception
        std::string perror_str = parse_python_exception();
        std::cout << "Error in Python: " << perror_str << std::endl;
    }

}}

We use the  parse_python_exception() which is defined below:

#highlight(c){{

std::string parse_python_exception(){
    PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL;
    // Fetch the exception info from the Python C API
    PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr);

    // Fallback error
    std::string ret("Unfetchable Python error");
    // If the fetch got a type pointer, parse the type into the exception string
    if(type_ptr != NULL){
        py::handle<> h_type(type_ptr);
        py::str type_pstr(h_type);
        // Extract the string from the boost::python object
        py::extract<std::string> e_type_pstr(type_pstr);
        // If a valid string extraction is available, use it
        //  otherwise use fallback
        if(e_type_pstr.check())
            ret = e_type_pstr();
        else
            ret = "Unknown exception type";
    }
    // Do the same for the exception value (the stringification of the exception)
    if(value_ptr != NULL){
        py::handle<> h_val(value_ptr);
        py::str a(h_val);
        py::extract<std::string> returned(a);
        if(returned.check())
            ret +=  ": " + returned();
        else
            ret += std::string(": Unparseable Python error: ");
    }
    // Parse lines from the traceback using the Python traceback module
    if(traceback_ptr != NULL){
        py::handle<> h_tb(traceback_ptr);
        // Load the traceback module and the format_tb function
        py::object tb(py::import("traceback"));
        py::object fmt_tb(tb.attr("format_tb"));
        // Call format_tb to get a list of traceback strings
        py::object tb_list(fmt_tb(h_tb));
        // Join the traceback strings into a single string
        py::object tb_str(py::str("\n").join(tb_list));
        // Extract the string, check the extraction, and fallback in necessary
        py::extract<std::string> returned(tb_str);
        if(returned.check())
            ret += ": " + returned();
        else
            ret += std::string(": Unparseable Python traceback");
    }
    return ret;
}

}}

Please add the code snippet shown above and make the executable again. The complete code is given below for quick copy.

#highlight(c){{

#include "ControllerEvent.h"
#include "Controller.h"
#include "Logger.h"
#include <iostream>
#include <boost/python.hpp>
#include <Python.h>
#include <dlfcn.h>

namespace py = boost::python; // create namespace variable for boost::python
using namespace std;

std::string parse_python_exception(); // functional declaration for exception handling




class MyController : public Controller { 
public: 
  void onInit(InitEvent &evt); 
  double onAction(ActionEvent&); 
  void onRecvMsg(RecvMsgEvent &evt);
  void onCollision(CollisionEvent &evt);
}; 
 
void MyController::onInit(InitEvent &evt) { 

  SimObj *my = getObj(myname());

  dlopen("libpython2.7.so", RTLD_LAZY | RTLD_GLOBAL);

  Py_Initialize();  //initialization of the python interpreter

}


double MyController::onAction(ActionEvent &evt) { 

double x=2.0;

    Py_Initialize();
    try{



        py::object main_module = py::import("__main__");
        // load the dictionary object out of the main module
        py::object main_namespace = main_module.attr("__dict__");
        // run simple code within the main namespace using the boost::python::exec

        // of course, you can also import functionality from the standard library
        py::exec("import some_module", main_namespace);
   
   
    }
        catch(boost::python::error_already_set const &){
        // Parse and output the exception
        std::string perror_str = parse_python_exception();
        std::cout << "Error in Python: " << perror_str << std::endl;
    }

  return 1.0;     
} 
 
void MyController::onRecvMsg(RecvMsgEvent &evt) { 
} 

void MyController::onCollision(CollisionEvent &evt) {
}
 
extern "C" Controller * createController() { 
  return new MyController; 
} 


std::string parse_python_exception(){
    PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL;
    // Fetch the exception info from the Python C API
    PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr);

    // Fallback error
    std::string ret("Unfetchable Python error");
    // If the fetch got a type pointer, parse the type into the exception string
    if(type_ptr != NULL){
        py::handle<> h_type(type_ptr);
        py::str type_pstr(h_type);
        // Extract the string from the boost::python object
        py::extract<std::string> e_type_pstr(type_pstr);
        // If a valid string extraction is available, use it
        //  otherwise use fallback
        if(e_type_pstr.check())
            ret = e_type_pstr();
        else
            ret = "Unknown exception type";
    }
    // Do the same for the exception value (the stringification of the exception)
    if(value_ptr != NULL){
        py::handle<> h_val(value_ptr);
        py::str a(h_val);
        py::extract<std::string> returned(a);
        if(returned.check())
            ret +=  ": " + returned();
        else
            ret += std::string(": Unparseable Python error: ");
    }
    // Parse lines from the traceback using the Python traceback module
    if(traceback_ptr != NULL){
        py::handle<> h_tb(traceback_ptr);
        // Load the traceback module and the format_tb function
        py::object tb(py::import("traceback"));
        py::object fmt_tb(tb.attr("format_tb"));
        // Call format_tb to get a list of traceback strings
        py::object tb_list(fmt_tb(h_tb));
        // Join the traceback strings into a single string
        py::object tb_str(py::str("\n").join(tb_list));
        // Extract the string, check the extraction, and fallback in necessary
        py::extract<std::string> returned(tb_str);
        if(returned.check())
            ret += ": " + returned();
        else
            ret += std::string(": Unparseable Python traceback");
    }
    return ret;
}

}}

Please execute on command line

    $make
    $sigserver.sh -w ./WorldSample.xml -p write_your_port_number

The following information about the error is shown now. 

     Error in Python: <type 'exceptions.ImportError'>: No module named   some_module:   File "<string>", line 1, in <module>
 

As you can see, we can understand from this error that it is related with importing a module, which is certainly more useful from previous error message. From now, on we shall always use this exception handling in our code.



#highlight(end)


Front page   New List of pages Search Recent changes   Help   RSS of recent changes