Skip to content

Commit

Permalink
Python support: use a wrapper for import statement that tracks module…
Browse files Browse the repository at this point in the history
…s 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/qgis@12690 c8812cc2-4d05-0410-92ff-de0c093fc19c
  • Loading branch information
wonder committed Jan 7, 2010
1 parent c953e5c commit c86e095
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 18 deletions.
96 changes: 94 additions & 2 deletions python/utils.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
QGIS utilities module
Expand All @@ -6,6 +7,9 @@
from PyQt4.QtCore import QCoreApplication
import sys
import traceback
import glob
import os.path
import re


#######################
Expand Down Expand Up @@ -94,6 +98,30 @@ def uninstallConsoleHooks():
# 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:
Expand All @@ -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)
Expand All @@ -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']")
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
20 changes: 4 additions & 16 deletions src/python/qgspythonutilsimpl.cpp
Expand Up @@ -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 )
Expand Down

0 comments on commit c86e095

Please sign in to comment.