Package elisa :: Package core :: Module component
[hide private]
[frames] | no frames]

Source Code for Module elisa.core.component

  1  # -*- coding: utf-8 -*- 
  2  # Elisa - Home multimedia server 
  3  # Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com). 
  4  # All rights reserved. 
  5  # 
  6  # This file is available under one of two license agreements. 
  7  # 
  8  # This file is licensed under the GPL version 3. 
  9  # See "LICENSE.GPL" in the root of this distribution including a special 
 10  # exception to use Elisa with Fluendo's plugins. 
 11  # 
 12  # The GPL part of Elisa is also available under a commercial licensing 
 13  # agreement from Fluendo. 
 14  # See "LICENSE.Elisa" in the root directory of this distribution package 
 15  # for details on that license. 
 16   
 17  """ 
 18  Component base class. Plugins create them. This is where the Plugins 
 19  logic stands. 
 20  """ 
 21   
 22   
 23  __maintainer__ = 'Philippe Normand <philippe@fluendo.com>' 
 24   
 25  from elisa.core.utils import classinit, misc 
 26  from elisa.core import common 
 27  from elisa.core import log 
 28  import os, re 
 29  import platform 
 30  from distutils.version import LooseVersion 
 31   
32 -class ComponentError(Exception):
33 """ 34 Generic exception raised when Component related error 35 happens. Specialized exceptions inherit from it. 36 37 @ivar component_name: the name of the Component which issued the error 38 @type component_name: string 39 @ivar error_message: human (or developer) readable explanation of the error 40 @type error_message: string 41 """ 42
43 - def __init__(self, component_name, error_message=None):
44 Exception.__init__(self) 45 self.component_name = component_name 46 self.error_message = error_message
47
48 - def __str__(self):
49 return "In component %s: %s" % (self.component_name, self.error_message)
50
51 -class UnSupportedPlatform(ComponentError):
52 """ 53 Raised when the component does not support user's platform 54 """ 55
56 - def __str__(self):
57 error = "Platform not supported for %r; supported are: %r" 58 return error % (self.component_name, self.error_message)
59
60 -class UnMetDependency(ComponentError):
61 """ 62 Raised by Component.check_dependencies() when it finds an un-met 63 dependency. 64 """ 65
66 - def __str__(self):
67 return "Un-met dependency for %s: %s" % (self.component_name, 68 self.error_message)
69
70 -class InitializeFailure(ComponentError):
71 """ 72 Raised by Component.initialize() when it fails to initialize 73 internal variables of the component. 74 """ 75
76 - def __str__(self):
77 return "Component %s failed to initialize: %s" % (self.component_name, 78 self.error_message)
79
80 -def parse_dependency(dependency):
81 """ 82 Scan an input string and detect dependency name, requirement sign 83 (>,<,...) and version:: 84 85 >>> parse_dependency("pgm >= 0.3") 86 >>> ("pgm", ">=", "0.3") 87 88 @param dependency: the dependency requirement to parse 89 @type dependency: string 90 @rtype: 3-tuple (name: string, sign: string, version: string) 91 """ 92 pattern = "(?P<name>[\.\w+]*)\s*(?P<sign>[><=]+)*\s*(?P<version>[0-9\.\-]+)*" 93 match = re.match(pattern, dependency) 94 name, sign, version = '', '', '' 95 if match: 96 group = match.groupdict() 97 name = group.get('name', '') 98 sign = group.get('sign') or '' 99 version = group.get('version') or '' 100 return (name, sign, version)
101
102 -def _check_version(component_name, module_name, module, sign, required_version):
103 """ 104 Check the version of the given module, given its version 105 requirements, for the given component name. 106 107 @param component_name: name of the component we are checking module's 108 version for 109 @type component_name: string 110 @param module_name: name of the module we're checking version for 111 @type module_name: string 112 @param module: module we're checking version for 113 @type module: object 114 @param sign: requirement sign (one of >,<,==,>=,<=) 115 @type sign: string 116 @param required_version: expected version of the module 117 @type required_version: string 118 @raises UnMetDependency: when the required version of the module was not found. 119 """ 120 if sign == '=': 121 sign = '==' 122 123 sign_map = { '<': (-1,), '<=': (-1, 0), 124 '>': (1,), '>=': (1, 0), 125 '==': (0,) } 126 127 module_version = None 128 129 # try to guess version from various module attributes 130 version_attributes = ('__version__', 'version') 131 for attr in version_attributes: 132 try: 133 value = getattr(module, attr) 134 except AttributeError: 135 continue 136 if callable(value): 137 module_version = value() 138 else: 139 module_version = value 140 if module_version: 141 break 142 143 if module_version: 144 if isinstance(module_version, basestring): 145 module_version = LooseVersion(module_version) 146 else: 147 module_version = LooseVersion('.'.join([str(c) 148 for c in module_version])) 149 compared = cmp(module_version, required_version) 150 expected = sign_map.get(sign,()) 151 152 if compared not in expected: 153 msg = "Version %s %s of %s is required. %s found" % (sign, 154 required_version, 155 module_name, 156 module_version) 157 raise UnMetDependency(component_name, msg) 158 return module_version
159
160 -def check_platforms(component_path, platforms):
161 """ 162 Check if the platforms list complies with the host's platform, for 163 the given component name. 164 165 @param component_path: name of the component we are checking 166 platform for 167 @type component_path: string 168 @param platforms: list of platforms supported by the component 169 @type platforms: list 170 @raises UnSupportedPlatform: when none of component's supported platforms 171 complies with host platform 172 @returns: the valud names for current platform 173 @rtype: list of strings 174 """ 175 user_platform = set([os.name, platform.system().lower()]) 176 177 if ':' in component_path: 178 log_category = component_path.split(':')[0] 179 else: 180 log_category = component_path 181 182 if platforms: 183 log.debug(log_category, "Checking supported platforms: %r", 184 platforms) 185 if not user_platform.intersection(platforms): 186 raise UnSupportedPlatform(component_path, platforms) 187 return user_platform
188
189 -def check_python_dependencies(component_path, deps):
190 """ 191 Verify that the Python dependencies are correctly installed for 192 the given component name. We first try to import each dependency 193 (using __import__) and then we check if the version matches the 194 required one specified in the dependency string. 195 196 @param component_path: name of the component we are checking python 197 dependencies for 198 @type component_path: string 199 @param deps: list of component's dependencies, as strings 200 @type deps: list 201 @raises UnMetDependency: when the required version of the module was not found. 202 @rtype: bool 203 """ 204 if deps: 205 if ':' in component_path: 206 log_category = component_path.split(':')[0] 207 else: 208 log_category = component_path 209 210 log.debug(log_category, "Checking dependencies: %r", deps) 211 for dep in deps: 212 module_name, sign, version = parse_dependency(dep) 213 try: 214 module = __import__(module_name) 215 except ImportError: 216 raise UnMetDependency(component_path, module_name) 217 if version: 218 _check_version(component_path, module_name, module, sign, 219 version) 220 return True
221
222 -class Component(log.Loggable):
223 """ 224 A Component is a simple object created by Plugins. Each Component has: 225 226 @cvar name: Component's name 227 @type name: string 228 @cvar id: Component's config id 229 @type id: int 230 @cvar plugin: Plugin instance of the component 231 @type plugin: L{elisa.core.plugin.Plugin} 232 @cvar default_config: used when nothing found in Application's config 233 @type default_config: dict 234 @cvar config_doc: documentation for each option of the default 235 configuration. Keys should be same as the ones in 236 default_config and values should be strings 237 @type config_doc: dict 238 @ivar config: Component's configuration 239 @type config: L{elisa.extern.configobj.ConfigObj} 240 @ivar path: unique string identifying the instance: 241 plugin_name:component_name:instance_id 242 @type path: string 243 """ 244 245 __metaclass__ = classinit.ClassInitMeta 246 __classinit__ = classinit.build_properties 247 248 default_config = {} 249 250 config_doc = {} 251 252 id = 0 253 plugin = None 254 path = None 255 256 config = None 257 258 checked = False 259
260 - def __init__(self):
261 """ Lazily set L{name} from class name styled with underscores 262 (class ComponentBar -> name component_bar. Also set log 263 category based on component name, with a 'comp_' prefix. 264 """ 265 if not hasattr(self.__class__, 'name'): 266 self.name = misc.un_camelify(self.__class__.__name__) 267 268 # configure the log category based on my name 269 self.log_category = 'comp_%s' % self.name 270 271 super(Component, self).__init__()
272
273 - def initialize(self):
274 """ 275 Initialize various variables internal to the Component. 276 277 This method is called by the plugin_registry after the 278 component's config has been loaded. 279 280 Override this method if you need to perform some 281 initializations that would normally go in Component's 282 constructor but can't be done there because they require 283 access to the component's config. 284 285 @raise InitializeFailure: when some internal initialization fails 286 """
287
288 - def clean(self):
289 """ 290 Perform some cleanups and save the Component config back to 291 application's config. This method should be called by the 292 L{elisa.core.manager.Manager} holding the component reference 293 when it stops itself. 294 """ 295 application = common.application 296 if application and application.config: 297 self.save_config(application.config)
298
299 - def load_config(self, application_config):
300 """ 301 Load the component's configuration. If none found, create 302 it using the default config stored in `default_config` 303 Component attribute. 304 305 @param application_config: the Application's Config 306 @type application_config: L{elisa.core.config.Config} 307 """ 308 self.debug('Component %s is loading its config' % self.name) 309 plugin_name = "" 310 c_section_name = '%s:%s' % (self.name, self.id) 311 if self.plugin: 312 plugin_name = "%s" % self.plugin.name 313 c_section_name = '%s:%s:%s' % (plugin_name, self.name, self.id) 314 315 component_section = application_config.get_section(c_section_name,{}) 316 317 # look for component.name option if instance id is 0 318 if self.id == 0 and not component_section: 319 c_section_name = c_section_name[:-2] 320 component_section = application_config.get_section(c_section_name, 321 {}) 322 323 if not component_section: 324 component_section = self.default_config 325 application_config.set_section(c_section_name, self.default_config, 326 doc=self.config_doc) 327 328 else: 329 for option, value in self.default_config.iteritems(): 330 if option not in component_section: 331 component_section[option] = value 332 333 self.config = component_section
334
335 - def save_config(self, application_config):
336 """ 337 Store the Component's config in the Application's config, 338 which can be saved back to a file later on. 339 340 @param application_config: the Application's Config to which save 341 our config 342 @type application_config: L{elisa.core.application.Application} 343 """ 344 self.debug('Component %s is saving its config' % self.name) 345 if self.config: 346 # warn of un-documented options 347 config_option_names = set(self.config.keys()) 348 documented_options = set(self.config_doc.keys()) 349 intersection = list(config_option_names - documented_options) 350 if intersection: 351 msg = "Undocumented options for %r: %r" % (self.name, 352 intersection) 353 self.info(msg) 354 355 if self.plugin: 356 section_name = "%s:%s" % (self.plugin.name, self.name) 357 else: 358 section_name = self.name 359 if self.id: 360 section_name += ":%s" % self.id 361 application_config.set_section(section_name, self.config, 362 doc=self.config_doc)
363