![]() |
|
|
Generated: 8 Jan 2009 |
00001 # File: AthenaCommon/python/Configurable.py 00002 # Author: Wim Lavrijsen (WLavrijsen@lbl.gov) 00003 # Author: Martin Woudstra (Martin.Woudstra@cern.ch) 00004 00005 import copy, string, types, os 00006 from inspect import isclass 00007 import GaudiKernel.ConfigurableMeta as ConfigurableMeta 00008 from GaudiKernel.Constants import error_explanation, \ 00009 VERBOSE, DEBUG, INFO, WARNING, ERROR, FATAL 00010 from GaudiKernel.PropertyProxy import PropertyProxy 00011 from GaudiKernel.GaudiHandles import * 00012 00013 ### data --------------------------------------------------------------------- 00014 __all__ = [ 'Configurable', 00015 'ConfigurableAlgorithm', 00016 'ConfigurableAlgTool', 00017 'ConfigurableAuditor', 00018 'ConfigurableService', 00019 'ConfigurableUser', 00020 'VERBOSE','DEBUG','INFO', 'WARNING', 'ERROR', 'FATAL', 00021 'appendPostConfigAction', 'removePostConfigAction' ] 00022 00023 ## for messaging 00024 import logging 00025 log = logging.getLogger( 'Configurable' ) 00026 00027 def expandvars(data): 00028 """ 00029 Expand environment variables "data". 00030 Data can be string, list, tuple and dictionary. For collection, all the 00031 contained strings will be manipulated (recursively). 00032 """ 00033 import os.path 00034 typ = type(data) 00035 if typ is str: 00036 return os.path.expandvars(data) 00037 elif typ in [list, tuple]: 00038 collect = [] 00039 for i in data: 00040 collect.append(expandvars(i)) 00041 return typ(collect) 00042 elif typ is dict: 00043 collect = {} 00044 for k in data: 00045 collect[expandvars(k)] = expandvars(data[k]) 00046 return collect 00047 return data 00048 00049 class Error(RuntimeError): 00050 """ 00051 Error occurred in the configuration process. 00052 """ 00053 pass 00054 00055 ## Allow references to options as in old style 00056 class PropertyReference(object): 00057 def __init__(self,propname): 00058 self.name = propname 00059 def __repr__(self): 00060 return "@%s"%self.name 00061 def __resolve__(self): 00062 # late binding for property references 00063 retval = None 00064 refname, refprop = self.name.rsplit('.',1) 00065 if refname in Configurable.allConfigurables: 00066 conf = Configurable.allConfigurables[refname] 00067 retval = getattr(conf,refprop) 00068 if hasattr(retval,"getFullName"): 00069 retval = retval.getFullName() 00070 else: 00071 raise NameError("name '%s' not found resolving '%s'"%(refname,self)) 00072 return retval 00073 def getFullName(self): 00074 """This function allow transparent integration with 00075 Configurable.getValuedProperties. 00076 """ 00077 try: 00078 return self.__resolve__() 00079 except NameError: 00080 # ignore the error if we cannot resolve the name yet 00081 return self 00082 except AttributeError: 00083 # ignore the error if we cannot resolve the attribute yet 00084 return self 00085 00086 ### base class for configurable Gaudi algorithms/services/algtools/etc. ====== 00087 class Configurable( object ): 00088 """Base class for Gaudi components that implement the IProperty interface. 00089 Provides most of the boilerplate code, but the actual useful classes 00090 are its derived ConfigurableAlgorithm, ConfigurableService, and 00091 ConfigurableAlgTool.""" 00092 00093 ## for detecting the default name 00094 class DefaultName: 00095 pass 00096 00097 propertyNoValue = '<no value>' 00098 indentUnit = '| ' 00099 printHeaderWidth=100 00100 printHeaderPre=5 00101 00102 __metaclass__ = ConfigurableMeta.ConfigurableMeta 00103 00104 __slots__ = ( 00105 '__children', # controlled components, e.g. private AlgTools 00106 '__tools', # private AlgTools (#PM-->) 00107 '_name', # the (unqualified) component name 00108 '_inSetDefaults', # currently setting default values 00109 '_initok', # used to enforce base class init 00110 '_setupok' # for debugging purposes (temporary) 00111 ) 00112 00113 allConfigurables = {} # just names would do, but currently refs to the actual 00114 configurableServices = {} # just names would do, but currently refs to the actual 00115 # configurables is needed for (temporary) backwards 00116 # compatibility; will change in the future 00117 _configurationLocked = False 00118 00119 def __new__ ( cls, *args, **kwargs ): 00120 """To Gaudi, any object with the same type/name is the same object. Hence, 00121 this is mimicked in the configuration: instantiating a new Configurable 00122 of a type with the same name will return the same instance.""" 00123 00124 global log 00125 # try to get the name of the Configurable (having a name is compulsory) 00126 if 'name' in kwargs: 00127 # simple keyword (by far the easiest) 00128 name = kwargs[ 'name' ] 00129 elif 'name' in cls.__init__.func_code.co_varnames: 00130 # either positional in args, or default 00131 index = list(cls.__init__.func_code.co_varnames).index( 'name' ) 00132 try: 00133 # var names index is offset by one as __init__ is to be called with self 00134 name = args[ index - 1 ] 00135 except IndexError: 00136 # retrieve default value, then 00137 name = cls.__init__.func_defaults[ index - (len(args)+1) ] 00138 else: 00139 # positional index is assumed (will work most of the time) 00140 try: 00141 name = args[1] # '0' is for self 00142 except (IndexError,TypeError): 00143 raise TypeError( 'no "name" argument while instantiating "%s"' % cls.__name__ ) 00144 00145 argname = name 00146 if name == Configurable.DefaultName : 00147 if hasattr(cls, 'DefaultedName' ) : 00148 name = cls.DefaultedName 00149 else : 00150 name = cls.getType() 00151 elif not name or type(name) != str: 00152 # unnamed, highly specialized user code, etc. ... unacceptable 00153 raise TypeError( 'could not retrieve name from %s.__init__ arguments' % cls.__name__ ) 00154 00155 # Handle the case of global tools to prepend ToolSvc in the name. 00156 # This is needed for compatibility with old JobOptions files being read 00157 if issubclass( cls, ConfigurableAlgTool) and '.' not in name : 00158 name = 'ToolSvc.' + name 00159 00160 # close backdoor access to otherwise private subalgs/tools 00161 #PM if 0 <= name.find( '.' ): 00162 #PM # temp protection for old style types 00163 #PM from OldStyleConfig import GenericConfigurable 00164 #PM if not issubclass( cls, GenericConfigurable ): # except raised for new types only 00165 #PM raise NameError( '"%s": backdoor access to private configurables not allowed' % name ) 00166 00167 # ordinary recycle case 00168 if name in cls.configurables: 00169 conf = cls.configurables[ name ] 00170 if name != argname: # special case: user derived <-> real ... make same 00171 cls.configurables[ conf.getType() ] = conf 00172 #---PM: Initialize additional properties 00173 for n,v in kwargs.items(): 00174 setattr(conf, n, v) 00175 if not cls._configurationLocked and not "_enabled" in kwargs and isinstance(conf, ConfigurableUser): 00176 # Ensure that the ConfigurableUser gets enabled if nothing is 00177 # specified in the constructor. 00178 setattr(conf, "_enabled", True) 00179 return conf 00180 00181 # a couple of special cases (note that these cases don't mix) 00182 spos = name.find( '/' ) 00183 ti_name = None 00184 if spos < 0: 00185 ti_name = "%s/%s" % (name,name) 00186 if ti_name in cls.configurables: 00187 # support for old-style name as type/name lookup where name==type 00188 return cls.configurables[ ti_name ] 00189 00190 i_name = None 00191 if spos > 0: 00192 i_name = name[:spos] 00193 if i_name == name[spos+1:] and i_name in cls.configurables: 00194 # this is the opposite of the above special case 00195 return cls.configurables[ i_name ] 00196 00197 # the following is purely for debugging support and should realistically bomb 00198 conf = cls.allConfigurables.get( name, None ) or\ 00199 (spos < 0 and cls.allConfigurables.get( ti_name, None )) or\ 00200 (spos > 0 and i_name == name[spos+1:] and cls.allConfigurables.get( i_name, None )) 00201 if conf: # wrong type used? 00202 if conf.__class__ is ConfigurableGeneric : 00203 # If the instance found is ConfigurableGeneric then 00204 # we create a new one with the proper type and fill with 00205 # the contents of the generic one 00206 newconf = object.__new__( cls, *args, **kwargs ) 00207 cls.__init__( newconf, *args ) 00208 # initialize with the properties of generic configurable 00209 # (we map the names of the properties to lowercase versions because 00210 # old options are not case sensitive) 00211 names = {} 00212 for n in newconf.__slots__: 00213 names[n.lower()] = n 00214 for n in conf._properties: 00215 if names[n.lower()] != n: 00216 log.warning( "Option '%s' was used for %s, but the correct spelling is '%s'"%(n,name,names[n.lower()]) ) 00217 setattr(newconf, names[n.lower()], getattr( conf, n ) ) 00218 for n,v in kwargs.items(): 00219 setattr(newconf, n, v) 00220 cls.configurables[ name ] = newconf 00221 cls.allConfigurables[ name ] = newconf 00222 return newconf 00223 else : 00224 # will be an actual error in the future (now only report as such) 00225 log.error( 'attempt to redefine type of "%s" (was: %s, new: %s)%s', 00226 name, conf.__class__.__name__, cls.__name__, error_explanation ) 00227 # in the future: 00228 # return None # will bomb on use (or go unharmed on non-use) 00229 # for now, allow use through allConfigurables lookup 00230 #---PM: Initialize additional properties 00231 for n,v in kwargs.items(): 00232 setattr(conf, n, v) 00233 return conf 00234 00235 # still here: create a new instance and initialize it 00236 conf = object.__new__( cls, *args, **kwargs ) 00237 cls.__init__( conf, *args, **kwargs ) 00238 00239 # update normal, per-class cache 00240 cls.configurables[ name ] = conf 00241 00242 for base in cls.__bases__: 00243 if base.__name__ == 'ConfigurableService': 00244 cls.configurableServices[ name ] = conf 00245 00246 # update generics super-cache, if needed 00247 cls.allConfigurables[ name ] = conf 00248 #-->PM#if hasattr( cls, 'getType' ) and name.find('/') < 0: 00249 #-->PM# cls.allConfigurables[ cls.getType() + '/' + name ] = conf 00250 00251 return conf 00252 00253 def __init__( self, name = DefaultName ): 00254 # check class readiness, all required overloads should be there now 00255 klass = self.__class__ 00256 00257 # this is an abstract class 00258 if klass == Configurable: 00259 raise TypeError, "%s is an ABC and can not be instantiated" % str(Configurable) 00260 00261 # the following methods require overloading 00262 #NOT YET meths = { 'getServices' : 1, # retrieve list of services to configure 00263 meths = { 'getDlls' : 1, # provide list of Dlls to load 00264 'getGaudiType' : 1, # return string describing component class 00265 'getHandle' : 1 } # provide access to C++ side component instance 00266 # 'getType' : 1 } # return the type of the actual C++ component 00267 00268 for meth, nArgs in meths.items(): 00269 try: 00270 f = getattr( klass, meth ).im_func 00271 except AttributeError: 00272 raise NotImplementedError, "%s is missing in class %s" % (meth,str(klass)) 00273 00274 # in addition, verify the number of arguments w/o defaults 00275 nargcount = f.func_code.co_argcount 00276 ndefaults = f.func_defaults and len(f.func_defaults) or 0 00277 if not nargcount - ndefaults <= nArgs <= nargcount: 00278 raise TypeError, "%s.%s requires exactly %d arguments" % (klass,meth,nArgs) 00279 00280 # for using this Configurable as a (Gaudi) sequence 00281 self.__children = [] 00282 self.__tools = {} 00283 00284 # know who we are 00285 if name == Configurable.DefaultName : 00286 if hasattr(self.__class__, 'DefaultedName' ) : 00287 self._name = self.__class__.DefaultedName 00288 else : 00289 self._name = self.getType() 00290 else : 00291 self._name = name 00292 00293 # set to True when collecting defaults, False otherwise 00294 self._inSetDefaults = False 00295 00296 # for later, in case __init__ itself is overridden 00297 self._initok = True 00298 00299 # for debugging purposes (temporary) 00300 self._setupok = False 00301 00302 # pickle support 00303 def __getstate__ (self): 00304 dict = {} 00305 for name, proxy in self._properties.items(): 00306 try: 00307 dict[ name ] = proxy.__get__( self ) 00308 except AttributeError: 00309 pass 00310 00311 dict[ '_Configurable__children' ] = self.__children 00312 dict[ '_Configurable__tools' ] = self.__tools 00313 dict[ '_name' ] = self._name 00314 return dict 00315 00316 def __getnewargs__(self) : 00317 return (self._name,) 00318 00319 def __setstate__ ( self, dict ): 00320 self._initok = True 00321 for n, v in dict.items(): 00322 setattr (self, n, v) 00323 return 00324 00325 # to allow a few basic sanity checks, as well as nice syntax 00326 def __len__( self ): 00327 return len( self.__children ) 00328 00329 def __iter__( self ): 00330 return iter( self.__children ) 00331 00332 # ownership rules of self through copying 00333 def __deepcopy__( self, memo ): 00334 newconf = object.__new__( self.__class__, self.getName() ) 00335 self.__class__.__init__( newconf, self.getName() ) 00336 00337 for proxy in self._properties.values(): 00338 try: 00339 proxy.__set__( newconf, proxy.__get__( self ) ) 00340 except AttributeError: 00341 pass # means property was not set for self 00342 00343 for c in self.__children: 00344 newconf += c # processes proper copy semantics 00345 00346 return newconf 00347 00348 # hierarchy building, and ownership rules of children 00349 def __iadd__( self, configs, descr = None ): 00350 if not type(configs) in (list,tuple): 00351 configs = ( configs, ) 00352 00353 joname = self.getJobOptName() 00354 00355 for cfg in configs: 00356 # prevent type mismatches 00357 if not isinstance( cfg, Configurable ): 00358 raise TypeError( "'%s' is not a Configurable" % str(cfg) ) 00359 00360 cc = self.copyChildAndSetParent( cfg, joname ) 00361 00362 # filters dupes; usually "ok" (backdoor should catch them) 00363 ccjo = cc.getJobOptName() 00364 for c in self.__children: 00365 if c.getJobOptName() == ccjo: 00366 log.error( 'attempt to add a duplicate ... dupe ignored%s', error_explanation ) 00367 break 00368 else: 00369 self.__children.append( cc ) 00370 00371 try: 00372 if descr: # support for tool properties 00373 descr.__set__( self, cc ) 00374 else: 00375 setattr( self, cc.getName(), cc ) 00376 except AttributeError: 00377 pass # to allow free addition of tools/subalgorithms 00378 00379 return self 00380 00381 def __getattr__( self, attr ): # until ToolProperties exist ... 00382 00383 if attr in self.__tools : return self.__tools[attr] 00384 00385 for c in self.__children: 00386 if c.getName() == attr: 00387 return c 00388 00389 raise AttributeError( "'%s' object has no attribute '%s'" % (self.__class__,attr) ) 00390 00391 def __setattr__( self, name, value ) : 00392 if self._configurationLocked: 00393 raise RuntimeError("%s: Configuration cannot be modified after the ApplicationMgr has been started."%self.name()) 00394 try : 00395 super( Configurable, self ).__setattr__( name, value ) 00396 except AttributeError: 00397 raise AttributeError( "Configurable '%s' does not have property '%s'." 00398 % ( self.__class__.__name__, name) ) 00399 00400 def __delattr__( self, attr ): 00401 # remove as property, otherwise try as child 00402 try: 00403 # remove history etc., then reset to default (in case set before) 00404 prop = self._properties[ attr ] 00405 prop.__delete__( self ) 00406 prop.__set__( self, prop.default ) 00407 return # reaches here? was property: done now 00408 except KeyError: 00409 pass 00410 # otherwise, remove the private tool 00411 if attr in self.__tools : 00412 del self.__tools[attr] 00413 00414 # otherwise, remove child, if one is so named 00415 for c in self.__children: 00416 if c.getName() == attr: 00417 self.__children.remove( c ) 00418 00419 # potentially, there are left over caches (certain user derived classes) 00420 try: 00421 del self.__dict__[ attr ] 00422 except (AttributeError,KeyError): 00423 pass 00424 00425 def __nonzero__(self): 00426 return True 00427 00428 00429 def remove( self, items ): 00430 if type(items) != list and type(items) != tuple: 00431 items = [ items ] 00432 00433 self.__children = [ e for e in self.__children if not e in items ] 00434 00435 def removeAll( self ): 00436 self.remove( self.__children ) 00437 00438 # called by __iadd__; determines child copy semantics 00439 def copyChild( self, child ): 00440 return copy.deepcopy( child ) 00441 00442 def setParent( self, parentName ): 00443 pass 00444 00445 def getParent( self ): 00446 return "" 00447 00448 def hasParent( self, parent ): 00449 return False 00450 00451 def copyChildAndSetParent(self,cfg,parent): 00452 cc = self.copyChild( cfg ) 00453 00454 if hasattr( cc, 'setParent' ) and parent: 00455 try: 00456 cc.setParent( parent ) 00457 except RuntimeError, e: 00458 # temporary backdoor resolution for compatibility 00459 log.error( str(e) + '%s', error_explanation ) 00460 ccbd = cc.configurables[ cc.getJobOptName() ] 00461 00462 # merge properties, new over pre-existing 00463 for proxy in self._properties.values(): 00464 if proxy.history.has_key( cc ): 00465 proxy.__set__( ccbd, proxy.__get__( cc ) ) 00466 00467 # consolidate 00468 cc = ccbd 00469 return cc 00470 00471 def getChildren( self ): 00472 return self.__children[:] # read only 00473 00474 def getTools( self ): 00475 return self.__tools.values() # read only 00476 00477 def children( self ): 00478 log.error( "children() is deprecated, use getChildren() instead for consistency" ) 00479 log.error( "getChildren() returns a copy; to add a child, use 'parent += child'%s", 00480 error_explanation ) 00481 return self.__children # by ref, for compatibility 00482 00483 def getAllChildren( self ): 00484 """Get all (private) configurable children, both explicit ones (added with +=) 00485 and the ones in the private GaudiHandle properties""" 00486 childs = [] 00487 # add private configurable properties (also inside handles) 00488 for proxy in self._properties.values(): 00489 try: 00490 c = proxy.__get__( self ) 00491 except AttributeError: 00492 pass 00493 else: 00494 if isinstance(c,Configurable) and not c.isPublic(): 00495 childs.append(c) 00496 elif isinstance(c,GaudiHandle): 00497 try: 00498 conf = c.configurable 00499 except AttributeError: 00500 pass 00501 else: 00502 if not conf.isPublic(): 00503 childs.append(conf) 00504 elif isinstance(c,GaudiHandleArray): 00505 # only setup private arrays 00506 if not c.isPublic(): 00507 for ci in c: 00508 if isinstance(ci,Configurable): 00509 childs.append(ci) 00510 else: 00511 try: 00512 conf = ci.configurable 00513 except AttributeError: 00514 pass 00515 else: 00516 childs.append(conf) 00517 00518 # add explicit children 00519 childs += self.__children 00520 return childs 00521 00522 def getSequence( self ): 00523 elems = [] 00524 for c in self.__children: 00525 elems.append( c.getFullName() ) 00526 return elems 00527 00528 def setup( self ): 00529 # make sure base class init has been called 00530 if not hasattr(self,'_initok') or not self._initok: 00531 # could check more, but this is the only explanation 00532 raise TypeError, \ 00533 "Configurable.__init__ not called in %s override" % self.__class__.__name__ 00534 00535 # log.debug("calling setup() on " + self.getFullJobOptName()) 00536 00537 # setup self: this collects all values on the python side 00538 self.__setupServices() 00539 self.__setupDlls() 00540 self.__setupDefaults() 00541 00542 # setup children 00543 for c in self.getAllChildren(): 00544 c.setup() 00545 00546 # now get handle to work with for moving properties into the catalogue 00547 handle = self.getHandle() 00548 if not handle: 00549 log.debug( 'no handle for %s: not transporting properties', self._name ) 00550 return # allowed, done early 00551 00552 # pass final set of properties on to handle on the C++ side or JobOptSvc 00553 for name in self._properties.keys(): 00554 if hasattr( self, name ): # means property has python-side value/default 00555 setattr( handle, name, getattr(self,name) ) 00556 00557 # for debugging purposes 00558 self._setupok = True 00559 00560 def getProperties( self ): 00561 props = {} 00562 for name, proxy in self._properties.items(): 00563 try: 00564 props[ name ] = proxy.__get__( self ) 00565 except AttributeError: 00566 props[ name ] = Configurable.propertyNoValue 00567 00568 return props 00569 00570 def getValuedProperties( self ): 00571 props = {} 00572 for name, proxy in self._properties.items(): 00573 if self.isPropertySet(name): 00574 value = proxy.__get__( self ) 00575 if hasattr(value, 'getFullName') : 00576 value = value.getFullName() 00577 elif type(value) in [list, tuple]: 00578 new_value = [] 00579 for i in value: 00580 if hasattr(i, 'getFullName'): 00581 new_value.append(i.getFullName()) 00582 else: 00583 new_value.append(i) 00584 value = type(value)(new_value) 00585 elif type(value) is dict: 00586 new_value = {} 00587 for i in value: 00588 if hasattr(value[i], 'getFullName'): 00589 new_value[i] = value[i].getFullName() 00590 else: 00591 new_value[i] = value[i] 00592 value = new_value 00593 props[ name ] = value 00594 00595 return props 00596 00597 def properties( self ): 00598 return self.getProperties() # compatibility 00599 00600 @classmethod 00601 def getDefaultProperties( cls ): 00602 class collector: 00603 pass 00604 00605 # user provided defaults 00606 c = collector() 00607 cls.setDefaults( c ) 00608 00609 # defaults from C++ 00610 for k,v in cls._properties.items(): 00611 if not k in c.__dict__ and hasattr( v, 'default' ): 00612 c.__dict__[ k ] = v.default 00613 00614 return c.__dict__ 00615 00616 @classmethod 00617 def getDefaultProperty( cls, name ): 00618 class collector: 00619 pass 00620 00621 # user provided defaults 00622 c = collector() 00623 cls.setDefaults( c ) 00624 00625 if name in c.__dict__: 00626 return c.__dict__[ name ] 00627 00628 # defaults from C++ 00629 try: 00630 v = cls._properties[name] 00631 if hasattr( v, 'default' ): 00632 return v.default 00633 except KeyError: 00634 pass 00635 00636 return None 00637 00638 def getProp(self, name): 00639 """Returns the value of the given property. 00640 """ 00641 if hasattr(self, name): 00642 return getattr(self, name) 00643 else: 00644 return self.getDefaultProperties()[name] 00645 00646 def setProp(self, name, value): 00647 """Set the value of a given property 00648 """ 00649 return setattr(self, name, value) 00650 00651 def isPropertySet(self, name): 00652 """Tell if the property 'name' has been set or not. 00653 00654 Because of a problem with list and dictionary properties, in those cases 00655 if the value is equal to the default, the property is considered as not 00656 set. 00657 """ 00658 if not hasattr(self, name): 00659 return False 00660 else: 00661 try: 00662 default = self.getDefaultProperties()[name] 00663 if isinstance(default, (list, dict)): 00664 value = getattr(self, name) 00665 return value != default 00666 except KeyError: 00667 pass # no default found 00668 return True 00669 00670 def getType( cls ): 00671 return cls.__name__ 00672 00673 def getName( self ): 00674 return self._name 00675 00676 def name( self ): 00677 return self.getName() 00678 00679 def getJobOptName( self ): # full hierachical name 00680 return self.getName() 00681 00682 def isPublic( self ): 00683 return True 00684 00685 # for a couple of existing uses out there 00686 def jobOptName( self ): 00687 log.error( "jobOptName() is deprecated, use getJobOptName() instead for consistency%s", 00688 error_explanation ) 00689 return self.getJobOptName() # compatibility 00690 00691 def getFullName( self ) : 00692 return str( self.getType() + '/' + self.getName() ) 00693 00694 def getFullJobOptName( self ): 00695 return "%s/%s" % (self.getType(),self.getJobOptName() or self.getName()) 00696 00697 def getPrintTitle(self): 00698 return self.getGaudiType() + ' ' + self.getTitleName() 00699 00700 def getTitleName( self ): 00701 if log.isEnabledFor( logging.DEBUG ): 00702 return self.getFullJobOptName() 00703 else: 00704 return self.getFullName() 00705 00706 def setDefaults( cls, handle ): 00707 pass 00708 00709 def clone( self, name = None, **kwargs ) : 00710 if not name : 00711 if hasattr(self, 'DefaultedName' ) : name = self.DefaultedName 00712 else : name = self.getType() 00713 00714 newconf = Configurable.__new__( self.__class__, name ) 00715 self.__class__.__init__( newconf, name ) 00716 00717 for proxy in self._properties.values(): 00718 try : 00719 value = proxy.__get__( self ) 00720 if type(value) in [ str, list, dict, tuple ]: 00721 # clone the values of the properties for basic types 00722 value = type(value)(value) 00723 proxy.__set__( newconf, value ) 00724 except AttributeError: 00725 pass 00726 00727 for c in self.__children: 00728 newconf += c # processes proper copy semantics 00729 00730 for n , t in self.__tools.items(): 00731 newconf.addTool(t, n) 00732 00733 for name, value in kwargs.items(): 00734 setattr(newconf, name, value) 00735 00736 return newconf 00737 00738 def splitName( self ) : 00739 fullname = self.getName() 00740 dot = fullname.find('.') 00741 if dot != -1 : 00742 parentname = fullname[:dot] 00743 longname = fullname[dot+1:] 00744 else : 00745 parentname = '' 00746 longname = fullname 00747 dot = longname.find('.') 00748 if dot != -1 : 00749 name = longname[:dot] 00750 else : 00751 name = longname 00752 return parentname, name, longname 00753 00754 def addTool( self, tool, name = None ) : 00755 if isclass(tool) and issubclass(tool, ConfigurableAlgTool): 00756 if name is None: 00757 name = tool.__name__ 00758 priv_tool = tool( self.getName()+ '.' + name ) 00759 elif isinstance(tool, ConfigurableAlgTool): 00760 if name is None: 00761 name = tool.splitName()[1] 00762 priv_tool = tool.clone( self.getName()+ '.' + name ) 00763 else: 00764 if isclass(tool): 00765 classname = tool.__name__ 00766 else: 00767 classname = type(tool).__name__ 00768 raise TypeError, "addTool requires AlgTool configurable. Got %s type" % classname 00769 self.__tools[name] = priv_tool 00770 if name in self.__slots__: 00771 # this is to avoid that the property hides the tool 00772 setattr(self,name,self.__tools[name]) 00773 00774 def _isInSetDefaults( self ): 00775 return self._inSetDefaults 00776 00777 def __setupServices( self ): 00778 #svcs = self.getServices() 00779 #if not svcs: 00780 svcs = [] 00781 #elif type(svcs) == types.StringType: 00782 # svcs = [ svcs ] 00783 00784 import __main__ 00785 for svc in svcs: 00786 handle = __main__.Service( svc ) 00787 # services should be configurables as well, but aren't for now 00788 # handle.setup() 00789 00790 # allow Configurable to make some changes 00791 if hasattr( self, 'configure' + svc ): 00792 eval( 'self.configure' + svc + '( handle )' ) 00793 00794 def __setupDlls( self ): 00795 dlls = self.getDlls() 00796 if not dlls: 00797 dlls = [] 00798 elif type(dlls) == types.StringType: 00799 dlls = [ dlls ] 00800 00801 from __main__ import theApp 00802 dlls = filter( lambda d: d not in theApp.Dlls, dlls ) 00803 if dlls: theApp.Dlls += dlls 00804 00805 def __setupDefaults( self ): 00806 # set handle defaults flags to inform __setattr__ that it is being 00807 # called during setDefaults of the concrete Configurable 00808 self._inSetDefaults = True 00809 self.setDefaults( self ) 00810 self._inSetDefaults = False 00811 00812 @staticmethod 00813 def _printHeader( indentStr, title ): 00814 preLen = Configurable.printHeaderPre 00815 postLen = Configurable.printHeaderWidth - preLen - 3 - len(title)# - len(indentStr) 00816 postLen = max(preLen,postLen) 00817 return indentStr + '/%s %s %s' % (preLen*'*',title,postLen*'*') 00818 00819 @staticmethod 00820 def _printFooter( indentStr, title ): 00821 preLen = Configurable.printHeaderPre 00822 postLen = Configurable.printHeaderWidth - preLen - 12 - len(title)# - len(indentStr) 00823 postLen = max(preLen,postLen) 00824 return indentStr + '\\%s (End of %s) %s' % (preLen*'-',title,postLen*'-') 00825 00826 def __repr__( self ): 00827 return '<%s at %s>' % (self.getFullJobOptName(),hex(id(self))) 00828 00829 def __str__( self, indent = 0, headerLastIndentUnit=indentUnit ): 00830 global log # to print some info depending on output level 00831 indentStr = indent*Configurable.indentUnit 00832 # print header 00833 title = self.getPrintTitle() 00834 # print line to easily see start-of-configurable 00835 if indent > 0: 00836 headerIndent = (indent-1)*Configurable.indentUnit + headerLastIndentUnit 00837 else: 00838 headerIndent = '' 00839 rep = Configurable._printHeader( headerIndent, title ) 00840 rep += os.linesep 00841 # print own properties 00842 props = self.getProperties() 00843 defs = self.getDefaultProperties() 00844 if not props: 00845 rep += indentStr + '|-<no properties>' + os.linesep 00846 else: 00847 # get property name with 00848 nameWidth = 0 00849 for p in props.keys(): 00850 nameWidth=max(nameWidth,len(p)) 00851 for p, v in props.items(): 00852 # start with indent and property name 00853 prefix = indentStr + '|-%-*s' % (nameWidth,p) 00854 # add memory address for debugging (not for defaults) 00855 if log.isEnabledFor( logging.DEBUG ): 00856 if v != Configurable.propertyNoValue: 00857 address = ' @%11s' % hex(id(v)) 00858 else: 00859 address = 13*' ' 00860 prefix += address 00861 # add value and default 00862 default = defs.get(p) 00863 if v == Configurable.propertyNoValue: 00864 # show default value as value, and no extra 'default' 00865 strVal = repr(default) 00866 strDef = None 00867 else: 00868 # convert configurable to handle 00869 if hasattr(v,"getGaudiHandle"): 00870 vv = v.getGaudiHandle() 00871 else: 00872 vv = v 00873 if isinstance(vv,GaudiHandle) or isinstance(vv,GaudiHandleArray): 00874 strVal = repr(vv) 00875 if hasattr(default,"toStringProperty"): # the default may not be a GaudiHandle (?) 00876 strDef = repr(default.toStringProperty()) 00877 else: 00878 strDef = repr(default) 00879 if strDef == repr(vv.toStringProperty()): 00880 strDef = None 00881 else: 00882 strVal = repr(vv) 00883 strDef = repr(default) 00884 # add the value 00885 line = prefix + ' = ' + strVal 00886 # add default if present 00887 if strDef is not None: 00888 # put default on new line if too big 00889 if len(line) + len(strDef) > Configurable.printHeaderWidth: 00890 line += os.linesep + indentStr + '| ' + (len(prefix)-len(indentStr)-3)*' ' 00891 line += ' (default: %s)' % (strDef,) 00892 # add the line to the total string 00893 rep += line + os.linesep 00894 # print out full private configurables 00895 ## if isinstance(v,Configurable) and not v.isPublic(): 00896 ## rep += v.__str__( indent + 1 ) + os.linesep 00897 ## elif isinstance(v,GaudiHandleArray): 00898 ## for vi in v: 00899 ## if isinstance(vi,Configurable) and not vi.isPublic(): 00900 ## rep += vi.__str__( indent + 1 ) + os.linesep 00901 00902 # print configurables + their properties, or loop over sequence 00903 ## for cfg in self.__children: 00904 for cfg in self.getAllChildren(): 00905 rep += cfg.__str__( indent + 1, '|=' ) + os.linesep 00906 00907 # print line to easily see end-of-configurable. Note: No linesep! 00908 rep += Configurable._printFooter( indentStr, title ) 00909 return rep 00910 00911 ### classes for generic Gaudi component =========== 00912 class DummyDescriptor( object ): 00913 def __init__( self, name ): 00914 self.__name__ = name # conventional 00915 00916 def __get__( self, obj, type = None ): 00917 return getattr( obj, self.__name__ ) 00918 00919 def __set__( self, obj, value ): 00920 object.__setattr__( obj, self.__name__, value ) 00921 00922 class ConfigurableGeneric( Configurable ): 00923 #__slots__ = { } 00924 00925 def __init__( self, name = Configurable.DefaultName ): 00926 Configurable.__init__( self, name ) 00927 self._name = name 00928 self._properties = {} 00929 00930 def __deepcopy__( self, memo ): 00931 return self # algorithms are always shared 00932 00933 def getGaudiType( self ): return 'GenericComponent' 00934 def getDlls( self ) : pass 00935 def getHandle( self ) : pass 00936 00937 def __setattr__( self, name, value ): 00938 # filter private (user) variables 00939 if name[0] == '_': 00940 super( ConfigurableGeneric, self ).__setattr__( name, value ) 00941 return 00942 00943 # filter configurable types 00944 if isinstance( value, Configurable ): 00945 self.__dict__[ name ] = value 00946 return 00947 00948 # assume all the rest are properties 00949 if not name in self._properties: 00950 self._properties[ name ] = PropertyProxy( DummyDescriptor( name ) ) 00951 self._properties[ name ].__set__( self, value ) 00952 00953 def getJobOptName( self ): return None 00954 00955 00956 ### base classes for individual Gaudi algorithms/services/algtools =========== 00957 class ConfigurableAlgorithm( Configurable ): 00958 __slots__ = { '_jobOptName' : 0, 'OutputLevel' : 0, \ 00959 'Enable' : 1, 'ErrorMax' : 1, 'ErrorCount' : 0, 'AuditAlgorithms' : 0, \ 00960 'AuditInitialize' : 0, 'AuditReinitialize' : 0, 'AuditExecute' : 0, \ 00961 'AuditFinalize' : 0, 'AuditBeginRun' : 0, 'AuditEndRun' : 0 } 00962 00963 def __init__( self, name = Configurable.DefaultName ): 00964 super( ConfigurableAlgorithm, self ).__init__( name ) 00965 name = self.getName() 00966 self._jobOptName = name[ name.find('/')+1 : ] # strips class 00967 00968 def __deepcopy__( self, memo ): 00969 return self # algorithms are always shared 00970 00971 def getHandle( self ): 00972 return iAlgorithm( self.getJobOptName() ) 00973 00974 def getGaudiType( self ): 00975 return 'Algorithm' 00976 00977 def getJobOptName( self ): 00978 return self._jobOptName 00979 00980 00981 class ConfigurableService( Configurable ): 00982 __slots__ = { 'OutputLevel' : 0, \ 00983 'AuditServices' : 0, 'AuditInitialize' : 0, 'AuditFinalize' : 0 } 00984 00985 def __deepcopy__( self, memo ): 00986 return self # services are always shared 00987 00988 def copyChild( self, child ): 00989 return child # full sharing 00990 00991 def getHandle( self ): 00992 return iService( self._name ) 00993 00994 def getGaudiType( self ): 00995 return 'Service' 00996 00997 def getGaudiHandle( self ): 00998 return ServiceHandle( self.toStringProperty() ) 00999 01000 def toStringProperty( self ): 01001 # called on conversion to a string property for the jocat 01002 return self.getName() 01003 01004 01005 class ConfigurableAlgTool( Configurable ): 01006 __slots__ = { '_jobOptName' : '', 'OutputLevel' : 0, \ 01007 'AuditTools' : 0, 'AuditInitialize' : 0, 'AuditFinalize' : 0 } 01008 01009 def __init__( self, name = Configurable.DefaultName ): 01010 super( ConfigurableAlgTool, self ).__init__( name ) 01011 if '.' not in self._name: 01012 # Public tools must have ToolSvc as parent 01013 self._name = "ToolSvc." + self._name 01014 name = self.getName() 01015 name = name[ name.find('/')+1 : ] # strips class, if any 01016 self._jobOptName = name 01017 01018 def getHandle( self ): 01019 # iAlgTool isn't useful, unless one knows for sure that the tool exists 01020 return iProperty( self.getJobOptName() ) 01021 01022 def getGaudiType( self ): 01023 return 'AlgTool' 01024 01025 def getGaudiHandle( self ): 01026 if self.isPublic(): 01027 return PublicToolHandle( self.toStringProperty() ) 01028 else: 01029 return PrivateToolHandle( self.toStringProperty() ) 01030 01031 def getPrintTitle(self): 01032 if self.isPublic(): 01033 pop = 'Public ' 01034 else: 01035 pop = 'Private ' 01036 return pop + Configurable.getPrintTitle(self) 01037 01038 def setParent( self, parentName ): 01039 # print "ConfigurableAlgTool.setParent(%s@%x,%r)" % (self.getName(),id(self),parentName) 01040 # print "Calling stack:" 01041 # import traceback 01042 # traceback.print_stack() 01043 # propagate parent to AlgTools in children 01044 for c in self.getAllChildren(): 01045 if isinstance(c,ConfigurableAlgTool): c.setParent( parentName ) 01046 01047 # update my own parent 01048 name = self.getName() 01049 name = name[name.rfind('.')+1:] # Name of the instance 01050 self._jobOptName = self._name = parentName + '.' + name 01051 01052 def getParent( self ): 01053 dot = self._jobOptName.rfind('.') 01054 if dot != -1: 01055 return self._jobOptName[:dot] 01056 else: 01057 return "" 01058 01059 def hasParent( self, parent ): 01060 return self._jobOptName.startswith( parent + '.' ) 01061 01062 def getJobOptName( self ): 01063 return self._jobOptName 01064 01065 def isPublic( self ): 01066 return self.isInToolSvc() 01067 01068 def isInToolSvc( self ): 01069 return self._jobOptName.startswith('ToolSvc.') 01070 01071 def toStringProperty( self ): 01072 # called on conversion to a string property for the jocat 01073 return self.getFullName() 01074 01075 def getFullName( self ) : 01076 # for Tools, the "full name" means "Type/LocalName", 01077 # without the names of the parents 01078 name = self.getName() 01079 # strip off everything before the last '.' 01080 name = name[name.rfind('.')+1:] 01081 return str( self.getType() + '/' + name ) 01082 01083 01084 ### FIXME: this is just a placeholder, waiting for a real implementation 01085 ### It is sufficient to get us going... (and import a PkgConf which 01086 ### happens to contain an Auditor...) 01087 class ConfigurableAuditor( Configurable ): 01088 __slots__ = { '_jobOptName' : 0, 'OutputLevel' : 0, \ 01089 'Enable' : 1 } 01090 01091 def __init__( self, name = Configurable.DefaultName ): 01092 super( ConfigurableAuditor, self ).__init__( name ) 01093 name = self.getName() 01094 name = name[ name.find('/')+1 : ] # strips class, if any 01095 self._jobOptName = name 01096 01097 def getHandle( self ): 01098 # iAlgTool isn't useful, unless one knows for sure that the tool exists 01099 return iProperty( self.getJobOptName() ) 01100 01101 def getGaudiType( self ): 01102 return 'Auditor' 01103 01104 def getJobOptName( self ): 01105 return self._jobOptName 01106 01107 def toStringProperty( self ): 01108 # called on conversion to a string property for the jocat 01109 return self.getType() + '/' + self.getName() 01110 01111 class ConfigurableUser( Configurable ): 01112 __slots__ = { "__users__": [], 01113 "__used_instances__": [], 01114 "_enabled": True } 01115 ## list of ConfigurableUser classes this one is going to modify in the 01116 # __apply_configuration__ method 01117 __used_configurables__ = [] 01118 ## list of ConfigurableUser classes this one is going to query in the 01119 # __apply_configuration__ method 01120 __queried_configurables__ = [] 01121 def __init__( self, name = Configurable.DefaultName, _enabled = True, **kwargs ): 01122 super( ConfigurableUser, self ).__init__( name ) 01123 for n, v in kwargs.items(): 01124 setattr(self, n, v) 01125 self._enabled = _enabled 01126 self.__users__ = [] 01127 01128 # Set the list of users of the used configurables 01129 self.__used_instances__ = [] 01130 for used in self.__used_configurables__: 01131 try: 01132 inst = used(_enabled = False) 01133 except AttributeError: 01134 inst = used() 01135 self.__addActiveUseOf(inst) 01136 for queried in self.__queried_configurables__: 01137 try: 01138 inst = queried(_enabled = False) 01139 except AttributeError: 01140 inst = queried() 01141 self.__addPassiveUseOf(inst) 01142 def __addActiveUseOf(self, other): 01143 """ 01144 Declare that we are going to modify the Configurable 'other' in our 01145 __apply_configuration__. 01146 """ 01147 self.__used_instances__.append(other) 01148 if hasattr(other, "__users__"): # allow usage of plain Configurables 01149 other.__users__.append(self) 01150 def __addPassiveUseOf(self, other): 01151 """ 01152 Declare that we are going to retrieve property values from the 01153 ConfigurableUser 'other' in our __apply_configuration__. 01154 """ 01155 if not isinstance(other, ConfigurableUser): 01156 raise Error("'%s': Cannot make passive use of '%s', it is not a ConfigurableUser" % (self.name(), other.name())) 01157 other.__addActiveUseOf(self) 01158 def getGaudiType( self ): 01159 return 'User' 01160 def getDlls( self ): 01161 return None 01162 def getHandle( self ): 01163 return None 01164 01165 def __detach_used__(self): 01166 """ 01167 Remove this ConfigurableUser instance from the users list of the used 01168 instances. 01169 """ 01170 for used in self.__used_instances__: 01171 if hasattr(used, "__users__"): # allow usage of plain Configurables 01172 used.__users__.remove(self) 01173 01174 def propagateProperty(self, name, others = None, force = True): 01175 """ 01176 Propagate the property 'name' (if set) to other configurables (if possible). 01177 'others' can be: 01178 None: 01179 propagate to all the entries in __used_configurables__ 01180 a configurable instance: 01181 propagate only to it 01182 list of configurable instances: 01183 propagate to all of them. 01184 01185 01186 The logic is: 01187 - if the local property is set, the other property will be overwritten 01188 - local property not set and other set => keep other 01189 - local property not set and other not set => overwrite the default for 01190 ConfigurableUser instances and set the property for Configurables 01191 """ 01192 # transform 'others' to a list of configurable instances 01193 if others is None: 01194 others = self.__used_instances__ 01195 elif type(others) not in [ list, tuple ] : 01196 others = [ others ] 01197 # these can be computed before the loop 01198 local_is_set = self.isPropertySet(name) 01199 value = self.getProp(name) 01200 # loop over the others that do have 'name' in their slots 01201 for other in [ o for o in others if name in o.__slots__ ]: 01202 # If self property is set, use it 01203 if local_is_set: 01204 if other.isPropertySet(name): 01205 log.warning("Property '%(prop)s' is set in both '%(self)s' and '%(other)s', using '%(self)s.%(prop)s'"% 01206 { "self": self.name(), 01207 "other": other.name(), 01208 "prop": name } ) 01209 other.setProp(name, value) 01210 # If not, and other property also not set, propagate the default 01211 elif not other.isPropertySet(name): 01212 if isinstance(other,ConfigurableUser): 01213 other._properties[name].setDefault(value) 01214 else: 01215 other.setProp(name, value) 01216 # If not set and other set, do nothing 01217 01218 def propagateProperties(self, names = None, others = None, force = True): 01219 """ 01220 Call propagateProperty for each property listed in 'names'. 01221 If 'names' is None, all the properties are propagated. 01222 """ 01223 if names is None: 01224 # use all the non-private slots 01225 names = [ p for p in self.__slots__ if not p.startswith("_") ] 01226 for n in names: 01227 self.propagateProperty(n, others, force) 01228 01229 def __apply_configuration__(self): 01230 """ 01231 Function to be overridden to convert the high level configuration into a 01232 low level one. 01233 The default implementation calls applyConf, which is the method defined 01234 in some ConfigurableUser implementations. 01235 """ 01236 return self.applyConf() 01237 01238 def applyConf( self ): 01239 """ 01240 Function to be overridden to convert the high level configuration into a 01241 low level one. 01242 """ 01243 pass 01244 01245 # list of callables to be called after all the __apply_configuration__ are called. 01246 postConfigActions = [] 01247 def appendPostConfigAction(function): 01248 """ 01249 Add a new callable ('function') to the list of post-configuration actions. 01250 If the callable is already in the list, it is moved to the end of the list. 01251 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'. 01252 """ 01253 try: 01254 postConfigActions.remove(function) 01255 except: 01256 pass 01257 postConfigActions.append(function) 01258 def removePostConfigAction(function): 01259 """ 01260 Remove a collable from the list of post-config actions. 01261 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'. 01262 """ 01263 postConfigActions.remove(function) 01264 01265 _appliedConfigurableUsers_ = False 01266 def applyConfigurableUsers(): 01267 """ 01268 Call the apply method of all the ConfigurableUser instances respecting the 01269 dependencies. First the C.U.s that are not used by anybody, then the used 01270 ones, when they are not used anymore. 01271 """ 01272 # Avoid double calls 01273 global _appliedConfigurableUsers_, postConfigActions 01274 if _appliedConfigurableUsers_: 01275 return 01276 _appliedConfigurableUsers_ = True 01277 01278 confUsers = [ c 01279 for c in Configurable.allConfigurables.values() 01280 if hasattr(c,"__apply_configuration__") ] 01281 applied = True # needed to detect dependency loops 01282 while applied and confUsers: 01283 newConfUsers = [] # list of conf users that cannot be applied yet 01284 applied = False 01285 for c in confUsers: 01286 if hasattr(c,"__users__") and c.__users__: 01287 newConfUsers.append(c) # cannot use this one yet 01288 else: # it does not have users or the list is empty 01289 applied = True 01290 # the ConfigurableUser is enabled if it doesn't have an _enabled 01291 # property or its value is True 01292 enabled = (not hasattr(c, "_enabled")) or c._enabled 01293 if enabled: 01294 log.info("applying configuration of %s", c.name()) 01295 c.__apply_configuration__() 01296 log.info(c) 01297 else: 01298 log.info("skipping configuration of %s", c.name()) 01299 if hasattr(c, "__detach_used__"): 01300 # tells the used configurables that they are not needed anymore 01301 c.__detach_used__() 01302 confUsers = newConfUsers # list of C.U.s still to go 01303 if confUsers: 01304 # this means that some C.U.s could not be applied because of a dependency loop 01305 raise Error("Detected loop in the ConfigurableUser " 01306 " dependencies: %r" % [ c.name() 01307 for c in confUsers ]) 01308 # Call post-config actions 01309 for action in postConfigActions: 01310 action() 01311 01312 def getNeededConfigurables(): 01313 """ 01314 Function to select all and only the configurables that have to be used in 01315 GaudiPython.AppMgr constructor. 01316 This is needed because in Athena the implementation have to be different (the 01317 configuration is used in a different moment). 01318 """ 01319 return [ k 01320 for k, v in Configurable.allConfigurables.items() 01321 if v.getGaudiType() != "User" ] # Exclude ConfigurableUser instances 01322 01323 def purge(): 01324 """ 01325 Clean up all configurations and configurables. 01326 """ 01327 for c in Configurable.allConfigurables.values(): 01328 c.__class__.configurables.clear() 01329 Configurable.allConfigurables.clear() 01330 # FIXME: (MCl) this is needed because instances of ConfigurableGeneric are not 01331 # migrated to the correct class when this is known. 01332 ConfigurableGeneric.configurables.clear() 01333 from ProcessJobOptions import _included_files 01334 import os.path, sys 01335 for file in _included_files: 01336 dirname, basname = os.path.split(file) 01337 basname, ext = os.path.splitext(basname) 01338 if basname in sys.modules: 01339 del sys.modules[basname] 01340 _included_files.clear()