Gaudi Framework, version v21r6

Home   Generated: 11 Nov 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 )

Generated at Wed Nov 11 16:23:05 2009 for Gaudi Framework, version v21r6 by Doxygen version 1.5.6 written by Dimitri van Heesch, © 1997-2004