PLearn 0.1
PythonCodeSnippet.cc
Go to the documentation of this file.
00001 // -*- C++ -*-
00002 
00003 // PythonCodeSnippet.cc
00004 //
00005 // Copyright (C) 2005 Nicolas Chapados
00006 //
00007 // Redistribution and use in source and binary forms, with or without
00008 // modification, are permitted provided that the following conditions are met:
00009 //
00010 //  1. Redistributions of source code must retain the above copyright
00011 //     notice, this list of conditions and the following disclaimer.
00012 //
00013 //  2. Redistributions in binary form must reproduce the above copyright
00014 //     notice, this list of conditions and the following disclaimer in the
00015 //     documentation and/or other materials provided with the distribution.
00016 //
00017 //  3. The name of the authors may not be used to endorse or promote
00018 //     products derived from this software without specific prior written
00019 //     permission.
00020 //
00021 // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
00022 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00023 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
00024 // NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00025 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
00026 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00027 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00028 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00029 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00030 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00031 //
00032 // This file is part of the PLearn library. For more information on the PLearn
00033 // library, go to the PLearn Web site at www.plearn.org
00034 
00035 /* *******************************************************
00036  * $Id: PythonCodeSnippet.cc 2771 2005-08-11 22:06:13Z chapados $
00037  ******************************************************* */
00038 
00039 // Authors: Nicolas Chapados
00040 
00043 // Python stuff must be included first
00044 #include "PythonCodeSnippet.h"
00045 #include "PythonEmbedder.h"
00046 #include "PythonExtension.h"
00047 
00048 // From PLearn
00049 #include <plearn/io/fileutils.h>
00050 #include <plearn/base/tostring.h>
00051 
00052 #ifdef WIN32
00053 #include <plearn/base/stringutils.h>   // For 'search_replace'.
00054 #endif
00055 
00056 
00057 namespace PLearn {
00058 using namespace std;
00059 
00060 // #if sizeof(long) < sizeof(void*)
00061 // #error "Snippets' addresses need to be casted to long"
00062 // #endif
00063 
00064 const char* PythonCodeSnippet::InjectSetupSnippet =\
00065 // Redefines import statement behavior
00066 "from plearn.utilities import inject_import as _inject_import_\n"
00067 // The dictionnary in which to inject
00068 "__injected__ = {}\n";
00069 
00070 const char* PythonCodeSnippet::SetCurrentSnippetVar =\
00071 // Completed by sprintf using the snippet's address hex value
00072 "_inject_import_.setCurrentSnippet(%p)\n";
00073 
00074 const char* PythonCodeSnippet::ResetCurrentSnippetVar = \
00075 "_inject_import_.resetCurrentSnippet()\n";
00076 
00077 //#####  PythonCodeSnippet  ###################################################
00078 
00079 PLEARN_IMPLEMENT_OBJECT(
00080     PythonCodeSnippet,
00081     "Enables embedded Python code to be called from PLearn/C++ code.",
00082     "This class enables an embedded Python code snippet to be compiled and\n"
00083     "called back later.  It is not designed to be used by itself, but rather in\n"
00084     "conjunction with specific PLearn objects that understand the\n"
00085     "PythonCodeSnippet calling protocol.\n"
00086     "\n"
00087     "Note that global variables can be used, in the Python code, to keep a\n"
00088     "\"living state\", used to carry information across calls to Python functions.\n"
00089     "\n"
00090     "A note on exception behavior within the PythonCodeSnippet:\n"
00091     "\n"
00092     "- Exceptions that are raised within executed Python code are handled\n"
00093     "  according to the 'remap_python_exceptions' option.  Basically, client\n"
00094     "  code to the PythonCodeSnippet has the choice of either generating a\n"
00095     "  PLERROR from the Python Exception, or of remapping this exception into\n"
00096     "  a C++ exception (of class PythonException, subclass of PLearnError).\n"
00097     "\n"
00098     "- C++ exceptions that are thrown from inside injected code functions\n"
00099     "  are remapped into Python exceptions by the trampoline handler.  These\n"
00100     "  Python exceptions are then handled according to the behavior in the\n"
00101     "  previous point.  Note that, for now, all C++ exceptions are turned into\n"
00102     "  a generic Python 'Exception' (base class for all exceptions).\n"
00103     "\n"
00104     "The current implementation of the PythonCodeSnippet is designed to be\n"
00105     "thread-safe, i.e. the Python Global Interpreter Lock is always acquired\n"
00106     "before sensitive operations are carried out.\n"
00107     );
00108 
00109 
00110 PythonCodeSnippet::PythonCodeSnippet(const string& code,
00111                                      bool remap_python_exceptions)
00112     : inherited(),
00113       m_code(code),
00114       m_remap_python_exceptions(remap_python_exceptions),
00115       m_instance_params(),
00116       m_instance(),
00117       m_handle(this),
00118       m_compiled_code(),
00119       m_injected_functions(4),
00120       m_python_methods(4)
00121 {
00122     // NOTE: build() not called
00123 }
00124 
00125 
00126 PythonCodeSnippet::PythonCodeSnippet(const PythonObjectWrapper& instance,
00127                                      bool remap_python_exceptions)
00128     : inherited(),
00129       m_code(""),
00130       m_remap_python_exceptions(remap_python_exceptions),
00131       m_instance_params(),
00132       m_instance(instance),
00133       m_handle(this),
00134       m_compiled_code(),
00135       m_injected_functions(4),
00136       m_python_methods(4)
00137 {
00138     PyObject* compiled_code= 
00139         PyObject_GetAttrString(m_instance.getPyObject(), const_cast<char*>("__dict__"));
00140     m_compiled_code= PythonObjectWrapper(compiled_code, PythonObjectWrapper::transfer_ownership);
00141     // NOTE: build() not called
00142 }
00143 
00144 void PythonCodeSnippet::declareOptions(OptionList& ol)
00145 {
00146     declareOption(
00147         ol, "code", &PythonCodeSnippet::m_code,
00148         OptionBase::buildoption,
00149         "Python statement list that should be compiled at build time to provide\n"
00150         "the desired functions (defined by the client code to PythonCodeSnippet)\n"
00151         "and otherwise set up the Python global namespace.  Note that the Python\n"
00152         "'__builtins__' module is always injected into the global namespace.\n"
00153         "You should also add the statement\n"
00154         "\n"
00155         "    from numarray import *'\n"
00156         "\n"
00157         "to manipulate PLearn Vec and Mat.\n");
00158 
00159     declareOption(
00160         ol, "remap_python_exceptions", &PythonCodeSnippet::m_remap_python_exceptions,
00161         OptionBase::buildoption,
00162         "If true, Python exceptions raised during function execution are mapped\n"
00163         "to a C++ exception.  If false, then a normal Python stack dump is\n"
00164         "output to stderr and a PLERROR is raised.  Default=false.");
00165 
00166     declareOption(
00167         ol, "instance_params", &PythonCodeSnippet::m_instance_params,
00168         OptionBase::buildoption,
00169         "If this snippet represents a python object, these are the\n"
00170         "parameters passed to the object's constructor.");
00171 
00172     // Now call the parent class' declareOptions
00173     inherited::declareOptions(ol);
00174 }
00175 
00176 void PythonCodeSnippet::build_()
00177 {
00178     static PythonEmbedder python;
00179     static bool numarray_initialized = false;
00180     if (! numarray_initialized) {
00181         // must be in each translation unit that makes use of libnumarray;
00182         // weird stuff related to table of function pointers that's being
00183         // initialized into a STATIC VARIABLE of the translation unit!
00184         import_libnumarray();
00185         numarray_initialized = true;
00186 
00187         PythonObjectWrapper::initializePython();
00188     }
00189 
00190     // Compile code into global environment
00191     if (m_code != ""){
00192         // Here we don't call setCurrentSnippet() because it has to be called
00193         // between the setup and the m_code... Still have to call
00194         // resetCurrentSnippet() afterwards though.
00195         char set_current_snippet[100];
00196         sprintf(set_current_snippet, SetCurrentSnippetVar, m_handle);
00197         m_compiled_code = compileGlobalCode(InjectSetupSnippet+
00198                                             string(set_current_snippet)+m_code);
00199         if(m_instance.isNull())
00200             resetCurrentSnippet();
00201     }
00202 
00203     // Forget about injected functions
00204     m_injected_functions.purge_memory();
00205     m_python_methods.purge_memory();
00206 }
00207 
00208 // ### Nothing to add here, simply calls build_
00209 void PythonCodeSnippet::build()
00210 {
00211     inherited::build();
00212     build_();
00213 }
00214 
00215 void PythonCodeSnippet::makeDeepCopyFromShallowCopy(
00216     CopiesMap& copies)
00217 {
00218     inherited::makeDeepCopyFromShallowCopy(copies);
00219 
00220     // Compile fresh code into global environment
00221     m_compiled_code = compileGlobalCode(m_code);
00222 
00223     // Forget about injected functions (not necessarily the correct thing to do...)
00224     m_injected_functions.purge_memory();
00225     m_python_methods.purge_memory();
00226 }
00227 
00228 
00229 //#####  Global Environment Interface  ########################################
00230 
00231 PythonObjectWrapper
00232 PythonCodeSnippet::getGlobalObject(const string& object_name) const
00233 {
00234     PythonGlobalInterpreterLock gil;         // For thread-safety
00235     PyObject* pyobj;
00236     if(!m_instance.isNull())
00237         pyobj= PyObject_GetAttrString(m_instance.getPyObject(),
00238                                       const_cast<char*>(object_name.c_str()));
00239     else
00240         pyobj= PyDict_GetItemString(m_compiled_code.getPyObject(),
00241                                     object_name.c_str());
00242     if (pyobj) {
00243         // pyobj == borrowed reference
00244         // Increment refcount to keep long-lived reference
00245         Py_XINCREF(pyobj);
00246         return PythonObjectWrapper(pyobj);
00247     }
00248     return PythonObjectWrapper();            // None
00249 }
00250 
00251 void PythonCodeSnippet::setGlobalObject(const string& object_name,
00252                                         const PythonObjectWrapper& pow)
00253 {
00254     PythonGlobalInterpreterLock gil;         // For thread-safety
00255 
00256     // Note that PyDict_SetItemString increments the reference count for us
00257     int non_success = 0;
00258     if(!m_instance.isNull())
00259         non_success= PyObject_SetAttrString(m_instance.getPyObject(),
00260                                             const_cast<char*>(object_name.c_str()),
00261                                             pow.getPyObject());
00262     else if (! pow.isNull())
00263         non_success = PyDict_SetItemString(m_compiled_code.getPyObject(),
00264                                            object_name.c_str(),
00265                                            pow.getPyObject());
00266     else
00267         non_success = PyDict_SetItemString(m_compiled_code.getPyObject(),
00268                                            object_name.c_str(),
00269                                            Py_None);
00270 
00271     if (non_success)
00272         PLERROR("PythonCodeSnippet::setGlobalObject: error inserting a global Python \n"
00273                 "object under the name '%s'", object_name.c_str());
00274 }
00275 
00276 
00277 //#####  Function Call Interface  #############################################
00278 
00279 bool PythonCodeSnippet::isInvokable(const char* function_name) const
00280 {
00281     PythonGlobalInterpreterLock gil;         // For thread-safety
00282 
00283     PyObject* pFunc= 0;
00284     bool instance_method= false;
00285     if(!m_instance.isNull())
00286     {
00287         char* fn= new char[strlen(function_name)+1];
00288         strcpy(fn, function_name);
00289         if(PyObject_HasAttrString(m_instance.getPyObject(), fn))
00290             pFunc= PyObject_GetAttrString(m_instance.getPyObject(), fn);
00291         delete[] fn;
00292     }
00293     if(pFunc)
00294         instance_method= true;
00295     else
00296         pFunc= PyDict_GetItemString(m_compiled_code.getPyObject(),
00297                                     function_name);
00298     // pFunc: Borrowed reference if not instance_method
00299     bool ret= pFunc && PyCallable_Check(pFunc);
00300     if(instance_method) {Py_DECREF(pFunc);}
00301     return ret;
00302 }
00303 
00304 
00305 // Zero-argument function call
00306 PythonObjectWrapper
00307 PythonCodeSnippet::invoke(const char* function_name) const
00308 {
00309     PythonGlobalInterpreterLock gil;         // For thread-safety
00310 
00311     PyObject* pFunc= 0;
00312     bool instance_method= false;
00313     if(!m_instance.isNull())
00314     {
00315         char* fn= new char[strlen(function_name)+1];
00316         strcpy(fn, function_name);
00317         if(PyObject_HasAttrString(m_instance.getPyObject(), fn))
00318             pFunc= PyObject_GetAttrString(m_instance.getPyObject(), fn);
00319         delete[] fn;
00320     }
00321     if(pFunc)
00322         instance_method= true;
00323     else
00324         pFunc= PyDict_GetItemString(m_compiled_code.getPyObject(),
00325                                     function_name);
00326 
00327     // pFunc: Borrowed reference if not instance_method
00328 
00329     PyObject* return_value = 0;
00330     if (pFunc && PyCallable_Check(pFunc)) {
00331         if(!instance_method)
00332             setCurrentSnippet(m_handle);
00333 
00334         return_value = PyObject_CallObject(pFunc, NULL);
00335         if (! return_value)
00336         {
00337             if(instance_method){
00338                 Py_DECREF(pFunc);
00339             }
00340             handlePythonErrors(string("Error while calling function '")
00341                                + function_name
00342                                + "' with no params.");
00343         }
00344 
00345         if(!instance_method)
00346             resetCurrentSnippet();
00347     }
00348     else
00349     {
00350         if(instance_method) {Py_DECREF(pFunc);}
00351         PLERROR("PythonCodeSnippet::invoke: cannot call function '%s' (not callable).",
00352                 function_name);
00353     }
00354 
00355     if(instance_method) {Py_DECREF(pFunc);}
00356     //return PythonObjectWrapper(return_value);
00357     PythonObjectWrapper r(return_value);
00358     Py_DECREF(return_value);
00359     return r;
00360 }
00361 
00362 
00363 // N-argument function call
00364 PythonObjectWrapper
00365 PythonCodeSnippet::invoke(const char* function_name,
00366                           const TVec<PythonObjectWrapper>& args) const
00367 {
00368     PythonGlobalInterpreterLock gil;         // For thread-safety
00369 
00370     PyObject* pFunc= 0;
00371     bool instance_method= false;
00372     if(!m_instance.isNull())
00373     {
00374         char* fn= new char[strlen(function_name)+1];
00375         strcpy(fn, function_name);
00376         if(PyObject_HasAttrString(m_instance.getPyObject(), fn))
00377             pFunc= PyObject_GetAttrString(m_instance.getPyObject(), fn);
00378         delete[] fn;
00379     }
00380     if(pFunc)
00381         instance_method= true;
00382     else
00383         pFunc= PyDict_GetItemString(m_compiled_code.getPyObject(),
00384                                     function_name);
00385 
00386     // pFunc: Borrowed reference if not instance_method
00387 
00388     PyObject* return_value = 0;
00389     if (pFunc && PyCallable_Check(pFunc)) {
00390         if(!instance_method)
00391             setCurrentSnippet(m_handle);
00392 
00393         // Create argument tuple.  Warning: PyTuple_SetItem STEALS references.
00394         PyObject* pArgs = PyTuple_New(args.size());
00395         for (int i=0, n=args.size() ; i<n ; ++i)
00396         {
00397             PyTuple_SetItem(pArgs, i, args[i].getPyObject());
00398             Py_INCREF(args[i].getPyObject());
00399         }
00400 
00401         return_value = PyObject_CallObject(pFunc, pArgs);
00402 
00403         Py_DECREF(pArgs);
00404         if (! return_value)
00405         {
00406             if(instance_method)
00407             {Py_DECREF(pFunc);}
00408             handlePythonErrors(string("Error while calling function '")
00409                                + function_name
00410                                + "' with "
00411                                + tostring(args.length())
00412                                + " params.");
00413         }
00414         if(!instance_method)
00415             resetCurrentSnippet();
00416     }
00417     else
00418     {
00419         if(instance_method)
00420         {Py_DECREF(pFunc);}
00421         PLERROR("PythonCodeSnippet::invoke: cannot call function '%s'",
00422                 function_name);
00423     }
00424 
00425     if(instance_method)
00426     {Py_DECREF(pFunc);}
00427     //return PythonObjectWrapper(return_value);
00428     PythonObjectWrapper r(return_value);
00429     Py_DECREF(return_value);
00430     return r;
00431 }
00432 
00433 
00434 //#####  Function Injection Interface  ########################################
00435 
00436 // This is the function actually called by Python.  Be careful to remap
00437 // exceptions thrown by C++ into Python exceptions.
00438 PyObject* PythonCodeSnippet::pythonTrampoline(PyObject* self, PyObject* args)
00439 {
00440     PythonGlobalInterpreterLock gil;         // For thread-safety
00441     try {
00442         // Transform the args tuple into a TVec of not-owned PythonObjectWrapper
00443         if (! PyTuple_Check(args))
00444             PLERROR("PythonCodeSnippet.cc:python_trampoline: the Python interpreter "
00445                     "did not pass a Tuple as the arguments object.");
00446 
00447         int size = PyTuple_GET_SIZE(args);
00448         TVec<PythonObjectWrapper> args_tvec(size);
00449         for (int i=0 ; i<size ; ++i) {
00450             args_tvec[i]=
00451                 PythonObjectWrapper(PyTuple_GET_ITEM(args,i));
00452         }
00453 
00454         // Now get the void* stored within the PyCObject of self
00455         StandaloneFunction* func =
00456             static_cast<StandaloneFunction*>(PyCObject_AsVoidPtr(self));
00457         PythonObjectWrapper returned_value = (*func)(args_tvec);
00458         PyObject* to_return = returned_value.getPyObject();
00459         Py_XINCREF(to_return);
00460         return to_return;
00461     }
00462     // Catch PLERROR and such
00463     catch (const PLearnError& e) {
00464         PyErr_SetString(PyExc_Exception,
00465                         (string("PLearn Error: ")+e.message()).c_str());
00466         return NULL;
00467     }
00468     // Catch C++ stdlib exceptions
00469     catch (const std::exception& e) {
00470         PyErr_SetString(PyExc_Exception,
00471                         (string("C++ stdlib error: ")+e.what()).c_str());
00472         return NULL;
00473     }
00474     // Catch any other unexpected exceptions
00475     catch (...) {
00476         PyErr_SetString(PyExc_Exception,
00477                         "Caught unknown C++ exception while executing injected function "
00478                         "inside a PythonCodeSnippet");
00479         return NULL;
00480     }
00481 }
00482 
00483 
00484 // Bind "standalone functions" to a Python name
00485 void PythonCodeSnippet::injectInternal(const char* python_name,
00486                                        StandaloneFunction* function_ptr)
00487 {
00488     PythonGlobalInterpreterLock gil;         // For thread-safety
00489 
00490     // Wrap the function_ptr into a PyCObject
00491     PyObject* self = PyCObject_FromVoidPtr(function_ptr, NULL);
00492 
00493     // Create a Python Function Object
00494     PyMethodDef* py_method = m_python_methods.allocate();
00495     py_method->ml_name  = const_cast<char*>(python_name);
00496     py_method->ml_meth  = pythonTrampoline;
00497     py_method->ml_flags = METH_VARARGS;
00498     py_method->ml_doc   = const_cast<char*>("injected-function-from-PythonCodeSnippet");
00499 
00500     PyObject* py_funcobj = PyCFunction_NewEx(py_method,
00501                                              self /* info for trampoline */,
00502                                              NULL /* module */);
00503 
00504     if (py_funcobj) {
00505         // Inject into the running snippet.  Note that when a
00506         // PythonObjectWrapper is constructed from a PyObject, it steals the
00507         // refcount, so we don't need to perform a Py_XDECREF on py_funcobj.
00508         this->setGlobalObject(python_name, py_funcobj);
00509         if(!m_instance.isNull())
00510         {
00511             char* fn= new char[strlen(python_name)+1];
00512             strcpy(fn, python_name);
00513             PyObject_SetAttrString(m_instance.getPyObject(),
00514                                    fn, py_funcobj);
00515             delete[] fn;
00516         }
00517         else
00518         {
00519             // Publish the injection in the '__injected__' dictionary for imported modules
00520             PythonObjectWrapper inj_dict = this->getGlobalObject("__injected__");
00521             PyDict_SetItemString(inj_dict.getPyObject(), python_name, py_funcobj);
00522 
00523             Py_XDECREF(self);
00524         }
00525     }
00526     else
00527         PLERROR("PythonCodeSnippet::injectInternal: failed to inject "
00528                 "Python function '%s'", python_name);
00529 }
00530 
00531 
00532 // High-level injection interface
00533 void PythonCodeSnippet::inject(const char* python_name,
00534                                StandaloneFunction function_ptr)
00535 {
00536     StandaloneFunction* pfunc = m_injected_functions.allocate();
00537     new(pfunc) StandaloneFunction(function_ptr); // In-place copy constructor
00538     injectInternal(python_name, pfunc);
00539 }
00540 
00541 
00542 //#####  Miscellaneous Functions  #############################################
00543 
00544 PythonObjectWrapper PythonCodeSnippet::compileGlobalCode(const string& code) //const
00545 {
00546     PythonGlobalInterpreterLock gil;         // For thread-safety
00547 
00548     PyObject* globals = PyDict_New();
00549     PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
00550 
00551     //always include EmbeddedCodeSnippet to check for an object to instantiate
00552     string extracode= "\nfrom plearn.pybridge.embedded_code_snippet "
00553         "import EmbeddedCodeSnippet\n"
00554         "from plearn.pybridge import pl_global_funcs\n";
00555 
00556     if (code != "") {
00557 #ifdef WIN32
00558         // Under Windows, it appears the Python code will not execute with
00559         // Windows carriage returns. Thus we first make a copy of the code and
00560         // replace any carriage return by a Unix one.
00561         string code_copy = code;
00562         PLearn::search_replace(code_copy, "\r\n", "\n");
00563 #else
00564         const string& code_copy = code;
00565 #endif
00566         PyObject* res= PyRun_String((code_copy+extracode).c_str(),
00567                                    Py_file_input /* exec code block */,
00568                                    globals, globals);
00569         Py_XDECREF(res);
00570         if (PyErr_Occurred()) {
00571             Py_XDECREF(globals);
00572             PyErr_Print();
00573             PLERROR("in PythonCodeSnippet::compileGlobalCode : error compiling "
00574                     "Python code contained in the 'code' option.");
00575         }
00576     }
00577 
00578     //get the global env. as an stl map
00579     PythonObjectWrapper wrapped_globals(globals);
00580     Py_XDECREF(globals);
00581     map<string, PyObject*> global_map=
00582         wrapped_globals.as<map<string, PyObject*> >();
00583 
00584     //inject global funcs, if not already done
00585     static bool global_funcs_injected= false;
00586     if(!global_funcs_injected)
00587     {
00588         map<string, PyObject*>::iterator it=
00589             global_map.find("pl_global_funcs");
00590         if(it == global_map.end())
00591             PLERROR("in PythonCodeSnippet::compileGlobalCode : "
00592                     "plearn.pybridge.pl_global_funcs not present in global env!");
00593         setPythonModuleAndInject(it->second);
00594         global_funcs_injected= true;
00595     }
00596 
00597     //try to find an EmbeddedCodeSnippet to instantiate
00598     PyObject* snippet_found= 0;
00599     map<string, PyObject*>::iterator it_id=
00600         global_map.find("pl_embedded_code_snippet_type");
00601 
00602     if(it_id != global_map.end())
00603         snippet_found= it_id->second;
00604     else //check for a single class deriving from EmbeddedCodeSnippet
00605     {
00606         list<pair<string, PyObject*> > classes_found;
00607 
00608         //iter (find)
00609         PyTypeObject* embedded_code_snippet_type=
00610             (PyTypeObject*)global_map["EmbeddedCodeSnippet"];
00611 
00612         //find all classes deriving from EmbeddedCodeSnippet
00613         for(map<string, PyObject*>::iterator it= global_map.begin();
00614             it != global_map.end(); ++it)
00615         {
00616             if(PyType_Check(it->second)
00617                && 0 != PyObject_Compare(it->second,
00618                                         (PyObject*)embedded_code_snippet_type)
00619                && PyType_IsSubtype((PyTypeObject*)it->second,
00620                                    embedded_code_snippet_type))
00621             {
00622                 classes_found.push_back(*it);
00623             }
00624         }
00625 
00626         int nclasses= classes_found.size();
00627         list<pair<string, PyObject*> >::iterator jt= classes_found.begin();
00628         if(nclasses > 1)
00629         {
00630             string classes_list= jt->first;
00631             for(++jt; jt != classes_found.end(); ++jt)
00632                 classes_list+= string(", ") + jt->first;
00633             PLERROR("in PythonCodeSnippet::compileGlobalCode : "
00634                     "more than one class derives from EmbeddedCodeSnippet "
00635                     "and pl_embedded_code_snippet_type is not defined. "
00636                     "classes= [%s]",
00637                     classes_list.c_str());
00638         }
00639         if(nclasses == 1)
00640             snippet_found= jt->second;
00641     }
00642 
00643     if(snippet_found)
00644     {//instantiate object of appropriate type
00645         PyObject* pyparams= PyDict_New();
00646         if(!pyparams)
00647             handlePythonErrors();
00648         for(map<string, string>::const_iterator it= m_instance_params.begin();
00649             it != m_instance_params.end(); ++it)
00650         {// fill kwargs
00651             PyObject* val= PyString_FromString(it->second.c_str());
00652             PyDict_SetItemString(pyparams, it->first.c_str(), val);
00653             Py_DECREF(val);
00654         }
00655 
00656         if(!PyCallable_Check(snippet_found))
00657             PLERROR("in PythonCodeSnippet::compileGlobalCode : "
00658                     "found something that is not callable [not a class?]");
00659 
00660         PyObject* pargs= PyTuple_New(0);
00661         PyObject* the_obj= PyObject_Call(snippet_found, pargs, pyparams);
00662         Py_DECREF(pyparams);
00663         Py_DECREF(pargs);
00664         if(!the_obj)
00665         {
00666             if (PyErr_Occurred())
00667                 PyErr_Print();
00668             PLERROR("in PythonCodeSnippet::compileGlobalCode : "
00669                     "found subclass of EmbeddedCodeSnippet, but can't "
00670                     "call constructor with given params.  "
00671                     "class='%s', params=%s",
00672                     ((PyTypeObject*)snippet_found)->tp_name,
00673                     tostring(m_instance_params).c_str());
00674         }
00675         m_instance= PythonObjectWrapper(the_obj);
00676     }
00677 
00678     return wrapped_globals;
00679 }
00680 
00681 void PythonCodeSnippet::run()
00682 {
00683     if(m_instance.isNull())
00684         PLERROR("in PythonCodeSnippet::run : this snippet is not "
00685                 "an instance of EmbeddedCodeSnippet");
00686     if(!PyCallable_Check(m_instance.getPyObject()))
00687         PLERROR("in PythonCodeSnippet::run : this instance of "
00688                 "EmbeddedCodeSnippet is not callable.");
00689     PyObject* pargs= PyTuple_New(0);
00690     PyObject* res= PyObject_Call(m_instance.getPyObject(), pargs, 0);
00691 
00692     Py_DECREF(pargs);
00693     if(!res) handlePythonErrors();
00694     Py_XDECREF(res);
00695 }
00696 
00697 void PythonCodeSnippet::setCurrentSnippet(const void* handle) const
00698 {
00699     PythonGlobalInterpreterLock gil;         // For thread-safety
00700 
00701     char set_current_snippet[100];
00702     sprintf(set_current_snippet, SetCurrentSnippetVar, handle);
00703     PyObject* res= PyRun_String(set_current_snippet,
00704                                 Py_file_input /* exec code block */,
00705                                 m_compiled_code.getPyObject(),
00706                                 m_compiled_code.getPyObject());
00707 
00708     Py_XDECREF(res);
00709     if (PyErr_Occurred()) {
00710         Py_XDECREF(m_compiled_code.getPyObject());
00711         PyErr_Print();
00712         PLERROR("PythonCodeSnippet::setCurrentSnippet: error compiling "
00713                 "Python code contained in the 'SetCurrentSnippetVar'."
00714                 "\n\t'%s'", set_current_snippet);
00715     }
00716 }
00717 
00718 void PythonCodeSnippet::resetCurrentSnippet() const
00719 {
00720     PythonGlobalInterpreterLock gil;         // For thread-safety
00721 
00722     PyObject* res= PyRun_String(ResetCurrentSnippetVar,
00723                                 Py_file_input /* exec code block */,
00724                                 m_compiled_code.getPyObject(),
00725                                 m_compiled_code.getPyObject());
00726     Py_XDECREF(res);
00727     if (PyErr_Occurred()) {
00728         Py_XDECREF(m_compiled_code.getPyObject());
00729         PyErr_Print();
00730         PLERROR("PythonCodeSnippet::resetCurrentSnippet: error compiling "
00731                 "Python code contained in the 'ResetCurrentSnippetVar'.");
00732     }
00733 }
00734 
00735 void PythonCodeSnippet::handlePythonErrors(const string& extramsg) const
00736 {
00737     PythonGlobalInterpreterLock gil;         // For thread-safety
00738     if (PyErr_Occurred()) {
00739         if (m_remap_python_exceptions) {
00740 
00741             // format using cgitb, throw as PythonError (PLearnError)
00742             PyObject *exception, *v, *traceback;
00743             PyErr_Fetch(&exception, &v, &traceback);
00744             PyErr_NormalizeException(&exception, &v, &traceback);
00745 
00746             if(!traceback)
00747             {
00748                 //perr << "$$$$ before print" << endl;
00749                 PyErr_Print();
00750                 //perr << "$$$$ after print" << endl;
00751                 throw PythonException(string("PythonCodeSnippet: encountered Python "
00752                                              "exception but there is no traceback.\n")
00753                                       + extramsg);
00754             }
00755 
00756 
00757             PyObject* tbstr=
00758                 PyString_FromString("plearn.utilities.pltraceback");
00759             PyObject* tbmod= PyImport_Import(tbstr);
00760             Py_XDECREF(tbstr);
00761             if(!tbmod)
00762                 throw PythonException("PythonCodeSnippet::handlePythonErrors :"
00763                                       " Unable to import cgitb module.");
00764             PyObject* tbdict= PyModule_GetDict(tbmod);
00765             Py_XDECREF(tbmod);
00766             PyObject* formatFunc= PyDict_GetItemString(tbdict, "text");
00767             if(!formatFunc)
00768                 throw PythonException("PythonCodeSnippet::handlePythonErrors :"
00769                                       " Can't find cgitb.text");
00770             PyObject* args= Py_BuildValue(const_cast<char*>("((OOO))"),
00771                                           exception, v, traceback);
00772             if(!args)
00773                 throw PythonException("PythonCodeSnippet::handlePythonErrors :"
00774                                       " Can't build args for cgitb.text");
00775             PyObject* pystr= PyObject_CallObject(formatFunc, args);
00776             Py_XDECREF(args);
00777             if(!pystr)
00778                 throw PythonException("PythonCodeSnippet::handlePythonErrors :"
00779                                       " call to cgitb.text failed");
00780             string str= PyString_AsString(pystr);
00781             Py_XDECREF(pystr);
00782 
00783             PyErr_Clear();
00784 
00785             Py_XDECREF(exception);
00786             Py_XDECREF(v);
00787             Py_XDECREF(traceback);
00788             throw PythonException(str+extramsg);
00789         }
00790         else {
00791             PyErr_Print();
00792             PyErr_Clear();
00793             PLERROR("PythonCodeSnippet: encountered Python exception.\n%s",
00794                     extramsg.c_str());
00795         }
00796     }
00797 }
00798 
00799 
00800 void PythonCodeSnippet::dumpPythonEnvironment()
00801 {
00802     PyObject_Print(m_compiled_code.getPyObject(), stderr, 0);
00803 }
00804 
00805 
00806 
00807 } // end of namespace PLearn
00808 
00809 
00810 /*
00811   Local Variables:
00812   mode:c++
00813   c-basic-offset:4
00814   c-file-style:"stroustrup"
00815   c-file-offsets:((innamespace . 0)(inline-open . 0))
00816   indent-tabs-mode:nil
00817   fill-column:79
00818   End:
00819 */
00820 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=79 :
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines