Package entropy :: Package core

Source Code for Package entropy.core

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3   
  4      @author: Fabio Erculiani <lxnay@sabayonlinux.org> 
  5      @contact: lxnay@sabayonlinux.org 
  6      @copyright: Fabio Erculiani 
  7      @license: GPL-2 
  8   
  9      B{Entropy Framework core module}. 
 10   
 11      This module contains base classes used by entropy.client, 
 12      entropy.server and entropy.services. 
 13   
 14      "Singleton" is a class that is inherited from singleton objects. 
 15   
 16  """ 
 17  import sys 
 18  import os 
 19  import inspect 
 20  from entropy.const import etpConst 
 21   
22 -class Singleton(object):
23 24 """ 25 If your class wants to become a sexy Singleton, 26 subclass this and replace __init__ with init_singleton. 27 Your subclass can expose a method called "is_destroyed()" that 28 returns a bool stating if singleton instance has been destroyed. 29 """ 30
31 - def __new__(cls, *args, **kwds):
32 33 singleton = getattr(cls, '__singleton__', None) 34 if singleton is not None: 35 destroyed = getattr(singleton, 'is_destroyed', None) 36 if destroyed is not None: 37 if not destroyed(): 38 return singleton 39 cls.__singleton__ = None 40 else: 41 return singleton 42 43 singleton = object.__new__(cls) 44 singleton.init_singleton(*args, **kwds) 45 cls.__singleton__ = singleton 46 return singleton
47
48 - def __init__(self, *args, **kwargs):
49 """ 50 This is a fake method, necessary for Python 3. 51 """ 52 pass
53
54 -class EntropyPluginStore:
55 56 """ 57 This is a base class for handling a map of plugin objects by providing 58 generic add/remove functions. 59 """ 60
61 - def __init__(self):
62 self.__plugins = {}
63
64 - def get_plugins(self):
65 """ 66 Return a copy of the internal dictionary that contains the currently 67 stored plugins map. 68 69 @return: stored plugins map 70 @rtype: dict 71 """ 72 return self.__plugins.copy()
73
74 - def drop_plugins(self):
75 """ 76 Drop all the currently stored plugins from storage. 77 """ 78 self.__plugins.clear()
79
80 - def add_plugin(self, plugin_id, plugin_object):
81 """ 82 This method lets you add a plugin to the store. 83 84 @param plugin_id: plugin identifier 85 @type plugin_id: string 86 @param plugin_object: valid SystemSettingsPlugin instance 87 @type plugin_object: any Python object 88 """ 89 self.__plugins[plugin_id] = plugin_object
90
91 - def remove_plugin(self, plugin_id):
92 """ 93 This method lets you remove previously added plugin objects via 94 identifiers. 95 96 @param plugin_id: plugin identifier 97 @type plugin_id: basestring 98 """ 99 del self.__plugins[plugin_id]
100 101
102 -class EntropyPluginFactory:
103 104 """ 105 Generic Entropy Components Plugin Factory (loader). 106 """ 107 108 _PLUGIN_SUFFIX = "_plugin" 109 _PYTHON_EXTENSION = ".py" 110
111 - def __init__(self, base_plugin_class, plugin_package_module, 112 default_plugin_name = None, fallback_plugin_name = None, 113 egg_entry_point_group = None):
114 """ 115 Entropy Generic Plugin Factory constructor. 116 MANDATORY: every plugin module/package(name) must end with _plugin 117 suffix. 118 119 Base plugin classes must have the following class attributes set: 120 121 - BASE_PLUGIN_API_VERSION: integer describing API revision in use 122 in class 123 124 Subclasses of Base plugin class must have the following class 125 attributes set: 126 127 - PLUGIN_API_VERSION: integer describing the currently implemented 128 plugin API revision, must match with BASE_PLUGIN_API_VERSION 129 above otherwise plugin won't be loaded and a warning will be 130 printed. 131 132 Moreover, plugin classes must be "Python new-style classes", otherwise 133 parser won't be able to determine if classes have subclasses and thus 134 pick the proper object (one with no subclasses!!). 135 See: http://www.python.org/doc/newstyle -- in other words, you have 136 to inherit the built-in "object" class (yeah, it's called object). 137 So, even if using normal classes could work, if you start doing nasty 138 things (nested inherittance of plugin classes), behaviour cannot 139 be guaranteed. 140 If it's not clear, let me repeat once again, valid plugin classes 141 must not have subclasses around! Think about it, it's an obvious thing. 142 143 If plugin class features a "PLUGIN_DISABLED" class attribute with 144 a boolean value of True, such plugin will be ignored. 145 146 If egg_entry_point_group is specified, Python Egg support is enabled 147 and classes are loaded via this infrastructure. 148 NOTE: if egg_entry_point_group is set, you NEED the setuptools package. 149 150 @param base_plugin_class: Base EntropyPlugin-based class that valid 151 plugin classes must inherit from. 152 @type base_plugin_class: class 153 @param plugin_package_module: every plugin repository must work as 154 Python package, the value of this argument must be a valid 155 Python package module that can be scanned looking for valid 156 Entropy Plugin classes. 157 @type plugin_package_module: Python module 158 @keyword default_plugin_name: identifier of the default plugin to load 159 @type default_plugin_name: string 160 @keyword fallback_plugin_name: identifier of the fallback plugin to load 161 if default is not available 162 @type fallback_plugin_name: string 163 @keyword egg_entry_point_group: valid Python Egg entry point group, in 164 this case, Python Egg support is used 165 @type egg_entry_point_group: string 166 @raise AttributeError: when passed plugin_package_module is not a 167 valid Python package module 168 """ 169 self.__modfile = plugin_package_module.__file__ 170 self.__base_class = base_plugin_class 171 self.__plugin_package_module = plugin_package_module 172 self.__default_plugin_name = default_plugin_name 173 self.__fallback_plugin_name = fallback_plugin_name 174 self.__egg_entry_group = egg_entry_point_group 175 self.__cache = None 176 self.__inspect_cache = None
177
178 - def clear_cache(self):
179 """ 180 Clear available plugins cache. When calling get_available_plugins() 181 module object is parsed again. 182 """ 183 self.__cache = None 184 self.__inspect_cache = None
185
186 - def _inspect_object(self, obj):
187 """ 188 This method verifies if given object is a valid plugin. 189 190 @return: True, if valid 191 @rtype: bool 192 """ 193 194 if self.__inspect_cache is None: 195 self.__inspect_cache = {} 196 197 # use memory position 198 obj_memory_pos = id(obj) 199 # To avoid infinite recursion and improve 200 # objects inspection, check if obj has been already 201 # analyzed 202 inspected_rc = self.__inspect_cache.get(obj_memory_pos) 203 if inspected_rc is not None: 204 # avoid infinite recursion 205 return inspected_rc 206 207 base_api = self.__base_class.BASE_PLUGIN_API_VERSION 208 209 if not inspect.isclass(obj): 210 self.__inspect_cache[obj_memory_pos] = False 211 return False 212 213 if not issubclass(obj, self.__base_class): 214 self.__inspect_cache[obj_memory_pos] = False 215 return False 216 217 if hasattr(obj, '__subclasses__'): 218 # new style class 219 if obj.__subclasses__(): # only lower classes taken 220 self.__inspect_cache[obj_memory_pos] = False 221 return False 222 else: 223 sys.stderr.write("!!! Entropy Plugin warning: " \ 224 "%s is not a new style class !!!\n" % (obj,)) 225 226 if obj is self.__base_class: 227 # in this case, obj is our base class, 228 # so we are very sure that obj is not valid 229 self.__inspect_cache[obj_memory_pos] = False 230 return False 231 232 if not hasattr(obj, "PLUGIN_API_VERSION"): 233 sys.stderr.write("!!! Entropy Plugin warning: " \ 234 "no PLUGIN_API_VERSION in %s !!!\n" % (obj,)) 235 self.__inspect_cache[obj_memory_pos] = False 236 return False 237 238 if obj.PLUGIN_API_VERSION != base_api: 239 sys.stderr.write("!!! Entropy Plugin warning: " \ 240 "PLUGIN_API_VERSION mismatch in %s !!!\n" % (obj,)) 241 self.__inspect_cache[obj_memory_pos] = False 242 return False 243 244 if hasattr(obj, 'PLUGIN_DISABLED'): 245 if obj.PLUGIN_DISABLED: 246 # this plugin has been disabled 247 self.__inspect_cache[obj_memory_pos] = False 248 return False 249 250 self.__inspect_cache[obj_memory_pos] = True 251 return True
252
253 - def _scan_dir(self):
254 """ 255 Scan modules in given directory looking for a valid plugin class. 256 Directory is os.path.dirname(self.__modfile). 257 258 @return: module dictionary composed by module name as key and plugin 259 class as value 260 @rtype: dict 261 """ 262 available = {} 263 pkg_modname = self.__plugin_package_module.__name__ 264 mod_dir = os.path.dirname(self.__modfile) 265 266 for modname in os.listdir(mod_dir): 267 268 if modname.startswith("__"): 269 continue # python stuff 270 if not (modname.endswith(EntropyPluginFactory._PYTHON_EXTENSION) \ 271 or "." not in modname): 272 continue # not something we want to load 273 274 if modname.endswith(EntropyPluginFactory._PYTHON_EXTENSION): 275 modname = modname[:-len(EntropyPluginFactory._PYTHON_EXTENSION)] 276 277 if not modname.endswith(EntropyPluginFactory._PLUGIN_SUFFIX): 278 continue 279 280 # remove suffix 281 modname_clean = modname[:-len(EntropyPluginFactory._PLUGIN_SUFFIX)] 282 283 modpath = "%s.%s" % (pkg_modname, modname,) 284 285 try: 286 __import__(modpath) 287 except ImportError as err: 288 sys.stderr.write("!!! Entropy Plugin warning, cannot " \ 289 "load module: %s | %s !!!\n" % (modpath, err,)) 290 continue 291 292 for obj in list(sys.modules[modpath].__dict__.values()): 293 294 valid = self._inspect_object(obj) 295 if not valid: 296 continue 297 298 available[modname_clean] = obj 299 300 return available
301
302 - def _scan_egg_group(self):
303 """ 304 Scan classes in given Python Egg group name looking for a valid plugin. 305 306 @return: module dictionary composed by module name as key and plugin 307 class as value 308 @rtype: dict 309 """ 310 # needs setuptools 311 import pkg_resources 312 available = {} 313 314 for entry in pkg_resources.iter_entry_points(self.__egg_entry_group): 315 316 obj = entry.load() 317 valid = self._inspect_object(obj) 318 if not valid: 319 continue 320 available[entry.name] = obj 321 322 return available
323 324
325 - def get_available_plugins(self):
326 """ 327 Return currently available EntropyPlugin plugin classes. 328 Note: Entropy plugins can either be Python packages or modules and 329 their name MUST end with PluginFactory._PLUGIN_SUFFIX ("_plugin"). 330 331 @return: dictionary composed by Entropy plugin id as key and Entropy 332 Python module as value 333 @rtype: dict 334 """ 335 if self.__cache is not None: 336 return self.__cache.copy() 337 338 if self.__egg_entry_group: 339 available = self._scan_egg_group() 340 else: 341 available = self._scan_dir() 342 343 self.__cache = available.copy() 344 return available
345
346 - def get_default_plugin(self):
347 """ 348 Return currently configured Entropy Plugin class. 349 350 @return: Entropy plugin class 351 @rtype: entropy.core.EntropyPlugin 352 @raise KeyError: if default plugin is not set or not found 353 """ 354 available = self.get_available_plugins() 355 plugin = self.__default_plugin_name 356 fallback = self.__fallback_plugin_name 357 klass = available.get(plugin) 358 359 if klass is None: 360 import warnings 361 warnings.warn("%s: %s" % ( 362 "selected Plugin not available, using fallback", plugin,)) 363 klass = available.get(fallback) 364 365 if klass is None: 366 raise KeyError 367 368 return klass
369