Merge pull request #60134 from btzy/codearg

Open and load --code file and --py-args arguments in C++
This commit is contained in:
Matthias Kuhn 2025-04-21 19:52:14 +02:00 committed by GitHub
commit 6aac924cc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 296 additions and 25 deletions

View File

@ -2,8 +2,10 @@
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)
QgsPythonRunner.__abstract_methods__ = ['runCommand', 'evalCommand']
QgsPythonRunner.__abstract_methods__ = ['runCommand', 'runFileCommand', 'evalCommand', 'setArgvCommand']
except (NameError, AttributeError):
pass

View File

@ -33,11 +33,23 @@ commands)
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 ``filename``, showing an error message if one occurred.
:return: true if no error occurred
%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/ );
@ -56,8 +68,24 @@ Protected constructor: can be instantiated only from children
virtual ~QgsPythonRunner();
virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;
%Docstring
Runs the given statement.
%End
virtual bool runFileCommand( const QString &filename, const QString &messageOnError = QString() ) = 0;
%Docstring
Runs the code from the given file.
%End
virtual bool evalCommand( QString command, QString &result ) = 0;
%Docstring
Evaluates the given expression, producing a result.
%End
virtual bool setArgvCommand( const QStringList &arguments, const QString &messageOnError = QString() ) = 0;
%Docstring
Sets sys.argv to the given arguments.
%End
};

View File

@ -14013,12 +14013,16 @@ QgsProxyProgressTask.finalize: src/core/qgsproxyprogresstask.h#L57
QgsProxyProgressTask.run: src/core/qgsproxyprogresstask.h#L59
QgsProxyProgressTask.setProxyProgress: src/core/qgsproxyprogresstask.h#L66
QgsProxyProgressTask: src/core/qgsproxyprogresstask.h#L37
QgsPythonRunner.eval: src/core/qgspythonrunner.h#L46
QgsPythonRunner.evalCommand: src/core/qgspythonrunner.h#L62
QgsPythonRunner.eval: src/core/qgspythonrunner.h#L52
QgsPythonRunner.evalCommand: src/core/qgspythonrunner.h#L76
QgsPythonRunner.isValid: src/core/qgspythonrunner.h#L40
QgsPythonRunner.run: src/core/qgspythonrunner.h#L43
QgsPythonRunner.runCommand: src/core/qgspythonrunner.h#L60
QgsPythonRunner.setInstance: src/core/qgspythonrunner.h#L53
QgsPythonRunner.runCommand: src/core/qgspythonrunner.h#L70
QgsPythonRunner.runFile: src/core/qgspythonrunner.h#L49
QgsPythonRunner.runFileCommand: src/core/qgspythonrunner.h#L73
QgsPythonRunner.setArgv: src/core/qgspythonrunner.h#L55
QgsPythonRunner.setArgvCommand: src/core/qgspythonrunner.h#L79
QgsPythonRunner.setInstance: src/core/qgspythonrunner.h#L62
QgsPythonRunner: src/core/qgspythonrunner.h#L32
QgsQtLocationConnection.broadcastConnectionAvailable: src/core/gps/qgsqtlocationconnection.h#L45
QgsQtLocationConnection.parseData: src/core/gps/qgsqtlocationconnection.h#L48

View File

@ -2,8 +2,10 @@
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)
QgsPythonRunner.__abstract_methods__ = ['runCommand', 'evalCommand']
QgsPythonRunner.__abstract_methods__ = ['runCommand', 'runFileCommand', 'evalCommand', 'setArgvCommand']
except (NameError, AttributeError):
pass

View File

@ -33,11 +33,23 @@ commands)
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 ``filename``, showing an error message if one occurred.
:return: true if no error occurred
%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/ );
@ -56,8 +68,24 @@ Protected constructor: can be instantiated only from children
virtual ~QgsPythonRunner();
virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;
%Docstring
Runs the given statement.
%End
virtual bool runFileCommand( const QString &filename, const QString &messageOnError = QString() ) = 0;
%Docstring
Runs the code from the given file.
%End
virtual bool evalCommand( QString command, QString &result ) = 0;
%Docstring
Evaluates the given expression, producing a result.
%End
virtual bool setArgvCommand( const QStringList &arguments, const QString &messageOnError = QString() ) = 0;
%Docstring
Sets sys.argv to the given arguments.
%End
};

View File

@ -14013,12 +14013,16 @@ QgsProxyProgressTask.finalize: src/core/qgsproxyprogresstask.h#L57
QgsProxyProgressTask.run: src/core/qgsproxyprogresstask.h#L59
QgsProxyProgressTask.setProxyProgress: src/core/qgsproxyprogresstask.h#L66
QgsProxyProgressTask: src/core/qgsproxyprogresstask.h#L37
QgsPythonRunner.eval: src/core/qgspythonrunner.h#L46
QgsPythonRunner.evalCommand: src/core/qgspythonrunner.h#L62
QgsPythonRunner.eval: src/core/qgspythonrunner.h#L52
QgsPythonRunner.evalCommand: src/core/qgspythonrunner.h#L76
QgsPythonRunner.isValid: src/core/qgspythonrunner.h#L40
QgsPythonRunner.run: src/core/qgspythonrunner.h#L43
QgsPythonRunner.runCommand: src/core/qgspythonrunner.h#L60
QgsPythonRunner.setInstance: src/core/qgspythonrunner.h#L53
QgsPythonRunner.runCommand: src/core/qgspythonrunner.h#L70
QgsPythonRunner.runFile: src/core/qgspythonrunner.h#L49
QgsPythonRunner.runFileCommand: src/core/qgspythonrunner.h#L73
QgsPythonRunner.setArgv: src/core/qgspythonrunner.h#L55
QgsPythonRunner.setArgvCommand: src/core/qgspythonrunner.h#L79
QgsPythonRunner.setInstance: src/core/qgspythonrunner.h#L62
QgsPythonRunner: src/core/qgspythonrunner.h#L32
QgsQtLocationConnection.broadcastConnectionAvailable: src/core/gps/qgsqtlocationconnection.h#L45
QgsQtLocationConnection.parseData: src/core/gps/qgsqtlocationconnection.h#L48

View File

@ -1637,23 +1637,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 );
}
/////////////////////////////////`////////////////////////////////////

View File

@ -12169,6 +12169,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
@ -12183,6 +12197,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;
};

View File

@ -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 )
@ -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;

View File

@ -42,9 +42,18 @@ class CORE_EXPORT QgsPythonRunner
//! Execute a Python statement
static bool run( const QString &command, const QString &messageOnError = QString() );
/**
* Execute a Python \a filename, showing an error message if one occurred.
* \returns true if no error occurred
*/
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.
@ -57,10 +66,18 @@ class CORE_EXPORT QgsPythonRunner
QgsPythonRunner() = default;
virtual ~QgsPythonRunner() = default;
//! Runs the given statement.
virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;
//! Runs the code from the given file.
virtual bool runFileCommand( const QString &filename, const QString &messageOnError = QString() ) = 0;
//! Evaluates the given expression, producing a result.
virtual bool evalCommand( QString command, QString &result ) = 0;
//! Sets sys.argv to the given arguments.
virtual bool setArgvCommand( const QStringList &arguments, const QString &messageOnError = QString() ) = 0;
static QgsPythonRunner *sInstance;
};

View File

@ -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

View File

@ -454,6 +454,132 @@ 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 = QStringLiteral( "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; " ) );
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; " ) );
QgsMessageOutput *msg = QgsMessageOutput::createMessageOutput();
msg->setTitle( QObject::tr( "Python error" ) );
msg->setMessage( str, QgsMessageOutput::MessageHtml );
msg->showMessage();
return false;
}
QString QgsPythonUtilsImpl::getTraceback()
{

View File

@ -44,9 +44,15 @@ class QgsPythonUtilsImpl : public QgsPythonUtils
bool isEnabled() final;
bool runString( const QString &command, QString msgOnError = QString(), bool single = true ) final;
QString runStringUnsafe( const QString &command, bool single = true ) final; // returns error traceback on failure, empty QString on success
bool runFile( const QString &filename, const QString &messageOnError = QString() ) final;
bool evalString( const QString &command, QString &result ) final;
bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() ) final;
bool getError( QString &errorClassName, QString &errorText ) final;
private:
QString runFileUnsafe( const QString &filename ); // returns error traceback on failure, empty QString on success
QString setArgvUnsafe( const QStringList &arguments ); // returns error traceback on failure, empty QString on success
public:
/**
* Returns the path where QGIS Python related files are located.
*/

View File

@ -233,10 +233,6 @@ ACCEPTABLE_MISSING_DOCS = {
"top() const",
],
"QgsScopeLogger": ["QgsScopeLogger(const char *file, const char *func, int line)"],
"QgsPythonRunner": [
"evalCommand(QString command, QString &result)=0",
"runCommand(QString command, QString messageOnError=QString())=0",
],
"QgsAttributeActionDialog": [
"init(const QgsActionManager &action, const QgsAttributeTableConfig &attributeTableConfig)",
"QgsAttributeActionDialog(const QgsActionManager &actions, QWidget *parent=nullptr)",