/***************************************************************************
* 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 .
*/
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["parent"] = 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 < 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 ) {
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.
}