The Gaudi Framework  master (9f3f8895)
Loading...
Searching...
No Matches
Configurable.py
Go to the documentation of this file.
14
15import copy
16import os
17import sys
18import types
19from inspect import isclass
20
21from GaudiConfig.ControlFlow import (
22 AndNode,
23 CFFalse,
24 CFTrue,
25 ControlFlowLeaf,
26 ControlFlowNode,
27 InvertNode,
28 OrderedNode,
29 OrNode,
30 ignore,
31)
32
33import GaudiKernel.ConfigurableMeta as ConfigurableMeta
34from GaudiKernel.Constants import (
35 DEBUG,
36 ERROR,
37 FATAL,
38 INFO,
39 VERBOSE,
40 WARNING,
41 error_explanation,
42)
43from GaudiKernel.DataHandle import DataHandle, DataHandleVector
44from GaudiKernel.GaudiHandles import (
45 GaudiHandle,
46 GaudiHandleArray,
47 PrivateToolHandle,
48 PublicToolHandle,
49 ServiceHandle,
50)
51from GaudiKernel.PropertyProxy import PropertyProxy
52
53# data ---------------------------------------------------------------------
54__all__ = [
55 "Configurable",
56 "ConfigurableAlgorithm",
57 "ConfigurableAlgTool",
58 "ConfigurableAuditor",
59 "ConfigurableService",
60 "ConfigurableUser",
61 "VERBOSE",
62 "DEBUG",
63 "INFO",
64 "WARNING",
65 "ERROR",
66 "FATAL",
67 "appendPostConfigAction",
68 "removePostConfigAction",
69]
70
71
73 pass
74
75
76# for messaging
77import logging
78
79log = logging.getLogger("Configurable")
80
81
82def expandvars(data):
83 """
84 Expand environment variables "data".
85 Data can be string, list, tuple and dictionary. For collection, all the
86 contained strings will be manipulated (recursively).
87 """
88 import os.path
89
90 typ = type(data)
91 if typ is str:
92 return os.path.expandvars(data)
93 elif typ in [list, tuple]:
94 collect = []
95 for i in data:
96 collect.append(expandvars(i))
97 return typ(collect)
98 elif typ is dict:
99 collect = {}
100 for k in data:
101 collect[expandvars(k)] = expandvars(data[k])
102 return collect
103 return data
104
105
106class Error(RuntimeError):
107 """
108 Error occurred in the configuration process.
109 """
110
111 pass
112
113
114# Allow references to options as in old style
115
116
117class PropertyReference:
118 def __init__(self, propname):
119 self.name = propname
120
121 def __str__(self):
122 return f"@{self.name}"
123
124 def __resolve__(self):
125 # late binding for property references
126 retval = None
127 refname, refprop = self.name.rsplit(".", 1)
128 if refname in Configurable.allConfigurables:
129 conf = Configurable.allConfigurables[refname]
130 retval = getattr(conf, refprop)
131 if hasattr(retval, "getFullName"):
132 retval = retval.getFullName()
133 else:
134 raise NameError(f"name '{refname}' not found resolving '{self}'")
135 return retval
136
137 def getFullName(self):
138 """This function allow transparent integration with
139 Configurable.getValuedProperties.
140 """
141 try:
142 return self.__resolve__()
143 except NameError:
144 # ignore the error if we cannot resolve the name yet
145 return self
146 except AttributeError:
147 # ignore the error if we cannot resolve the attribute yet
148 return self
149
150
151# base class for configurable Gaudi algorithms/services/algtools/etc. ======
152
153
155 """Base class for Gaudi components that implement the IProperty interface.
156 Provides most of the boilerplate code, but the actual useful classes
157 are its derived ConfigurableAlgorithm, ConfigurableService, and
158 ConfigurableAlgTool."""
159
160 # for detecting the default name
162 pass
163
164 propertyNoValue = "<no value>"
165 indentUnit = "| "
166 printHeaderWidth = 100
167 printHeaderPre = 5
168
169 __slots__ = (
170 "__children", # controlled components, e.g. private AlgTools
171 "__tools", # private AlgTools (#PM-->)
172 "_name", # the (unqualified) component name
173 "_inSetDefaults", # currently setting default values
174 "_initok", # used to enforce base class init
175 "_setupok", # for debugging purposes (temporary)
176 "_unpickling", # flag for actions done during unpickling
177 )
178
179 allConfigurables = {} # just names would do, but currently refs to the actual
180 configurableServices = {} # just names would do, but currently refs to the actual
181 # configurables is needed for (temporary) backwards
182 # compatibility; will change in the future
183 _configurationLocked = False
184
185 def __new__(cls, *args, **kwargs):
186 """To Gaudi, any object with the same type/name is the same object. Hence,
187 this is mimicked in the configuration: instantiating a new Configurable
188 of a type with the same name will return the same instance."""
189
190 global log
191 func_code = cls.__init__.__code__
192 func_defaults = cls.__init__.__defaults__
193 # try to get the name of the Configurable (having a name is compulsory)
194 if "name" in kwargs:
195 # simple keyword (by far the easiest)
196 name = kwargs["name"]
197 elif "name" in func_code.co_varnames:
198 # either positional in args, or default
199 index = list(func_code.co_varnames).index("name")
200 try:
201 # var names index is offset by one as __init__ is to be called with self
202 name = args[index - 1]
203 except IndexError:
204 # retrieve default value, then
205 name = func_defaults[index - (len(args) + 1)]
206 else:
207 # positional index is assumed (will work most of the time)
208 try:
209 name = args[1] # '0' is for self
210 except (IndexError, TypeError):
211 raise TypeError(
212 f'no "name" argument while instantiating "{cls.__name__}"'
213 )
214
215 argname = name
216 if name == Configurable.DefaultName:
217 if hasattr(cls, "DefaultedName"):
218 name = cls.DefaultedName
219 else:
220 name = cls.getType()
221 elif not name or not isinstance(name, str):
222 # unnamed, highly specialized user code, etc. ... unacceptable
223 raise TypeError(
224 f"could not retrieve name from {cls.__name__}.__init__ arguments"
225 )
226
227 # Handle the case of global tools to prepend ToolSvc in the name.
228 # This is needed for compatibility with old JobOptions files being read
229 if issubclass(cls, ConfigurableAlgTool) and "." not in name:
230 name = "ToolSvc." + name
231
232 # close backdoor access to otherwise private subalgs/tools
233 # PM if 0 <= name.find( '.' ):
234 # PM # temp protection for old style types
235 # PM from OldStyleConfig import GenericConfigurable
236 # PM if not issubclass( cls, GenericConfigurable ): # except raised for new types only
237 # PM raise NameError( '"%s": backdoor access to private configurables not allowed' % name )
238
239 # ordinary recycle case
240 if name in cls.configurables:
241 conf = cls.configurables[name]
242 if name != argname: # special case: user derived <-> real ... make same
243 cls.configurables[conf.getType()] = conf
244 # ---PM: Initialize additional properties
245 for n, v in kwargs.items():
246 if n != "name": # it should not be confused with a normal property
247 setattr(conf, n, v)
248 if (
250 and "_enabled" not in kwargs
251 and isinstance(conf, ConfigurableUser)
252 ):
253 # Ensure that the ConfigurableUser gets enabled if nothing is
254 # specified in the constructor.
255 setattr(conf, "_enabled", True)
256 return conf
257
258 # a couple of special cases (note that these cases don't mix)
259 spos = name.find("/")
260 ti_name = None
261 if spos < 0:
262 ti_name = f"{name}/{name}"
263 if ti_name in cls.configurables:
264 # support for old-style name as type/name lookup where name==type
265 return cls.configurables[ti_name]
266
267 i_name = None
268 if spos > 0:
269 i_name = name[:spos]
270 if i_name == name[spos + 1 :] and i_name in cls.configurables:
271 # this is the opposite of the above special case
272 return cls.configurables[i_name]
273
274 # the following is purely for debugging support and should realistically bomb
275 conf = (
276 cls.allConfigurables.get(name, None)
277 or (spos < 0 and cls.allConfigurables.get(ti_name, None))
278 or (
279 spos > 0
280 and i_name == name[spos + 1 :]
281 and cls.allConfigurables.get(i_name, None)
282 )
283 )
284 if conf: # wrong type used?
285 if conf.__class__ is ConfigurableGeneric:
286 # If the instance found is ConfigurableGeneric then
287 # we create a new one with the proper type and fill with
288 # the contents of the generic one
289 newconf = object.__new__(cls)
290 cls.__init__(newconf, *args, **kwargs)
291 # initialize with the properties of generic configurable
292 # (we map the names of the properties to lowercase versions because
293 # old options are not case sensitive)
294 names = {}
295 for n in newconf.__slots__:
296 names[n.lower()] = n
297 for n in conf._properties:
298 if names[n.lower()] != n:
299 log.warning(
300 f"Option '{n}' was used for {name}, but the correct spelling is '{names[n.lower()]}'"
301 )
302 setattr(newconf, names[n.lower()], getattr(conf, n))
303 for n, v in kwargs.items():
304 setattr(newconf, n, v)
305 cls.configurables[name] = newconf
306 cls.allConfigurables[name] = newconf
307 return newconf
308 else:
309 # will be an actual error in the future (now only report as such)
310 log.error(
311 'attempt to redefine type of "%s" (was: %s, new: %s)%s',
312 name,
313 conf.__class__.__name__,
315 error_explanation,
316 )
317 # in the future:
318 # return None # will bomb on use (or go unharmed on non-use)
319 # for now, allow use through allConfigurables lookup
320 # ---PM: Initialize additional properties
321 for n, v in kwargs.items():
322 setattr(conf, n, v)
323 return conf
324
325 # still here: create a new instance and initialize it
326 conf = object.__new__(cls)
327 cls.__init__(conf, *args, **kwargs)
328
329 # update normal, per-class cache
330 cls.configurables[name] = conf
331
332 for base in cls.__bases__:
333 if base.__name__ == "ConfigurableService":
334 cls.configurableServices[name] = conf
335
336 # update generics super-cache, if needed
337 cls.allConfigurables[name] = conf
338 # -->PM#if hasattr( cls, 'getType' ) and name.find('/') < 0:
339 # -->PM# cls.allConfigurables[ cls.getType() + '/' + name ] = conf
340
341 return conf
342
343 def __init__(self, name=DefaultName):
344 # check class readiness, all required overloads should be there now
345 klass = self.__class__
346
347 # this is an abstract class
348 if klass == Configurable:
349 raise TypeError(f"{Configurable} is an ABC and can not be instantiated")
350
351 # the following methods require overloading
352 # NOT YET meths = { 'getServices' : 1, # retrieve list of services to configure
353 meths = {
354 "getDlls": 1, # provide list of Dlls to load
355 "getGaudiType": 1, # return string describing component class
356 "getHandle": 1,
357 } # provide access to C++ side component instance
358 # 'getType' : 1 } # return the type of the actual C++ component
359
360 for meth, nArgs in meths.items():
361 try:
362 f = getattr(klass, meth)
363 except AttributeError:
364 raise NotImplementedError(f"{meth} is missing in class {klass}")
365
366 # in addition, verify the number of arguments w/o defaults
367 nargcount = f.__code__.co_argcount
368 fdefaults = f.__defaults__
369 ndefaults = fdefaults and len(fdefaults) or 0
370 if not nargcount - ndefaults <= nArgs <= nargcount:
371 raise TypeError(f"{klass}.{meth} requires exactly {nArgs} arguments")
372
373 # for using this Configurable as a (Gaudi) sequence
374 self.__children = []
375 self.__tools = {}
376
377 # know who we are
378 if name == Configurable.DefaultName:
379 if hasattr(self.__class__, "DefaultedName"):
380 self._name = self.__class__.DefaultedName
381 else:
382 self._name = self.getType()
383 else:
384 self._name = name
385
386 # set to True when collecting defaults, False otherwise
387 self._inSetDefaults = False
388
389 # for later, in case __init__ itself is overridden
390 self._initok = True
391
392 # for debugging purposes (temporary)
393 self._setupok = False
394
395 # used to prevent spurious deprecation warnings when unpickling
396 self._unpickling = False
397
398 # pickle support
399 def __getstate__(self):
400 dict = {}
401 for name, proxy in self._properties.items():
402 try:
403 dict[name] = proxy.__get__(self)
404 except AttributeError:
405 pass
406
407 dict["_Configurable__children"] = self.__children
408 dict["_Configurable__tools"] = self.__tools
409 dict["_name"] = self._name
410 return dict
411
412 def __getnewargs__(self):
413 return (self._name,)
414
415 def __setstate__(self, dict):
416 self._initok = True
417 from contextlib import contextmanager
418
419 @contextmanager
420 def unpickling():
421 try:
422 self._unpickling = True
423 yield
424 finally:
425 self._unpickling = False
426
427 with unpickling():
428 for n, v in dict.items():
429 setattr(self, n, v)
430
431 # to allow a few basic sanity checks, as well as nice syntax
432 def __len__(self):
433 return len(self.__children)
434
435 def __iter__(self):
436 return iter(self.__children)
437
438 # ownership rules of self through copying
439 def __deepcopy__(self, memo):
440 newconf = object.__new__(self.__class__)
441 self.__class__.__init__(newconf, self.getName())
442
443 for proxy in self._properties.values():
444 try:
445 proxy.__set__(newconf, proxy.__get__(self))
446 except AttributeError:
447 pass # means property was not set for self
448
449 for c in self.__children:
450 newconf += c # processes proper copy semantics
451
452 return newconf
453
454 # hierarchy building, and ownership rules of children
455 def __iadd__(self, configs, descr=None):
456 if not isinstance(configs, (list, tuple)):
457 configs = (configs,)
458
459 joname = self.getJobOptName()
460
461 for cfg in configs:
462 # prevent type mismatches
463 if not isinstance(cfg, Configurable):
464 raise TypeError(f"'{cfg}' is not a Configurable")
465
466 cc = self.copyChildAndSetParent(cfg, joname)
467
468 # filters dupes; usually "ok" (backdoor should catch them)
469 ccjo = cc.getJobOptName()
470 for c in self.__children:
471 if c.getJobOptName() == ccjo:
472 log.error(
473 "attempt to add a duplicate ... dupe ignored%s",
474 error_explanation,
475 )
476 break
477 else:
478 self.__children.append(cc)
479
480 try:
481 if descr: # support for tool properties
482 descr.__set__(self, cc)
483 else:
484 setattr(self, cc.getName(), cc)
485 except AttributeError:
486 pass # to allow free addition of tools/subalgorithms
487
488 return self
489
490 def __getattr__(self, attr): # until ToolProperties exist ...
491 if attr in self.__tools:
492 return self.__tools[attr]
493
494 if attr in self._properties:
495 if isinstance(
496 self._properties[attr].__get__(self),
497 (DataHandle, DataHandleVector),
498 ):
499 return self._properties[attr].__get__(self)
500
501 for c in self.__children:
502 if c.getName() == attr:
503 return c
504
505 raise AttributeError(f"'{self.__class__}' object has no attribute '{attr}'")
506
507 def __setattr__(self, name, value):
508 if self._configurationLocked:
509 raise RuntimeError(
510 f"{self.name()}: Configuration cannot be modified after the ApplicationMgr has been started."
511 )
512 try:
513 super().__setattr__(name, value)
514 except AttributeError:
515 raise AttributeError(
516 f"Configurable '{self.__class__.__name__}' does not have property '{name}'."
517 )
518
519 def __delattr__(self, attr):
520 # remove as property, otherwise try as child
521 try:
522 # remove history etc., then reset to default (in case set before)
523 prop = self._properties[attr]
524 prop.__delete__(self)
525 prop.__set__(self, prop.default)
526 return # reaches here? was property: done now
527 except KeyError:
528 pass
529 # otherwise, remove the private tool
530 if attr in self.__tools:
531 del self.__tools[attr]
532
533 # otherwise, remove child, if one is so named
534 for c in self.__children:
535 if c.getName() == attr:
536 self.__children.remove(c)
537
538 # potentially, there are left over caches (certain user derived classes)
539 try:
540 del self.__dict__[attr]
541 except (AttributeError, KeyError):
542 pass
543
544 def __bool__(self):
545 return True
546
547 def remove(self, items):
548 if not isinstance(items, (list, tuple)):
549 items = [items]
550
551 self.__children = [e for e in self.__children if e not in items]
552
553 def removeAll(self):
554 self.remove(self.__children)
555
556 # called by __iadd__; determines child copy semantics
557 def copyChild(self, child):
558 return copy.deepcopy(child)
559
560 def setParent(self, parentName):
561 pass
562
563 def getParent(self):
564 return ""
565
566 def hasParent(self, parent):
567 return False
568
569 def copyChildAndSetParent(self, cfg, parent):
570 cc = self.copyChild(cfg)
571
572 if hasattr(cc, "setParent") and parent:
573 try:
574 cc.setParent(parent)
575 except RuntimeError as e:
576 # temporary backdoor resolution for compatibility
577 log.error(str(e) + "%s", error_explanation)
578 ccbd = cc.configurables[cc.getJobOptName()]
579
580 # merge properties, new over pre-existing
581 for proxy in self._properties.values():
582 if cc in proxy.history:
583 proxy.__set__(ccbd, proxy.__get__(cc))
584
585 # consolidate
586 cc = ccbd
587 return cc
588
589 def getChildren(self):
590 return self.__children[:] # read only
591
592 def getTools(self):
593 return self.__tools.values() # read only
594
595 def children(self):
596 log.error("children() is deprecated, use getChildren() instead for consistency")
597 log.error(
598 "getChildren() returns a copy; to add a child, use 'parent += child'%s",
599 error_explanation,
600 )
601 return self.__children # by ref, for compatibility
602
603 def getAllChildren(self):
604 """Get all (private) configurable children, both explicit ones (added with +=)
605 and the ones in the private GaudiHandle properties"""
606 childs = []
607 # add private configurable properties (also inside handles)
608 for proxy in self._properties.values():
609 try:
610 c = proxy.__get__(self)
611 except AttributeError:
612 pass
613 else:
614 if isinstance(c, Configurable) and not c.isPublic():
615 childs.append(c)
616 elif isinstance(c, GaudiHandle):
617 try:
618 conf = c.configurable
619 except AttributeError:
620 pass
621 else:
622 if not conf.isPublic():
623 childs.append(conf)
624 elif isinstance(c, GaudiHandleArray):
625 # only setup private arrays
626 if not c.isPublic():
627 for ci in c:
628 if isinstance(ci, Configurable):
629 childs.append(ci)
630 else:
631 try:
632 conf = ci.configurable
633 except AttributeError:
634 pass
635 else:
636 childs.append(conf)
637
638 # add explicit children
639 childs += self.__children
640 return childs
641
642 def getSequence(self):
643 elems = []
644 for c in self.__children:
645 elems.append(c.getFullName())
646 return elems
647
648 def setup(self):
649 # make sure base class init has been called
650 if not hasattr(self, "_initok") or not self._initok:
651 # could check more, but this is the only explanation
652 raise TypeError(
653 f"Configurable.__init__ not called in {self.__class__.__name__} override"
654 )
655
656 # log.debug("calling setup() on " + self.getFullJobOptName())
657
658 # setup self: this collects all values on the python side
659 self.__setupServices()
660 self.__setupDlls()
661 self.__setupDefaults()
662
663 # setup children
664 for c in self.getAllChildren():
665 c.setup()
666
667 # now get handle to work with for moving properties into the catalogue
668 handle = self.getHandle()
669 if not handle:
670 log.debug("no handle for %s: not transporting properties", self._name)
671 return # allowed, done early
672
673 # pass final set of properties on to handle on the C++ side or JobOptSvc
674 for name in self._properties.keys():
675 if hasattr(self, name): # means property has python-side value/default
676 setattr(handle, name, getattr(self, name))
677
678 # for debugging purposes
679 self._setupok = True
680
681 def getProperties(self):
682 props = {}
683 for name, proxy in self._properties.items():
684 try:
685 props[name] = proxy.__get__(self)
686 except AttributeError:
687 props[name] = Configurable.propertyNoValue
688
689 return props
690
692 """Get all properties with their description string as { name : (value, desc) }."""
693 props = {}
694 for name, proxy in self._properties.items():
695 try:
696 props[name] = (proxy.__get__(self), proxy.__doc__)
697 except AttributeError:
698 props[name] = (Configurable.propertyNoValue, proxy.__doc__)
699 return props
700
702 props = {}
703 for name, proxy in self._properties.items():
704 if self.isPropertySet(name):
705 value = proxy.__get__(self)
706 if hasattr(value, "getFullName"):
707 value = value.getFullName()
708 elif isinstance(value, (list, set, tuple)):
709 new_value = []
710 for i in value:
711 if hasattr(i, "getFullName"):
712 new_value.append(i.getFullName())
713 else:
714 new_value.append(i)
715 value = type(value)(new_value)
716 elif isinstance(value, dict):
717 new_value = {}
718 for i in value:
719 if hasattr(value[i], "getFullName"):
720 new_value[i] = value[i].getFullName()
721 else:
722 new_value[i] = value[i]
723 value = new_value
724 props[name] = value
725
726 return props
727
728 def properties(self):
729 return self.getProperties() # compatibility
730
731 @classmethod
733 # user provided defaults
735 cls.setDefaults(c)
736
737 # defaults from C++
738 for k, v in cls._properties.items():
739 if k not in c.__dict__ and hasattr(v, "default"):
740 c.__dict__[k] = v.default
741
742 return c.__dict__
743
744 @classmethod
745 def getDefaultProperty(cls, name):
746 # user provided defaults
748 cls.setDefaults(c)
749
750 if name in c.__dict__:
751 return c.__dict__[name]
752
753 # defaults from C++
754 try:
755 v = cls._properties[name]
756 if hasattr(v, "default"):
757 return v.default
758 except KeyError:
759 pass
760
761 return None
762
763 def getProp(self, name):
764 """Returns the value of the given property."""
765 if hasattr(self, name):
766 return getattr(self, name)
767 else:
768 return self.getDefaultProperties()[name]
769
770 def setProp(self, name, value):
771 """Set the value of a given property"""
772 return setattr(self, name, value)
773
774 def isPropertySet(self, name):
775 """Tell if the property 'name' has been set or not.
776
777 Because of a problem with list and dictionary properties, in those cases
778 if the value is equal to the default, the property is considered as not
779 set.
780 """
781 if not hasattr(self, name):
782 return False
783 default = self.getDefaultProperty(name)
784 if isinstance(
785 default,
786 (list, dict, set, DataHandle, DataHandleVector, GaudiHandleArray),
787 ):
788 value = getattr(self, name)
789 return value != default
790 return True
791
792 def getType(cls):
793 return cls.__name__
794
795 def getName(self):
796 return self._name
797
798 def name(self):
799 return self.getName()
800
801 def getJobOptName(self): # full hierachical name
802 return self.getName()
803
804 def isPublic(self):
805 return True
806
807 # for a couple of existing uses out there
808 def jobOptName(self):
809 log.error(
810 "jobOptName() is deprecated, use getJobOptName() instead for consistency%s",
811 error_explanation,
812 )
813 return self.getJobOptName() # compatibility
814
815 def getFullName(self):
816 return str(self.getType() + "/" + self.getName())
817
819 return f"{self.getType()}/{self.getJobOptName() or self.getName()}"
820
821 def getPrintTitle(self):
822 return self.getGaudiType() + " " + self.getTitleName()
823
824 def getTitleName(self):
825 if log.isEnabledFor(logging.DEBUG):
826 return self.getFullJobOptName()
827 else:
828 return self.getFullName()
829
830 def setDefaults(cls, handle):
831 pass
832
833 def clone(self, name=None, **kwargs):
834 if not name:
835 if hasattr(self, "DefaultedName"):
836 name = self.DefaultedName
837 else:
838 name = self.getType()
839
840 newconf = Configurable.__new__(self.__class__, name)
841 self.__class__.__init__(newconf, name)
842
843 for proxy in self._properties.values():
844 try:
845 value = proxy.__get__(self)
846 if isinstance(value, (str, list, dict, tuple, set)):
847 # clone the values of the properties for basic types
848 value = type(value)(value)
849 proxy.__set__(newconf, value)
850 except AttributeError:
851 pass
852
853 for c in self.__children:
854 newconf += c # processes proper copy semantics
855
856 for n, t in self.__tools.items():
857 newconf.addTool(t, n)
858
859 for name, value in kwargs.items():
860 setattr(newconf, name, value)
861
862 return newconf
863
864 def splitName(self):
865 fullname = self.getName()
866 dot = fullname.find(".")
867 if dot != -1:
868 parentname = fullname[:dot]
869 longname = fullname[dot + 1 :]
870 else:
871 parentname = ""
872 longname = fullname
873 dot = longname.find(".")
874 if dot != -1:
875 name = longname[:dot]
876 else:
877 name = longname
878 return parentname, name, longname
879
880 def addTool(self, tool, name=None):
881 if isclass(tool) and issubclass(tool, ConfigurableAlgTool):
882 if name is None:
883 name = tool.__name__
884 priv_tool = tool(self.getName() + "." + name)
885 elif isinstance(tool, ConfigurableAlgTool):
886 if name is None:
887 name = tool.splitName()[1]
888 priv_tool = tool.clone(self.getName() + "." + name)
889 else:
890 if isclass(tool):
891 classname = tool.__name__
892 else:
893 classname = type(tool).__name__
894 raise TypeError(
895 f"addTool requires AlgTool configurable. Got {classname} type"
896 )
897 self.__tools[name] = priv_tool
898 if name in self.__slots__:
899 # this is to avoid that the property hides the tool
900 setattr(self, name, self.__tools[name])
901 return self.__tools[name]
902
904 return self._inSetDefaults
905
907 # svcs = self.getServices()
908 # if not svcs:
909 svcs = []
910 # elif type(svcs) == types.StringType:
911 # svcs = [ svcs ]
912
913 import __main__
914
915 for svc in svcs:
916 handle = __main__.Service(svc) # noqa: F841 (used in eval below)
917 # services should be configurables as well, but aren't for now
918 # handle.setup()
919
920 # allow Configurable to make some changes
921 if hasattr(self, "configure" + svc):
922 eval("self.configure" + svc + "( handle )")
923
924 def __setupDlls(self):
925 dlls = self.getDlls()
926 if not dlls:
927 dlls = []
928 elif isinstance(dlls, types.StringType):
929 dlls = [dlls]
930
931 from __main__ import theApp
932
933 dlls = filter(lambda d: d not in theApp.Dlls, dlls)
934 if dlls:
935 theApp.Dlls += dlls
936
938 # set handle defaults flags to inform __setattr__ that it is being
939 # called during setDefaults of the concrete Configurable
940 self._inSetDefaults = True
941 self.setDefaults(self)
942 self._inSetDefaults = False
943
944 @staticmethod
945 def _printHeader(indentStr, title):
946 preLen = Configurable.printHeaderPre
947 postLen = (
948 Configurable.printHeaderWidth - preLen - 3 - len(title)
949 ) # - len(indentStr)
950 postLen = max(preLen, postLen)
951 return indentStr + "/{} {} {}".format(preLen * "*", title, postLen * "*")
952
953 @staticmethod
954 def _printFooter(indentStr, title):
955 preLen = Configurable.printHeaderPre
956 postLen = (
957 Configurable.printHeaderWidth - preLen - 12 - len(title)
958 ) # - len(indentStr)
959 postLen = max(preLen, postLen)
960 return indentStr + "\\{} (End of {}) {}".format(
961 preLen * "-", title, postLen * "-"
962 )
963
964 def __repr__(self):
965 return f"{self.__class__.__name__}({self.name()!r})"
966
967 def __str__(self, indent=0, headerLastIndentUnit=indentUnit):
968 global log # to print some info depending on output level
969
970 def _sorted_repr_set(value):
971 """Helper to print sorted set representation"""
972 return "{" + repr(sorted(value))[1:-1] + "}" if value else "set()"
973
974 indentStr = indent * Configurable.indentUnit
975 # print header
976 title = self.getPrintTitle()
977 # print line to easily see start-of-configurable
978 if indent > 0:
979 headerIndent = (indent - 1) * Configurable.indentUnit + headerLastIndentUnit
980 else:
981 headerIndent = ""
982 rep = Configurable._printHeader(headerIndent, title)
983 rep += os.linesep
984 # print own properties
985 props = self.getProperties()
986 defs = self.getDefaultProperties()
987 if not props:
988 rep += f"{indentStr}|-<no properties>{os.linesep}"
989 else:
990 # get property name with
991 nameWidth = 0
992 for p in props.keys():
993 nameWidth = max(nameWidth, len(p))
994 for p, v in props.items():
995 # start with indent and property name
996 prefix = f"{indentStr}|-{p:<{nameWidth}}"
997 # add memory address for debugging (not for defaults)
998 if log.isEnabledFor(logging.DEBUG):
999 if v != Configurable.propertyNoValue:
1000 address = f" @{id(v):#018x}"
1001 else:
1002 address = 20 * " "
1003 prefix += address
1004 # add value and default
1005 default = defs.get(p)
1006 if v == Configurable.propertyNoValue:
1007 # show default value as value, and no extra 'default'
1008 strVal = repr(default)
1009 strDef = None
1010 else:
1011 # convert configurable to handle
1012 if hasattr(v, "getGaudiHandle"):
1013 vv = v.getGaudiHandle()
1014 else:
1015 vv = v
1016 if isinstance(vv, (GaudiHandle, GaudiHandleArray, DataHandle)):
1017 strVal = repr(vv)
1018 # the default may not be a GaudiHandle (?)
1019 if hasattr(default, "toStringProperty"):
1020 strDef = repr(default.toStringProperty())
1021 else:
1022 strDef = repr(default)
1023 if strDef == repr(vv.toStringProperty()):
1024 strDef = None
1025 elif isinstance(vv, set):
1026 strVal = _sorted_repr_set(vv)
1027 strDef = _sorted_repr_set(default)
1028 else:
1029 strVal = repr(vv)
1030 strDef = repr(default)
1031 # add the value
1032 line = prefix + " = " + strVal
1033 # add default if present
1034 if strDef is not None:
1035 # put default on new line if too big
1036 if len(line) + len(strDef) > Configurable.printHeaderWidth:
1037 line += (
1038 os.linesep
1039 + indentStr
1040 + "| "
1041 + (len(prefix) - len(indentStr) - 3) * " "
1042 )
1043 line += f" (default: {strDef})"
1044 # add the line to the total string
1045 rep += line + os.linesep
1046 # print out full private configurables
1047 # if isinstance(v,Configurable) and not v.isPublic():
1048
1053
1054 # print configurables + their properties, or loop over sequence
1055 # for cfg in self.__children:
1056 for cfg in self.getAllChildren():
1057 rep += cfg.__str__(indent + 1, "|=") + os.linesep
1058
1059 # print line to easily see end-of-configurable. Note: No linesep!
1060 rep += Configurable._printFooter(indentStr, title)
1061 return rep
1062
1063 def isApplicable(self):
1064 """
1065 Return True is the instance can be "applied".
1066 Always False for plain Configurable instances
1067 (i.e. not ConfigurableUser).
1068 """
1069 return False
1070
1071
1072# classes for generic Gaudi component ===========
1073
1074
1076 def __init__(self, name):
1077 self.__name__ = name # conventional
1078
1079 def __get__(self, obj, type=None):
1080 return getattr(obj, self.__name__)
1081
1082 def __set__(self, obj, value):
1083 object.__setattr__(obj, self.__name__, value)
1084
1085
1087 # __slots__ = { }
1088
1089 def __init__(self, name=Configurable.DefaultName):
1090 Configurable.__init__(self, name)
1091 self._name = name
1092 self._properties = {}
1093
1094 def __deepcopy__(self, memo):
1095 return self # algorithms are always shared
1096
1097 @classmethod
1099 return "GenericComponent"
1100
1101 def getDlls(self):
1102 pass
1103
1104 def getHandle(self):
1105 pass
1106
1107 def __setattr__(self, name, value):
1108 # filter private (user) variables
1109 if name[0] == "_":
1110 super().__setattr__(name, value)
1111 return
1112
1113 # filter configurable types
1114 if isinstance(value, Configurable):
1115 self.__dict__[name] = value
1116 return
1117
1118 # assume all the rest are properties
1119 if name not in self._properties:
1120 self._properties[name] = PropertyProxy(DummyDescriptor(name))
1121 self._properties[name].__set__(self, value)
1122
1123 def getJobOptName(self):
1124 return None
1125
1126
1127# base classes for individual Gaudi algorithms/services/algtools ===========
1129 __slots__ = {
1130 "_jobOptName": 0,
1131 }
1132
1133 def __init__(self, name=Configurable.DefaultName):
1134 super().__init__(name)
1135 name = self.getName()
1136 self._jobOptName = name[name.find("/") + 1 :] # strips class
1137
1138 def __deepcopy__(self, memo):
1139 return self # algorithms are always shared
1140
1141 def getHandle(self):
1142 return iAlgorithm( # noqa: F821 (to avoid circular dependeny)
1143 self.getJobOptName()
1144 )
1145
1146 @classmethod
1148 return "Algorithm"
1149
1150 def getJobOptName(self):
1151 return self._jobOptName
1152
1153 # mimick the ControlFlowLeaf interface
1154 def __and__(self, rhs):
1155 if rhs is CFTrue:
1156 return self
1157 elif rhs is CFFalse:
1158 return CFFalse
1159 return AndNode(self, rhs)
1160
1161 def __or__(self, rhs):
1162 if rhs is CFFalse:
1163 return self
1164 elif rhs is CFTrue:
1165 return CFTrue
1166 return OrNode(self, rhs)
1167
1168 def __invert__(self):
1169 return InvertNode(self)
1170
1171 def __rshift__(self, rhs):
1172 return OrderedNode(self, rhs)
1173
1174 def visitNode(self, visitor):
1175 visitor.enter(self)
1176 self._visitSubNodes(visitor)
1177 visitor.leave(self)
1178
1179 def _visitSubNodes(self, visitor):
1180 pass
1181
1182 def __eq__(self, other):
1183 return repr(self) == repr(other)
1184
1185 def __hash__(self):
1186 """Return a unique identifier for this object.
1187
1188 As we use the `repr` of this object to check for equality, we use it
1189 here to define uniqueness.
1190 """
1191 # The hash of the 1-tuple containing the repr of this object
1192 return hash((repr(self),))
1193
1194
1196 __slots__ = {
1197 "OutputLevel": 0,
1198 "AuditInitialize": 0,
1199 "AuditFinalize": 0,
1200 }
1201
1202 def __deepcopy__(self, memo):
1203 return self # services are always shared
1204
1205 def copyChild(self, child):
1206 # Copy private tools but all else is shared
1207 if isinstance(child, ConfigurableAlgTool) and not child.isPublic():
1208 return copy.deepcopy(child)
1209 else:
1210 return child
1211
1212 def getHandle(self):
1213 return iService(self._name) # noqa: F821 (to avoid circular dependeny)
1214
1215 @classmethod
1217 return "Service"
1218
1220 return ServiceHandle(self.toStringProperty())
1221
1223 # called on conversion to a string property for the jocat
1224 return self.getName()
1225
1226
1228 __slots__ = {
1229 "_jobOptName": "",
1230 "OutputLevel": 0,
1231 "AuditInitialize": 0,
1232 "AuditFinalize": 0,
1233 }
1234
1235 def __init__(self, name=Configurable.DefaultName):
1236 super().__init__(name)
1237 if "." not in self._name:
1238 # Public tools must have ToolSvc as parent
1239 self._name = "ToolSvc." + self._name
1240 name = self.getName()
1241 name = name[name.find("/") + 1 :] # strips class, if any
1242 self._jobOptName = name
1243
1244 def getHandle(self):
1245 # iAlgTool isn't useful, unless one knows for sure that the tool exists
1246 return iProperty( # noqa: F821 (to avoid circular dependeny)
1247 self.getJobOptName()
1248 )
1249
1250 @classmethod
1252 return "AlgTool"
1253
1255 if self.isPublic():
1256 return PublicToolHandle(self.toStringProperty())
1257 else:
1258 return PrivateToolHandle(self.toStringProperty())
1259
1260 def getPrintTitle(self):
1261 if self.isPublic():
1262 pop = "Public "
1263 else:
1264 pop = "Private "
1265 return pop + Configurable.getPrintTitle(self)
1266
1267 def setParent(self, parentName):
1268 # print "ConfigurableAlgTool.setParent(%s@%x,%r)" % (self.getName(),id(self),parentName)
1269 # print "Calling stack:"
1270 # import traceback
1271 # traceback.print_stack()
1272
1273 # Find name of this instance.
1274 name = self.getName()
1275 instance = name[name.rfind(".") + 1 :]
1276
1277 # propagate parent to AlgTools in children
1278 for c in self.getAllChildren():
1279 if isinstance(c, ConfigurableAlgTool):
1280 c.setParent(parentName + "." + instance)
1281
1282 # update my own parent
1283 self._jobOptName = self._name = parentName + "." + instance
1284
1285 def getParent(self):
1286 dot = self._jobOptName.rfind(".")
1287 if dot != -1:
1288 return self._jobOptName[:dot]
1289 else:
1290 return ""
1291
1292 def hasParent(self, parent):
1293 return self._jobOptName.startswith(parent + ".")
1294
1295 def getJobOptName(self):
1296 return self._jobOptName
1297
1298 def isPublic(self):
1299 return self.isInToolSvc()
1300
1301 def isInToolSvc(self):
1302 (parent, child) = self._jobOptName.rsplit(".", 1)
1303 return parent == "ToolSvc"
1304
1306 # called on conversion to a string property for the jocat
1307 return self.getFullName()
1308
1309 def getFullName(self):
1310 # for Tools, the "full name" means "Type/LocalName",
1311 # without the names of the parents
1312 name = self.getName()
1313 # strip off everything before the last '.'
1314 name = name[name.rfind(".") + 1 :]
1315 return str(self.getType() + "/" + name)
1316
1317
1318# FIXME: this is just a placeholder, waiting for a real implementation
1319# It is sufficient to get us going... (and import a PkgConf which
1320# happens to contain an Auditor...)
1322 __slots__ = {"_jobOptName": 0, "OutputLevel": 0, "Enable": 1}
1323
1324 def __init__(self, name=Configurable.DefaultName):
1325 super().__init__(name)
1326 name = self.getName()
1327 name = name[name.find("/") + 1 :] # strips class, if any
1328 self._jobOptName = name
1329
1330 def getHandle(self):
1331 # iAlgTool isn't useful, unless one knows for sure that the tool exists
1332 return iProperty( # noqa: F821 (to avoid circular dependeny)
1333 self.getJobOptName()
1334 )
1335
1336 @classmethod
1338 return "Auditor"
1339
1340 def getJobOptName(self):
1341 return self._jobOptName
1342
1344 # called on conversion to a string property for the jocat
1345 return self.getType() + "/" + self.getName()
1346
1347
1349 __slots__ = {
1350 "__users__": [],
1351 "__used_instances__": [],
1352 "_enabled": True,
1353 "_applied": False,
1354 }
1355 # list of ConfigurableUser classes this one is going to modify in the
1356 # __apply_configuration__ method.
1357 # The list may contain class objects, strings representing class objects or
1358 # tuples with the class object (or a string) as first element and the instance
1359 # name as second element.
1360 # If the instance name is None or not present, the function _instanceName()
1361 # is used to determine the name of the instance (the default implementation
1362 # returns "<this name>_<other name>".
1363 __used_configurables__ = []
1364 # list of ConfigurableUser classes this one is going to query in the
1365 # __apply_configuration__ method
1366 __queried_configurables__ = []
1367
1368 def __init__(self, name=Configurable.DefaultName, _enabled=True, **kwargs):
1369 super().__init__(name)
1370 for n, v in kwargs.items():
1371 setattr(self, n, v)
1372 self._enabled = _enabled
1373 self.__users__ = []
1374 self._applied = False
1375
1376 # Needed to retrieve the actual class if the declaration in __used_configurables__
1377 # and __queried_configurables__ is done with strings.
1378 from GaudiKernel.ConfigurableDb import getConfigurable as confDbGetConfigurable
1379
1380 # Set the list of users of the used configurables
1381 #
1383 for used in self.__used_configurables__:
1384 # By default we want to use the default name of the instances
1385 # for the used configurables
1386 used_name = Configurable.DefaultName
1387 # If the entry in the list is a tuple, we need a named instance
1388 if isinstance(used, tuple):
1389 used, used_name = used # we re-set used to re-use the code below
1390 if not used_name:
1391 used_name = self._instanceName(used)
1392 # Check is 'used' is a string or not
1393 if isinstance(used, str):
1394 used_class = confDbGetConfigurable(used)
1395 else:
1396 used_class = used
1397 # Instantiate the configurable that we are going to use
1398 try:
1399 inst = used_class(name=used_name, _enabled=False)
1400 except AttributeError:
1401 # This cover the case where the used configurable is not a
1402 # ConfigurableUser instance, i.e. id doesn't have the attribute
1403 # '_enabled'.
1404 inst = used_class(name=used_name)
1405 self.__addActiveUseOf(inst)
1406 for queried in self.__queried_configurables__:
1407 try:
1408 if isinstance(queried, str):
1409 queried = confDbGetConfigurable(queried)
1410 inst = queried(_enabled=False)
1411 except AttributeError:
1412 inst = queried()
1413 self.__addPassiveUseOf(inst)
1414
1415 def __addActiveUseOf(self, other):
1416 """
1417 Declare that we are going to modify the Configurable 'other' in our
1418 __apply_configuration__.
1419 """
1420 self.__used_instances__.append(other)
1421 if hasattr(other, "__users__"): # allow usage of plain Configurables
1422 other.__users__.append(self)
1423
1424 def __addPassiveUseOf(self, other):
1425 """
1426 Declare that we are going to retrieve property values from the
1427 ConfigurableUser 'other' in our __apply_configuration__.
1428 """
1429 if not isinstance(other, ConfigurableUser):
1430 raise Error(
1431 f"'{self.name()}': Cannot make passive use of '{other.name()}', it is not a ConfigurableUser"
1432 )
1433 other.__addActiveUseOf(self)
1434
1435 @classmethod
1437 return "User"
1438
1439 def getDlls(self):
1440 return None
1441
1442 def getHandle(self):
1443 return None
1444
1446 """
1447 Remove this ConfigurableUser instance from the users list of the used
1448 instances.
1449 """
1450 for used in self.__used_instances__:
1451 if hasattr(used, "__users__"): # allow usage of plain Configurables
1452 used.__users__.remove(self)
1453
1454 def propagateProperty(self, name, others=None, force=True):
1455 """
1456 Propagate the property 'name' (if set) to other configurables (if possible).
1457 'others' can be:
1458 None:
1459 propagate to all the entries in __used_configurables__
1460 a configurable instance:
1461 propagate only to it
1462 list of configurable instances:
1463 propagate to all of them.
1464
1465
1466 The logic is:
1467 - if the local property is set, the other property will be overwritten
1468 - local property not set and other set => keep other
1469 - local property not set and other not set => overwrite the default for
1470 ConfigurableUser instances and set the property for Configurables
1471 """
1472 # transform 'others' to a list of configurable instances
1473 if others is None:
1474 others = self.__used_instances__
1475 elif type(others) not in [list, tuple]:
1476 others = [others]
1477 # these can be computed before the loop
1478 local_is_set = self.isPropertySet(name)
1479 value = self.getProp(name)
1480 # loop over the others that do have 'name' in their slots
1481 for other in [o for o in others if name in o.__slots__]:
1482 # If self property is set, use it
1483 if local_is_set:
1484 if other.isPropertySet(name):
1485 log.warning(
1486 "Property '{prop}' is set in both '{self}' and '{other}', using '{self}.{prop}'".format(
1487 self=self.name(), other=other.name(), prop=name
1488 )
1489 )
1490 other.setProp(name, value)
1491 # If not, and other property also not set, propagate the default
1492 elif not other.isPropertySet(name):
1493 if isinstance(other, ConfigurableUser):
1494 otherType = type(other._properties[name].getDefault())
1495 other._properties[name].setDefault(value)
1496 if otherType in (list, dict, set):
1497 # Special case for list and dictionaries:
1498 # also set the property to the same value of the default (copy)
1499 other.setProp(name, otherType(value))
1500 else:
1501 other.setProp(name, value)
1502 # If not set and other set, do nothing
1503
1504 def propagateProperties(self, names=None, others=None, force=True):
1505 """
1506 Call propagateProperty for each property listed in 'names'.
1507 If 'names' is None, all the properties are propagated.
1508 """
1509 if names is None:
1510 # use all the non-private slots
1511 names = [p for p in self.__slots__ if not p.startswith("_")]
1512 for n in names:
1513 self.propagateProperty(n, others, force)
1514
1516 """
1517 Function to be overridden to convert the high level configuration into a
1518 low level one.
1519 The default implementation calls applyConf, which is the method defined
1520 in some ConfigurableUser implementations.
1521 """
1522 return self.applyConf()
1523
1524 def applyConf(self):
1525 """
1526 Function to be overridden to convert the high level configuration into a
1527 low level one.
1528 """
1529 pass
1530
1531 def _instanceName(self, cls):
1532 """
1533 Function used to define the name of the private instance of a given class
1534 name.
1535 This method is used when the __used_configurables_property__ declares the
1536 need of a private used configurable without specifying the name.
1537 """
1538 if isinstance(cls, str):
1539 clName = cls
1540 else:
1541 clName = cls.__name__
1542 return f"{self.name()}_{clName}"
1543
1544 def getUsedInstance(self, name):
1545 """
1546 Return the used instance with a given name.
1547 """
1548 for i in self.__used_instances__:
1549 if i.name() == name:
1550 if hasattr(i, "_enabled"):
1551 # ensure that the instances retrieved through the method are
1552 # enabled
1553 i._enabled = True
1554 return i
1555 raise KeyError(name)
1556
1557 def isApplicable(self):
1558 """
1559 Return True is the instance can be "applied".
1560 """
1561 return (not self.__users__) and (not self._applied)
1562
1563
1564# list of callables to be called after all the __apply_configuration__ are called.
1565postConfigActions = []
1566
1567
1569 """
1570 Add a new callable ('function') to the list of post-configuration actions.
1571 If the callable is already in the list, it is moved to the end of the list.
1572 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
1573 """
1574 try:
1575 postConfigActions.remove(function)
1576 except Exception:
1577 pass
1578 postConfigActions.append(function)
1579
1580
1582 """
1583 Remove a callable from the list of post-config actions.
1584 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
1585 """
1586 postConfigActions.remove(function)
1587
1588
1589_appliedConfigurableUsers_ = False
1590
1591
1593 """
1594 Call the apply method of all the ConfigurableUser instances respecting the
1595 dependencies. First the C.U.s that are not used by anybody, then the used
1596 ones, when they are not used anymore.
1597 """
1598 # Avoid double calls
1599 global _appliedConfigurableUsers_, postConfigActions
1600 if _appliedConfigurableUsers_:
1601 return
1602 _appliedConfigurableUsers_ = True
1603
1604 def applicableConfUsers():
1605 """
1606 Generator returning all the configurables that can be applied in the
1607 order in which they can be applied.
1608 """
1609 # This is tricky...
1610 # We keep on looking for the first configurable that can be applied.
1611 # When we cannot find any, 'next()' raises a StopIteration that is
1612 # propagated outside of the infinite loop and the function, then handled
1613 # correctly from outside (it is the exception that is raised when you
1614 # exit from a generator).
1615 # Checking every time the full list is inefficient, but it is the
1616 # easiest way to fix bug #103803.
1617 # <https://savannah.cern.ch/bugs/?103803>
1618 while True:
1619 try:
1620 yield next(
1621 c
1622 for c in Configurable.allConfigurables.values()
1623 if c.isApplicable()
1624 )
1625 except StopIteration:
1626 break
1627
1628 debugApplyOrder = "GAUDI_DUBUG_CONF_USER" in os.environ
1629 for c in applicableConfUsers():
1630 if c._enabled:
1631 log.info("applying configuration of %s", c.name())
1632 if debugApplyOrder:
1633 sys.stderr.write(f"applying {c!r}")
1634 c.__apply_configuration__()
1635 log.info(c)
1636 else:
1637 log.info("skipping configuration of %s", c.name())
1638 c._applied = True # flag the instance as already applied
1639 if hasattr(c, "__detach_used__"):
1640 # tells the used configurables that they are not needed anymore
1641 c.__detach_used__()
1642
1643 # check for dependency loops
1644 leftConfUsers = [
1645 c
1646 for c in Configurable.allConfigurables.values()
1647 if hasattr(c, "__apply_configuration__") and c._enabled and not c._applied
1648 ]
1649 # if an enabled configurable has not been applied, there must be a dependency loop
1650 if leftConfUsers:
1651 raise Error(
1652 "Detected loop in the ConfigurableUser"
1653 " dependencies: %r" % [c.name() for c in leftConfUsers]
1654 )
1655 # ensure that all the Handles have been triggered
1656 known = set()
1657 unknown = set(Configurable.allConfigurables)
1658 while unknown:
1659 for k in unknown:
1660 if not known: # do not print during the first iteration
1661 log.debug("new configurable created automatically: %s", k)
1662 # this trigger the instantiation from handles
1663 Configurable.allConfigurables[k].properties()
1664 known.add(k)
1665 unknown -= known
1666 # Call post-config actions
1667 for action in postConfigActions:
1668 action()
1669
1670
1672 """
1673 Obsolete (buggy) implementation of applyConfigurableUsers(), kept to provide
1674 backward compatibility for configurations that where relying (implicitly) on
1675 bug #103803, or on a specific (non guaranteed) order of execution.
1676
1677 @see applyConfigurableUsers()
1678 """
1679 # Avoid double calls
1680 global _appliedConfigurableUsers_, postConfigActions
1681 if _appliedConfigurableUsers_:
1682 return
1683 _appliedConfigurableUsers_ = True
1684
1685 debugApplyOrder = "GAUDI_DUBUG_CONF_USER" in os.environ
1686 confUsers = [
1687 c
1688 for c in Configurable.allConfigurables.values()
1689 if hasattr(c, "__apply_configuration__")
1690 ]
1691 applied = True # needed to detect dependency loops
1692 while applied and confUsers:
1693 newConfUsers = [] # list of conf users that cannot be applied yet
1694 applied = False
1695 for c in confUsers:
1696 if hasattr(c, "__users__") and c.__users__:
1697 newConfUsers.append(c) # cannot use this one yet
1698 else: # it does not have users or the list is empty
1699 applied = True
1700 # the ConfigurableUser is enabled if it doesn't have an _enabled
1701 # property or its value is True
1702 enabled = (not hasattr(c, "_enabled")) or c._enabled
1703 if enabled:
1704 log.info("applying configuration of %s", c.name())
1705 if debugApplyOrder:
1706 sys.stderr.write(f"applying {c!r}")
1707 c.__apply_configuration__()
1708 log.info(c)
1709 else:
1710 log.info("skipping configuration of %s", c.name())
1711 if hasattr(c, "__detach_used__"):
1712 # tells the used configurables that they are not needed anymore
1713 c.__detach_used__()
1714 confUsers = newConfUsers # list of C.U.s still to go
1715 if confUsers:
1716 # this means that some C.U.s could not be applied because of a dependency loop
1717 raise Error(
1718 "Detected loop in the ConfigurableUser "
1719 " dependencies: %r" % [c.name() for c in confUsers]
1720 )
1721 # ensure that all the Handles have been triggered
1722 known = set()
1723 unknown = set(Configurable.allConfigurables)
1724 while unknown:
1725 for k in unknown:
1726 if not known: # do not print during the first iteration
1727 log.debug("new configurable created automatically: %s", k)
1728 # this trigger the instantiation from handles
1729 Configurable.allConfigurables[k].properties()
1730 known.add(k)
1731 unknown -= known
1732 # Call post-config actions
1733 for action in postConfigActions:
1734 action()
1735
1736
1738 """
1739 Function to select all and only the configurables that have to be used in
1740 GaudiPython.AppMgr constructor.
1741 This is needed because in Athena the implementation have to be different (the
1742 configuration is used in a different moment).
1743 """
1744 return sorted(
1745 k
1746 for k, v in Configurable.allConfigurables.items()
1747 if v.getGaudiType() != "User"
1748 ) # Exclude ConfigurableUser instances
1749
1750
1751def purge():
1752 """
1753 Clean up all configurations and configurables.
1754 """
1755 for c in Configurable.allConfigurables.values():
1756 c.__class__.configurables.clear()
1757 Configurable.allConfigurables.clear()
1758 # FIXME: (MCl) this is needed because instances of ConfigurableGeneric are not
1759 # migrated to the correct class when this is known.
1760 ConfigurableGeneric.configurables.clear()
1761 import os.path
1762 import sys
1763
1764 from .ProcessJobOptions import _included_files
1765
1766 for file in _included_files:
1767 dirname, basname = os.path.split(file)
1768 basname, ext = os.path.splitext(basname)
1769 if basname in sys.modules:
1770 del sys.modules[basname]
1771 _included_files.clear()
1772
1773
1775 def __init__(self):
1776 self.stack = []
1777
1778 @property
1779 def sequence(self):
1780 return self.stack[-1]
1781
1782 def enter(self, visitee):
1783 pass
1784
1785 def _getUniqueName(self, prefix):
1786 from Gaudi.Configuration import allConfigurables
1787
1788 cnt = 0
1789 name = prefix + str(cnt)
1790 while name in allConfigurables:
1791 cnt += 1
1792 name = prefix + str(cnt)
1793 return name
1794
1795 def _newSeq(self, prefix="seq_", **kwargs):
1796 from Configurables import Gaudi__Sequencer
1797
1798 return Gaudi__Sequencer(self._getUniqueName("seq_"), **kwargs)
1799
1800 def leave(self, visitee):
1801 stack = self.stack
1802 if visitee in (CFTrue, CFFalse):
1803 stack.append(self._newSeq(Invert=visitee is CFFalse))
1804 elif isinstance(visitee, (ControlFlowLeaf, ConfigurableAlgorithm)):
1805 stack.append(visitee)
1806 elif isinstance(visitee, (OrNode, AndNode, OrderedNode)):
1807 b = stack.pop()
1808 a = stack.pop()
1809 seq = self._newSeq(
1810 Members=[a, b],
1811 ModeOR=isinstance(visitee, OrNode),
1812 ShortCircuit=not isinstance(visitee, OrderedNode),
1813 )
1814 stack.append(seq)
1815 elif isinstance(visitee, ignore):
1816 if hasattr(stack[-1], "IgnoreFilterPassed"):
1817 stack[-1].IgnoreFilterPassed = True
1818 else:
1819 stack.append(
1820 self._newSeq(Members=[stack.pop()], IgnoreFilterPassed=True)
1821 )
1822 elif isinstance(visitee, InvertNode):
1823 if hasattr(stack[-1], "Invert"):
1824 stack[-1].Invert = True
1825 else:
1826 stack.append(self._newSeq(Members=[stack.pop()], Invert=True))
1827
1828
1829def makeSequences(expression):
1830 """
1831 Convert a control flow expression to nested GaudiSequencers.
1832 """
1833 if not isinstance(expression, ControlFlowNode):
1834 raise ValueError(
1835 f"ControlFlowNode instance expected, got {type(expression).__name__}"
1836 )
1837 visitor = CreateSequencesVisitor()
1838 expression.visitNode(visitor)
1839 return visitor.sequence
1840
1841
1842class SuperAlgorithm(ControlFlowNode):
1843 """
1844 Helper class to use a ControlFlowNode as an algorithm configurable
1845 instance.
1846 """
1847
1848 def __new__(cls, name=None, **kwargs):
1849 if name is None:
1850 name = cls.__name__
1851 if name in Configurable.allConfigurables:
1852 instance = Configurable.allConfigurables[name]
1853 assert type(instance) is cls, (
1854 f"trying to reuse {name!r} as name of a {cls.__name__} instance while it"
1855 "s "
1856 f"already used for an instance of {type(instance).__name__}"
1857 )
1858 return instance
1859 else:
1860 instance = super().__new__(cls)
1861 Configurable.allConfigurables[name] = instance
1862 return instance
1863
1864 def __init__(self, name=None, **kwargs):
1865 self._name = name or self.getType()
1866 self.graph = self._initGraph()
1867 for key in kwargs:
1868 setattr(self, key, kwargs[key])
1869
1870 @property
1871 def name(self): # name is a read only property
1872 return self._name
1873
1874 @classmethod
1875 def getType(cls):
1876 return cls.__name__
1877
1878 # required to be registered in allConfigurables
1879 def properties(self):
1880 pass
1881
1882 # required to be registered in allConfigurables
1883 def isApplicable(self):
1884 return False
1885
1886 # required to be registered in allConfigurables
1887 @classmethod
1889 return "User"
1890
1891 def _makeAlg(self, typ, **kwargs):
1892 """
1893 Instantiate and algorithm of type 'typ' with a name suitable for use
1894 inside a SuperAlgorithm.
1895 """
1896 name = "{}_{}".format(self.name, kwargs.pop("name", typ.getType()))
1897 return typ(name, **kwargs)
1898
1899 def _initGraph(self):
1900 raise NotImplementedError()
1901
1902 def __repr__(self):
1903 return f"{self.getType()}({self.name!r})"
1904
1905 def _visitSubNodes(self, visitor):
1906 if self.graph:
1907 self.graph.visitNode(visitor)
1908
1909 def __setattr__(self, name, value):
1910 super().__setattr__(name, value)
1911 if name in ("_name", "graph"):
1912 # do not propagate internal data members
1913 return
1914
1915 class PropSetter:
1916 def enter(self, node):
1917 try:
1918 setattr(node, name, value)
1919 except (ValueError, AttributeError):
1920 # ignore type and name mismatch
1921 pass
1922
1923 def leave(self, node):
1924 pass
1925
1926 self._visitSubNodes(PropSetter())
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition MsgStream.cpp:93
__init__(self, name=Configurable.DefaultName)
__init__(self, name=Configurable.DefaultName)
__init__(self, name=Configurable.DefaultName)
__init__(self, name=Configurable.DefaultName)
__iadd__(self, configs, descr=None)
__str__(self, indent=0, headerLastIndentUnit=indentUnit)
clone(self, name=None, **kwargs)
__init__(self, name=Configurable.DefaultName, _enabled=True, **kwargs)
propagateProperty(self, name, others=None, force=True)
propagateProperties(self, names=None, others=None, force=True)
__init__(self, name=None, **kwargs)