1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
"""Facility to load plugins."""
import sys
import os
from importlib import import_module
from straight.plugin.manager import PluginManager
class Loader(object):
"""Base loader class. Only used as a base-class for other loaders."""
def __init__(self, *args, **kwargs):
self._cache = []
def load(self, *args, **kwargs):
self._fill_cache(*args, **kwargs)
self._post_fill()
self._order()
return PluginManager(self._cache)
def _meta(self, plugin):
meta = getattr(plugin, '__plugin__', None)
return meta
def _post_fill(self):
for plugin in self._cache:
meta = self._meta(plugin)
if not getattr(meta, 'load', True):
self._cache.remove(plugin)
for implied_namespace in getattr(meta, 'imply_plugins', []):
plugins = self._cache
self._cache = self.load(implied_namespace)
self._post_fill()
combined = []
combined.extend(plugins)
combined.extend(self._cache)
self._cache = combined
def _order(self):
self._cache.sort(key=self._plugin_priority, reverse=True)
def _plugin_priority(self, plugin):
meta = self._meta(plugin)
return getattr(meta, 'priority', 0.0)
class ModuleLoader(Loader):
"""Performs the work of locating and loading straight plugins.
This looks for plugins in every location in the import path.
"""
def __init__(self, recurse=False):
super(ModuleLoader, self).__init__()
self.recurse = recurse
def _isPackage(self, path):
pkg_init = os.path.join(path, '__init__.py')
if os.path.exists(pkg_init):
return True
return False
def _findPluginFilePaths(self, namespace):
already_seen = set()
# Look in each location in the path
for path in sys.path:
# Within this, we want to look for a package for the namespace
namespace_rel_path = namespace.replace(".", os.path.sep)
namespace_path = os.path.join(path, namespace_rel_path)
if os.path.exists(namespace_path):
for possible in os.listdir(namespace_path):
poss_path = os.path.join(namespace_path, possible)
if os.path.isdir(poss_path):
if not self._isPackage(poss_path):
continue
if self.recurse:
subns = '.'.join((namespace, possible.split('.py')[0]))
for path in self._findPluginFilePaths(subns):
yield path
base = possible
else:
base, ext = os.path.splitext(possible)
if base == '__init__' or ext != '.py':
continue
if base not in already_seen:
already_seen.add(base)
yield os.path.join(namespace, possible)
def _findPluginModules(self, namespace):
for filepath in self._findPluginFilePaths(namespace):
path_segments = list(filepath.split(os.path.sep))
path_segments = [p for p in path_segments if p]
path_segments[-1] = os.path.splitext(path_segments[-1])[0]
import_path = '.'.join(path_segments)
try:
module = import_module(import_path)
except ImportError:
#raise Exception(import_path)
module = None
if module is not None:
yield module
def _fill_cache(self, namespace):
"""Load all modules found in a namespace"""
modules = self._findPluginModules(namespace)
self._cache = list(modules)
class ObjectLoader(Loader):
"""Loads classes or objects out of modules in a namespace, based on a
provided criteria.
The load() method returns all objects exported by the module.
"""
def __init__(self, recurse=False):
self.module_loader = ModuleLoader(recurse=recurse)
def _fill_cache(self, namespace):
modules = self.module_loader.load(namespace)
objects = []
for module in modules:
for attr_name in dir(module):
if not attr_name.startswith('_'):
objects.append(getattr(module, attr_name))
self._cache = objects
return objects
class ClassLoader(ObjectLoader):
"""Loads classes out of plugin modules which are subclasses of a single
given base class.
"""
def _fill_cache(self, namespace, subclasses=None):
objects = super(ClassLoader, self)._fill_cache(namespace)
classes = []
for cls in objects:
if isinstance(cls, type):
if subclasses is None:
classes.append(cls)
elif issubclass(cls, subclasses) and cls is not subclasses:
classes.append(cls)
self._cache = classes
return classes
def unified_load(namespace, subclasses=None, recurse=False):
"""Provides a unified interface to both the module and class loaders,
finding modules by default or classes if given a ``subclasses`` parameter.
"""
if subclasses is not None:
return ClassLoader(recurse=recurse).load(namespace, subclasses=subclasses)
else:
return ModuleLoader(recurse=recurse).load(namespace)
|