| # Gaudi Framework, version v20r4
Generated: 8 Jan 2009

# 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             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 )