Python support: use a wrapper for import statement that tracks modules of plugins.

This enables complete unload and reload of plugins:
- qgis.utils.unloadPlugin(name) - now removes also plugin's modules (files) from python
- qgis.utils.reloadPlugin(name) - unloads and starts the plugin again (using fresh source files)


git-svn-id: http://svn.osgeo.org/qgis/trunk@12690 c8812cc2-4d05-0410-92ff-de0c093fc19c
This commit is contained in:
wonder 2010-01-07 14:59:27 +00:00
parent 01e993cf4c
commit bb5f4139ab
2 changed files with 98 additions and 18 deletions

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
QGIS utilities module
@ -6,6 +7,9 @@ QGIS utilities module
from PyQt4.QtCore import QCoreApplication
import sys
import traceback
import glob
import os.path
import re
#######################
@ -94,6 +98,30 @@ plugins = {}
# list of active (started) plugins
active_plugins = []
# list of plugins in plugin directory and home plugin directory
available_plugins = []
def updateAvailablePlugins():
from qgis.core import QgsApplication
pythonPath = unicode(QgsApplication.pkgDataPath()) + "/python"
homePythonPath = unicode(QgsApplication.qgisSettingsDirPath()) + "/python"
plugins = map(os.path.basename, glob.glob(pythonPath + "/plugins/*"))
homePlugins = map(os.path.basename, glob.glob(homePythonPath + "/plugins/*"))
# merge the lists
for p in homePlugins:
if p not in plugins:
plugins.append(p)
global available_plugins
available_plugins = plugins
# update list on start
updateAvailablePlugins()
def pluginMetadata(packageName, fct):
""" fetch metadata from a plugin """
try:
@ -103,7 +131,7 @@ def pluginMetadata(packageName, fct):
return "__error__"
def loadPlugin(packageName):
""" load plugin's package and ensure that plugin is reloaded when changed """
""" load plugin's package """
try:
__import__(packageName)
@ -117,7 +145,6 @@ def loadPlugin(packageName):
# retry
try:
__import__(packageName)
reload(packageName)
return True
except:
msgTemplate = QCoreApplication.translate("Python", "Couldn't load plugin '%1' from ['%2']")
@ -140,6 +167,7 @@ def startPlugin(packageName):
try:
plugins[packageName] = package.classFactory(iface)
except:
_unloadPluginModules(packageName)
msg = QCoreApplication.translate("Python", "%1 due an error when calling its classFactory() method").arg(errMsg)
showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg)
return False
@ -148,6 +176,8 @@ def startPlugin(packageName):
try:
plugins[packageName].initGui()
except:
del plugins[packageName]
_unloadPluginModules(packageName)
msg = QCoreApplication.translate("Python", "%1 due an error when calling its initGui() method" ).arg( errMsg )
showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg)
return False
@ -169,15 +199,77 @@ def unloadPlugin(packageName):
plugins[packageName].unload()
del plugins[packageName]
active_plugins.remove(packageName)
_unloadPluginModules(packageName)
return True
except Exception, e:
msg = QCoreApplication.translate("Python", "Error while unloading plugin %1").arg(packageName)
showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg)
return False
def _unloadPluginModules(packageName):
""" unload plugin package with all its modules (files) """
global _plugin_modules
mods = _plugin_modules[packageName]
for mod in mods:
# if it looks like a Qt resource file, try to do a cleanup
# otherwise we might experience a segfault next time the plugin is loaded
# because Qt will try to access invalid plugin resource data
if "resources" in mod:
try:
sys.modules[mod].qCleanupResources()
except:
pass
# try to remove the module from python
try:
del sys.modules[mod]
except:
pass
# remove the plugin entry
del _plugin_modules[packageName]
def isPluginLoaded(packageName):
""" find out whether a plugin is active (i.e. has been started) """
global plugins, active_plugins
if not plugins.has_key(packageName): return False
return (packageName in active_plugins)
def reloadPlugin(packageName):
""" unload and start again a plugin """
global active_plugins
if packageName not in active_plugins:
return # it's not active
unloadPlugin(packageName)
loadPlugin(packageName)
startPlugin(packageName)
#######################
# IMPORT wrapper
import __builtin__
_builtin_import = __builtin__.__import__
_plugin_modules = { }
def _import(name, globals={}, locals={}, fromlist=[], level=-1):
""" wrapper around builtin import that keeps track of loaded plugin modules """
mod = _builtin_import(name, globals, locals, fromlist, level)
if mod and '__file__' in mod.__dict__:
module_name = mod.__name__
package_name = module_name.split('.')[0]
# check whether the module belongs to one of our plugins
if package_name in available_plugins:
if package_name not in _plugin_modules:
_plugin_modules[package_name] = set()
_plugin_modules[package_name].add(module_name)
return mod
__builtin__.__import__ = _import

View File

@ -424,23 +424,11 @@ QString QgsPythonUtilsImpl::homePluginsPath()
QStringList QgsPythonUtilsImpl::pluginList()
{
QDir pluginDir( QgsPythonUtilsImpl::pluginsPath(), "*",
QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoDotAndDotDot );
runString( "qgis.utils.updateAvailablePlugins()" );
QDir homePluginDir( QgsPythonUtilsImpl::homePluginsPath(), "*",
QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoDotAndDotDot );
QStringList pluginList = pluginDir.entryList();
for ( uint i = 0; i < homePluginDir.count(); i++ )
{
QString packageName = homePluginDir[i];
if ( !pluginList.contains( packageName ) )
pluginList.append( packageName );
}
return pluginList;
QString output;
evalString( "'\\n'.join(qgis.utils.available_plugins)", output );
return output.split( QChar( '\n' ) );
}
QString QgsPythonUtilsImpl::getPluginMetadata( QString pluginName, QString function )