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