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