ATLAS Offline Software
PropertyProxy.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2 
3 # File: AthenaCommon/python/PropertyProxy.py
4 # Author: Wim Lavrijsen (WLavrijsen@lbl.gov)
5 # Author: Martin Woudstra (Martin.Woudstra@cern.ch)
6 
7 import os, weakref, copy
8 from GaudiKernel.GaudiHandles import GaudiHandle, GaudiHandleArray
9 from GaudiKernel.DataHandle import DataHandle
10 
11 # dictionary with configurable class : python module entries
12 from AthenaCommon import ConfigurableDb
13 
14 
15 __version__ = '2.0.0'
16 __author__ = 'Wim Lavrijsen (WLavrijsen@lbl.gov), Martin Woudstra (Martin.Woudstra@cern.ch)'
17 
18 __all__ = [ 'PropertyProxy',
19  'GaudiHandlePropertyProxy',
20  'GaudiHandleArrayPropertyProxy' ]
21 
22 
23 from AthenaCommon.Logging import logging
24 log = logging.getLogger( 'PropertyProxy' )
25 
26 
27 def derives_from( derived, base ):
28  """A string version of isinstance().
29  <derived> is either an object instance, or a type
30  <base> is a string containing the name of the base class (or <derived> class)"""
31  if not isinstance( derived, type ):
32  derived = type(derived)
33 
34  if derived.__name__ == base:
35  return True
36 
37  for b in derived.__bases__:
38  if derives_from( b,base ):
39  return True
40 
41  return False
42 
43 
44 def _isCompatible( tp, value, context = "" ):
45  # Note: this function is only called for builtin types. Lists of
46  # configurables (most likely GaudiHandleArrays) don't get here, since
47  # their PropertyProxy types are special cases (see below). Thus, a
48  # compatibility check that relies on conversion (which will always fail
49  # for configurables) is acceptable.
50 
51  if ( tp == str or type(value) is str ) and not isinstance( value, tp ):
52  # special case, insist on exact match for str (no conversions allowed)
53  raise ValueError( "received an instance of %s, but %s expected, context: %s" % (type(value),tp, context) )
54  elif ( tp == int ) and type(value) is float:
55  # special case, insist on strict match for integer types
56  raise ValueError( "received an instance of %s, but %s expected, context: %s" % (type(value),tp, context) )
57  else:
58  # all other types: accept if conversion allowed
59  try:
60  dummy = tp( value )
61  except ValueError as verr:
62  if isinstance( value, tp ):
63  # this is a conversion mismatch, but an "ok" type (i.e. the type can
64  # not be copied/constructed from itself), which is a typical case
65  # for configurables being added to an ordinary list (as opposed to
66  # a GaudiHandleArray), where the actual type of the list is unknown
67  raise ValueError( "received non-convertible type %s, but builtin type expected, reason: %s context: %s" % (type(value), str(verr), context) )
68  else:
69  # this is a complete miss and the error message will be clear
70  raise ValueError( "received an instance of %s, but %s expected, reason: %s, context: %s" % (type(value),tp, str(verr), context) )
71  except TypeError as terr:
72  raise TypeError( "received an instance of %s, but %s expected, reason: %s, context: %s" % (type(value),tp, str(terr), context) )
73  return dummy # in case of e.g. classes with __int__, __iter__, etc. implemented
74 
75 
77  def __init__( self, descr, docString=None, default=None ):
78  self.history = weakref.WeakKeyDictionary()
79  self.descr = descr
80  if docString:
81  self.__doc__ = docString
82  if default is not None:
83  self.default = default
84 
85  def setDefault( self, value ):
86  self.__default = value
87 
88  def getDefault( self ):
89  return self.__default
90 
91  default = property( getDefault, setDefault )
92 
93  def fullPropertyName( self, obj ):
94  return (obj.getJobOptName() or obj.getName()) + '.' + self.descr.__name__
95 
96  def __get__( self, obj, type = None ):
97  value = None
98  try:
99  value = self.descr.__get__( obj, type )
100  except AttributeError:
101  # special case for list and set: allow default to work with on +=;
102  # unfortunately, that means that direct access also succeeds, even
103  # as the property wasn't set yet ... (TODO: ideas??)
104  if self.__default.__class__ == list or self.__default.__class__ == set:
105  self.descr.__set__( obj, self.__default.__class__(self.__default) )
106  value = self.descr.__get__( obj, type ) # no history
107  else:
108  raise
109 
110  if value is None:
111  value = self.descr.__get__( obj, type )
112 
113  # locked configurables return copies to prevent modifications
114  if obj.isLocked():
115  import builtins
116  if builtins.type( value ) is dict:
117  from ctypes import pythonapi, py_object
118  from _ctypes import PyObj_FromPtr
119  PyDictProxy_New = pythonapi.PyDictProxy_New
120  PyDictProxy_New.argtypes = (py_object,)
121  PyDictProxy_New.rettype = py_object
122  value = PyObj_FromPtr( PyDictProxy_New( value ) )
123  elif builtins.type( value ) is list:
124  value = tuple( value ) # point being that tuple is read-only
125  else:
126  import copy
127  value = copy.copy( value )
128 
129  return value
130 
131  def __set__( self, obj, value ):
132  # locked configurables can no longer be changed
133  if obj.isLocked():
134  raise RuntimeError(
135  'can not change property "%s" of locked configurable "%s"' %
136  (self.descr.__name__, obj.getJobOptName()) )
137 
138  # check value/property compatibility if possible
139  proptype = None
140  if hasattr( self, 'default' ):
141  proptype = type(self.default)
142  elif obj in self.history:
143  proptype = type( self.history[ obj ][ 0 ] )
144 
145  # check if type known; allow special initializer for typed instances
146  if proptype and not isinstance(proptype, type(None)):
147  # check value itself
148  value = _isCompatible( proptype, value, self.fullPropertyName(obj) )
149 
150  # check elements in case of list
151  if proptype == tuple or proptype == list:
152  try:
153  oldvec = self.descr.__get__( obj, type )
154  if oldvec:
155  tpo = type(oldvec[0])
156  for v in value:
157  _isCompatible( tpo, v, self.fullPropertyName(obj) )
158  except AttributeError:
159  # value not yet set
160  pass
161 
162  # allow a property to be set if we're in non-default mode, or if it
163  # simply hasn't been set before
164  if not obj._isInSetDefaults() or obj not in self.history:
165  # by convention, 'None' for default is used to designate objects setting
166  if hasattr( self, 'default' ) and self.default is None:
167  obj.__iadd__( value, self.descr ) # to establish hierarchy
168  else:
169  self.descr.__set__( obj, value )
170  self.history.setdefault( obj, [] ).append( value )
171 
172  def __delete__( self, obj ):
173  if obj in self.history:
174  del self.history[ obj ]
175  self.descr.__delete__( obj )
176 
177 
178 
180  """A class with some utilities for GaudiHandles and GaudiHandleArrays"""
181 
182  def __init__(self, descr, docString, default, handleType, allowedType ):
183  """<descr>: the real property in the object instance (from __slots__)
184  <docString>: the documentation string of this property
185  <default>: default value from C++ (via python generated by genconf)
186  <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
187  <allowedType>: allowed instance type for default
188  """
189  # check that default is of allowed type for this proxy
190  if not isinstance(default,allowedType):
191  raise TypeError( "%s: %s default: %r is not a %s" % \
192  ( descr.__name__, self.__class__.__name__, default, allowedType.__name__ ) )
193  PropertyProxy.__init__( self, descr, docString, default )
194  self._handleType = handleType
195  self._confTypeName = 'Configurable' + handleType.componentType
196 # print "%s: %r (%s)" % (self.__class__.__name__,self._handleType,self._confTypeName)
197 
198 
199  def __get__( self, obj, type = None ):
200  try:
201  return self.descr.__get__( obj, type )
202  except AttributeError:
203  # Get default
204  try:
205  default = obj.__class__.getDefaultProperty( self.descr.__name__ )
206  # if printing, just show the toolhandle. No need to auto-retrieve default configurable instance
207  if obj.isPrinting(): return default
208  default = self.convertDefaultToBeSet( obj, default )
209  # special case if default is auto-retrieved configurable instance
210  if self.isConfig(default):
211  if obj.isLocked():
212  # return a locked copy to produce error in case of setting any property
213  # rather then changing the value of the default
214  default = copy.deepcopy(default)
215  default.lock()
216  return default
217  else:
218  # Set to default configurable instance to allow MyTool.MySubTool.MyProperty = MyValue
219  self.__set__( obj, default )
220  elif isinstance(default,GaudiHandleArray):
221  if obj.isLocked():
222  # return a locked copy to produce error in case of setting any property
223  # rather then changing the value of the default
224  default = copy.deepcopy(default)
225  for c in default:
226  if self.isConfig(c):
227  c.lock()
228  return default
229  else:
230  # Set array to allow to add to default with syntax: MyTool.MyArray.append()
231  self.__set__( obj, default )
232  else:
233  return default
234  except AttributeError as e:
235  import traceback
236  traceback.print_exc()
237  # change type of exception to avoid false error message
238  raise RuntimeError("AttributeError(%s)" % e.args)
239 
240  return self.descr.__get__( obj, type )
241 
242 
243  def __set__( self, obj, value ):
244  # locked configurables can no longer be changed
245  if obj.isLocked():
246  raise RuntimeError(
247  'can not change property "%s" of locked configurable "%s"' %
248  (self.descr.__name__, obj.getJobOptName()) )
249 
250  # allow a property to be set if we're in non-default mode, or if it
251  # simply hasn't been set before
252  if not obj._isInSetDefaults() or obj not in self.history:
253  value = self.convertValueToBeSet( obj, value )
254  # assign the value
255  self.descr.__set__( obj, value )
256  log.verbose( "Setting %s = %r", self.fullPropertyName( obj ), value )
257  self.history.setdefault( obj, [] ).append( value )
258 
259 
260  def isHandle(self,value):
261  """Check if <value> is a handle of the correct type"""
262  return isinstance(value,self._handleType)
263 
264 
265  def isConfig(self,value):
266  """Check if <value> is a configurable of the correct type"""
267  return derives_from(value,self._confTypeName)
268 
269 
270  def getDefaultConfigurable(self,typeAndName,requester):
271  """Return the configurable instance corresponding to the toolhandle if possible.
272  Otherwise return None"""
273  global log
274  # find the module
275  typeAndNameTuple = typeAndName.split('/')
276  confType = typeAndNameTuple[0]
277  confClass = ConfigurableDb.getConfigurable(confType)
278  # check the type of the configurable
279  if not derives_from(confClass,self._confTypeName):
280  log.error( "%s: Configurable %s is not a %s",
281  requester, confType, self._confTypeName )
282  return None
283  try:
284  confName = typeAndNameTuple[1]
285  except IndexError:
286  return confClass() # use default name
287  else:
288  return confClass(confName)
289 
290 
291  def convertDefaultToBeSet( self, obj, default ):
292  # turn string into handle
293  isString = type(default) is str
294  if not isString and self.isConfig(default):
295 # print self.fullPropertyName(obj) + ": Setting default configurable: %r" % default
296  return default
297  elif isString or self.isHandle(default):
298  if isString:
299  # convert string into handle
300  typeAndName = default
301  default = self._handleType( typeAndName )
302  else:
303  typeAndName = default.typeAndName
304  if not self._handleType.isPublic and typeAndName:
305  # Find corresponding default configurable of private handles
306  try:
307  conf = self.getDefaultConfigurable(typeAndName, self.fullPropertyName(obj))
308 # print self.fullPropertyName(obj) + ": Setting default private configurable (from default handle): %r" % conf
309  except AttributeError as e:
310  # change type of exception to avoid false error message
311  raise RuntimeError(*e.args)
312  if conf is None:
313  raise RuntimeError( "%s: Default configurable for class %s not found in ConfigurableDb.CfgDb" % \
314  (self.fullPropertyName(obj),default.getType() ) )
315  return conf
316  else: # not a config, not a handle, not a string
317  raise TypeError( "%s: default value %r is not of type %s or %s" % \
318  (self.fullPropertyName(obj),default,self._confTypeName,self._handleType.__name__) )
319 
320  return default
321 
322  def convertValueToBeSet( self, obj, value ):
323  if value is None: value = ''
324  isString = type(value) is str
325  if isString:
326  # create an new handle
327  return self._handleType(value)
328  elif self.isHandle(value):
329  # make a copy of the handle
330  return self._handleType(value.toStringProperty())
331  elif self.isConfig(value):
332  if self._handleType.isPublic:
333  # A public tool must be registered to ToolSvc before assigning it
334  if derives_from(value,'ConfigurableAlgTool'):
335  if not value.isInToolSvc():
336  suggestion = 'You may need to add jobOptions lines something like:' + os.linesep + \
337  'from AthenaCommon.AppMgr import ToolSvc' + os.linesep + \
338  'ToolSvc += '
339  if value.getName() == value.getType(): # using default name
340  suggestion += '%s()' % value.__class__.__name__
341  else: # using user-defined name
342  suggestion += '%s(%r)' % (value.__class__.__name__,value.getName())
343  raise RuntimeError( self.fullPropertyName(obj) +
344  ': Public tool %s is not yet in ToolSvc. %s' %
345  (value.getJobOptName(),suggestion) )
346  # make it read-only
347  return self._handleType(value.toStringProperty())
348  elif value.hasParent( obj.getJobOptName() ):
349  # is already a child, keep as-is
350  return value
351  else:
352  # make a copy of the configurable
353  return obj.copyChildAndSetParent( value, obj.getJobOptName() )
354  else:
355  raise TypeError( "Property %s value %r is not a %s nor a %s nor a string" % \
356  (self.fullPropertyName(obj),value,self._confTypeName,self._handleType.__name__) )
357 
358  return value
359 
360 
362  def __init__( self, descr, docString, default ):
363  GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default), GaudiHandle )
364 
365 
367  def __init__( self, descr, docString, default ):
368  """<descr>: the real property in the object instance (from __slots__)
369  <confTypeName>: string indicating the (base) class of allowed Configurables to be assigned.
370  <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
371  """
372  GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default).handleType, GaudiHandleArray )
373  self.arrayType = type(default)
374 
375 
376  def checkType( self, obj, value ):
377  if not isinstance( value, list ) and not isinstance( value, self.arrayType ):
378  raise TypeError( "%s: Value %r is not a list nor a %s" % \
379  ( self.fullPropertyName(obj), value, self.arrayType.__name__ ) )
380 
381 
382  def convertDefaultToBeSet( self, obj, default ):
383  self.checkType( obj, default )
384  newDefault = self.arrayType()
385  for d in default:
386  cd = GaudiHandlePropertyProxyBase.convertDefaultToBeSet( self, obj, d )
387  if cd: newDefault.append( cd )
388 
389  return newDefault
390 
391 
392  def convertValueToBeSet( self, obj, value ):
393  self.checkType( obj, value )
394  newValue = self.arrayType()
395  for v in value:
396  cv = GaudiHandlePropertyProxyBase.convertValueToBeSet( self, obj, v )
397  if cv: newValue.append( cv )
398 
399  return newValue
400 
401 
403  def __init__(self, descr, docString, default):
404  PropertyProxy.__init__(self, descr, docString, default)
405 
406  def __get__(self, obj, type=None):
407  try:
408  return self.descr.__get__(obj, type)
409  except AttributeError:
410  # Get default
411  try:
412  default = obj.__class__.getDefaultProperty(self.descr.__name__)
413  default = self.convertValueToBeSet(obj, default)
414  if default:
415  self.__set__(obj, default)
416  except AttributeError as e:
417  # change type of exception to avoid false error message
418  raise RuntimeError(*e.args)
419 
420  return self.descr.__get__(obj, type)
421 
422  def __set__(self, obj, value):
423  if not obj._isInSetDefaults() or obj not in self.history:
424  value = self.convertValueToBeSet(obj, value)
425  # assign the value
426  self.descr.__set__(obj, value)
427  log.debug("Setting %s = %r", self.fullPropertyName(obj), value)
428  self.history.setdefault(obj, []).append(value)
429 
430  def convertValueToBeSet(self, obj, value):
431  if value is None:
432  value = ''
433 
434  mode = obj.__class__.getDefaultProperty(self.descr.__name__).mode()
435  _type = obj.__class__.getDefaultProperty(self.descr.__name__).type()
436  if type(value) is str:
437  return DataHandle(value, mode, _type)
438  elif isinstance(value, DataHandle):
439  return DataHandle(value.__str__(), mode, _type)
440  else:
441  raise ValueError("received an instance of %s, but %s expected" %
442  (type(value), 'str or DataHandle'))
443 
444 
445 def PropertyProxyFactory( descr, doc, default ):
446 # print "PropertyProxyFactory( %s, %r )" % (descr.__name__,default)
447  if isinstance(default,GaudiHandleArray):
448  return GaudiHandleArrayPropertyProxy( descr, doc, default )
449 
450  if isinstance(default,GaudiHandle):
451  return GaudiHandlePropertyProxy( descr, doc, default )
452 
453  if isinstance(default,DataHandle):
454  return DataHandlePropertyProxy( descr, doc, default )
455 
456  return PropertyProxy( descr, doc, default )
python.PropertyProxy.DataHandlePropertyProxy.convertValueToBeSet
def convertValueToBeSet(self, obj, value)
Definition: PropertyProxy.py:430
python.PropertyProxy.derives_from
def derives_from(derived, base)
Definition: PropertyProxy.py:27
python.PropertyProxy.GaudiHandlePropertyProxyBase.isHandle
def isHandle(self, value)
Definition: PropertyProxy.py:260
python.PropertyProxy.PropertyProxy.__get__
def __get__(self, obj, type=None)
Definition: PropertyProxy.py:96
python.PropertyProxy.GaudiHandlePropertyProxyBase.__get__
def __get__(self, obj, type=None)
Definition: PropertyProxy.py:199
python.PropertyProxy.PropertyProxy.getDefault
def getDefault(self)
Definition: PropertyProxy.py:88
python.PropertyProxy.GaudiHandleArrayPropertyProxy.__init__
def __init__(self, descr, docString, default)
Definition: PropertyProxy.py:367
python.PropertyProxy.GaudiHandlePropertyProxy
Definition: PropertyProxy.py:361
python.PropertyProxy.GaudiHandleArrayPropertyProxy
Definition: PropertyProxy.py:366
python.PropertyProxy.GaudiHandlePropertyProxyBase._handleType
_handleType
Definition: PropertyProxy.py:194
python.PropertyProxy.GaudiHandleArrayPropertyProxy.checkType
def checkType(self, obj, value)
Definition: PropertyProxy.py:376
python.PropertyProxy.PropertyProxyFactory
def PropertyProxyFactory(descr, doc, default)
Definition: PropertyProxy.py:445
ParticleTest.tp
tp
Definition: ParticleTest.py:25
python.PropertyProxy.PropertyProxy.history
history
Definition: PropertyProxy.py:78
python.PropertyProxy.DataHandlePropertyProxy.__set__
def __set__(self, obj, value)
Definition: PropertyProxy.py:422
python.PropertyProxy.DataHandlePropertyProxy.__init__
def __init__(self, descr, docString, default)
Definition: PropertyProxy.py:403
python.PropertyProxy.PropertyProxy.setDefault
def setDefault(self, value)
Definition: PropertyProxy.py:85
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
python.PropertyProxy.PropertyProxy.fullPropertyName
def fullPropertyName(self, obj)
Definition: PropertyProxy.py:93
python.PropertyProxy.GaudiHandlePropertyProxyBase.isConfig
def isConfig(self, value)
Definition: PropertyProxy.py:265
python.PropertyProxy.DataHandlePropertyProxy
Definition: PropertyProxy.py:402
python.PropertyProxy.GaudiHandlePropertyProxyBase.getDefaultConfigurable
def getDefaultConfigurable(self, typeAndName, requester)
Definition: PropertyProxy.py:270
python.PropertyProxy.GaudiHandlePropertyProxyBase._confTypeName
_confTypeName
Definition: PropertyProxy.py:195
python.PropertyProxy.PropertyProxy.__set__
def __set__(self, obj, value)
Definition: PropertyProxy.py:131
python.PropertyProxy.GaudiHandlePropertyProxyBase.__init__
def __init__(self, descr, docString, default, handleType, allowedType)
Definition: PropertyProxy.py:182
python.PropertyProxy.PropertyProxy.__default
__default
Definition: PropertyProxy.py:86
python.PropertyProxy.PropertyProxy.__init__
def __init__(self, descr, docString=None, default=None)
Definition: PropertyProxy.py:77
Preparation.mode
mode
Definition: Preparation.py:95
python.PropertyProxy.GaudiHandleArrayPropertyProxy.arrayType
arrayType
Definition: PropertyProxy.py:373
python.PropertyProxy.DataHandlePropertyProxy.__get__
def __get__(self, obj, type=None)
Definition: PropertyProxy.py:406
python.PropertyProxy._isCompatible
def _isCompatible(tp, value, context="")
Definition: PropertyProxy.py:44
python.PropertyProxy.PropertyProxy
Definition: PropertyProxy.py:76
python.PropertyProxy.GaudiHandlePropertyProxyBase.convertDefaultToBeSet
def convertDefaultToBeSet(self, obj, default)
Definition: PropertyProxy.py:291
python.PropertyProxy.GaudiHandleArrayPropertyProxy.convertDefaultToBeSet
def convertDefaultToBeSet(self, obj, default)
Definition: PropertyProxy.py:382
python.PropertyProxy.PropertyProxy.__doc__
__doc__
Definition: PropertyProxy.py:81
python.CaloScaleNoiseConfig.type
type
Definition: CaloScaleNoiseConfig.py:78
pickleTool.object
object
Definition: pickleTool.py:30
str
Definition: BTagTrackIpAccessor.cxx:11
python.PropertyProxy.GaudiHandlePropertyProxyBase
Definition: PropertyProxy.py:179
python.PropertyProxy.PropertyProxy.descr
descr
Definition: PropertyProxy.py:79
python.PropertyProxy.GaudiHandleArrayPropertyProxy.convertValueToBeSet
def convertValueToBeSet(self, obj, value)
Definition: PropertyProxy.py:392
python.PropertyProxy.PropertyProxy.default
default
Definition: PropertyProxy.py:91
python.PropertyProxy.PropertyProxy.__delete__
def __delete__(self, obj)
Definition: PropertyProxy.py:172
python.PropertyProxy.GaudiHandlePropertyProxyBase.__set__
def __set__(self, obj, value)
Definition: PropertyProxy.py:243
python.PropertyProxy.GaudiHandlePropertyProxy.__init__
def __init__(self, descr, docString, default)
Definition: PropertyProxy.py:362
python.PropertyProxy.GaudiHandlePropertyProxyBase.convertValueToBeSet
def convertValueToBeSet(self, obj, value)
Definition: PropertyProxy.py:322