1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
49 return "In component %s: %s" % (self.component_name, self.error_message)
50
59
61 """
62 Raised by Component.check_dependencies() when it finds an un-met
63 dependency.
64 """
65
67 return "Un-met dependency for %s: %s" % (self.component_name,
68 self.error_message)
69
71 """
72 Raised by Component.initialize() when it fails to initialize
73 internal variables of the component.
74 """
75
77 return "Component %s failed to initialize: %s" % (self.component_name,
78 self.error_message)
79
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
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
188
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
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
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
269 self.log_category = 'comp_%s' % self.name
270
271 super(Component, self).__init__()
272
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
298
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
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
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
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