From 2f82bab1d9f62de445de277b819a7940852b9390 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 1 Mar 2019 11:02:58 +1000 Subject: [PATCH] Add Python utils method to start a Processing specific plugin This command adds a plugin to active plugins and calls initProcessing(), initializing only Processing related components of that plugin. The new initProcessing() hook should be implemented by plugins which provide Processing providers or algorithm, and should only implement code which is required to load the provider and algorithms. Strictly no GUI related code should be used here, that MUST be moved out of initializers and deferred to the plugin's initGui implementation. --- python/utils.py | 59 ++++++++++++++++--- src/python/qgspythonutils.h | 10 ++++ src/python/qgspythonutilsimpl.cpp | 7 +++ src/python/qgspythonutilsimpl.h | 1 + tests/src/app/testqgisapppython.cpp | 33 +++++++++++ .../ProcessingPluginTest/__init__.py | 45 ++++++++++++++ .../ProcessingPluginTest/metadata.txt | 7 +++ 7 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 tests/testdata/test_plugin_path/ProcessingPluginTest/__init__.py create mode 100644 tests/testdata/test_plugin_path/ProcessingPluginTest/metadata.txt diff --git a/python/utils.py b/python/utils.py index cd959ad0755..8495bbefa07 100644 --- a/python/utils.py +++ b/python/utils.py @@ -314,8 +314,8 @@ def loadPlugin(packageName): return False -def startPlugin(packageName): - """ initialize the plugin """ +def _startPlugin(packageName): + """ initializes a plugin, but does not load GUI """ global plugins, active_plugins, iface, plugin_times if packageName in active_plugins: @@ -326,18 +326,30 @@ def startPlugin(packageName): package = sys.modules[packageName] - errMsg = QCoreApplication.translate("Python", "Couldn't load plugin '{0}'").format(packageName) - - start = time.process_time() - # create an instance of the plugin try: plugins[packageName] = package.classFactory(iface) except: _unloadPluginModules(packageName) + errMsg = QCoreApplication.translate("Python", "Couldn't load plugin '{0}'").format(packageName) msg = QCoreApplication.translate("Python", "{0} due to an error when calling its classFactory() method").format(errMsg) showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True) return False + return True + + +def _addToActivePlugins(packageName, duration): + """ Adds a plugin to the list of active plugins """ + active_plugins.append(packageName) + plugin_times[packageName] = "{0:02f}s".format(duration) + + +def startPlugin(packageName): + """ initialize the plugin """ + global plugins, active_plugins, iface, plugin_times + start = time.process_time() + if not _startPlugin(packageName): + return False # initGui try: @@ -345,14 +357,43 @@ def startPlugin(packageName): except: del plugins[packageName] _unloadPluginModules(packageName) + errMsg = QCoreApplication.translate("Python", "Couldn't load plugin '{0}'").format(packageName) msg = QCoreApplication.translate("Python", "{0} due to an error when calling its initGui() method").format(errMsg) showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True) return False - # add to active plugins - active_plugins.append(packageName) end = time.process_time() - plugin_times[packageName] = "{0:02f}s".format(end - start) + _addToActivePlugins(packageName, end - start) + return True + + +def startProcessingPlugin(packageName): + """ initialize only the Processing components of a plugin """ + global plugins, active_plugins, iface, plugin_times + start = time.process_time() + if not _startPlugin(packageName): + return False + + errMsg = QCoreApplication.translate("Python", "Couldn't load plugin '{0}'").format(packageName) + if not hasattr(plugins[packageName], 'initProcessing'): + del plugins[packageName] + _unloadPluginModules(packageName) + msg = QCoreApplication.translate("Python", "{0} - plugin has no initProcessing() method").format(errMsg) + showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True) + return False + + # initProcessing + try: + plugins[packageName].initProcessing() + except: + del plugins[packageName] + _unloadPluginModules(packageName) + msg = QCoreApplication.translate("Python", "{0} due to an error when calling its initProcessing() method").format(errMsg) + showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True) + return False + + end = time.process_time() + _addToActivePlugins(packageName, end - start) return True diff --git a/src/python/qgspythonutils.h b/src/python/qgspythonutils.h index cbc0658d767..e0f19c5742a 100644 --- a/src/python/qgspythonutils.h +++ b/src/python/qgspythonutils.h @@ -161,6 +161,16 @@ class PYTHON_EXPORT QgsPythonUtils */ virtual bool startPlugin( const QString &packageName ) = 0; + /** + * Start a Processing plugin + * + * This command adds a plugin to active plugins and calls initProcessing(), + * initializing only Processing related components of that plugin. + * + * \since QGIS 3.8 + */ + virtual bool startProcessingPlugin( const QString &packageName ) = 0; + /** * Helper function to return some information about a plugin. * diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp index a6d71933de2..cc567535301 100644 --- a/src/python/qgspythonutilsimpl.cpp +++ b/src/python/qgspythonutilsimpl.cpp @@ -623,6 +623,13 @@ bool QgsPythonUtilsImpl::startPlugin( const QString &packageName ) return ( output == QLatin1String( "True" ) ); } +bool QgsPythonUtilsImpl::startProcessingPlugin( const QString &packageName ) +{ + QString output; + evalString( "qgis.utils.startProcessingPlugin('" + packageName + "')", output ); + return ( output == QLatin1String( "True" ) ); +} + bool QgsPythonUtilsImpl::canUninstallPlugin( const QString &packageName ) { QString output; diff --git a/src/python/qgspythonutilsimpl.h b/src/python/qgspythonutilsimpl.h index c7e6acff337..7aea73e6255 100644 --- a/src/python/qgspythonutilsimpl.h +++ b/src/python/qgspythonutilsimpl.h @@ -85,6 +85,7 @@ class QgsPythonUtilsImpl : public QgsPythonUtils QStringList listActivePlugins() override; bool loadPlugin( const QString &packageName ) override; bool startPlugin( const QString &packageName ) override; + bool startProcessingPlugin( const QString &packageName ) override; QString getPluginMetadata( const QString &pluginName, const QString &function ) override; bool canUninstallPlugin( const QString &packageName ) override; bool unloadPlugin( const QString &packageName ) override; diff --git a/tests/src/app/testqgisapppython.cpp b/tests/src/app/testqgisapppython.cpp index e2cc4d7f32e..a2b32c2c606 100644 --- a/tests/src/app/testqgisapppython.cpp +++ b/tests/src/app/testqgisapppython.cpp @@ -40,6 +40,9 @@ class TestQgisAppPython : public QObject void init() {} // will be called before each testfunction is executed. void cleanup() {} // will be called after every testfunction. + void hasPython(); + void plugins(); + void pythonPlugin(); void runString(); void evalString(); @@ -53,6 +56,8 @@ TestQgisAppPython::TestQgisAppPython() = default; //runs before all tests void TestQgisAppPython::initTestCase() { + qputenv( "QGIS_PLUGINPATH", QByteArray( TEST_DATA_DIR ) + "/test_plugin_path" ); + // Set up the QgsSettings environment QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); @@ -73,6 +78,33 @@ void TestQgisAppPython::cleanupTestCase() QgsApplication::exitQgis(); } +void TestQgisAppPython::hasPython() +{ + QVERIFY( mQgisApp->mPythonUtils->isEnabled() ); +} + +void TestQgisAppPython::plugins() +{ + QVERIFY( mQgisApp->mPythonUtils->pluginList().contains( QStringLiteral( "PluginPathTest" ) ) ); + QVERIFY( !mQgisApp->mPythonUtils->isPluginLoaded( QStringLiteral( "PluginPathTest" ) ) ); + QVERIFY( mQgisApp->mPythonUtils->listActivePlugins().isEmpty() ); + // load plugin + QVERIFY( !mQgisApp->mPythonUtils->unloadPlugin( QStringLiteral( "PluginPathTest" ) ) ); + QVERIFY( mQgisApp->mPythonUtils->loadPlugin( QStringLiteral( "PluginPathTest" ) ) ); + QVERIFY( !mQgisApp->mPythonUtils->isPluginLoaded( QStringLiteral( "PluginPathTest" ) ) ); + QVERIFY( mQgisApp->mPythonUtils->startPlugin( QStringLiteral( "PluginPathTest" ) ) ); + QVERIFY( mQgisApp->mPythonUtils->isPluginLoaded( QStringLiteral( "PluginPathTest" ) ) ); + QCOMPARE( mQgisApp->mPythonUtils->listActivePlugins(), QStringList() << QStringLiteral( "PluginPathTest" ) ); +} + +void TestQgisAppPython::pythonPlugin() +{ + QVERIFY( mQgisApp->mPythonUtils->pluginList().contains( QStringLiteral( "ProcessingPluginTest" ) ) ); + QVERIFY( mQgisApp->mPythonUtils->loadPlugin( QStringLiteral( "ProcessingPluginTest" ) ) ); + QVERIFY( mQgisApp->mPythonUtils->startProcessingPlugin( QStringLiteral( "ProcessingPluginTest" ) ) ); + QVERIFY( !mQgisApp->mPythonUtils->startProcessingPlugin( QStringLiteral( "PluginPathTest" ) ) ); +} + void TestQgisAppPython::runString() { QVERIFY( mQgisApp->mPythonUtils->runString( "a=1+1" ) ); @@ -91,5 +123,6 @@ void TestQgisAppPython::evalString() QVERIFY( !mQgisApp->mPythonUtils->evalString( "1+", result ) ); } + QGSTEST_MAIN( TestQgisAppPython ) #include "testqgisapppython.moc" diff --git a/tests/testdata/test_plugin_path/ProcessingPluginTest/__init__.py b/tests/testdata/test_plugin_path/ProcessingPluginTest/__init__.py new file mode 100644 index 00000000000..c36f5ce9706 --- /dev/null +++ b/tests/testdata/test_plugin_path/ProcessingPluginTest/__init__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + __init__.py + --------------------- + Date : July 2013 + Copyright : (C) 2013 by Hugo Mercier + Email : hugo dot mercier at oslandia dot com +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + +__author__ = 'Hugo Mercier' +__date__ = 'July 2013' +__copyright__ = '(C) 2013, Hugo Mercier' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import os + + +class Test: + + def __init__(self, iface): + pass + + def initGui(self): + assert False + + def initProcessing(self): + pass + + def unload(self): + pass + +def classFactory(iface): + # load Test class from file Test + return Test(iface) diff --git a/tests/testdata/test_plugin_path/ProcessingPluginTest/metadata.txt b/tests/testdata/test_plugin_path/ProcessingPluginTest/metadata.txt new file mode 100644 index 00000000000..079e9ef8219 --- /dev/null +++ b/tests/testdata/test_plugin_path/ProcessingPluginTest/metadata.txt @@ -0,0 +1,7 @@ +[general] +name=plugin path test +qgisMinimumVersion=2.0 +description=desc +version=0.1 +author=HM/Oslandia +email=hugo.mercier@oslandia.com