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