Package libxyz :: Package core :: Package plugins :: Module manager
[hide private]
[frames] | no frames]

Source Code for Module libxyz.core.plugins.manager

  1  #-*- coding: utf8 -* 
  2  # 
  3  # Max E. Kuznecov ~syhpoon <syhpoon@syhpoon.name> 2008 
  4  # 
  5  # This file is part of XYZCommander. 
  6  # XYZCommander is free software: you can redistribute it and/or modify 
  7  # it under the terms of the GNU Lesser Public License as published by 
  8  # the Free Software Foundation, either version 3 of the License, or 
  9  # (at your option) any later version. 
 10  # XYZCommander is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 13  # GNU Lesser Public License for more details. 
 14  # You should have received a copy of the GNU Lesser Public License 
 15  # along with XYZCommander. If not, see <http://www.gnu.org/licenses/>. 
 16   
 17  import sys 
 18   
 19  from libxyz.exceptions import PluginError 
 20  from libxyz.exceptions import XYZValueError 
 21  from libxyz.core.plugins import BasePlugin 
 22  from libxyz.core.plugins import Namespace 
 23  from libxyz.core.utils import ustring, typer 
24 25 -def ns_transform(func):
26 """ 27 Transform passed ns plugin path to libxyz.core.plugins.Namespace instance 28 """ 29 30 def _trans(instance, *args, **kwargs): 31 _path, _rest = args[0], args[1:] 32 33 if not isinstance(_path, Namespace): 34 _path = Namespace(_path) 35 36 return func(instance, _path, *_rest, **kwargs)
37 38 return _trans 39
40 #++++++++++++++++++++++++++++++++++++++++++++++++ 41 42 -class PluginManager(object):
43 """ 44 Plugin manager class 45 It is supposed to provide easy access to plugin data 46 """ 47 48 PLUGIN_CLASS = u"XYZPlugin" 49 PLUGIN_FILE = u"main" 50 VIRTUAL_NAMESPACE = u"sys" 51 EVENT_INIT = u"event:plugin_init" 52 EVENT_FROM_LOAD = u"event:plugin_from_load" 53 EVENT_FROM_LOAD_DATA = u"event:plugin_from_load_data" 54 EVENT_PREPARE = u"event:plugin_prepare" 55 EVENT_FIN = u"event:plugin_fin" 56 57 @typer(None, None, list)
58 - def __init__(self, xyz, dirs):
59 """ 60 @param xyz: XYZ data 61 @param dirs: Plugin directories list 62 @type dirs: list 63 """ 64 65 sys.path = dirs + sys.path 66 self.xyz = xyz 67 68 self._enabled = None 69 # Do not load all the enabled plugin at once 70 # Do it on demand 71 self._loaded = {} 72 self._waiting = {}
73 74 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 75 76 @ns_transform
77 - def load(self, plugin, *initargs, **initkwargs):
78 """ 79 Load and initiate required plugin 80 @param plugin: Plugin namespace path 81 @param initargs: Necessary arguments to initiate plugin 82 @param initkwargs: Necessary kw arguments to initiate plugin 83 """ 84 85 virtual = self.is_virtual(plugin) 86 87 if plugin.pfull not in self.enabled: 88 raise PluginError(_(u"Plugin %s is disabled or does not exist") % 89 plugin) 90 91 if self.is_loaded(plugin): 92 return self.get_loaded(plugin) 93 94 if virtual: 95 # Do not raise any error here, because virtual plugins may be 96 # initiated at runtime after keys conf is parsed. 97 return None 98 99 plugin.set_method(self.PLUGIN_FILE) 100 101 self.xyz.hm.dispatch(self.EVENT_INIT, plugin) 102 103 # Import plugin 104 # Plugin entry-point is XYZPlugin class in a main.py file 105 try: 106 _loaded = __import__(plugin.internal, globals(), locals(), 107 [self.PLUGIN_CLASS]) 108 except ImportError, e: 109 raise PluginError(_(u"Unable to load plugin %s: %s") % 110 (plugin, unicode(e))) 111 112 try: 113 _loaded = getattr(_loaded, self.PLUGIN_CLASS) 114 except AttributeError, e: 115 raise PluginError(_(u"Unable to find required %s class") % \ 116 self.PLUGIN_CLASS) 117 118 # Initiate plugin 119 _obj = _loaded(self.xyz, *initargs, **initkwargs) 120 121 # Run prepare (constructor) 122 self.xyz.hm.dispatch(self.EVENT_PREPARE, _obj) 123 _obj.prepare() 124 125 self.set_loaded(plugin, _obj) 126 127 return _obj
128 129 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 130 131 @ns_transform
132 - def reload(self, plugin, *initargs, **initkwargs):
133 """ 134 Force load plugin if it's already in cache. 135 """ 136 137 if self.is_virtual(plugin): 138 # Virtual plugins do not support reloading 139 return None 140 141 if self.is_loaded(plugin): 142 self.del_loaded(plugin) 143 144 return self.load(plugin, *initargs, **initkwargs)
145 146 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 147 148 @ns_transform
149 - def from_load(self, plugin, method):
150 """ 151 Load method from plugin. 152 If plugin was not loaded before, load and initiate it first. 153 154 @param plugin: Plugin namespace path 155 @param method: Public method name 156 """ 157 158 self.xyz.hm.dispatch(self.EVENT_FROM_LOAD, plugin, method) 159 160 if not self.is_loaded(plugin): 161 _obj = self.load(plugin) 162 else: 163 _obj = self.get_loaded(plugin) 164 165 # Possible case for virtual plugins 166 if _obj is None: 167 return None 168 169 if method not in _obj.public: 170 raise PluginError(_(u"%s plugin instance does not export "\ 171 u"method %s") % (plugin, method)) 172 else: 173 return _obj.public[method]
174 175 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 176 177 @ns_transform
178 - def from_load_data(self, plugin, obj):
179 """ 180 Load data object from plugin. 181 If plugin was not loaded before, load and initiate it first. 182 183 @param plugin: Plugin namespace path 184 @param obj: Public data object name 185 """ 186 187 self.xyz.hm.dispatch(self.EVENT_FROM_LOAD_DATA, plugin, obj) 188 189 if not self.is_loaded(plugin): 190 _obj = self.load(plugin) 191 else: 192 _obj = self.get_loaded(plugin) 193 194 if obj not in _obj.public_data: 195 raise PluginError(_(u"%s plugin instance does not export "\ 196 u"data object %s") % (plugin, obj)) 197 else: 198 return _obj.public_data[obj]
199 200 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 201 202 @ns_transform
203 - def is_loaded(self, plugin):
204 """ 205 Check if plugin already loaded 206 @param plugin: Plugin namespace path 207 """ 208 209 return plugin.full in self._loaded
210 211 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 212 213 @ns_transform
214 - def get_loaded(self, plugin=None):
215 """ 216 Return loaded and initiated inistance of plugin 217 @param plugin: Plugin namespace path 218 """ 219 220 return self._loaded[plugin.pfull]
221 222 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 223
224 - def get_all_loaded(self):
225 """ 226 Return all currenty loaded plugins as dictionary with plugins ns path 227 as keys and instances as values 228 """ 229 230 return self._loaded
231 232 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 233 234 @ns_transform
235 - def set_loaded(self, plugin, inst):
236 """ 237 Set loaded and initiated inistance of plugin 238 @param plugin: Plugin namespace path 239 @param inst: Plugin instance 240 """ 241 242 # Check for pending waiting plugins 243 if plugin.pfull in self._waiting: 244 # Try to run callback 245 for _cb, _args in self._waiting[plugin.pfull]: 246 try: 247 _cb(inst, *_args) 248 except Exception, e: 249 xyzlog.warning(_(u"Error in wait_for() callback: %s") % 250 unicode(e)) 251 del(self._waiting[plugin.pfull]) 252 253 self._loaded[plugin.pfull] = inst
254 255 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 256 257 @ns_transform
258 - def del_loaded(self, plugin):
259 """ 260 Delete loaded instance from cache 261 @param plugin: Plugin namespace path 262 """ 263 264 try: 265 self.shutdown(plugin.pfull) 266 del(self._loaded[plugin.pfull]) 267 except KeyError: 268 pass
269 270 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 271 272 @ns_transform
273 - def wait_for(self, plugin, callback, *args):
274 """ 275 Some virtual plugins are not available at the parsing time. 276 This method is used to wait while plugin is loaded and then run 277 callback. 278 Arguments to callback: loaded plugin obj, and all optional *args passed 279 """ 280 281 # WTF? already loaded? No need to wait 282 if self.is_loaded(plugin): 283 return callback(self.get_loaded(plugin), *args) 284 285 # Initiate storage 286 if plugin.pfull not in self._waiting: 287 self._waiting[plugin.pfull] = [] 288 289 # Register for waiting 290 self._waiting[plugin.pfull].append((callback, args))
291 292 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 293
294 - def shutdown(self, plugin=None):
295 """ 296 Run destructors on specified or all loaded plugins 297 """ 298 299 def _fin(p): 300 try: 301 self._loaded[p].finalize() 302 except Exception: 303 pass
304 305 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 306 307 if self._loaded: 308 _plugins = self._loaded 309 else: 310 _plugins = [plugin] 311 312 for plugin_name in self._loaded: 313 self.xyz.hm.dispatch(self.EVENT_FIN, self._loaded[plugin_name]) 314 _fin(plugin_name)
315 316 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 317
318 - def register(self, obj):
319 """ 320 Register new plugin. 321 @param obj: libxyz.core.BasePlugin inherited instance 322 """ 323 324 if not isinstance(obj, BasePlugin): 325 raise XYZValueError(_(u"BasePlugin instance expected, got: %s") % 326 type(obj)) 327 328 self.set_loaded(obj.ns, obj)
329 330 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 331
332 - def is_virtual(self, plugin):
333 return plugin.ns == self.VIRTUAL_NAMESPACE
334 335 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 336
337 - def _enabled_list(self):
338 """ 339 Make list of enabled plugins 340 """ 341 342 if self._enabled is None: 343 _data = self.xyz.conf[u"xyz"][u"plugins"] 344 self._enabled = [_pname for _pname in _data if _data[_pname]] 345 346 return self._enabled
347 348 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 349 350 enabled = property(_enabled_list) 351