summaryrefslogtreecommitdiffstats
path: root/lib/kross/python/pythonscript.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kross/python/pythonscript.cpp')
-rw-r--r--lib/kross/python/pythonscript.cpp460
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.
+}
+