| Classes | Job Modules | Data Objects | Services | Algorithms | Tools | Packages | Directories | Tracs |

In This Package:

Configurable.py

Go to the documentation of this file.
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         # propagate parent to AlgTools in children
01040         for c in self.getAllChildren():
01041             if isinstance(c,ConfigurableAlgTool): c.setParent( parentName )
01042 
01043         # update my own parent
01044         name = self.getName()
01045         name = name[name.rfind('.')+1:] # Name of the instance
01046         self._jobOptName = self._name = parentName + '.' + name
01047 
01048     def getParent( self ):
01049         dot = self._jobOptName.rfind('.')
01050         if dot != -1:
01051             return self._jobOptName[:dot]
01052         else:
01053             return ""
01054 
01055     def hasParent( self, parent ):
01056         return self._jobOptName.startswith( parent + '.' )
01057 
01058     def getJobOptName( self ):
01059         return self._jobOptName
01060 
01061     def isPublic( self ):
01062         return self.isInToolSvc()
01063 
01064     def isInToolSvc( self ):
01065         return self._jobOptName.startswith('ToolSvc.')
01066 
01067     def toStringProperty( self ):
01068         # called on conversion to a string property for the jocat
01069         return self.getFullName()
01070 
01071     def getFullName( self ) :
01072         # for Tools, the "full name" means "Type/LocalName",
01073         # without the names of the parents
01074         name = self.getName()
01075         # strip off everything before the last '.'
01076         name = name[name.rfind('.')+1:]
01077         return str( self.getType() + '/' + name )
01078 
01079 
01080 ### FIXME: this is just a placeholder, waiting for a real implementation
01081 ###        It is sufficient to get us going... (and import a PkgConf which
01082 ###        happens to contain an Auditor...)
01083 class ConfigurableAuditor( Configurable ):
01084     __slots__ = { '_jobOptName' : 0, 'OutputLevel' : 0, \
01085                   'Enable' : 1 }
01086 
01087     def __init__( self, name = Configurable.DefaultName ):
01088         super( ConfigurableAuditor, self ).__init__( name )
01089         name = self.getName()
01090         name = name[ name.find('/')+1 : ]   # strips class, if any
01091         self._jobOptName = name
01092 
01093     def getHandle( self ):
01094         # iAlgTool isn't useful, unless one knows for sure that the tool exists
01095         return iProperty( self.getJobOptName() )
01096 
01097     def getGaudiType( self ):
01098         return 'Auditor'
01099 
01100     def getJobOptName( self ):
01101         return self._jobOptName
01102 
01103     def toStringProperty( self ):
01104     # called on conversion to a string property for the jocat
01105         return self.getType() + '/' + self.getName()
01106 
01107 class ConfigurableUser( Configurable ):
01108     __slots__ = { "__users__": [],
01109                   "__used_instances__": [],
01110                   "_enabled": True }
01111     ## list of ConfigurableUser classes this one is going to modify in the
01112     #  __apply_configuration__ method
01113     __used_configurables__ = []
01114     ## list of ConfigurableUser classes this one is going to query in the
01115     #  __apply_configuration__ method
01116     __queried_configurables__ = []
01117     def __init__( self, name = Configurable.DefaultName, _enabled = True, **kwargs ):
01118         super( ConfigurableUser, self ).__init__( name )
01119         for n, v in kwargs.items():
01120             setattr(self, n, v)
01121         self._enabled = _enabled
01122         self.__users__ = []
01123         
01124         # Set the list of users of the used configurables
01125         self.__used_instances__ = []
01126         for used in self.__used_configurables__:
01127             try:
01128                 inst = used(_enabled = False)
01129             except AttributeError:
01130                 inst = used()
01131             self.__addActiveUseOf(inst)
01132         for queried in self.__queried_configurables__:
01133             try:
01134                 inst = queried(_enabled = False)
01135             except AttributeError:
01136                 inst = queried()
01137             self.__addPassiveUseOf(inst)
01138     def __addActiveUseOf(self, other):
01139         """
01140         Declare that we are going to modify the Configurable 'other' in our
01141         __apply_configuration__. 
01142         """
01143         self.__used_instances__.append(other)
01144         if hasattr(other, "__users__"): # allow usage of plain Configurables
01145             other.__users__.append(self)
01146     def __addPassiveUseOf(self, other):
01147         """
01148         Declare that we are going to retrieve property values from the
01149         ConfigurableUser 'other' in our __apply_configuration__. 
01150         """
01151         if not isinstance(other, ConfigurableUser):
01152             raise Error("'%s': Cannot make passive use of '%s', it is not a ConfigurableUser" % (self.name(), other.name()))
01153         other.__addActiveUseOf(self)
01154     def getGaudiType( self ):
01155         return 'User'
01156     def getDlls( self ):
01157         return None
01158     def getHandle( self ):
01159         return None
01160     
01161     def __detach_used__(self):
01162         """
01163         Remove this ConfigurableUser instance from the users list of the used
01164         instances.
01165         """
01166         for used in self.__used_instances__:
01167             if hasattr(used, "__users__"): # allow usage of plain Configurables
01168                 used.__users__.remove(self)
01169     
01170     def propagateProperty(self, name, others = None, force = True):
01171         """
01172         Propagate the property 'name' (if set) to other configurables (if possible).
01173         'others' can be:
01174             None:
01175                 propagate to all the entries in __used_configurables__
01176             a configurable instance:
01177                 propagate only to it
01178             list of configurable instances:
01179                 propagate to all of them.
01180         
01181         
01182         The logic is:
01183         - if the local property is set, the other property will be overwritten
01184         - local property not set and other set => keep other
01185         - local property not set and other not set => overwrite the default for
01186             ConfigurableUser instances and set the property for Configurables
01187         """
01188         # transform 'others' to a list of configurable instances
01189         if others is None:
01190             others = self.__used_instances__
01191         elif type(others) not in [ list, tuple ] :
01192             others = [ others ]
01193         # these can be computed before the loop
01194         local_is_set = self.isPropertySet(name)
01195         value = self.getProp(name)
01196         # loop over the others that do have 'name' in their slots
01197         for other in [ o for o in others if name in o.__slots__ ]:
01198             # If self property is set, use it
01199             if local_is_set:
01200                 if other.isPropertySet(name):
01201                     log.warning("Property '%(prop)s' is set in both '%(self)s' and '%(other)s', using '%(self)s.%(prop)s'"%
01202                                 { "self": self.name(),
01203                                   "other": other.name(),
01204                                   "prop": name } )
01205                 other.setProp(name, value)
01206             # If not, and other property also not set, propagate the default
01207             elif not other.isPropertySet(name):
01208                 if isinstance(other,ConfigurableUser):
01209                     other._properties[name].setDefault(value)
01210                 else:
01211                     other.setProp(name, value)
01212             # If not set and other set, do nothing
01213         
01214     def propagateProperties(self, names = None, others = None, force = True):
01215         """
01216         Call propagateProperty for each property listed in 'names'.
01217         If 'names' is None, all the properties are propagated.
01218         """
01219         if names is None:
01220             # use all the non-private slots
01221             names = [ p for p in self.__slots__ if not p.startswith("_") ]
01222         for n in names:
01223             self.propagateProperty(n, others, force)
01224 
01225     def __apply_configuration__(self):
01226         """
01227         Function to be overridden to convert the high level configuration into a
01228         low level one.
01229         The default implementation calls applyConf, which is the method defined
01230         in some ConfigurableUser implementations.
01231         """
01232         return self.applyConf()
01233     
01234     def applyConf( self ):
01235         """
01236         Function to be overridden to convert the high level configuration into a
01237         low level one. 
01238         """
01239         pass
01240 
01241 # list of callables to be called after all the __apply_configuration__ are called.
01242 postConfigActions = []
01243 def appendPostConfigAction(function):
01244     """
01245     Add a new callable ('function') to the list of post-configuration actions.
01246     If the callable is already in the list, it is moved to the end of the list.
01247     The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
01248     """
01249     try:
01250         postConfigActions.remove(function)
01251     except:
01252         pass
01253     postConfigActions.append(function)
01254 def removePostConfigAction(function):
01255     """
01256     Remove a collable from the list of post-config actions. 
01257     The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
01258     """
01259     postConfigActions.remove(function)
01260 
01261 _appliedConfigurableUsers_ = False
01262 def applyConfigurableUsers():
01263     """
01264     Call the apply method of all the ConfigurableUser instances respecting the
01265     dependencies. First the C.U.s that are not used by anybody, then the used
01266     ones, when they are not used anymore.
01267     """
01268     # Avoid double calls
01269     global _appliedConfigurableUsers_, postConfigActions
01270     if _appliedConfigurableUsers_:
01271         return
01272     _appliedConfigurableUsers_ = True
01273     
01274     confUsers = [ c
01275                   for c in Configurable.allConfigurables.values()
01276                   if hasattr(c,"__apply_configuration__") ]
01277     applied = True # needed to detect dependency loops
01278     while applied and confUsers:
01279         newConfUsers = [] # list of conf users that cannot be applied yet
01280         applied = False
01281         for c in confUsers:
01282             if hasattr(c,"__users__") and c.__users__:
01283                 newConfUsers.append(c) # cannot use this one yet
01284             else: # it does not have users or the list is empty
01285                 applied = True
01286                 # the ConfigurableUser is enabled if it doesn't have an _enabled
01287                 # property or its value is True 
01288                 enabled = (not hasattr(c, "_enabled")) or c._enabled
01289                 if enabled:
01290                     log.info("applying configuration of %s", c.name())
01291                     c.__apply_configuration__()
01292                     log.info(c)
01293                 else:
01294                     log.info("skipping configuration of %s", c.name())
01295                 if hasattr(c, "__detach_used__"):
01296                     # tells the used configurables that they are not needed anymore
01297                     c.__detach_used__()
01298         confUsers = newConfUsers # list of C.U.s still to go
01299     if confUsers:
01300         # this means that some C.U.s could not be applied because of a dependency loop
01301         raise Error("Detected loop in the ConfigurableUser "
01302                     " dependencies: %r" % [ c.name()
01303                                             for c in confUsers ])
01304     # Call post-config actions
01305     for action in postConfigActions:
01306         action()
01307 
01308 def getNeededConfigurables():
01309     """
01310     Function to select all and only the configurables that have to be used in
01311     GaudiPython.AppMgr constructor.
01312     This is needed because in Athena the implementation have to be different (the
01313     configuration is used in a different moment).
01314     """
01315     return [ k
01316              for k, v in Configurable.allConfigurables.items()
01317              if v.getGaudiType() != "User" ] # Exclude ConfigurableUser instances
01318 
01319 def purge():
01320     """
01321     Clean up all configurations and configurables.
01322     """
01323     for c in Configurable.allConfigurables.values():
01324         c.__class__.configurables.clear()
01325     Configurable.allConfigurables.clear()
01326     # FIXME: (MCl) this is needed because instances of ConfigurableGeneric are not
01327     #        migrated to the correct class when this is known.
01328     ConfigurableGeneric.configurables.clear()
01329     from ProcessJobOptions import _included_files
01330     import os.path, sys
01331     for file in _included_files:
01332         dirname, basname = os.path.split(file)
01333         basname, ext = os.path.splitext(basname)
01334         if basname in sys.modules:
01335             del sys.modules[basname]
01336     _included_files.clear()
| Classes | Job Modules | Data Objects | Services | Algorithms | Tools | Packages | Directories | Tracs |

Generated on Mon Apr 11 19:56:58 2011 for GaudiKernel by doxygen 1.4.7