diff options
Diffstat (limited to 'lib/kross/python/pythonscript.cpp')
-rw-r--r-- | lib/kross/python/pythonscript.cpp | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/lib/kross/python/pythonscript.cpp b/lib/kross/python/pythonscript.cpp new file mode 100644 index 00000000..082b5440 --- /dev/null +++ b/lib/kross/python/pythonscript.cpp @@ -0,0 +1,460 @@ +/*************************************************************************** + * 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 <kapplication.h> + +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. + */ + QStringList m_functions; + + /** + * A list of classnames. + */ + QStringList 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(QString("Invalid scripting code for script '%1'").arg( m_scriptcontainer->getName() )) ); + + if(m_scriptcontainer->getName().isNull()) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("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(QString("Failed to initialize local module context for script '%1'").arg( m_scriptcontainer->getName() )) ); + +#ifdef KROSS_PYTHON_SCRIPT_INIT_DEBUG + krossdebug( QString("PythonScript::initialize() module='%1' refcount='%2'").arg(d->m_module->as_string().c_str()).arg(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["parent"] = PythonExtension::toPyObject( m_manager ); + +/* + // Prepare the local context. + QString 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", QVariant(false,0), true).toBool(); + + krossdebug( QString("PythonScript::initialize() name=%1 restricted=%2").arg(m_scriptcontainer->getName()).arg(restricted) ); + if(restricted) { + + // Use the RestrictedPython module wrapped by the PythonSecurity class. + code = dynamic_cast<PythonInterpreter*>(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) { + QString err = Py::value(e).as_string().c_str(); + Kross::Api::Exception::Ptr exception = toException( QString("Failed to compile python code: %1").arg(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( QString("PythonScript::finalize() module='%1' refcount='%2'").arg(d->m_module->as_string().c_str()).arg(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 QString& error) +{ + long lineno = -1; + QStringList 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) { + QString err = Py::value(e).as_string().c_str(); + e.clear(); // exception is handled. clear it now. + krosswarning( QString("Kross::Python::PythonScript::toException() Failed to fetch a traceback: %1").arg(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( QString("%1#%2: \"%3\"").arg(filename).arg(lineno).arg(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 QStringList& PythonScript::getFunctionNames() +{ + if(! d->m_module) + initialize(); //TODO catch exception + return d->m_functions; + /* + QStringList 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( QString("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. + QString 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( QString("PythonScript::execute() result=%1").arg(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( QString("PythonScript::execute() class '%1' added.").arg(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( QString("PythonScript::execute() function '%1' added.").arg(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); + QString err = errobj.as_string().c_str(); + + Kross::Api::Exception::Ptr exception = toException( QString("Failed to execute python code: %1").arg(err) ); + e.clear(); // exception is handled. clear it now. + setException( exception ); + } + catch(Py::Exception& e) { + QString err = Py::value(e).as_string().c_str(); + Kross::Api::Exception::Ptr exception = toException( QString("Failed to execute python code: %1").arg(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 QString& name, Kross::Api::List::Ptr args) +{ +#ifdef KROSS_PYTHON_SCRIPT_CALLFUNC_DEBUG + krossdebug( QString("PythonScript::callFunction(%1, %2)") + .arg(name) + .arg(args ? QString::number(args->count()) : QString("NULL")) ); +#endif + + if(hadException()) return 0; // abort if we had an unresolved exception. + + if(! d->m_module) { + setException( new Kross::Api::Exception(QString("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(QString("No such function '%1'.").arg(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(QString("Function is not callable.")) ); + + // Call the function. + Py::Object result = funcobject.apply(PythonExtension::toPyTuple(args)); + return PythonExtension::toObject(result); + } + catch(Py::Exception& e) { + QString err = Py::value(e).as_string().c_str(); + e.clear(); // exception is handled. clear it now. + setException( new Kross::Api::Exception(QString("Python Exception: %1").arg(err)) ); + } + catch(Kross::Api::Exception::Ptr e) { + setException(e); + } + + return 0; // return nothing if exception got thrown. +} + +const QStringList& PythonScript::getClassNames() +{ + if(! d->m_module) + initialize(); //TODO catch exception + return d->m_classes; +} + +Kross::Api::Object::Ptr PythonScript::classInstance(const QString& name) +{ + if(hadException()) return 0; // abort if we had an unresolved exception. + + if(! d->m_module) { + setException( new Kross::Api::Exception(QString("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(QString("No such class '%1'.").arg(name)) ); + + PyObject *pyobj = PyInstance_New(pyclass, 0, 0);//aclarg, 0); + if(! pyobj) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Failed to create instance of class '%1'.").arg(name)) ); + + Py::Object classobject(pyobj, true); + +#ifdef KROSS_PYTHON_SCRIPT_CLASSINSTANCE_DEBUG + krossdebug( QString("PythonScript::classInstance() inst='%1'").arg(classobject.as_string().c_str()) ); +#endif + return PythonExtension::toObject(classobject); + } + catch(Py::Exception& e) { + QString 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. +} + |