![]() |
|
|
Generated: 8 Jan 2009 |
00001 # File: AthenaCommon/python/PropertyProxy.py 00002 # Author: Wim Lavrijsen (WLavrijsen@lbl.gov) 00003 # Author: Martin Woudstra (Martin.Woudstra@cern.ch) 00004 00005 ### data --------------------------------------------------------------------- 00006 __all__ = [ 'PropertyProxy', 'GaudiHandlePropertyProxy', 'GaudiHandleArrayPropertyProxy' ] 00007 00008 import os,glob 00009 from GaudiKernel.GaudiHandles import * 00010 from GaudiKernel import ConfigurableDb 00011 00012 import logging 00013 log = logging.getLogger( 'PropertyProxy' ) 00014 00015 # dictionary with configurable class : python module entries 00016 #-->PM#from AthenaCommon import ConfigurableDb 00017 00018 00019 def derives_from(derived,base): 00020 """A string version of isinstance(). 00021 <derived> is either an object instance, or a type 00022 <base> is a string containing the name of the base class (or <derived> class)""" 00023 if not isinstance(derived,type): derived=type(derived) 00024 if derived.__name__ == base: return True 00025 for b in derived.__bases__: 00026 if derives_from(b,base): return True 00027 00028 return False 00029 00030 def _isCompatible( tp, value ): 00031 errmsg = "received an instance of %s, but %s expected" % (type(value),tp) 00032 00033 if derives_from(value, 'PropertyReference'): 00034 # TODO: implement type checking for references 00035 return value # references are valid 00036 if ( tp is str ): 00037 if ( type(value) is str ) or derives_from(value, 'Configurable'): 00038 # we can set string properties only from strings or configurables 00039 return value 00040 else: 00041 raise ValueError( errmsg ) 00042 elif ( tp in [ list, tuple, dict ] ): 00043 if ( type(value) is tp ): 00044 # We need to check that the types match for lists, tuples and 00045 # dictionaries (bug #34769). 00046 return value 00047 else: 00048 raise ValueError( errmsg ) 00049 elif derives_from(tp, 'Configurable'): 00050 return value 00051 else: 00052 # all other types: accept if conversion allowed 00053 try: 00054 dummy = tp( value ) 00055 except (TypeError,ValueError): 00056 raise ValueError( errmsg ) 00057 00058 return dummy # in case of e.g. classes with __int__, __iter__, etc. implemented 00059 00060 00061 class PropertyProxy( object ): 00062 def __init__( self, descr, docString=None, default=None ): 00063 self.history = {} 00064 self.descr = descr 00065 if docString: 00066 self.__doc__ = docString 00067 if default is not None: 00068 self.default = default 00069 00070 00071 def setDefault( self, value ): 00072 self.__default = value 00073 00074 def getDefault( self ): 00075 return self.__default 00076 00077 default = property( getDefault, setDefault ) 00078 00079 def fullPropertyName( self, obj ): 00080 return (obj.getJobOptName() or obj.getName()) + '.' + self.descr.__name__ 00081 00082 def __get__( self, obj, type = None ): 00083 try: 00084 return self.descr.__get__( obj, type ) 00085 except AttributeError: 00086 # special case for lists and dictionaries: 00087 # allow default to work with on += and [] 00088 if self.__default.__class__ in [ list, dict ]: 00089 self.descr.__set__( obj, self.__default.__class__(self.__default) ) 00090 return self.descr.__get__( obj, type ) 00091 else: 00092 # for non lists (or dicts) return a reference to the default 00093 #return self.__default 00094 raise 00095 00096 def __set__( self, obj, value ): 00097 # check value/property compatibility if possible 00098 proptype, allowcompat = None, False 00099 if hasattr( self, 'default' ): 00100 proptype = type(self.default) 00101 if self.descr.__name__ == 'OutputLevel': # old-style compat for Btag 00102 allowcompat = True 00103 elif obj in self.history: 00104 proptype = type( self.history[ obj ][ 0 ] ) 00105 allowcompat = True 00106 00107 # check if type known; allow special initializer for typed instances 00108 # Do not perform the check for PropertyReference, should be delayed until 00109 # binding (if ever done) 00110 if proptype and proptype != type(None) and \ 00111 not derives_from(value, 'PropertyReference'): 00112 try: 00113 # check value itself 00114 value = _isCompatible( proptype, value ) 00115 00116 # check element in case of list 00117 if proptype == list: 00118 try: 00119 oldvec = self.descr.__get__( obj, type ) 00120 if oldvec: 00121 tpo = type(oldvec[0]) 00122 for v in value: 00123 _isCompatible( tpo, v ) 00124 except AttributeError: 00125 # value not yet set 00126 pass 00127 except ValueError, e: 00128 if allowcompat: 00129 log.error( 'inconsistent value types for %s.%s (%s)' %\ 00130 (obj.getName(),self.descr.__name__,str(e)) ) 00131 else: 00132 raise 00133 00134 # allow a property to be set if we're in non-default mode, or if it 00135 # simply hasn't been set before 00136 if not obj._isInSetDefaults() or not obj in self.history: 00137 # by convention, 'None' for default is used to designate objects setting 00138 if hasattr( self, 'default' ) and self.default == None: 00139 obj.__iadd__( value, self.descr ) # to establish hierarchy 00140 else: 00141 self.descr.__set__( obj, value ) 00142 self.history.setdefault( obj, [] ).append( value ) 00143 00144 def __delete__( self, obj ): 00145 if obj in self.history: 00146 del self.history[ obj ] 00147 self.descr.__delete__( obj ) 00148 00149 00150 00151 class GaudiHandlePropertyProxyBase(PropertyProxy): 00152 """A class with some utilities for GaudiHandles and GaudiHandleArrays""" 00153 00154 def __init__(self, descr, docString, default, handleType, allowedType ): 00155 """<descr>: the real property in the object instance (from __slots__) 00156 <docString>: the documentation string of this property 00157 <default>: default value from C++ (via python generated by genconf) 00158 <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...) 00159 <allowedType>: allowed instance type for default 00160 """ 00161 # check that default is of allowed type for this proxy 00162 if not isinstance(default,allowedType): 00163 raise TypeError( "%s: %s default: %r is not a %s" % \ 00164 ( descr.__name__, self.__class__.__name__, default, allowedType.__name__ ) ) 00165 PropertyProxy.__init__( self, descr, docString, default ) 00166 self._handleType = handleType 00167 self._confTypeName = 'Configurable' + handleType.componentType 00168 # print "%s: %r (%s)" % (self.__class__.__name__,self._handleType,self._confTypeName) 00169 00170 00171 def __get__( self, obj, type = None ): 00172 try: 00173 return self.descr.__get__( obj, type ) 00174 except AttributeError: 00175 # Get default 00176 try: 00177 default = obj.__class__.getDefaultProperty( self.descr.__name__ ) 00178 default = self.convertDefaultToBeSet( obj, default ) 00179 self.__set__( obj, default ) 00180 except AttributeError,e: 00181 # change type of exception to avoid false error message 00182 raise RuntimeError(*e.args) 00183 00184 return self.descr.__get__( obj, type ) 00185 00186 00187 def __set__( self, obj, value ): 00188 # allow a property to be set if we're in non-default mode, or if it 00189 # simply hasn't been set before 00190 if not obj._isInSetDefaults() or not obj in self.history: 00191 value = self.convertValueToBeSet( obj, value ) 00192 # assign the value 00193 self.descr.__set__( obj, value ) 00194 log.debug( "Setting %s = %r", self.fullPropertyName( obj ), value ) 00195 self.history.setdefault( obj, [] ).append( value ) 00196 00197 00198 00199 def isHandle(self,value): 00200 """Check if <value> is a handle of the correct type""" 00201 return isinstance(value,self._handleType) 00202 00203 00204 def isConfig(self,value): 00205 """Check if <value> is a configurable of the correct type""" 00206 return derives_from(value,self._confTypeName) 00207 00208 00209 def getDefaultConfigurable(self,typeAndName,requester): 00210 """Return the configurable instance corresponding to the toolhandle if possible. 00211 Otherwise return None""" 00212 global log 00213 # find the module 00214 typeAndNameTuple = typeAndName.split('/') 00215 confType = typeAndNameTuple[0] 00216 confClass=ConfigurableDb.getConfigurable(confType) 00217 # check the type of the configurable 00218 if not derives_from(confClass,self._confTypeName): 00219 log.error( "%s: Configurable %s is not a %s", 00220 requester, confType, self._confTypeName ) 00221 return None 00222 try: 00223 confName = typeAndNameTuple[1] 00224 except IndexError: 00225 return confClass() # use default name 00226 else: 00227 return confClass(confName) 00228 00229 00230 def convertDefaultToBeSet( self, obj, default ): 00231 # turn string into handle 00232 isString = type(default) == str 00233 if not isString and self.isConfig(default): 00234 # print self.fullPropertyName(obj) + ": Setting default configurable: %r" % default 00235 return default 00236 elif isString or self.isHandle(default): 00237 if isString: 00238 # convert string into handle 00239 typeAndName = default 00240 default = self._handleType( typeAndName ) 00241 else: 00242 typeAndName = default.typeAndName 00243 if not self._handleType.isPublic: 00244 # Find corresponding default configurable of private handles 00245 try: 00246 conf = self.getDefaultConfigurable(typeAndName, self.fullPropertyName(obj)) 00247 # print self.fullPropertyName(obj) + ": Setting default private configurable (from default handle): %r" % conf 00248 except AttributeError,e: 00249 # change type of exception to avoid false error message 00250 raise RuntimeError(*e.args) 00251 if conf is None: 00252 raise RuntimeError( "%s: Default configurable for class %s not found in ConfigurableDb.CfgDb" % \ 00253 (self.fullPropertyName(obj),default.getType() ) ) 00254 return conf 00255 else: # not a config, not a handle, not a string 00256 raise TypeError( "%s: default value %r is not of type %s or %s" % \ 00257 (self.fullPropertyName(obj),default,self._confTypeName,self._handleType.__name__) ) 00258 00259 return default 00260 00261 def convertValueToBeSet( self, obj, value ): 00262 if value is None: value = '' 00263 isString = type(value) == str 00264 if isString: 00265 # create an new handle 00266 return self._handleType(value) 00267 elif self.isHandle(value): 00268 # make a copy of the handle 00269 return self._handleType(value.toStringProperty()) 00270 elif self.isConfig(value): 00271 if self._handleType.isPublic: 00272 # A public tool must be registered to ToolSvc before assigning it 00273 if derives_from(value,'ConfigurableAlgTool'): 00274 if not value.isInToolSvc(): 00275 suggestion = 'You may need to add jobOptions lines something like:' + os.linesep + \ 00276 'from AthenaCommon.AppMgr import ToolSvc' + os.linesep + \ 00277 'ToolSvc += ' 00278 if value.getName() == value.getType(): # using default name 00279 suggestion += '%s()' % value.__class__.__name__ 00280 else: # using user-defined name 00281 suggestion += '%s(%r)' % (value.__class__.__name__,value.getName()) 00282 raise RuntimeError( self.fullPropertyName(obj) + 00283 ': Public tool %s is not yet in ToolSvc. %s' % 00284 (value.getJobOptName(),suggestion) ) 00285 # make it read-only 00286 return self._handleType(value.toStringProperty()) 00287 elif value.hasParent( obj.getJobOptName() ): 00288 # is already a child, keep as-is 00289 return value 00290 else: 00291 # make a copy of the configurable 00292 value = obj.copyChildAndSetParent( value, obj.getJobOptName() ) 00293 # ensure that the new object is in allConfigurables 00294 obj.allConfigurables[value.name()] = value 00295 return value 00296 else: 00297 raise TypeError( "Property %s value %r is not a %s nor a %s nor a string" % \ 00298 (self.fullPropertyName(obj),value,self._confTypeName,self._handleType.__name__) ) 00299 00300 return value 00301 00302 00303 class GaudiHandlePropertyProxy(GaudiHandlePropertyProxyBase): 00304 def __init__( self, descr, docString, default ): 00305 GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default), GaudiHandle ) 00306 00307 00308 class GaudiHandleArrayPropertyProxy(GaudiHandlePropertyProxyBase): 00309 def __init__( self, descr, docString, default ): 00310 """<descr>: the real property in the object instance (from __slots__) 00311 <confTypeName>: string indicating the (base) class of allowed Configurables to be assigned. 00312 <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...) 00313 """ 00314 GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default).handleType, GaudiHandleArray ) 00315 self.arrayType = type(default) 00316 00317 00318 def checkType( self, obj, value ): 00319 if not isinstance( value, list ) and not isinstance( value, self.arrayType ): 00320 raise TypeError( "%s: Value %r is not a list nor a %s" % \ 00321 ( self.fullPropertyName(obj), value, self.arrayType.__name__ ) ) 00322 00323 00324 def convertDefaultToBeSet( self, obj, default ): 00325 self.checkType( obj, default ) 00326 newDefault = self.arrayType() 00327 for d in default: 00328 cd = GaudiHandlePropertyProxyBase.convertDefaultToBeSet( self, obj, d ) 00329 if cd: newDefault.append( cd ) 00330 00331 return newDefault 00332 00333 00334 def convertValueToBeSet( self, obj, value ): 00335 self.checkType( obj, value ) 00336 newValue = self.arrayType() 00337 for v in value: 00338 cv = GaudiHandlePropertyProxyBase.convertValueToBeSet( self, obj, v ) 00339 if cv: newValue.append( cv ) 00340 00341 return newValue 00342 00343 00344 00345 def PropertyProxyFactory( descr, doc, default ): 00346 # print "PropertyProxyFactory( %s, %r )" % (descr.__name__,default) 00347 if isinstance(default,GaudiHandleArray): 00348 return GaudiHandleArrayPropertyProxy( descr, doc, default ) 00349 00350 if isinstance(default,GaudiHandle): 00351 return GaudiHandlePropertyProxy( descr, doc, default ) 00352 00353 return PropertyProxy( descr, doc, default )