The Gaudi Framework  master (181af51f)
Loading...
Searching...
No Matches
PropertyProxy.py
Go to the documentation of this file.
14
15# data ---------------------------------------------------------------------
16__all__ = ["PropertyProxy", "GaudiHandlePropertyProxy", "GaudiHandleArrayPropertyProxy"]
17
18import logging
19import os
20
21from GaudiKernel import ConfigurableDb
22from GaudiKernel.DataHandle import DataHandle
23from GaudiKernel.GaudiHandles import GaudiHandle, GaudiHandleArray
24
25log = logging.getLogger("PropertyProxy")
26
27
28def 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
43def _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
82def _registerConfigurables(allConfigurables, c):
83 allConfigurables[c.name()] = c
84 for cc in c.getAllChildren():
85 _registerConfigurables(allConfigurables, cc)
86 return
87
88
89class 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
479def 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)
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition MsgStream.cpp:93
__init__(self, descr, docString, default)
__init__(self, descr, docString, default, handleType, allowedType)
__init__(self, descr, docString=None, default=None)
_registerConfigurables(allConfigurables, c)
PropertyProxyFactory(descr, doc, default)
_isCompatible(tp, value, name)
derives_from(derived, base)