The Gaudi Framework  master (9f3f8895)
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, DataHandleVector
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
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 f"inconsistent value types for {obj.getName()}.{self.descr.__name__} ({e})"
172 )
173 else:
174 raise
175
176 # allow a property to be set if we're in non-default mode, or if it
177 # simply hasn't been set before
178 if not obj._isInSetDefaults() or obj not in self.history:
179 # by convention, 'None' for default is used to designate objects setting
180 if hasattr(self, "default") and self.default is None:
181 obj.__iadd__(value, self.descr) # to establish hierarchy
182 else:
183 self.descr.__set__(obj, value)
184 self.history.setdefault(obj, []).append(value)
185
186 def __delete__(self, obj):
187 if obj in self.history:
188 del self.history[obj]
189 self.descr.__delete__(obj)
190
191
193 """A class with some utilities for GaudiHandles and GaudiHandleArrays"""
194
195 def __init__(self, descr, docString, default, handleType, allowedType):
196 """<descr>: the real property in the object instance (from __slots__)
197 <docString>: the documentation string of this property
198 <default>: default value from C++ (via python generated by genconf)
199 <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
200 <allowedType>: allowed instance type for default
201 """
202 # check that default is of allowed type for this proxy
203 if not isinstance(default, allowedType):
204 raise TypeError(
205 f"{descr.__name__}: {self.__class__.__name__} default: {default!r} is not a {allowedType.__name__}"
206 )
207 PropertyProxy.__init__(self, descr, docString, default)
208 self._handleType = handleType
209 self._confTypeName = "Configurable" + handleType.componentType
210
211 def __get__(self, obj, type=None):
212 try:
213 return self.descr.__get__(obj, type)
214 except AttributeError:
215 # Get default
216 try:
217 default = obj.__class__.getDefaultProperty(self.descr.__name__)
218 default = self.convertDefaultToBeSet(obj, default)
219 if default is not None:
220 self.__set__(obj, default)
221
222 except AttributeError as e:
223 # change type of exception to avoid false error message
224 raise RuntimeError(*e.args)
225
226 return self.descr.__get__(obj, type)
227
228 def __set__(self, obj, value):
229 # allow a property to be set if we're in non-default mode, or if it
230 # simply hasn't been set before
231 if not obj._isInSetDefaults() or obj not in self.history:
232 value = self.convertValueToBeSet(obj, value)
233 # assign the value
234 self.descr.__set__(obj, value)
235 log.debug("Setting %s = %r", self.fullPropertyName(obj), value)
236 self.history.setdefault(obj, []).append(value)
237
238 def isHandle(self, value):
239 """Check if <value> is a handle of the correct type"""
240 return isinstance(value, self._handleType)
241
242 def isConfig(self, value):
243 """Check if <value> is a configurable of the correct type"""
244 return derives_from(value, self._confTypeName)
245
246 def getDefaultConfigurable(self, typeAndName, requester):
247 """Return the configurable instance corresponding to the toolhandle if possible.
248 Otherwise return None"""
249 global log
250 # find the module
251 typeAndNameTuple = typeAndName.split("/")
252 confType = typeAndNameTuple[0]
253 confClass = ConfigurableDb.getConfigurable(confType)
254 # check the type of the configurable
255 if not derives_from(confClass, self._confTypeName):
256 log.error(
257 "%s: Configurable %s is not a %s",
258 requester,
259 confType,
260 self._confTypeName,
261 )
262 return None
263 try:
264 confName = typeAndNameTuple[1]
265 except IndexError:
266 return confClass() # use default name
267 else:
268 return confClass(confName)
269
270 def convertDefaultToBeSet(self, obj, default):
271 # turn string into handle
272 isString = isinstance(default, str)
273 if not isString and self.isConfig(default):
274 # print self.fullPropertyName(obj) + ": Setting default configurable: %r" % default
275 return default
276 elif isString or self.isHandle(default):
277 if isString:
278 # convert string into handle
279 typeAndName = default
280 default = self._handleType(typeAndName)
281 else:
282 typeAndName = default.typeAndName
283 if not self._handleType.isPublic:
284 if not typeAndName:
285 return None
286 # Find corresponding default configurable of private handles
287 # (make sure the name used to instantiate the private tool
288 # includes the name of the owner, see https://gitlab.cern.ch/gaudi/Gaudi/-/issues/141)
289 if "/" in typeAndName:
290 typeAndName = typeAndName.replace("/", f"/{obj.name()}.", 1)
291 else:
292 typeAndName = f"{typeAndName}/{obj.name()}.{typeAndName}"
293 try:
294 conf = self.getDefaultConfigurable(
295 typeAndName, self.fullPropertyName(obj)
296 )
297
298 # print self.fullPropertyName(obj) + ": Setting default private configurable (from default handle): %r" % conf
299 except AttributeError as e:
300 # change type of exception to avoid false error message
301 raise RuntimeError(*e.args)
302 if conf is None:
303 raise RuntimeError(
304 f"{self.fullPropertyName(obj)}: Default configurable for class {default.getType()} not found in ConfigurableDb.CfgDb"
305 )
306 return conf
307 else: # not a config, not a handle, not a string
308 raise TypeError(
309 f"{self.fullPropertyName(obj)}: default value {default!r} is not of type {self._confTypeName} or {self._handleType.__name__}"
310 )
311
312 return default
313
314 def convertValueToBeSet(self, obj, value):
315 if value is None:
316 value = ""
317 isString = isinstance(value, str)
318 if isString:
319 # create an new handle
320 return self._handleType(value)
321 elif self.isHandle(value):
322 # make a copy of the handle
323 return self._handleType(value.toStringProperty())
324 elif self.isConfig(value):
325 if self._handleType.isPublic:
326 # A public tool must be registered to ToolSvc before assigning it
327 if derives_from(value, "ConfigurableAlgTool"):
328 if not value.isInToolSvc():
329 suggestion = (
330 "You may need to add jobOptions lines something like:"
331 + os.linesep
332 + "from AthenaCommon.AppMgr import ToolSvc"
333 + os.linesep
334 + "ToolSvc += "
335 )
336 if value.getName() == value.getType(): # using default name
337 suggestion += f"{value.__class__.__name__}()"
338 else: # using user-defined name
339 suggestion += (
340 f"{value.__class__.__name__}({value.getName()!r})"
341 )
342 raise RuntimeError(
343 self.fullPropertyName(obj)
344 + f": Public tool {value.getJobOptName()} is not yet in ToolSvc. {suggestion}"
345 )
346 # make it read-only
347 return self._handleType(value.toStringProperty())
348 elif value.hasParent(obj.getJobOptName()):
349 # is already a child, keep as-is
350 return value
351 else:
352 # make a copy of the configurable
353 value = obj.copyChildAndSetParent(value, obj.getJobOptName())
354 # ensure that the new object (as well as all its children)
355 # are in allConfigurables
356 _registerConfigurables(obj.allConfigurables, value)
357 return value
358 else:
359 raise TypeError(
360 f"Property {self.fullPropertyName(obj)} value {value!r} is not a {self._confTypeName} nor a {self._handleType.__name__} nor a string"
361 )
362
363 return value
364
365
367 def __init__(self, descr, docString, default):
368 GaudiHandlePropertyProxyBase.__init__(
369 self, descr, docString, default, type(default), GaudiHandle
370 )
371
372
374 def __init__(self, descr, docString, default):
375 """<descr>: the real property in the object instance (from __slots__)
376 <confTypeName>: string indicating the (base) class of allowed Configurables to be assigned.
377 <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
378 """
379 GaudiHandlePropertyProxyBase.__init__(
380 self, descr, docString, default, type(default).handleType, GaudiHandleArray
381 )
382 self.arrayType = type(default)
383
384 def checkType(self, obj, value):
385 if not isinstance(value, list) and not isinstance(value, self.arrayType):
386 raise TypeError(
387 f"{self.fullPropertyName(obj)}: Value {value!r} is not a list nor a {self.arrayType.__name__}"
388 )
389
390 def convertDefaultToBeSet(self, obj, default):
391 self.checkType(obj, default)
392 newDefault = self.arrayType()
393 for d in default:
394 cd = GaudiHandlePropertyProxyBase.convertDefaultToBeSet(self, obj, d)
395 if cd:
396 newDefault.append(cd)
397
398 return newDefault
399
400 def convertValueToBeSet(self, obj, value):
401 self.checkType(obj, value)
402 newValue = self.arrayType()
403 for v in value:
404 cv = GaudiHandlePropertyProxyBase.convertValueToBeSet(self, obj, v)
405 if cv:
406 newValue.append(cv)
407
408 return newValue
409
410
412 def __init__(self, descr, docString, default):
413 PropertyProxy.__init__(self, descr, docString, default)
414
415 def __get__(self, obj, type=None):
416 try:
417 return self.descr.__get__(obj, type)
418 except AttributeError:
419 # Get default
420 try:
421 default = obj.__class__.getDefaultProperty(self.descr.__name__)
422 default = self.convertValueToBeSet(obj, default)
423 if default is not None:
424 self.__set__(obj, default)
425 except AttributeError as e:
426 # change type of exception to avoid false error message
427 raise RuntimeError(*e.args)
428
429 return self.descr.__get__(obj, type)
430
431 def __set__(self, obj, value):
432 if not obj._isInSetDefaults() or obj not in self.history:
433 value = self.convertValueToBeSet(obj, value)
434 # assign the value
435 self.descr.__set__(obj, value)
436 log.debug("Setting %s = %r", self.fullPropertyName(obj), value)
437 self.history.setdefault(obj, []).append(value)
438
439 def convertValueToBeSet(self, obj, value):
440 if value is None:
441 value = ""
442
443 mode = obj.__class__.getDefaultProperty(self.descr.__name__).mode()
444 _type = obj.__class__.getDefaultProperty(self.descr.__name__).type()
445 if isinstance(value, str):
446 return DataHandle(value, mode, _type)
447 elif isinstance(value, DataHandle):
448 return DataHandle(value.__str__(), mode, _type)
449 else:
450 raise ValueError(
451 "received an instance of {}, but {} expected".format(
452 type(value), "str or DataHandle"
453 )
454 )
455
456
458 def __init__(self, descr, docString, default):
459 DataHandlePropertyProxy.__init__(self, descr, docString, default)
460
461 def convertValueToBeSet(self, obj, value):
462 if value is None:
463 value = []
464
465 default = obj.__class__.getDefaultProperty(self.descr.__name__)
466 mode = default.mode()
467 _type = default.type()
468 if isinstance(value, DataHandleVector):
469 return DataHandleVector(value.paths(), mode, _type)
470 if isinstance(value, (list, tuple)):
471 paths = []
472 for path in value:
473 if isinstance(path, DataHandle):
474 path = str(path)
475 if not isinstance(path, str):
476 raise ValueError(
477 f"received an instance of {type(path)}, but str or DataHandle expected"
478 )
479 paths.append(path)
480 return DataHandleVector(paths, mode, _type)
481 raise ValueError(
482 "received an instance of {}, but {} expected".format(
483 type(value), "list, tuple or DataHandleVector"
484 )
485 )
486
487
488def PropertyProxyFactory(descr, doc, default):
489 # print "PropertyProxyFactory( %s, %r )" % (descr.__name__,default)
490
491 if isinstance(default, GaudiHandleArray):
492 return GaudiHandleArrayPropertyProxy(descr, doc, default)
493
494 if isinstance(default, GaudiHandle):
495 return GaudiHandlePropertyProxy(descr, doc, default)
496
497 if isinstance(default, DataHandle):
498 return DataHandlePropertyProxy(descr, doc, default)
499
500 if isinstance(default, DataHandleVector):
501 return DataHandleVectorPropertyProxy(descr, doc, default)
502
503 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)
_isCompatible(tp, value, name)
_registerConfigurables(allConfigurables, c)
PropertyProxyFactory(descr, doc, default)
derives_from(derived, base)