PropertyProxy.py
Go to the documentation of this file.
1 # File: AthenaCommon/python/PropertyProxy.py
2 # Author: Wim Lavrijsen (WLavrijsen@lbl.gov)
3 # Author: Martin Woudstra (Martin.Woudstra@cern.ch)
4 
5 ### data ---------------------------------------------------------------------
6 __all__ = [ 'PropertyProxy', 'GaudiHandlePropertyProxy', 'GaudiHandleArrayPropertyProxy' ]
7 
8 import os,glob
9 from GaudiKernel.GaudiHandles import *
10 from GaudiKernel import ConfigurableDb
12 
13 import logging
14 log = logging.getLogger( 'PropertyProxy' )
15 
16 # dictionary with configurable class : python module entries
17 #-->PM#from AthenaCommon import ConfigurableDb
18 
19 
20 def derives_from(derived,base):
21  """A string version of isinstance().
22  <derived> is either an object instance, or a type
23  <base> is a string containing the name of the base class (or <derived> class)"""
24  if not isinstance(derived,type): derived=type(derived)
25  if derived.__name__ == base: return True
26  for b in derived.__bases__:
27  if derives_from(b,base): return True
28 
29  return False
30 
31 def _isCompatible( tp, value ):
32  errmsg = "received an instance of %s, but %s expected" % (type(value),tp)
33 
34  if derives_from(value, 'PropertyReference'):
35  # TODO: implement type checking for references
36  return value # references are valid
37  if ( tp is str ):
38  if ( type(value) is str ) or derives_from(value, 'Configurable'):
39  # we can set string properties only from strings or configurables
40  return value
41  else:
42  raise ValueError( errmsg )
43  elif ( tp in [ list, tuple, dict ] ):
44  if ( type(value) is tp ):
45  # We need to check that the types match for lists, tuples and
46  # dictionaries (bug #34769).
47  return value
48  else:
49  raise ValueError( errmsg )
50  elif derives_from(tp, 'Configurable'):
51  return value
52  else:
53  # all other types: accept if conversion allowed
54  try:
55  dummy = tp( value )
56  except (TypeError,ValueError):
57  raise ValueError( errmsg )
58 
59  return dummy # in case of e.g. classes with __int__, __iter__, etc. implemented
60 
61 
62 class PropertyProxy( object ):
63  def __init__( self, descr, docString=None, default=None ):
64  self.history = {}
65  self.descr = descr
66  if docString:
67  self.__doc__ = docString
68  if default is not None:
69  self.default = default
70 
71 
72  def setDefault( self, value ):
73  self.__default = value
74 
75  def getDefault( self ):
76  return self.__default
77 
78  default = property( getDefault, setDefault )
79 
80  def fullPropertyName( self, obj ):
81  return (obj.getJobOptName() or obj.getName()) + '.' + self.descr.__name__
82 
83  def __get__( self, obj, type = None ):
84  try:
85  return self.descr.__get__( obj, type )
86  except AttributeError:
87  # special case for lists and dictionaries:
88  # allow default to work with on += and []
89  if self.__default.__class__ in [ list, dict ]:
90  self.descr.__set__( obj, self.__default.__class__(self.__default) )
91  return self.descr.__get__( obj, type )
92  else:
93  # for non lists (or dicts) return a reference to the default
94  #return self.__default
95  raise
96 
97  def __set__( self, obj, value ):
98  # check value/property compatibility if possible
99  proptype, allowcompat = None, False
100  if hasattr( self, 'default' ):
101  proptype = type(self.default)
102  if self.descr.__name__ == 'OutputLevel': # old-style compat for Btag
103  allowcompat = True
104  elif obj in self.history:
105  proptype = type( self.history[ obj ][ 0 ] )
106  allowcompat = True
107 
108  # check if type known; allow special initializer for typed instances
109  # Do not perform the check for PropertyReference, should be delayed until
110  # binding (if ever done)
111  if proptype and proptype != type(None) and \
112  not derives_from(value, 'PropertyReference'):
113  try:
114  # check value itself
115  value = _isCompatible( proptype, value )
116 
117  # check element in case of list
118  if proptype == list:
119  try:
120  oldvec = self.descr.__get__( obj, type )
121  if oldvec:
122  tpo = type(oldvec[0])
123  for v in value:
124  _isCompatible( tpo, v )
125  except AttributeError:
126  # value not yet set
127  pass
128  except ValueError, e:
129  if allowcompat:
130  log.error( 'inconsistent value types for %s.%s (%s)' %\
131  (obj.getName(),self.descr.__name__,str(e)) )
132  else:
133  raise
134 
135  # allow a property to be set if we're in non-default mode, or if it
136  # simply hasn't been set before
137  if not obj._isInSetDefaults() or not obj in self.history:
138  # by convention, 'None' for default is used to designate objects setting
139  if hasattr( self, 'default' ) and self.default == None:
140  obj.__iadd__( value, self.descr ) # to establish hierarchy
141  else:
142  self.descr.__set__( obj, value )
143  self.history.setdefault( obj, [] ).append( value )
144 
145  def __delete__( self, obj ):
146  if obj in self.history:
147  del self.history[ obj ]
148  self.descr.__delete__( obj )
149 
150 
151 
153  """A class with some utilities for GaudiHandles and GaudiHandleArrays"""
154 
155  def __init__(self, descr, docString, default, handleType, allowedType ):
156  """<descr>: the real property in the object instance (from __slots__)
157  <docString>: the documentation string of this property
158  <default>: default value from C++ (via python generated by genconf)
159  <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
160  <allowedType>: allowed instance type for default
161  """
162  # check that default is of allowed type for this proxy
163  if not isinstance(default,allowedType):
164  raise TypeError( "%s: %s default: %r is not a %s" % \
165  ( descr.__name__, self.__class__.__name__, default, allowedType.__name__ ) )
166  PropertyProxy.__init__( self, descr, docString, default )
167  self._handleType = handleType
168  self._confTypeName = 'Configurable' + handleType.componentType
169 # print "%s: %r (%s)" % (self.__class__.__name__,self._handleType,self._confTypeName)
170 
171 
172  def __get__( self, obj, type = None ):
173  try:
174  return self.descr.__get__( obj, type )
175  except AttributeError:
176  # Get default
177  try:
178  default = obj.__class__.getDefaultProperty( self.descr.__name__ )
179  default = self.convertDefaultToBeSet( obj, default )
180  if default:
181  self.__set__( obj, default )
182  except AttributeError,e:
183  # change type of exception to avoid false error message
184  raise RuntimeError(*e.args)
185 
186  return self.descr.__get__( obj, type )
187 
188 
189  def __set__( self, obj, value ):
190  # allow a property to be set if we're in non-default mode, or if it
191  # simply hasn't been set before
192  if not obj._isInSetDefaults() or not obj in self.history:
193  value = self.convertValueToBeSet( obj, value )
194  # assign the value
195  self.descr.__set__( obj, value )
196  log.debug( "Setting %s = %r", self.fullPropertyName( obj ), value )
197  self.history.setdefault( obj, [] ).append( value )
198 
199 
200 
201  def isHandle(self,value):
202  """Check if <value> is a handle of the correct type"""
203  return isinstance(value,self._handleType)
204 
205 
206  def isConfig(self,value):
207  """Check if <value> is a configurable of the correct type"""
208  return derives_from(value,self._confTypeName)
209 
210 
211  def getDefaultConfigurable(self,typeAndName,requester):
212  """Return the configurable instance corresponding to the toolhandle if possible.
213  Otherwise return None"""
214  global log
215  # find the module
216  typeAndNameTuple = typeAndName.split('/')
217  confType = typeAndNameTuple[0]
218  confClass=ConfigurableDb.getConfigurable(confType)
219  # check the type of the configurable
220  if not derives_from(confClass,self._confTypeName):
221  log.error( "%s: Configurable %s is not a %s",
222  requester, confType, self._confTypeName )
223  return None
224  try:
225  confName = typeAndNameTuple[1]
226  except IndexError:
227  return confClass() # use default name
228  else:
229  return confClass(confName)
230 
231 
232  def convertDefaultToBeSet( self, obj, default ):
233  # turn string into handle
234  isString = type(default) == str
235  if not isString and self.isConfig(default):
236 # print self.fullPropertyName(obj) + ": Setting default configurable: %r" % default
237  return default
238  elif isString or self.isHandle(default):
239  if isString:
240  # convert string into handle
241  typeAndName = default
242  default = self._handleType( typeAndName )
243  else:
244  typeAndName = default.typeAndName
245  if not self._handleType.isPublic:
246  if not typeAndName:
247  return None
248  # Find corresponding default configurable of private handles
249  try:
250  conf = self.getDefaultConfigurable(typeAndName, self.fullPropertyName(obj))
251 # print self.fullPropertyName(obj) + ": Setting default private configurable (from default handle): %r" % conf
252  except AttributeError,e:
253  # change type of exception to avoid false error message
254  raise RuntimeError(*e.args)
255  if conf is None:
256  raise RuntimeError( "%s: Default configurable for class %s not found in ConfigurableDb.CfgDb" % \
257  (self.fullPropertyName(obj),default.getType() ) )
258  return conf
259  else: # not a config, not a handle, not a string
260  raise TypeError( "%s: default value %r is not of type %s or %s" % \
261  (self.fullPropertyName(obj),default,self._confTypeName,self._handleType.__name__) )
262 
263  return default
264 
265  def convertValueToBeSet( self, obj, value ):
266  if value is None: value = ''
267  isString = type(value) == str
268  if isString:
269  # create an new handle
270  return self._handleType(value)
271  elif self.isHandle(value):
272  # make a copy of the handle
273  return self._handleType(value.toStringProperty())
274  elif self.isConfig(value):
275  if self._handleType.isPublic:
276  # A public tool must be registered to ToolSvc before assigning it
277  if derives_from(value,'ConfigurableAlgTool'):
278  if not value.isInToolSvc():
279  suggestion = 'You may need to add jobOptions lines something like:' + os.linesep + \
280  'from AthenaCommon.AppMgr import ToolSvc' + os.linesep + \
281  'ToolSvc += '
282  if value.getName() == value.getType(): # using default name
283  suggestion += '%s()' % value.__class__.__name__
284  else: # using user-defined name
285  suggestion += '%s(%r)' % (value.__class__.__name__,value.getName())
286  raise RuntimeError( self.fullPropertyName(obj) +
287  ': Public tool %s is not yet in ToolSvc. %s' %
288  (value.getJobOptName(),suggestion) )
289  # make it read-only
290  return self._handleType(value.toStringProperty())
291  elif value.hasParent( obj.getJobOptName() ):
292  # is already a child, keep as-is
293  return value
294  else:
295  # make a copy of the configurable
296  value = obj.copyChildAndSetParent( value, obj.getJobOptName() )
297  # ensure that the new object is in allConfigurables
298  obj.allConfigurables[value.name()] = value
299  return value
300  else:
301  raise TypeError( "Property %s value %r is not a %s nor a %s nor a string" % \
302  (self.fullPropertyName(obj),value,self._confTypeName,self._handleType.__name__) )
303 
304  return value
305 
306 
308  def __init__( self, descr, docString, default ):
309  GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default), GaudiHandle )
310 
311 
313  def __init__( self, descr, docString, default ):
314  """<descr>: the real property in the object instance (from __slots__)
315  <confTypeName>: string indicating the (base) class of allowed Configurables to be assigned.
316  <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
317  """
318  GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default).handleType, GaudiHandleArray )
319  self.arrayType = type(default)
320 
321 
322  def checkType( self, obj, value ):
323  if not isinstance( value, list ) and not isinstance( value, self.arrayType ):
324  raise TypeError( "%s: Value %r is not a list nor a %s" % \
325  ( self.fullPropertyName(obj), value, self.arrayType.__name__ ) )
326 
327 
328  def convertDefaultToBeSet( self, obj, default ):
329  self.checkType( obj, default )
330  newDefault = self.arrayType()
331  for d in default:
332  cd = GaudiHandlePropertyProxyBase.convertDefaultToBeSet( self, obj, d )
333  if cd: newDefault.append( cd )
334 
335  return newDefault
336 
337 
338  def convertValueToBeSet( self, obj, value ):
339  self.checkType( obj, value )
340  newValue = self.arrayType()
341  for v in value:
342  cv = GaudiHandlePropertyProxyBase.convertValueToBeSet( self, obj, v )
343  if cv: newValue.append( cv )
344 
345  return newValue
346 
348 
349  def __init__(self, descr, docString, default):
350  PropertyProxy.__init__( self, descr, docString, default )
351 
352  def __get__(self, obj, type=None):
353  try:
354  return self.descr.__get__( obj, type )
355  except AttributeError:
356  # Get default
357  try:
358  default = obj.__class__.getDefaultProperty( self.descr.__name__ )
359  default = self.convertValueToBeSet( obj, default )
360  if default:
361  self.__set__( obj, default )
362  except AttributeError,e:
363  # change type of exception to avoid false error message
364  raise RuntimeError(*e.args)
365 
366  return self.descr.__get__( obj, type )
367 
368  def __set__(self, obj, value):
369  if not obj._isInSetDefaults() or not obj in self.history:
370  value = self.convertValueToBeSet( obj, value )
371  # assign the value
372  self.descr.__set__( obj, value )
373  log.debug( "Setting %s = %r", self.fullPropertyName( obj ), value )
374  self.history.setdefault( obj, [] ).append( value)
375 
376  def convertValueToBeSet(self, obj, value):
377  if value is None: value = ''
378 
379  if type(value) == str:
380  return DataObjectHandleBase(value)
381  elif isinstance(value, DataObjectHandleBase):
382  return DataObjectHandleBase(value.__str__())
383 
384 
385 def PropertyProxyFactory( descr, doc, default ):
386 # print "PropertyProxyFactory( %s, %r )" % (descr.__name__,default)
387 
388  if isinstance(default,GaudiHandleArray):
389  return GaudiHandleArrayPropertyProxy( descr, doc, default )
390 
391  if isinstance(default,GaudiHandle):
392  return GaudiHandlePropertyProxy( descr, doc, default )
393 
394  if isinstance(default,DataObjectHandleBase):
395  return DataObjectHandleBasePropertyProxy( descr, doc, default )
396 
397  return PropertyProxy( descr, doc, default )
def getDefaultConfigurable(self, typeAndName, requester)
def __get__(self, obj, type=None)
Gaudi::Details::PropertyBase * property(const std::string &name) const
def __init__(self, descr, docString=None, default=None)
def __init__(self, descr, docString, default)
def __init__(self, descr, docString, default, handleType, allowedType)
def __init__(self, descr, docString, default)
def derives_from(derived, base)
def PropertyProxyFactory(descr, doc, default)
def _isCompatible(tp, value)