| # Gaudi Framework, version v21r10p1 |

Home |  | Generated: 29 Jul 2010

# PropertyProxy.py

Go to the documentation of this file.

    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                 if default:
    00180                     self.__set__( obj, default )
    00181             except AttributeError,e:
    00182                 # change type of exception to avoid false error message
    00183                 raise RuntimeError(*e.args)
    00184
    00185         return self.descr.__get__( obj, type )
    00186
    00187
    00188     def __set__( self, obj, value ):
    00189      # allow a property to be set if we're in non-default mode, or if it
    00190      # simply hasn't been set before
    00191         if not obj._isInSetDefaults() or not obj in self.history:
    00192             value = self.convertValueToBeSet( obj, value )
    00193             # assign the value
    00194             self.descr.__set__( obj, value )
    00195             log.debug( "Setting %s = %r", self.fullPropertyName( obj ), value )
    00196             self.history.setdefault( obj, [] ).append( value )
    00197
    00198
    00199
    00200     def isHandle(self,value):
    00201         """Check if <value> is a handle of the correct type"""
    00202         return isinstance(value,self._handleType)
    00203
    00204
    00205     def isConfig(self,value):
    00206         """Check if <value> is a configurable of the correct type"""
    00207         return derives_from(value,self._confTypeName)
    00208
    00209
    00210     def getDefaultConfigurable(self,typeAndName,requester):
    00211         """Return the configurable instance corresponding to the toolhandle if possible.
    00212         Otherwise return None"""
    00213         global log
    00214         # find the module
    00215         typeAndNameTuple = typeAndName.split('/')
    00216         confType = typeAndNameTuple[0]
    00217         confClass=ConfigurableDb.getConfigurable(confType)
    00218         # check the type of the configurable
    00219         if not derives_from(confClass,self._confTypeName):
    00220             log.error( "%s: Configurable %s is not a %s",
    00221                        requester, confType, self._confTypeName )
    00222             return None
    00223         try:
    00224             confName = typeAndNameTuple[1]
    00225         except IndexError:
    00226             return confClass() # use default name
    00227         else:
    00228             return confClass(confName)
    00229
    00230
    00231     def convertDefaultToBeSet( self, obj, default ):
    00232         # turn string into handle
    00233         isString = type(default) == str
    00234         if not isString and self.isConfig(default):
    00235 #         print self.fullPropertyName(obj) + ": Setting default configurable: %r" % default
    00236             return default
    00237         elif isString or self.isHandle(default):
    00238             if isString:
    00239                 # convert string into handle
    00240                 typeAndName = default
    00241                 default = self._handleType( typeAndName )
    00242             else:
    00243                 typeAndName = default.typeAndName
    00244             if not self._handleType.isPublic:
    00245                 if not typeAndName:
    00246                     return None
    00247                 # Find corresponding default configurable of private handles
    00248                 try:
    00249                     conf = self.getDefaultConfigurable(typeAndName, self.fullPropertyName(obj))
    00250 #               print self.fullPropertyName(obj) + ": Setting default private configurable (from default handle): %r" % conf
    00251                 except AttributeError,e:
    00252                     # change type of exception to avoid false error message
    00253                     raise RuntimeError(*e.args)
    00254                 if conf is None:
    00255                     raise RuntimeError( "%s: Default configurable for class %s not found in ConfigurableDb.CfgDb" % \
    00256                                         (self.fullPropertyName(obj),default.getType() ) )
    00257                 return conf
    00258         else: # not a config, not a handle, not a string
    00259             raise TypeError( "%s: default value %r is not of type %s or %s" % \
    00260                              (self.fullPropertyName(obj),default,self._confTypeName,self._handleType.__name__) )
    00261
    00262         return default
    00263
    00264     def convertValueToBeSet( self, obj, value ):
    00265         if value is None: value = ''
    00266         isString = type(value) == str
    00267         if isString:
    00268         # create an new handle
    00269             return self._handleType(value)
    00270         elif self.isHandle(value):
    00271         # make a copy of the handle
    00272             return self._handleType(value.toStringProperty())
    00273         elif self.isConfig(value):
    00274             if self._handleType.isPublic:
    00275              # A public tool must be registered to ToolSvc before assigning it
    00276                 if derives_from(value,'ConfigurableAlgTool'):
    00277                     if not value.isInToolSvc():
    00278                         suggestion = 'You may need to add jobOptions lines something like:' + os.linesep + \
    00279                                      'from AthenaCommon.AppMgr import ToolSvc' + os.linesep + \
    00280                                      'ToolSvc += '
    00281                         if value.getName() == value.getType(): # using default name
    00282                             suggestion += '%s()' % value.__class__.__name__
    00283                         else: # using user-defined name
    00284                             suggestion += '%s(%r)' % (value.__class__.__name__,value.getName())
    00285                         raise RuntimeError( self.fullPropertyName(obj) +
    00286                                             ': Public tool %s is not yet in ToolSvc. %s' %
    00287                                             (value.getJobOptName(),suggestion) )
    00288              # make it read-only
    00289                 return self._handleType(value.toStringProperty())
    00290             elif value.hasParent( obj.getJobOptName() ):
    00291              # is already a child, keep as-is
    00292                 return value
    00293             else:
    00294              # make a copy of the configurable
    00295                 value = obj.copyChildAndSetParent( value, obj.getJobOptName() )
    00296                 # ensure that the new object is in allConfigurables
    00297                 obj.allConfigurables[value.name()] = value
    00298                 return value
    00299         else:
    00300             raise TypeError( "Property %s value %r is not a %s nor a %s nor a string" % \
    00301                              (self.fullPropertyName(obj),value,self._confTypeName,self._handleType.__name__) )
    00302
    00303         return value
    00304
    00305
    00306 class GaudiHandlePropertyProxy(GaudiHandlePropertyProxyBase):
    00307     def __init__( self, descr, docString, default ):
    00308         GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default), GaudiHandle )
    00309
    00310
    00311 class GaudiHandleArrayPropertyProxy(GaudiHandlePropertyProxyBase):
    00312     def __init__( self, descr, docString, default ):
    00313         """<descr>: the real property in the object instance (from __slots__)
    00314         <confTypeName>: string indicating the (base) class of allowed Configurables to be assigned.
    00315         <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
    00316         """
    00317         GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default).handleType, GaudiHandleArray )
    00318         self.arrayType = type(default)
    00319
    00320
    00321     def checkType( self, obj, value ):
    00322         if not isinstance( value, list ) and not isinstance( value, self.arrayType ):
    00323             raise TypeError( "%s: Value %r is not a list nor a %s" % \
    00324                              ( self.fullPropertyName(obj), value, self.arrayType.__name__ ) )
    00325
    00326
    00327     def convertDefaultToBeSet( self, obj, default ):
    00328         self.checkType( obj, default )
    00329         newDefault = self.arrayType()
    00330         for d in default:
    00331             cd = GaudiHandlePropertyProxyBase.convertDefaultToBeSet( self, obj, d )
    00332             if cd: newDefault.append( cd )
    00333
    00334         return newDefault
    00335
    00336
    00337     def convertValueToBeSet( self, obj, value ):
    00338         self.checkType( obj, value )
    00339         newValue = self.arrayType()
    00340         for v in value:
    00341             cv = GaudiHandlePropertyProxyBase.convertValueToBeSet( self, obj, v )
    00342             if cv: newValue.append( cv )
    00343
    00344         return newValue
    00345
    00346
    00347
    00348 def PropertyProxyFactory( descr, doc, default ):
    00349 #   print "PropertyProxyFactory( %s, %r )" % (descr.__name__,default)
    00350     if isinstance(default,GaudiHandleArray):
    00351         return GaudiHandleArrayPropertyProxy( descr, doc, default )
    00352
    00353     if isinstance(default,GaudiHandle):
    00354         return GaudiHandlePropertyProxy( descr, doc, default )
    00355
    00356     return PropertyProxy( descr, doc, default )