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