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