Skip to content

Commit

Permalink
[#547] Refactor and clean plugins.core
Browse files Browse the repository at this point in the history
  • Loading branch information
tobes committed Apr 5, 2013
1 parent a01bd21 commit 2371c05
Showing 1 changed file with 166 additions and 130 deletions.
296 changes: 166 additions & 130 deletions ckan/plugins/core.py
@@ -1,11 +1,9 @@
"""
'''
Provides plugin services to the CKAN
"""
'''

from contextlib import contextmanager
import logging
from inspect import isclass
from itertools import chain

from pkg_resources import iter_entry_points
from pyutilib.component.core import PluginGlobals, implements
from pyutilib.component.core import ExtensionPoint as PluginImplementations
Expand All @@ -19,195 +17,233 @@
'PluginImplementations', 'implements',
'PluginNotFoundException', 'Plugin', 'SingletonPlugin',
'load', 'load_all', 'unload', 'unload_all',
'reset'
'get_plugin', 'plugins_update',
'use_plugin',
]

log = logging.getLogger(__name__)

# Entry point group.
PLUGINS_ENTRY_POINT_GROUP = "ckan.plugins"

# Entry point group for system plugins (those that are part of core ckan and do
# not need to be explicitly enabled by the user)
# Entry point group for system plugins (those that are part of core ckan and
# do not need to be explicitly enabled by the user)
SYSTEM_PLUGINS_ENTRY_POINT_GROUP = "ckan.system_plugins"

# These lists are used to ensure that the correct extensions are enabled.
_PLUGINS = []
_PLUGINS_CLASS = []

# To aid retrieving extensions by name
_PLUGINS_SERVICE = {}


@contextmanager
def use_plugin(*plugins):
'''Load plugin(s) for testing purposes
e.g.
```
import ckan.plugins as p
with p.use_plugin('my_plugin') as my_plugin:
# run tests with plugin loaded
```
'''

p = load(*plugins)
try:
yield p
finally:
unload(*plugins)


class PluginNotFoundException(Exception):
"""
'''
Raised when a requested plugin cannot be found.
"""
'''


class Plugin(_pca_Plugin):
"""
'''
Base class for plugins which require multiple instances.
Unless you need multiple instances of your plugin object you should
probably use SingletonPlugin.
"""
'''


class SingletonPlugin(_pca_SingletonPlugin):
"""
'''
Base class for plugins which are singletons (ie most of them)
One singleton instance of this class will be created when the plugin is
loaded. Subsequent calls to the class constructor will always return the
same singleton instance.
"""


def _get_service(plugin):
"""
Return a service (ie an instance of a plugin class).
'''

:param plugin: any of: the name of a plugin entry point; a plugin class; an
instantiated plugin object.
:return: the service object
"""

if isinstance(plugin, basestring):
try:
name = plugin
(plugin,) = iter_entry_points(
group=PLUGINS_ENTRY_POINT_GROUP,
name=name
)
except ValueError:
raise PluginNotFoundException(plugin)
def get_plugin(plugin):
''' Get an instance of a active plugin by name. This is helpful for
testing. '''
if plugin in _PLUGINS_SERVICE:
return _PLUGINS_SERVICE[plugin]

return plugin.load()(name=name)

elif isinstance(plugin, _pca_Plugin):
return plugin
def plugins_update():
''' This is run when plugins have been loaded or unloaded and allows us
to run any specific code to ensure that the new plugin setting are
correctly setup '''

elif isclass(plugin) and issubclass(plugin, _pca_Plugin):
return plugin()
# It is posible for extra SingletonPlugin extensions to be activated if
# the file containing them is imported, for example if two or more
# extensions are defined in the same file. Therefore we do a sanity
# check and disable any that should not be active.
for env in PluginGlobals.env_registry.values():
for service in env.services.copy():
if service.__class__ not in _PLUGINS_CLASS:
service.deactivate()

else:
raise TypeError("Expected a plugin name, class or instance", plugin)
# Reset CKAN to reflect the currently enabled extensions.
import ckan.config.environment as environment
environment.update_config()


def load_all(config):
"""
'''
Load all plugins listed in the 'ckan.plugins' config directive.
"""
plugins = chain(
find_system_plugins(),
find_user_plugins(config)
)
'''
# Clear any loaded plugins
unload_all()

# PCA default behaviour is to activate SingletonPlugins at import time. We
# only want to activate those listed in the config, so clear
# everything then activate only those we want.
unload_all(update=False)

for plugin in plugins:
load(plugin, update=False)

# Load the synchronous search plugin, unless already loaded or
plugins = config.get('ckan.plugins', '').split() + find_system_plugins()
# Add the synchronous search plugin, unless already loaded or
# explicitly disabled
if not 'synchronous_search' in config.get('ckan.plugins',[]) and \
if 'synchronous_search' not in plugins and \
asbool(config.get('ckan.search.automatic_indexing', True)):
log.debug('Loading the synchronous search plugin')
load('synchronous_search', update=False)
plugins.append('synchronous_search')

plugins_update()
load(*plugins)


def reset():
"""
Clear and reload all configured plugins
"""
# FIXME This looks like it should be removed
from pylons import config
load_all(config)
def load(*plugins):
'''
Load named plugin(s).
'''

output = []

def plugins_update():
''' This is run when plugins have been loaded or unloaded and allows us
to run any specific code to ensure that the new plugin setting are
correctly setup '''
import ckan.config.environment as environment
environment.update_config()
observers = PluginImplementations(interfaces.IPluginObserver)
for plugin in plugins:
if plugin in _PLUGINS:
raise Exception('Plugin `%s` already loaded' % plugin)

service = _get_service(plugin)
for observer_plugin in observers:
observer_plugin.before_load(service)
service.activate()
for observer_plugin in observers:
observer_plugin.after_load(service)

def load(plugin, update=True):
"""
Load a single plugin, given a plugin name, class or instance
"""
observers = PluginImplementations(interfaces.IPluginObserver)
for observer_plugin in observers:
observer_plugin.before_load(plugin)
service = _get_service(plugin)
service.activate()
for observer_plugin in observers:
observer_plugin.after_load(service)
if interfaces.IGenshiStreamFilter in service.__interfaces__:
log.warn("Plugin '%s' is using deprecated interface "
"IGenshiStreamFilter" % plugin)

if interfaces.IGenshiStreamFilter in service.__interfaces__:
log.warn("Plugin '%s' is using deprecated interface IGenshiStreamFilter" % plugin)
_PLUGINS.append(plugin)
_PLUGINS_CLASS.append(service.__class__)

if update:
plugins_update()
if isinstance(service, SingletonPlugin):
_PLUGINS_SERVICE[plugin] = service

return service
output.append(service)
plugins_update()

# Return extension instance if only one was loaded. If more that one
# has been requested then a list of instances is returned in the order
# they were asked for.
if len(output) == 1:
return output[0]
return output

def unload_all(update=True):
"""
Unload (deactivate) all loaded plugins
"""
for env in PluginGlobals.env_registry.values():
for service in env.services.copy():
unload(service, update=False)
if update:
plugins_update()

def unload_all():
'''
Unload (deactivate) all loaded plugins in the reverse order that they
were loaded.
'''
unload(*reversed(_PLUGINS))

def unload(plugin, update=True):
"""
Unload a single plugin, given a plugin name, class or instance
"""
observers = PluginImplementations(interfaces.IPluginObserver)
service = _get_service(plugin)
for observer_plugin in observers:
observer_plugin.before_unload(service)

service.deactivate()
def unload(*plugins):
'''
Unload named plugin(s).
'''

for observer_plugin in observers:
observer_plugin.after_unload(service)
observers = PluginImplementations(interfaces.IPluginObserver)

if update:
plugins_update()
for plugin in plugins:
if plugin in _PLUGINS:
_PLUGINS.remove(plugin)
if plugin in _PLUGINS_SERVICE:
del _PLUGINS_SERVICE[plugin]
else:
raise Exception('Cannot unload plugin `%s`' % plugin)

return service
service = _get_service(plugin)
for observer_plugin in observers:
observer_plugin.before_unload(service)

service.deactivate()

def find_user_plugins(config):
"""
Return all plugins specified by the user in the 'ckan.plugins' config
directive.
"""
plugins = []
for name in config.get('ckan.plugins', '').split():
entry_points = list(
iter_entry_points(group=PLUGINS_ENTRY_POINT_GROUP, name=name)
)
if not entry_points:
raise PluginNotFoundException(name)
plugins.extend(ep.load() for ep in entry_points)
return plugins
_PLUGINS_CLASS.remove(service.__class__)

for observer_plugin in observers:
observer_plugin.after_unload(service)
plugins_update()


def find_system_plugins():
"""
'''
Return all plugins in the ckan.system_plugins entry point group.
These are essential for operation and therefore cannot be enabled/disabled
through the configuration file.
"""
return (
These are essential for operation and therefore cannot be
enabled/disabled through the configuration file.
'''

eps = []
for ep in iter_entry_points(group=SYSTEM_PLUGINS_ENTRY_POINT_GROUP):
ep.load()
for ep in iter_entry_points(group=SYSTEM_PLUGINS_ENTRY_POINT_GROUP)
)
eps.append(ep.name)
return eps


def _get_service(plugin):
'''
Return a service (ie an instance of a plugin class).
:param plugin: the name of a plugin entry point
:type plugin: string
:return: the service object
'''

if isinstance(plugin, basestring):
try:
name = plugin
(plugin,) = iter_entry_points(
group=PLUGINS_ENTRY_POINT_GROUP,
name=name
)
except ValueError:
try:
name = plugin
(plugin,) = iter_entry_points(
group=SYSTEM_PLUGINS_ENTRY_POINT_GROUP,
name=name
)
except ValueError:
raise PluginNotFoundException(plugin)

return plugin.load()(name=name)
else:
raise TypeError('Expected a plugin name', plugin)

0 comments on commit 2371c05

Please sign in to comment.