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