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 
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 - def __init__(self, xyz, dirs):
58 """ 59 @param xyz: XYZ data 60 @param dirs: Plugin directories list 61 @type dirs: list 62 """ 63 64 if not isinstance(dirs, list): 65 raise XYZValueError(_(u"Invalid argument type %s. List expected" % 66 type(dirs))) 67 else: 68 sys.path = dirs + sys.path 69 70 self.xyz = xyz 71 72 self.enabled = self._enabled_list() 73 # Do not load all the enabled plugin at once 74 # Do it on demand 75 self._loaded = {} 76 self._waiting = {}
77 78 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 79 80 @ns_transform
81 - def load(self, plugin, *initargs, **initkwargs):
82 """ 83 Load and initiate required plugin 84 @param plugin: Plugin namespace path 85 @param initargs: Necessary arguments to initiate plugin 86 @param initkwargs: Necessary kw arguments to initiate plugin 87 """ 88 89 virtual = self.is_virtual(plugin) 90 91 if plugin.pfull not in self.enabled: 92 raise PluginError(_(u"Plugin %s is disabled or does not exist" % 93 plugin)) 94 95 if self.is_loaded(plugin): 96 return self.get_loaded(plugin) 97 98 if virtual: 99 # Do not raise any error here, because virtual plugins may be 100 # initiated at runtime after keys conf is parsed. 101 return None 102 103 plugin.set_method(self.PLUGIN_FILE) 104 105 self.xyz.hm.dispatch(self.EVENT_INIT, plugin) 106 107 # Import plugin 108 # Plugin entry-point is XYZPlugin class in a main.py file 109 try: 110 _loaded = __import__(plugin.internal, globals(), locals(), 111 [self.PLUGIN_CLASS]) 112 except ImportError, e: 113 raise PluginError(_(u"Unable to load plugin %s: %s" % (plugin, e))) 114 115 try: 116 _loaded = getattr(_loaded, self.PLUGIN_CLASS) 117 except AttributeError, e: 118 raise PluginError(_(u"Unable to find required %s class" % \ 119 self.PLUGIN_CLASS)) 120 121 # Initiate plugin 122 _obj = _loaded(self.xyz, *initargs, **initkwargs) 123 124 # Run prepare (constructor) 125 self.xyz.hm.dispatch(self.EVENT_PREPARE, _obj) 126 _obj.prepare() 127 128 self.set_loaded(plugin, _obj) 129 130 return _obj
131 132 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 133 134 @ns_transform
135 - def reload(self, plugin, *initargs, **initkwargs):
136 """ 137 Force load plugin if it's already in cache. 138 """ 139 140 if self.is_virtual(plugin): 141 # Virtual plugins do not support reloading 142 return None 143 144 if self.is_loaded(plugin): 145 self.del_loaded(plugin) 146 147 return self.load(plugin, *initargs, **initkwargs)
148 149 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 150 151 @ns_transform
152 - def from_load(self, plugin, method):
153 """ 154 Load method from plugin. 155 If plugin was not loaded before, load and initiate it first. 156 157 @param plugin: Plugin namespace path 158 @param method: Public method name 159 """ 160 161 self.xyz.hm.dispatch(self.EVENT_FROM_LOAD, plugin, method) 162 163 if not self.is_loaded(plugin): 164 _obj = self.load(plugin) 165 else: 166 _obj = self.get_loaded(plugin) 167 168 # Possible case for virtual plugins 169 if _obj is None: 170 return None 171 172 if method not in _obj.public: 173 raise PluginError(_(u"%s plugin instance does not export "\ 174 u"method %s" % (plugin, method))) 175 else: 176 return _obj.public[method]
177 178 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 179 180 @ns_transform
181 - def from_load_data(self, plugin, obj):
182 """ 183 Load data object from plugin. 184 If plugin was not loaded before, load and initiate it first. 185 186 @param plugin: Plugin namespace path 187 @param obj: Public data object name 188 """ 189 190 self.xyz.hm.dispatch(self.EVENT_FROM_LOAD_DATA, plugin, obj) 191 192 if not self.is_loaded(plugin): 193 _obj = self.load(plugin) 194 else: 195 _obj = self.get_loaded(plugin) 196 197 if obj not in _obj.public_data: 198 raise PluginError(_(u"%s plugin instance does not export "\ 199 u"data object %s" % (plugin, obj))) 200 else: 201 return _obj.public_data[obj]
202 203 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 204 205 @ns_transform
206 - def is_loaded(self, plugin):
207 """ 208 Check if plugin already loaded 209 @param plugin: Plugin namespace path 210 """ 211 212 return plugin.full in self._loaded
213 214 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 215 216 @ns_transform
217 - def get_loaded(self, plugin=None):
218 """ 219 Return loaded and initiated inistance of plugin 220 @param plugin: Plugin namespace path 221 """ 222 223 return self._loaded[plugin.pfull]
224 225 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 226
227 - def get_all_loaded(self):
228 """ 229 Return all currenty loaded plugins as dictionary with plugins ns path 230 as keys and instances as values 231 """ 232 233 return self._loaded
234 235 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 236 237 @ns_transform
238 - def set_loaded(self, plugin, inst):
239 """ 240 Set loaded and initiated inistance of plugin 241 @param plugin: Plugin namespace path 242 @param inst: Plugin instance 243 """ 244 245 # Check for pending waiting plugins 246 if plugin.pfull in self._waiting: 247 # Try to run callback 248 for _cb, _args in self._waiting[plugin.pfull]: 249 try: 250 _cb(inst, *_args) 251 except Exception, e: 252 xyzlog.warning(_(u"Error in wait_for() callback: %s" % 253 ustring(str(e)))) 254 del(self._waiting[plugin.pfull]) 255 256 self._loaded[plugin.pfull] = inst
257 258 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 259 260 @ns_transform
261 - def del_loaded(self, plugin):
262 """ 263 Delete loaded instance from cache 264 @param plugin: Plugin namespace path 265 """ 266 267 try: 268 self.shutdown(plugin.pfull) 269 del(self._loaded[plugin.pfull]) 270 except KeyError: 271 pass
272 273 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 274 275 @ns_transform
276 - def wait_for(self, plugin, callback, *args):
277 """ 278 Some virtual plugins are not available at the parsing time. 279 This method is used to wait while plugin is loaded and then run 280 callback. 281 Arguments to callback: loaded plugin obj, and all optional *args passed 282 """ 283 284 # WTF? already loaded? No need to wait 285 if self.is_loaded(plugin): 286 return callback(self.get_loaded(plugin), *args) 287 288 # Initiate storage 289 if plugin.pfull not in self._waiting: 290 self._waiting[plugin.pfull] = [] 291 292 # Register for waiting 293 self._waiting[plugin.pfull].append((callback, args))
294 295 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 296
297 - def shutdown(self, plugin=None):
298 """ 299 Run destructors on specified or all loaded plugins 300 """ 301 302 def _fin(p): 303 try: 304 self._loaded[p].finalize() 305 except Exception: 306 pass
307 308 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 309 310 if self._loaded: 311 _plugins = self._loaded 312 else: 313 _plugins = [plugin] 314 315 for plugin_name in self._loaded: 316 self.xyz.hm.dispatch(self.EVENT_FIN, self._loaded[plugin_name]) 317 _fin(plugin_name)
318 319 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 320
321 - def register(self, obj):
322 """ 323 Register new plugin. 324 @param obj: libxyz.core.BasePlugin inherited instance 325 """ 326 327 if not isinstance(obj, BasePlugin): 328 raise XYZValueError(_(u"BasePlugin instance expected, got: %s" % 329 type(obj))) 330 331 self.set_loaded(obj.ns, obj)
332 333 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 334
335 - def is_virtual(self, plugin):
336 return plugin.ns == self.VIRTUAL_NAMESPACE
337 338 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 339
340 - def _enabled_list(self):
341 """ 342 Make list of enabled plugins 343 """ 344 345 _data = self.xyz.conf[u"xyz"][u"plugins"] 346 return [_pname for _pname in _data if _data[_pname]]
347