/*************************************************************************** * pythonscript.cpp * This file is part of the KDE project * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. ***************************************************************************/ #include "pythonscript.h" #include "pythonmodule.h" #include "pythoninterpreter.h" #include "pythonsecurity.h" #include "../main/scriptcontainer.h" //#include using namespace Kross::Python; namespace Kross { namespace Python { /// @internal class PythonScriptPrivate { public: /** * The \a Py::Module instance this \a PythonScript * has as local context. */ Py::Module* m_module; /** * The PyCodeObject object representing the * compiled python code. Internaly we first * compile the python code and later execute * it. */ Py::Object* m_code; /** * A list of functionnames. */ TQStringList m_functions; /** * A list of classnames. */ TQStringList m_classes; }; }} PythonScript::PythonScript(Kross::Api::Interpreter* interpreter, Kross::Api::ScriptContainer* scriptcontainer) : Kross::Api::Script(interpreter, scriptcontainer) , d(new PythonScriptPrivate()) { #ifdef KROSS_PYTHON_SCRIPT_CTOR_DEBUG krossdebug("PythonScript::PythonScript() Constructor."); #endif d->m_module = 0; d->m_code = 0; } PythonScript::~PythonScript() { #ifdef KROSS_PYTHON_SCRIPT_DTOR_DEBUG krossdebug("PythonScript::~PythonScript() Destructor."); #endif finalize(); delete d; } void PythonScript::initialize() { finalize(); clearException(); // clear previously thrown exceptions. try { if(m_scriptcontainer->getCode().isNull()) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("Invalid scripting code for script '%1'").tqarg( m_scriptcontainer->getName() )) ); if(m_scriptcontainer->getName().isNull()) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("Name for the script is invalid!")) ); PyObject* pymod = PyModule_New( (char*) m_scriptcontainer->getName().latin1() ); d->m_module = new Py::Module(pymod, true); if(! d->m_module) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("Failed to initialize local module context for script '%1'").tqarg( m_scriptcontainer->getName() )) ); #ifdef KROSS_PYTHON_SCRIPT_INIT_DEBUG krossdebug( TQString("PythonScript::initialize() module='%1' refcount='%2'").tqarg(d->m_module->as_string().c_str()).tqarg(d->m_module->reference_count()) ); #endif // Set the "self" variable to point to the ScriptContainer // we are using for the script. That way we are able to // simply access the ScriptContainer itself from within // python scripting code. Py::Dict moduledict = d->m_module->getDict(); moduledict["self"] = PythonExtension::toPyObject( m_scriptcontainer ); //moduledict["tqparent"] = PythonExtension::toPyObject( m_manager ); /* // Prepare the local context. TQString s = //"import sys\n" "if self.has(\"stdout\"):\n" " self.stdout = Redirect( self.get(\"stdout\") )\n" "if self.has(\"stderr\"):\n" " self.stderr = Redirect( self.get(\"stderr\") )\n" ; Py::Dict mainmoduledict = ((PythonInterpreter*)m_interpreter)->mainModule()->getDict(); PyObject* pyrun = PyRun_StringFlags((char*)s.latin1(), Py_file_input, mainmoduledict.ptr(), moduledict.ptr()); if(! pyrun) throw Py::Exception(); // throw exception Py_XDECREF(pyrun); // free the reference. */ // Compile the python script code. It will be later on request // executed. That way we cache the compiled code. PyObject* code = 0; bool restricted = m_scriptcontainer->getOption("restricted", TQVariant(false,0), true).toBool(); krossdebug( TQString("PythonScript::initialize() name=%1 restricted=%2").tqarg(m_scriptcontainer->getName()).tqarg(restricted) ); if(restricted) { // Use the RestrictedPython module wrapped by the PythonSecurity class. code = dynamic_cast(m_interpreter)->securityModule()->compile_restricted( m_scriptcontainer->getCode(), m_scriptcontainer->getName(), "exec" ); } else { //PyCompilerFlags* cf = new PyCompilerFlags; //cf->cf_flags |= PyCF_SOURCE_IS_UTF8; // Just compile the code without any restrictions. code = Py_CompileString( (char*) m_scriptcontainer->getCode().latin1(), (char*) m_scriptcontainer->getName().latin1(), Py_file_input ); } if(! code) throw Py::Exception(); d->m_code = new Py::Object(code, true); } catch(Py::Exception& e) { TQString err = Py::value(e).as_string().c_str(); Kross::Api::Exception::Ptr exception = toException( TQString("Failed to compile python code: %1").tqarg(err) ); e.clear(); // exception is handled. clear it now. throw exception; } } void PythonScript::finalize() { #ifdef KROSS_PYTHON_SCRIPT_FINALIZE_DEBUG if(d->m_module) krossdebug( TQString("PythonScript::finalize() module='%1' refcount='%2'").tqarg(d->m_module->as_string().c_str()).tqarg(d->m_module->reference_count()) ); #endif delete d->m_module; d->m_module = 0; delete d->m_code; d->m_code = 0; d->m_functions.clear(); d->m_classes.clear(); } Kross::Api::Exception::Ptr PythonScript::toException(const TQString& error) { long lineno = -1; TQStringList errorlist; PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); Py_FlushLine(); PyErr_NormalizeException(&type, &value, &traceback); if(traceback) { Py::List tblist; try { Py::Module tbmodule( PyImport_Import(Py::String("traceback").ptr()), true ); Py::Dict tbdict = tbmodule.getDict(); Py::Callable tbfunc(tbdict.getItem("format_tb")); Py::Tuple args(1); args.setItem(0, Py::Object(traceback)); tblist = tbfunc.apply(args); uint length = tblist.length(); for(Py::List::size_type i = 0; i < length; ++i) errorlist.append( Py::Object(tblist[i]).as_string().c_str() ); } catch(Py::Exception& e) { TQString err = Py::value(e).as_string().c_str(); e.clear(); // exception is handled. clear it now. krosswarning( TQString("Kross::Python::PythonScript::toException() Failed to fetch a traceback: %1").tqarg(err) ); } PyObject *next; while (traceback && traceback != Py_None) { PyFrameObject *frame = (PyFrameObject*)PyObject_GetAttrString(traceback, "tb_frame"); Py_DECREF(frame); { PyObject *getobj = PyObject_GetAttrString(traceback, "tb_lineno"); lineno = PyInt_AsLong(getobj); Py_DECREF(getobj); } if(Py_OptimizeFlag) { PyObject *getobj = PyObject_GetAttrString(traceback, "tb_lasti"); int lasti = PyInt_AsLong(getobj); Py_DECREF(getobj); lineno = PyCode_Addr2Line(frame->f_code, lasti); } //const char* filename = PyString_AsString(frame->f_code->co_filename); //const char* name = PyString_AsString(frame->f_code->co_name); //errorlist.append( TQString("%1#%2: \"%3\"").tqarg(filename).tqarg(lineno).tqarg(name) ); next = PyObject_GetAttrString(traceback, "tb_next"); Py_DECREF(traceback); traceback = next; } } if(lineno < 0) { if(value) { PyObject *getobj = PyObject_GetAttrString(value, "lineno"); if(getobj) { lineno = PyInt_AsLong(getobj); Py_DECREF(getobj); } } if(lineno < 0) lineno = 0; } //PyErr_Restore(type, value, traceback); Kross::Api::Exception::Ptr exception = new Kross::Api::Exception(error, lineno - 1); if(errorlist.count() > 0) exception->setTrace( errorlist.join("\n") ); return exception; } const TQStringList& PythonScript::getFunctionNames() { if(! d->m_module) initialize(); //TODO catch exception return d->m_functions; /* TQStringList list; Py::List l = d->m_module->getDict().keys(); int length = l.length(); for(Py::List::size_type i = 0; i < length; ++i) list.append( l[i].str().as_string().c_str() ); return list; */ } Kross::Api::Object::Ptr PythonScript::execute() { #ifdef KROSS_PYTHON_SCRIPT_EXEC_DEBUG krossdebug( TQString("PythonScript::execute()") ); #endif try { if(! d->m_module) initialize(); // the main module dictonary. Py::Dict mainmoduledict = ((PythonInterpreter*)m_interpreter)->mainModule()->getDict(); // the local context dictonary. Py::Dict moduledict( d->m_module->getDict().ptr() ); // Initialize context before execution. TQString s = "import sys\n" //"if self.has(\"stdout\"):\n" //" sys.stdout = Redirect( self.get(\"stdout\") )\n" //"if self.has(\"stderr\"):\n" //" sys.stderr = Redirect( self.get(\"stderr\") )\n" ; PyObject* pyrun = PyRun_String(s.latin1(), Py_file_input, mainmoduledict.ptr(), moduledict.ptr()); if(! pyrun) throw Py::Exception(); // throw exception Py_XDECREF(pyrun); // free the reference. // Acquire interpreter lock*/ PyGILState_STATE gilstate = PyGILState_Ensure(); // Evaluate the already compiled code. PyObject* pyresult = PyEval_EvalCode( (PyCodeObject*)d->m_code->ptr(), mainmoduledict.ptr(), moduledict.ptr() ); // Free interpreter lock PyGILState_Release(gilstate); if(! pyresult || PyErr_Occurred()) { krosswarning("Kross::Python::PythonScript::execute(): Failed to PyEval_EvalCode"); throw Py::Exception(); } Py::Object result(pyresult, true); #ifdef KROSS_PYTHON_SCRIPT_EXEC_DEBUG krossdebug( TQString("PythonScript::execute() result=%1").tqarg(result.as_string().c_str()) ); #endif for(Py::Dict::iterator it = moduledict.begin(); it != moduledict.end(); ++it) { Py::Dict::value_type vt(*it); if(PyClass_Check( vt.second.ptr() )) { #ifdef KROSS_PYTHON_SCRIPT_EXEC_DEBUG krossdebug( TQString("PythonScript::execute() class '%1' added.").tqarg(vt.first.as_string().c_str()) ); #endif d->m_classes.append( vt.first.as_string().c_str() ); } else if(vt.second.isCallable()) { #ifdef KROSS_PYTHON_SCRIPT_EXEC_DEBUG krossdebug( TQString("PythonScript::execute() function '%1' added.").tqarg(vt.first.as_string().c_str()) ); #endif d->m_functions.append( vt.first.as_string().c_str() ); } } Kross::Api::Object::Ptr r = PythonExtension::toObject(result); return r; } catch(Py::Exception& e) { try { Py::Object errobj = Py::value(e); if(errobj.ptr() == Py_None) // at least string-exceptions have there errormessage in the type-object errobj = Py::type(e); TQString err = errobj.as_string().c_str(); Kross::Api::Exception::Ptr exception = toException( TQString("Failed to execute python code: %1").tqarg(err) ); e.clear(); // exception is handled. clear it now. setException( exception ); } catch(Py::Exception& e) { TQString err = Py::value(e).as_string().c_str(); Kross::Api::Exception::Ptr exception = toException( TQString("Failed to execute python code: %1").tqarg(err) ); e.clear(); // exception is handled. clear it now. setException( exception ); } } catch(Kross::Api::Exception::Ptr e) { setException(e); } return 0; // return nothing if exception got thrown. } Kross::Api::Object::Ptr PythonScript::callFunction(const TQString& name, Kross::Api::List::Ptr args) { #ifdef KROSS_PYTHON_SCRIPT_CALLFUNC_DEBUG krossdebug( TQString("PythonScript::callFunction(%1, %2)") .tqarg(name) .tqarg(args ? TQString::number(args->count()) : TQString("NULL")) ); #endif if(hadException()) return 0; // abort if we had an unresolved exception. if(! d->m_module) { setException( new Kross::Api::Exception(TQString("Script not initialized.")) ); return 0; } try { Py::Dict moduledict = d->m_module->getDict(); // Try to determinate the function we like to execute. PyObject* func = PyDict_GetItemString(moduledict.ptr(), name.latin1()); if( (! d->m_functions.contains(name)) || (! func) ) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("No such function '%1'.").tqarg(name)) ); Py::Callable funcobject(func, true); // the funcobject takes care of freeing our func pyobject. // Check if the object is really a function and therefore callable. if(! funcobject.isCallable()) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("Function is not callable.")) ); // Call the function. Py::Object result = funcobject.apply(PythonExtension::toPyTuple(args)); return PythonExtension::toObject(result); } catch(Py::Exception& e) { TQString err = Py::value(e).as_string().c_str(); e.clear(); // exception is handled. clear it now. setException( new Kross::Api::Exception(TQString("Python Exception: %1").tqarg(err)) ); } catch(Kross::Api::Exception::Ptr e) { setException(e); } return 0; // return nothing if exception got thrown. } const TQStringList& PythonScript::getClassNames() { if(! d->m_module) initialize(); //TODO catch exception return d->m_classes; } Kross::Api::Object::Ptr PythonScript::classInstance(const TQString& name) { if(hadException()) return 0; // abort if we had an unresolved exception. if(! d->m_module) { setException( new Kross::Api::Exception(TQString("Script not initialized.")) ); return 0; } try { Py::Dict moduledict = d->m_module->getDict(); // Try to determinate the class. PyObject* pyclass = PyDict_GetItemString(moduledict.ptr(), name.latin1()); if( (! d->m_classes.contains(name)) || (! pyclass) ) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("No such class '%1'.").tqarg(name)) ); PyObject *pyobj = PyInstance_New(pyclass, 0, 0);//aclarg, 0); if(! pyobj) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("Failed to create instance of class '%1'.").tqarg(name)) ); Py::Object classobject(pyobj, true); #ifdef KROSS_PYTHON_SCRIPT_CLASSINSTANCE_DEBUG krossdebug( TQString("PythonScript::classInstance() inst='%1'").tqarg(classobject.as_string().c_str()) ); #endif return PythonExtension::toObject(classobject); } catch(Py::Exception& e) { TQString err = Py::value(e).as_string().c_str(); e.clear(); // exception is handled. clear it now. setException( Kross::Api::Exception::Ptr( new Kross::Api::Exception(err) ) ); } catch(Kross::Api::Exception::Ptr e) { setException(e); } return 0; // return nothing if exception got thrown. }