1 """
2 A simple configuration interface for Ganga packages.
3
4 1) Overview
5
6 PackageConfig object corresponds to a configuration section in the INI file.
7 Typically each plugin handler has its own section. Additionally Ganga provides
8 a somewhat arbitrary number of other configuration packages.
9
10 Configuration of the package is done in several phases:
11 - phase one: developer defines the hardcoded values in his package (default level);
12 addOption
13 - phase two: at startup ganga reads config files and set options (session level);
14 setSessionValue() is used
15 - phase three: at runtime user may modify options via GPI (user level);
16 setUserValue() is used for setting, getEffectiveOption() for getting
17
18
19 2) Defining default options for your package:
20
21 #MyPackage.py#
22
23 import Ganga.Utility.Config
24
25 config = Ganga.Utility.Config.getConfig('MyPackage')
26
27 config.addOption('opt1','x','this is option 1')
28 config.addOption('opt2',3.0, 'this is option 2')
29
30 The default values of the options may be strings, numbers,other
31 built-in types or any GPI objects names defined in the global
32 config_scope dictionary (in this module).
33
34 IMPORTANT: choose the type of the default value carefully: session and
35 user options will be automatically converted to the type implied by
36 the default value (you may also override the type checking default in
37 setDefaultOption) . The conversion is done as following (for
38 setSessionValue and setUserValue):
39
40 - nothing is done (value is assigned 'as-is'):
41 - if the default type is a string and the assigned value is a string
42 - if there is no default value
43
44 - eval the string value
45
46 - check if the type matches the default type and raise ConfigError in case of mismatch
47 - unless the default type is None
48
49 Typically strings are assigned via setSessionValue() as they are read
50 from the config file or command line.
51
52 Note for bootstrap procedure: it is OK to first set the session option
53 and then to set it's default value. That is to say that the
54 configuration may be read from the config file prior to loading the
55 corresponding module which uses it. It is NOT OK to set user options
56 before the end of the bootstrap procedure.
57
58
59 3) Getting effective values and using callback handlers
60
61 To get the effective value of an option you may use:
62
63 print config['opt1']
64 print config.getEffectiveOption('opt1')
65 print config.getEffectiveOptions() # all options
66
67 The effective value takes into account default, session and user
68 settings and may change at runtime. In GPI users only get the
69 effective values and may only set values at the user level.
70
71 You may also attach the callback handlers at the session and user
72 level. Pre-process handlers may modify what has been
73 set. Post-process handlers may be used to trigger extra actions.
74
75 def pre(opt,val):
76 print 'always setting a square of the value'
77 return val*2
78
79 def post(opt,val):
80 print 'effectively set',val
81
82 config.attachUserHandler(pre,post)
83
84 4) How does user see all this in GPI ?
85
86 There is a GPI wrapper in Ganga.GPIDev.Lib.Config which:
87 - internally uses setUserValue and getEffectiveOption
88 - has a more appealing interface
89
90
91 """
92
93 from Ganga.Core.exceptions import GangaException
95 """ ConfigError indicates that an option does not exist or it cannot be set.
96 """
100
102 return "ConfigError: %s "%(self.what)
103
104
105
106
107
108 logger = None
109
126 return f
127 return X()
128
129
130
131 allConfigs = {}
132
133
134
135
136 allConfigFileValues = {}
137
138
139
140
156
157
159 """ Get an exisiting PackageConfig or create a new one if needed.
160 Temporary name migration conversion applies -- see _migrate_name().
161 Principle is the same as for getLogger() -- the config instances may
162 be easily shared between different parts of the program."""
163
164 name = _migrate_name(name)
165 try:
166 return allConfigs[name]
167 except KeyError:
168
169 allConfigs[name] = PackageConfig(name,'Documentation not available')
170 return allConfigs[name]
171
173 """
174 Create a config package and attach metadata to it. makeConfig() should be called once for each package.
175 """
176
177 if _after_bootstrap:
178 raise ConfigError('attempt to create a configuration section [%s] after bootstrap'%name)
179
180 name = _migrate_name(name)
181 try:
182
183 c = allConfigs[name]
184 c.docstring = docstring
185 for k in kwds:
186 setattr(c,k,kwds[k])
187 except KeyError:
188 c = allConfigs[name] = PackageConfig(name,docstring,**kwds)
189
190
191
192
193
194
195
196 c._config_made = True
197 return c
198
199
201 """ Configuration Option has a name, default value and a docstring.
202
203 Metainformation:
204 * type - if not specified, then the type is inferred from the default value, type may be a type object such as type(1), StringType, type(None) or a list
205 of such objects - in this case any type in the list is accepted
206 * examples - example how to use the option
207 * hidden - True => do not show the option at the level of GPI proxy (default False)
208 * cfile - False => do not put the option in the generated config file (default True)
209 * filter - None => filter the option value when set (session and user levels)
210 * typelist - None => a typelist as in GPI schema
211 The configuration option may also define the session_value and default_value. The value property gives the effective value.
212 """
215
217
218
219
220
221 self.default_value = default_value
222 self.docstring = docstring
223 self.hidden=False
224 self.cfile=True
225 self.examples = None
226 self.filter = None
227 self.typelist = None
228
229 for m in meta:
230 setattr(self,m,meta[m])
231
232 self.convert_type('session_value')
233 self.convert_type('user_value')
234
236
237 if self.filter:
238 session_value = self.filter(self,session_value)
239
240
241
242
243
244
245 try:
246 session_value = self.transform_PATH_option(session_value,self.session_value)
247 except AttributeError:
248 pass
249
250 try:
251 old_value = self.session_value
252 except AttributeError:
253 pass
254
255 self.session_value = session_value
256 try:
257 self.convert_type('session_value')
258 except Exception,x:
259
260 try:
261 self.session_value = old_value
262 except NameError:
263 del self.session_value
264 raise x
265
267
268 try:
269 if self.filter:
270 user_value = self.filter(self,user_value)
271 except Exception,x:
272 logger = getLogger()
273 logger.warning('problem with option filter: %s: %s',self.name,x)
274
275 try:
276 user_value = self.transform_PATH_option(user_value,self.user_value)
277 except AttributeError:
278 pass
279
280 try:
281 old_value = self.user_value
282 except AttributeError:
283 pass
284
285 self.user_value = user_value
286 try:
287 self.convert_type('user_value')
288 except Exception,x:
289
290 try:
291 self.user_value = old_value
292 except NameError:
293 del self.user_value
294 raise x
295
297 try:
298 default_value = self.transform_PATH_option(default_value,self.default_value)
299 except AttributeError:
300 pass
301 self.default_value = default_value
302 self.convert_type('user_value')
303 self.convert_type('session_value')
304
306 if name == 'value':
307 values = []
308
309 for n in ['user','session','default']:
310 try:
311 values.append(getattr(self,n+'_value'))
312 except AttributeError:
313 pass
314
315 if values:
316 return reduce(self.transform_PATH_option,values)
317
318
319
320
321
322
323
324 if name == 'level':
325
326 for level,name in [(0,'user'),(1,'session'),(2,'default')]:
327 if hasattr(self,name+'_value'):
328 return level
329 raise AttributeError,name
330
332 if name in ['value','level']:
333 raise AttributeError('Cannot set "%s" attribute of the option object'%name)
334 self.__dict__[name]=value
335
337 return hasattr(self,'default_value')
338
341
343 ''' Convert the type of session_value or user_value (referred to by x_name) according to the types defined by the self.
344 If the option has not been defined or the x_name in question is not defined, then this method is no-op.
345 If conversion cannot be performed (type mismatch) then raise ConfigError.
346 '''
347
348 try:
349 value = getattr(self,x_name)
350 except AttributeError:
351 return
352
353 logger = getLogger()
354
355
356
357 if not self.typelist is None:
358 cast_type = self.typelist
359 else:
360 try:
361 cast_type = type(self.default_value)
362 except AttributeError:
363 return
364
365 new_value = value
366
367
368 optdesc = 'while setting option [.]%s = %s ' % (self.name,str(value))
369
370
371 if type(value) is type('') and not cast_type is type(''):
372 try:
373 new_value = eval(value,config_scope)
374 logger.debug('applied eval(%s) -> %s (%s)',value,new_value,optdesc)
375 except Exception,x:
376 logger.debug('ignored failed eval(%s): %s (%s)',value,x,optdesc)
377
378
379 logger.debug('checking value type: %s (%s)',str(cast_type),optdesc)
380
381 import types
382 def check_type(x,t):
383 return type(x) is t or x is t
384
385 type_matched = False
386
387
388 try:
389 import Ganga.GPIDev.TypeCheck
390 type_matched = Ganga.GPIDev.TypeCheck._valueTypeAllowed(new_value,cast_type,logger)
391 except TypeError:
392 type_matched = check_type(new_value,cast_type)
393
394 from Ganga.Utility.logic import implies
395 if not implies(not cast_type is type(None), type_matched):
396 raise ConfigError('type mismatch: expected %s got %s (%s)'%(str(cast_type),str(type(new_value)),optdesc))
397
398 setattr(self,x_name,new_value)
399
400
401 _after_bootstrap = False
402
403
404
405
406 config_scope = {}
407
409 """ Package Config object represents a Configuration Unit (typically
410 related to Ganga Packages). It should not be created directly
411 but only via the getConfig method.
412
413 PackageConfig has a name which corresponds to the [name] section in
414 the .gangarc file. Once initialized the configuration may only be
415 modified by special setUserValues methods. This will give a chance
416 to Ganga to take further actions such as automatic update of the
417 .gangarc file.
418
419 The PackageConfig interface is designed for Ganga Package Developers.
420 User oriented interface is available via the GPI.
421
422 meta keywords:
423 - is_open : True => new options may be added by the users (default False)
424 - cfile : True => section will be generated in the config file
425 - hidden: True => section is not visible in the GPI
426
427 """
428
429 - def __init__(self,name,docstring,**meta):
430 """ Arguments:
431 - name may not contain blanks and should be a valid python identifier otherwise ValueError is raised
432 - temporary name migration conversion applies -- see _migrate_name()
433 meta args have the same meaning as for the ConfigOption:
434 - hidden
435 - cfile
436 """
437 self.name = _migrate_name(name)
438 self.options = {}
439 self.docstring = docstring
440 self.hidden = False
441 self.cfile = True
442
443
444 self._user_handlers = []
445 self._session_handlers = []
446
447 self.is_open = False
448
449 for m in meta:
450 setattr(self,m,meta[m])
451
452
453 self._config_made = False
454
455 if _configured and self.is_open:
456 print 'cannot define open configuration section %s after configure() step'%self.name
457 logger = getLogger()
458 logger.error('cannot define open configuration section %s after configure() step',self.name)
459
462
464 """ Iterate over the effective options. """
465
466 return self.options.__iter__()
467
471
472 - def addOption(self,name,default_value, docstring, override=False, **meta):
496
497
498
499
500
501
502
504 """ Add or override options as a part of second phase of
505 initialization of this configuration module (PHASE 2) If the
506 default type of the option is not string, then the expression
507 will be evaluated. Optional argument raw indicates if special
508 treatment of PATH-like variables is disabled (enabled by
509 default). The special treatment applies to the session level
510 values only (and not the default one!). """
511
512 logger = getLogger()
513
514 logger.debug('trying to set session option [%s]%s = %s',self.name, name,value)
515
516 for h in self._session_handlers:
517 value = h[0](name,value)
518
519 if not self.options.has_key(name):
520 self.options[name] = ConfigOption(name)
521
522 self.options[name].setSessionValue(value)
523
524 logger.debug('sucessfully set session option [%s]%s = %s',self.name, name,value)
525
526 for h in self._session_handlers:
527 h[1](name,value)
528
529
531 """ Modify option at runtime. This method corresponds to the user
532 action so the value of the option is considered 'modified' If
533 the default type of the option is not string, then the
534 expression will be evaluated. """
535
536 logger = getLogger()
537
538 logger.debug('trying to set user option [%s]%s = %s',self.name, name,value)
539
540 for h in self._user_handlers:
541 value = h[0](name,value)
542
543 self.options[name].setUserValue(value)
544
545 logger.debug('successfully set user option [%s]%s = %s',self.name, name,value)
546
547 for h in self._user_handlers:
548 h[1](name,value)
549
550
553
555 try:
556 del self.options[name].user_value
557 except AttributeError:
558 pass
559
561 self.revertToSession(name)
562 try:
563 del self.options[name].session_value
564 except AttributeError:
565 pass
566
570
575
577 eff = {}
578 for name in self.options:
579 eff[name] = self.options[name].value
580 return eff
581
583 try:
584 return self.options[name].value
585 except KeyError:
586 raise ConfigError('option "%s" does not exist in "%s"'%(name,self.name))
587
589 """ Return 0 if option is effectively set at the user level, 1
590 if at session level or 2 if at default level """
591 try:
592 return self.options[name].level
593 except KeyError,x:
594 raise ConfigError('option "%s" does not exist in "%s"'%(x,self.name))
595
597 """ Attach a user handler:
598 - pre(name,x) will be always called before setUserValue(name,x)
599 - post(name,x2) will be always called after setUserValue(name,x)
600
601 pre() acts as a filter for the value of the option: its return value (x2) will be set
602 Before setting the value will be evaluated (unless a default value type is a string)
603 post() will get x2 as the option value.
604
605 It is OK to give None for pre or post. For example:
606 config.attachUserHandler(None,post) attaches only the post handler. """
607
608 if pre is None: pre = lambda opt,val:val
609 if post is None: post = lambda opt,val:None
610
611 self._user_handlers.append((pre,post))
612
614 """See attachUserHandler(). """
615
616 if pre is None: pre = lambda opt,val:val
617 if post is None: post = lambda opt,val:None
618
619 self._session_handlers.append((pre,post))
620
622 for o in self.options.keys():
623 if not self.options[o].check_defined():
624 del self.options[o]
625
626 import ConfigParser
627
629 cfg = ConfigParser.ConfigParser()
630 cfg.optionxform = str
631 cfg.defaults().update(system_vars)
632 return cfg
633
676
678 """ Return a ConfigParser object which contains all options from the
679 sequence of files (which are parsed from left-to-right).
680 Apply special rules for PATH-like variables - see transform_PATH_option() """
681
682 import re, os
683 logger = getLogger()
684
685 logger.debug('reading ini files: %s',str(filenames))
686
687 main = make_config_parser(system_vars)
688
689
690
691
692 if type(filenames) is type(''):
693 filenames = [filenames]
694
695 for f in filenames:
696 if f is None or f=='':
697 continue
698 cc = make_config_parser(system_vars)
699 logger.info('reading config file %s',f)
700 try:
701 cc.readfp(file(f))
702 except IOError,x:
703 logger.warning('%s',str(x))
704
705 for sec in cc.sections():
706 if not main.has_section(sec):
707 main.add_section(sec)
708
709 for name in cc.options(sec):
710 value = cc.get(sec,name)
711
712
713 if name in cc.defaults().keys():
714 continue
715
716
717
718 try:
719 current_value = main.get(sec,name)
720 except ConfigParser.NoOptionError:
721 current_value = None
722
723 value = transform_PATH_option(name,value,current_value)
724
725
726
727 m = re.search('\$\$[^${}]*\$\$', value)
728 while m:
729 envvar = m.group(0).strip('$')
730
731
732 envval = ''
733 if os.environ.has_key(envvar):
734 envval = os.environ[envvar]
735
736 value = value.replace( m.group(0), envval )
737 m = re.search('\$\{[^${}]*\}', value )
738
739
740 value = value.rstrip()
741 main.set(sec,name,value)
742
743 return main
744
745
760
761
762 _configured = False
763
787
788
789
790
791
792
794 """Function to overwrite option values set in configuration file:
795
796 Arguments:
797 section - Name of relevant section within configuration file
798 item - Item for which value is to be changed
799 value - Value to be assigned
800
801 Function needs to be called after configuration file has been parsed.
802
803 Return value: None"""
804
805 if bool( section ) and bool( item ):
806 try:
807 config = getConfig( section )
808 if config.getEffectiveOptions().has_key( item ):
809 config.setSessionValue( item, value )
810 except:
811 pass
812
813 return None
814
815
816
818 """ Split the path and return a list, where all relative path components will have top prepended.
819 Example: 'A:/B/C::D/E' -> ['top/A','/B/C','top/D/E']
820 """
821 import os.path
822 l = []
823 for p in path.split(':'):
824 if p:
825 if not os.path.isabs(p):
826 p = os.path.join(top,p)
827 l.append(p)
828 return l
829
853
855
856 runtimepath = getConfig('Configuration')['RUNTIME_PATH']
857
858 if 'GangaLHCb' in runtimepath:
859 lhcb = True
860 else:
861 lhcb = False
862
863 if 'GangaAtlas' in runtimepath:
864 atlas = True
865 else:
866 atlas = False
867
868 if lhcb and atlas:
869 raise ConfigError('Atlas and LHCb conflict')
870
871 if lhcb:
872 return 'LHCb'
873
874 if atlas:
875 return 'Atlas'
876
877 return ''
878