diff --git a/src/app/main.cpp b/src/app/main.cpp
index d64a6aeef29b..53c1bd644258 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 0d2bfecfc44a..16471e28c503 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -12101,6 +12101,20 @@ class QgsPythonRunnerImpl : public QgsPythonRunner
return false;
}
+ bool runFile( 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
@@ -12115,6 +12129,20 @@ class QgsPythonRunnerImpl : public QgsPythonRunner
return false;
}
+ bool setArgv( 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..91efbc620b39 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->runFile( 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,9 +66,21 @@ bool QgsPythonRunner::eval( const QString &command, QString &result )
}
}
+bool QgsPythonRunner::setArgv( const QStringList &arguments, const QString &messageOnError )
+{
+ if ( sInstance )
+ {
+ return sInstance->setArgv( arguments, messageOnError );
+ }
+ else
+ {
+ QgsDebugError( QStringLiteral( "Unable to run Python command: runner not available!" ) );
+ return false;
+ }
+}
+
void QgsPythonRunner::setInstance( QgsPythonRunner *runner )
{
delete sInstance;
sInstance = runner;
}
-
diff --git a/src/core/qgspythonrunner.h b/src/core/qgspythonrunner.h
index 30920de902b8..560264fe69bf 100644
--- a/src/core/qgspythonrunner.h
+++ b/src/core/qgspythonrunner.h
@@ -32,7 +32,6 @@
class CORE_EXPORT QgsPythonRunner
{
public:
-
/**
* Returns TRUE if the runner has an instance
* (and thus is able to run commands)
@@ -42,9 +41,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 +64,12 @@ class CORE_EXPORT QgsPythonRunner
virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;
+ virtual bool runFile( const QString &filename, const QString &messageOnError = QString() ) = 0;
+
virtual bool evalCommand( QString command, QString &result ) = 0;
+ virtual bool setArgv( 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..4f22592c4414 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, QString msgOnError = QString(), bool single = true ) = 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 setArgs( const QStringList &arguments, QString msgOnError = QString(), bool single = true ) = 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..01b89318b17b 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;
+
+ QFile file( filename );
+ if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
+ {
+ ret = "Cannot open file";
+ goto error;
+ }
+
+ PyObject *obj = PyRun_String( file.readAll().constData(), Py_file_input, mMainDict, mMainDict );
+ PyObject *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 = QString() )
+{
+ const QString traceback = runFileUnsafe( filename );
+ if ( traceback.isEmpty() )
+ return true;
+
+ if ( msgOnError.isEmpty() )
+ {
+ // use some default message if custom hasn't been specified
+ msgOnError = 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 = "" + msgOnError + "
\n" + traceback + "\n" + + QObject::tr( "Python version:" ) + "
\n" + traceback + "\n" + + QObject::tr( "Python version:" ) + "