diff --git a/python/PyQt6/core/auto_additions/qgspythonrunner.py b/python/PyQt6/core/auto_additions/qgspythonrunner.py
index 78e70ef42d23..140aee65a9d5 100644
--- a/python/PyQt6/core/auto_additions/qgspythonrunner.py
+++ b/python/PyQt6/core/auto_additions/qgspythonrunner.py
@@ -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
diff --git a/python/PyQt6/core/auto_generated/qgspythonrunner.sip.in b/python/PyQt6/core/auto_generated/qgspythonrunner.sip.in
index d085b259b212..6e3c7b5aa71f 100644
--- a/python/PyQt6/core/auto_generated/qgspythonrunner.sip.in
+++ b/python/PyQt6/core/auto_generated/qgspythonrunner.sip.in
@@ -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/ );
@@ -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;
+
};
/************************************************************************
diff --git a/python/core/auto_additions/qgspythonrunner.py b/python/core/auto_additions/qgspythonrunner.py
index 78e70ef42d23..140aee65a9d5 100644
--- a/python/core/auto_additions/qgspythonrunner.py
+++ b/python/core/auto_additions/qgspythonrunner.py
@@ -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
diff --git a/python/core/auto_generated/qgspythonrunner.sip.in b/python/core/auto_generated/qgspythonrunner.sip.in
index d085b259b212..6e3c7b5aa71f 100644
--- a/python/core/auto_generated/qgspythonrunner.sip.in
+++ b/python/core/auto_generated/qgspythonrunner.sip.in
@@ -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/ );
@@ -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;
+
};
/************************************************************************
diff --git a/src/app/main.cpp b/src/app/main.cpp
index afaaa23eedf8..9e50b8b775a6 100644
--- a/src/app/main.cpp
+++ b/src/app/main.cpp
@@ -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 );
}
/////////////////////////////////`////////////////////////////////////
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index bc5c5eea0504..646b20115484 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -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
@@ -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;
};
diff --git a/src/core/qgspythonrunner.cpp b/src/core/qgspythonrunner.cpp
index 8dd7dab37ed1..70518cc7de22 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 30920de902b8..bff33f448ca4 100644
--- a/src/core/qgspythonrunner.h
+++ b/src/core/qgspythonrunner.h
@@ -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.
@@ -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;
};
diff --git a/src/python/qgspythonutils.h b/src/python/qgspythonutils.h
index 68bb9fc1490d..9f1786d921b1 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 14facb8ea425..0ba1185fcd3e 100644
--- a/src/python/qgspythonutilsimpl.cpp
+++ b/src/python/qgspythonutilsimpl.cpp
@@ -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" + 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:" ) + "