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