diff --git a/python/PyQt6/core/auto_additions/qgspythonrunner.py b/python/PyQt6/core/auto_additions/qgspythonrunner.py
index 70223f5b1a3..eea3f842be6 100644
--- a/python/PyQt6/core/auto_additions/qgspythonrunner.py
+++ b/python/PyQt6/core/auto_additions/qgspythonrunner.py
@@ -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
diff --git a/python/PyQt6/core/auto_generated/qgspythonrunner.sip.in b/python/PyQt6/core/auto_generated/qgspythonrunner.sip.in
index 03154aeecc3..4d047a3dd9c 100644
--- a/python/PyQt6/core/auto_generated/qgspythonrunner.sip.in
+++ b/python/PyQt6/core/auto_generated/qgspythonrunner.sip.in
@@ -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
};
diff --git a/python/PyQt6/core/class_map.yaml b/python/PyQt6/core/class_map.yaml
index 400c3ebcb6e..738a005338b 100644
--- a/python/PyQt6/core/class_map.yaml
+++ b/python/PyQt6/core/class_map.yaml
@@ -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
diff --git a/python/core/auto_additions/qgspythonrunner.py b/python/core/auto_additions/qgspythonrunner.py
index 70223f5b1a3..eea3f842be6 100644
--- a/python/core/auto_additions/qgspythonrunner.py
+++ b/python/core/auto_additions/qgspythonrunner.py
@@ -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
diff --git a/python/core/auto_generated/qgspythonrunner.sip.in b/python/core/auto_generated/qgspythonrunner.sip.in
index 03154aeecc3..4d047a3dd9c 100644
--- a/python/core/auto_generated/qgspythonrunner.sip.in
+++ b/python/core/auto_generated/qgspythonrunner.sip.in
@@ -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
};
diff --git a/python/core/class_map.yaml b/python/core/class_map.yaml
index eb6c1df61fa..25f5d0d7d85 100644
--- a/python/core/class_map.yaml
+++ b/python/core/class_map.yaml
@@ -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
diff --git a/src/app/main.cpp b/src/app/main.cpp
index fef82c85263..6f2804d6604 100644
--- a/src/app/main.cpp
+++ b/src/app/main.cpp
@@ -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 );
}
/////////////////////////////////`////////////////////////////////////
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index d7c4f790c97..bcc8fd2844c 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -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;
};
diff --git a/src/core/qgspythonrunner.cpp b/src/core/qgspythonrunner.cpp
index 8dd7dab37ed..70518cc7de2 100644
--- a/src/core/qgspythonrunner.cpp
+++ b/src/core/qgspythonrunner.cpp
@@ -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;
diff --git a/src/core/qgspythonrunner.h b/src/core/qgspythonrunner.h
index 30920de902b..a9a2db56510 100644
--- a/src/core/qgspythonrunner.h
+++ b/src/core/qgspythonrunner.h
@@ -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;
};
diff --git a/src/python/qgspythonutils.h b/src/python/qgspythonutils.h
index 68bb9fc1490..9f1786d921b 100644
--- a/src/python/qgspythonutils.h
+++ b/src/python/qgspythonutils.h
@@ -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
diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp
index 14facb8ea42..0d5854b2475 100644
--- a/src/python/qgspythonutilsimpl.cpp
+++ b/src/python/qgspythonutilsimpl.cpp
@@ -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" + filename + "";
+
+ QString path, version;
+ evalString( QStringLiteral( "str(sys.path)" ), path );
+ evalString( QStringLiteral( "sys.version" ), version );
+
+ QString str = "" + errMsg + "
\n" + traceback + "\n" + + QObject::tr( "Python version:" ) + "
\n" + traceback + "\n" + + QObject::tr( "Python version:" ) + "