Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open and load --code file and arguments in C++ #60134

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/PyQt6/core/auto_additions/qgspythonrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
try:
QgsPythonRunner.isValid = staticmethod(QgsPythonRunner.isValid)
QgsPythonRunner.run = staticmethod(QgsPythonRunner.run)
QgsPythonRunner.runFile = staticmethod(QgsPythonRunner.runFile)
QgsPythonRunner.eval = staticmethod(QgsPythonRunner.eval)
QgsPythonRunner.setArgv = staticmethod(QgsPythonRunner.setArgv)
QgsPythonRunner.setInstance = staticmethod(QgsPythonRunner.setInstance)
except (NameError, AttributeError):
pass
14 changes: 14 additions & 0 deletions python/PyQt6/core/auto_generated/qgspythonrunner.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,21 @@ Returns ``True`` if the runner has an instance
static bool run( const QString &command, const QString &messageOnError = QString() );
%Docstring
Execute a Python statement
%End

static bool runFile( const QString &filename, const QString &messageOnError = QString() );
%Docstring
Execute a Python file
%End

static bool eval( const QString &command, QString &result /Out/ );
%Docstring
Eval a Python statement
%End

static bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() );
%Docstring
Set sys.argv
%End

static void setInstance( QgsPythonRunner *runner /Transfer/ );
Expand All @@ -56,8 +66,12 @@ Protected constructor: can be instantiated only from children

virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;

virtual bool runFileCommand( const QString &filename, const QString &messageOnError = QString() ) = 0;

virtual bool evalCommand( QString command, QString &result ) = 0;

virtual bool setArgvCommand( const QStringList &arguments, const QString &messageOnError = QString() ) = 0;

};

/************************************************************************
Expand Down
2 changes: 2 additions & 0 deletions python/core/auto_additions/qgspythonrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
try:
QgsPythonRunner.isValid = staticmethod(QgsPythonRunner.isValid)
QgsPythonRunner.run = staticmethod(QgsPythonRunner.run)
QgsPythonRunner.runFile = staticmethod(QgsPythonRunner.runFile)
QgsPythonRunner.eval = staticmethod(QgsPythonRunner.eval)
QgsPythonRunner.setArgv = staticmethod(QgsPythonRunner.setArgv)
QgsPythonRunner.setInstance = staticmethod(QgsPythonRunner.setInstance)
except (NameError, AttributeError):
pass
14 changes: 14 additions & 0 deletions python/core/auto_generated/qgspythonrunner.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,21 @@ Returns ``True`` if the runner has an instance
static bool run( const QString &command, const QString &messageOnError = QString() );
%Docstring
Execute a Python statement
%End

static bool runFile( const QString &filename, const QString &messageOnError = QString() );
%Docstring
Execute a Python file
%End

static bool eval( const QString &command, QString &result /Out/ );
%Docstring
Eval a Python statement
%End

static bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() );
%Docstring
Set sys.argv
%End

static void setInstance( QgsPythonRunner *runner /Transfer/ );
Expand All @@ -56,8 +66,12 @@ Protected constructor: can be instantiated only from children

virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;

virtual bool runFileCommand( const QString &filename, const QString &messageOnError = QString() ) = 0;

virtual bool evalCommand( QString command, QString &result ) = 0;

virtual bool setArgvCommand( const QStringList &arguments, const QString &messageOnError = QString() ) = 0;

};

/************************************************************************
Expand Down
13 changes: 2 additions & 11 deletions src/app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1634,23 +1634,14 @@ int main( int argc, char *argv[] )
{
if ( !pythonfile.isEmpty() )
{
#ifdef Q_OS_WIN
//replace backslashes with forward slashes
pythonfile.replace( '\\', '/' );
#endif
pythonArgs.prepend( pythonfile );
}

QgsPythonRunner::run( QStringLiteral( "sys.argv = ['%1']" ).arg( pythonArgs.replaceInStrings( QChar( '\'' ), QStringLiteral( "\\'" ) ).join( "','" ) ) );
QgsPythonRunner::setArgv( pythonArgs );
}

if ( !pythonfile.isEmpty() )
{
#ifdef Q_OS_WIN
//replace backslashes with forward slashes
pythonfile.replace( '\\', '/' );
#endif
QgsPythonRunner::run( QStringLiteral( "with open('%1','r') as f: exec(f.read())" ).arg( pythonfile ) );
QgsPythonRunner::runFile( pythonfile );
}

/////////////////////////////////`////////////////////////////////////
Expand Down
28 changes: 28 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12246,6 +12246,20 @@ class QgsPythonRunnerImpl : public QgsPythonRunner
return false;
}

bool runFileCommand( const QString &filename, const QString &messageOnError = QString() ) override
{
#ifdef WITH_BINDINGS
if ( mPythonUtils && mPythonUtils->isEnabled() )
{
return mPythonUtils->runFile( filename, messageOnError );
}
#else
Q_UNUSED( filename )
Q_UNUSED( messageOnError )
#endif
return false;
}

bool evalCommand( QString command, QString &result ) override
{
#ifdef WITH_BINDINGS
Expand All @@ -12260,6 +12274,20 @@ class QgsPythonRunnerImpl : public QgsPythonRunner
return false;
}

bool setArgvCommand( const QStringList &arguments, const QString &messageOnError = QString() ) override
{
#ifdef WITH_BINDINGS
if ( mPythonUtils && mPythonUtils->isEnabled() )
{
return mPythonUtils->setArgv( arguments, messageOnError );
}
#else
Q_UNUSED( arguments )
Q_UNUSED( messageOnError )
#endif
return false;
}

protected:
QgsPythonUtils *mPythonUtils = nullptr;
};
Expand Down
27 changes: 27 additions & 0 deletions src/core/qgspythonrunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ bool QgsPythonRunner::run( const QString &command, const QString &messageOnError
}
}

bool QgsPythonRunner::runFile( const QString &filename, const QString &messageOnError )
{
if ( sInstance )
{
QgsDebugMsgLevel( "Running " + filename, 3 );
return sInstance->runFileCommand( filename, messageOnError );
}
else
{
QgsDebugError( QStringLiteral( "Unable to run Python command: runner not available!" ) );
return false;
}
}

bool QgsPythonRunner::eval( const QString &command, QString &result )
{
if ( sInstance )
Expand All @@ -52,6 +66,19 @@ bool QgsPythonRunner::eval( const QString &command, QString &result )
}
}

bool QgsPythonRunner::setArgv( const QStringList &arguments, const QString &messageOnError )
{
if ( sInstance )
{
return sInstance->setArgvCommand( arguments, messageOnError );
}
else
{
QgsDebugError( QStringLiteral( "Unable to run Python command: runner not available!" ) );
return false;
}
}

void QgsPythonRunner::setInstance( QgsPythonRunner *runner )
{
delete sInstance;
Expand Down
10 changes: 10 additions & 0 deletions src/core/qgspythonrunner.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@ class CORE_EXPORT QgsPythonRunner
//! Execute a Python statement
static bool run( const QString &command, const QString &messageOnError = QString() );

//! Execute a Python file
static bool runFile( const QString &filename, const QString &messageOnError = QString() );

//! Eval a Python statement
static bool eval( const QString &command, QString &result SIP_OUT );

//! Set sys.argv
static bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() );

/**
* Assign an instance of Python runner so that run() can be used.
* This method should be called during app initialization.
Expand All @@ -59,8 +65,12 @@ class CORE_EXPORT QgsPythonRunner

virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;

virtual bool runFileCommand( const QString &filename, const QString &messageOnError = QString() ) = 0;

virtual bool evalCommand( QString command, QString &result ) = 0;

virtual bool setArgvCommand( const QStringList &arguments, const QString &messageOnError = QString() ) = 0;

static QgsPythonRunner *sInstance;
};

Expand Down
12 changes: 12 additions & 0 deletions src/python/qgspythonutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,23 @@ class PYTHON_EXPORT QgsPythonUtils
*/
virtual QString runStringUnsafe( const QString &command, bool single = true ) = 0;

/**
* Runs a Python \a filename, showing an error message if one occurred.
* \returns TRUE if no error occurred
*/
virtual bool runFile( const QString &filename, const QString &messageOnError = QString() ) = 0;

/**
* Evaluates a Python \a command and stores the result in a the \a result string.
*/
virtual bool evalString( const QString &command, QString &result ) = 0;

/**
* Sets sys.argv to the given Python \a arguments, showing an error message if one occurred.
* \returns TRUE if no error occurred
*/
virtual bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() ) = 0;

/**
* Gets information about error to the supplied arguments
* \returns FALSE if there was no Python error
Expand Down
128 changes: 128 additions & 0 deletions src/python/qgspythonutilsimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,134 @@ bool QgsPythonUtilsImpl::runString( const QString &command, QString msgOnError,
return res;
}

QString QgsPythonUtilsImpl::runFileUnsafe( const QString &filename )
{
// acquire global interpreter lock to ensure we are in a consistent state
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
QString ret;

PyObject *obj, *errobj;

QFile file( filename );
if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
{
ret = "Cannot open file";
goto error;
}

obj = PyRun_String( file.readAll().constData(), Py_file_input, mMainDict, mMainDict );
errobj = PyErr_Occurred();
if ( nullptr != errobj )
{
ret = getTraceback();
}
Py_XDECREF( obj );

error:
// we are done calling python API, release global interpreter lock
PyGILState_Release( gstate );

return ret;
}

bool QgsPythonUtilsImpl::runFile( const QString &filename, const QString &messageOnError )
{
const QString &traceback = runFileUnsafe( filename );
if ( traceback.isEmpty() )
return true;

// use some default message if custom hasn't been specified
const QString &errMsg = !messageOnError.isEmpty() ? messageOnError : QObject::tr( "An error occurred during execution of following file:" ) + "\n<tt>" + filename + "</tt>";

QString path, version;
evalString( QStringLiteral( "str(sys.path)" ), path );
evalString( QStringLiteral( "sys.version" ), version );

QString str = "<font color=\"red\">" + errMsg + "</font><br><pre>\n" + traceback + "\n</pre>"
+ QObject::tr( "Python version:" ) + "<br>" + version + "<br><br>"
+ QObject::tr( "QGIS version:" ) + "<br>" + QStringLiteral( "%1 '%2', %3" ).arg( Qgis::version(), Qgis::releaseName(), Qgis::devVersion() ) + "<br><br>"
+ QObject::tr( "Python path:" ) + "<br>" + path;
str.replace( '\n', QLatin1String( "<br>" ) ).replace( QLatin1String( " " ), QLatin1String( "&nbsp; " ) );

qDebug() << str;
QgsMessageOutput *msg = QgsMessageOutput::createMessageOutput();
msg->setTitle( QObject::tr( "Python error" ) );
msg->setMessage( str, QgsMessageOutput::MessageHtml );
msg->showMessage();

return false;
}

QString QgsPythonUtilsImpl::setArgvUnsafe( const QStringList &arguments )
{
// acquire global interpreter lock to ensure we are in a consistent state
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
QString ret;

PyObject *sysobj = nullptr, *errobj = nullptr, *argsobj = nullptr;
sysobj = PyImport_ImportModule( "sys" );
if ( !sysobj )
{
errobj = PyErr_Occurred();
if ( errobj )
ret = QString( "SetArgvTraceback" ) + getTraceback();
else
ret = "Error occurred in PyImport_ImportModule";
goto error;
}
argsobj = PyList_New( arguments.size() );
if ( !argsobj )
{
ret = "Error occurred in PyList_New";
goto error;
}
for ( int i = 0; i != arguments.size(); ++i )
PyList_SET_ITEM( argsobj, i, PyUnicode_FromString( arguments[i].toUtf8().constData() ) );
if ( PyObject_SetAttrString( sysobj, "argv", argsobj ) != 0 )
{
ret = "Error occurred in PyObject_SetAttrString";
goto error;
}
error:
Py_XDECREF( argsobj );
Py_XDECREF( sysobj );

// we are done calling python API, release global interpreter lock
PyGILState_Release( gstate );

return ret;
}

bool QgsPythonUtilsImpl::setArgv( const QStringList &arguments, const QString &messageOnError )
{
const QString &traceback = setArgvUnsafe( arguments );
if ( traceback.isEmpty() )
return true;

// use some default message if custom hasn't been specified
const QString &errMsg = !messageOnError.isEmpty() ? messageOnError : QObject::tr( "An error occurred while setting sys.argv from following list:" ) + "\n<tt>" + arguments.join( ',' ) + "</tt>";

QString path, version;
evalString( QStringLiteral( "str(sys.path)" ), path );
evalString( QStringLiteral( "sys.version" ), version );

QString str = "<font color=\"red\">" + errMsg + "</font><br><pre>\n" + traceback + "\n</pre>"
+ QObject::tr( "Python version:" ) + "<br>" + version + "<br><br>"
+ QObject::tr( "QGIS version:" ) + "<br>" + QStringLiteral( "%1 '%2', %3" ).arg( Qgis::version(), Qgis::releaseName(), Qgis::devVersion() ) + "<br><br>"
+ QObject::tr( "Python path:" ) + "<br>" + path;
str.replace( '\n', QLatin1String( "<br>" ) ).replace( QLatin1String( " " ), QLatin1String( "&nbsp; " ) );

qDebug() << str;
QgsMessageOutput *msg = QgsMessageOutput::createMessageOutput();
msg->setTitle( QObject::tr( "Python error" ) );
msg->setMessage( str, QgsMessageOutput::MessageHtml );
msg->showMessage();

return false;
}


QString QgsPythonUtilsImpl::getTraceback()
{
Expand Down
Loading
Loading