The Gaudi Framework  master (69a68366)
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 }
1136
1137 def __init__(self, name=Configurable.DefaultName):
1138 super(ConfigurableAlgorithm, self).__init__(name)
1139 name = self.getName()
1140 self._jobOptName = name[name.find("/") + 1 :] # strips class
1141
1142 def __deepcopy__(self, memo):
1143 return self # algorithms are always shared
1144
1145 def getHandle(self):
1146 return iAlgorithm( # noqa: F821 (to avoid circular dependeny)
1147 self.getJobOptName()
1148 )
1149
1150 @classmethod
1152 return "Algorithm"
1153
1154 def getJobOptName(self):
1155 return self._jobOptName
1156
1157 # mimick the ControlFlowLeaf interface
1158 def __and__(self, rhs):
1159 if rhs is CFTrue:
1160 return self
1161 elif rhs is CFFalse:
1162 return CFFalse
1163 return AndNode(self, rhs)
1164
1165 def __or__(self, rhs):
1166 if rhs is CFFalse:
1167 return self
1168 elif rhs is CFTrue:
1169 return CFTrue
1170 return OrNode(self, rhs)
1171
1172 def __invert__(self):
1173 return InvertNode(self)
1174
1175 def __rshift__(self, rhs):
1176 return OrderedNode(self, rhs)
1177
1178 def visitNode(self, visitor):
1179 visitor.enter(self)
1180 self._visitSubNodes(visitor)
1181 visitor.leave(self)
1182
1183 def _visitSubNodes(self, visitor):
1184 pass
1185
1186 def __eq__(self, other):
1187 return repr(self) == repr(other)
1188
1189 def __hash__(self):
1190 """Return a unique identifier for this object.
1191
1192 As we use the `repr` of this object to check for equality, we use it
1193 here to define uniqueness.
1194 """
1195 # The hash of the 1-tuple containing the repr of this object
1196 return hash((repr(self),))
1197
1198
1200 __slots__ = {
1201 "OutputLevel": 0,
1202 "AuditInitialize": 0,
1203 "AuditFinalize": 0,
1204 }
1205
1206 def __deepcopy__(self, memo):
1207 return self # services are always shared
1208
1209 def copyChild(self, child):
1210 # Copy private tools but all else is shared
1211 if isinstance(child, ConfigurableAlgTool) and not child.isPublic():
1212 return copy.deepcopy(child)
1213 else:
1214 return child
1215
1216 def getHandle(self):
1217 return iService(self._name) # noqa: F821 (to avoid circular dependeny)
1218
1219 @classmethod
1221 return "Service"
1222
1224 return ServiceHandle(self.toStringProperty())
1225
1227 # called on conversion to a string property for the jocat
1228 return self.getName()
1229
1230
1232 __slots__ = {
1233 "_jobOptName": "",
1234 "OutputLevel": 0,
1235 "AuditInitialize": 0,
1236 "AuditFinalize": 0,
1237 }
1238
1239 def __init__(self, name=Configurable.DefaultName):
1240 super(ConfigurableAlgTool, self).__init__(name)
1241 if "." not in self._name:
1242 # Public tools must have ToolSvc as parent
1243 self._name = "ToolSvc." + self._name
1244 name = self.getName()
1245 name = name[name.find("/") + 1 :] # strips class, if any
1246 self._jobOptName = name
1247
1248 def getHandle(self):
1249 # iAlgTool isn't useful, unless one knows for sure that the tool exists
1250 return iProperty( # noqa: F821 (to avoid circular dependeny)
1251 self.getJobOptName()
1252 )
1253
1254 @classmethod
1256 return "AlgTool"
1257
1259 if self.isPublic():
1260 return PublicToolHandle(self.toStringProperty())
1261 else:
1262 return PrivateToolHandle(self.toStringProperty())
1263
1264 def getPrintTitle(self):
1265 if self.isPublic():
1266 pop = "Public "
1267 else:
1268 pop = "Private "
1269 return pop + Configurable.getPrintTitle(self)
1270
1271 def setParent(self, parentName):
1272 # print "ConfigurableAlgTool.setParent(%s@%x,%r)" % (self.getName(),id(self),parentName)
1273 # print "Calling stack:"
1274 # import traceback
1275 # traceback.print_stack()
1276
1277 # Find name of this instance.
1278 name = self.getName()
1279 instance = name[name.rfind(".") + 1 :]
1280
1281 # propagate parent to AlgTools in children
1282 for c in self.getAllChildren():
1283 if isinstance(c, ConfigurableAlgTool):
1284 c.setParent(parentName + "." + instance)
1285
1286 # update my own parent
1287 self._jobOptName = self._name = parentName + "." + instance
1288
1289 def getParent(self):
1290 dot = self._jobOptName.rfind(".")
1291 if dot != -1:
1292 return self._jobOptName[:dot]
1293 else:
1294 return ""
1295
1296 def hasParent(self, parent):
1297 return self._jobOptName.startswith(parent + ".")
1298
1299 def getJobOptName(self):
1300 return self._jobOptName
1301
1302 def isPublic(self):
1303 return self.isInToolSvc()
1304
1305 def isInToolSvc(self):
1306 (parent, child) = self._jobOptName.rsplit(".", 1)
1307 return parent == "ToolSvc"
1308
1310 # called on conversion to a string property for the jocat
1311 return self.getFullName()
1312
1313 def getFullName(self):
1314 # for Tools, the "full name" means "Type/LocalName",
1315 # without the names of the parents
1316 name = self.getName()
1317 # strip off everything before the last '.'
1318 name = name[name.rfind(".") + 1 :]
1319 return str(self.getType() + "/" + name)
1320
1321
1322# FIXME: this is just a placeholder, waiting for a real implementation
1323# It is sufficient to get us going... (and import a PkgConf which
1324# happens to contain an Auditor...)
1326 __slots__ = {"_jobOptName": 0, "OutputLevel": 0, "Enable": 1}
1327
1328 def __init__(self, name=Configurable.DefaultName):
1329 super(ConfigurableAuditor, self).__init__(name)
1330 name = self.getName()
1331 name = name[name.find("/") + 1 :] # strips class, if any
1332 self._jobOptName = name
1333
1334 def getHandle(self):
1335 # iAlgTool isn't useful, unless one knows for sure that the tool exists
1336 return iProperty( # noqa: F821 (to avoid circular dependeny)
1337 self.getJobOptName()
1338 )
1339
1340 @classmethod
1342 return "Auditor"
1343
1344 def getJobOptName(self):
1345 return self._jobOptName
1346
1348 # called on conversion to a string property for the jocat
1349 return self.getType() + "/" + self.getName()
1350
1351
1353 __slots__ = {
1354 "__users__": [],
1355 "__used_instances__": [],
1356 "_enabled": True,
1357 "_applied": False,
1358 }
1359 # list of ConfigurableUser classes this one is going to modify in the
1360 # __apply_configuration__ method.
1361 # The list may contain class objects, strings representing class objects or
1362 # tuples with the class object (or a string) as first element and the instance
1363 # name as second element.
1364 # If the instance name is None or not present, the function _instanceName()
1365 # is used to determine the name of the instance (the default implementation
1366 # returns "<this name>_<other name>".
1367 __used_configurables__ = []
1368 # list of ConfigurableUser classes this one is going to query in the
1369 # __apply_configuration__ method
1370 __queried_configurables__ = []
1371
1372 def __init__(self, name=Configurable.DefaultName, _enabled=True, **kwargs):
1373 super(ConfigurableUser, self).__init__(name)
1374 for n, v in kwargs.items():
1375 setattr(self, n, v)
1376 self._enabled = _enabled
1377 self.__users__ = []
1378 self._applied = False
1379
1380 # Needed to retrieve the actual class if the declaration in __used_configurables__
1381 # and __queried_configurables__ is done with strings.
1382 from GaudiKernel.ConfigurableDb import getConfigurable as confDbGetConfigurable
1383
1384 # Set the list of users of the used configurables
1385 #
1387 for used in self.__used_configurables__:
1388 # By default we want to use the default name of the instances
1389 # for the used configurables
1390 used_name = Configurable.DefaultName
1391 # If the entry in the list is a tuple, we need a named instance
1392 if isinstance(used, tuple):
1393 used, used_name = used # we re-set used to re-use the code below
1394 if not used_name:
1395 used_name = self._instanceName(used)
1396 # Check is 'used' is a string or not
1397 if isinstance(used, str):
1398 used_class = confDbGetConfigurable(used)
1399 else:
1400 used_class = used
1401 # Instantiate the configurable that we are going to use
1402 try:
1403 inst = used_class(name=used_name, _enabled=False)
1404 except AttributeError:
1405 # This cover the case where the used configurable is not a
1406 # ConfigurableUser instance, i.e. id doesn't have the attribute
1407 # '_enabled'.
1408 inst = used_class(name=used_name)
1409 self.__addActiveUseOf(inst)
1410 for queried in self.__queried_configurables__:
1411 try:
1412 if isinstance(queried, str):
1413 queried = confDbGetConfigurable(queried)
1414 inst = queried(_enabled=False)
1415 except AttributeError:
1416 inst = queried()
1417 self.__addPassiveUseOf(inst)
1418
1419 def __addActiveUseOf(self, other):
1420 """
1421 Declare that we are going to modify the Configurable 'other' in our
1422 __apply_configuration__.
1423 """
1424 self.__used_instances__.append(other)
1425 if hasattr(other, "__users__"): # allow usage of plain Configurables
1426 other.__users__.append(self)
1427
1428 def __addPassiveUseOf(self, other):
1429 """
1430 Declare that we are going to retrieve property values from the
1431 ConfigurableUser 'other' in our __apply_configuration__.
1432 """
1433 if not isinstance(other, ConfigurableUser):
1434 raise Error(
1435 "'%s': Cannot make passive use of '%s', it is not a ConfigurableUser"
1436 % (self.name(), other.name())
1437 )
1438 other.__addActiveUseOf(self)
1439
1440 @classmethod
1442 return "User"
1443
1444 def getDlls(self):
1445 return None
1446
1447 def getHandle(self):
1448 return None
1449
1451 """
1452 Remove this ConfigurableUser instance from the users list of the used
1453 instances.
1454 """
1455 for used in self.__used_instances__:
1456 if hasattr(used, "__users__"): # allow usage of plain Configurables
1457 used.__users__.remove(self)
1458
1459 def propagateProperty(self, name, others=None, force=True):
1460 """
1461 Propagate the property 'name' (if set) to other configurables (if possible).
1462 'others' can be:
1463 None:
1464 propagate to all the entries in __used_configurables__
1465 a configurable instance:
1466 propagate only to it
1467 list of configurable instances:
1468 propagate to all of them.
1469
1470
1471 The logic is:
1472 - if the local property is set, the other property will be overwritten
1473 - local property not set and other set => keep other
1474 - local property not set and other not set => overwrite the default for
1475 ConfigurableUser instances and set the property for Configurables
1476 """
1477 # transform 'others' to a list of configurable instances
1478 if others is None:
1479 others = self.__used_instances__
1480 elif type(others) not in [list, tuple]:
1481 others = [others]
1482 # these can be computed before the loop
1483 local_is_set = self.isPropertySet(name)
1484 value = self.getProp(name)
1485 # loop over the others that do have 'name' in their slots
1486 for other in [o for o in others if name in o.__slots__]:
1487 # If self property is set, use it
1488 if local_is_set:
1489 if other.isPropertySet(name):
1490 log.warning(
1491 "Property '%(prop)s' is set in both '%(self)s' and '%(other)s', using '%(self)s.%(prop)s'"
1492 % {"self": self.name(), "other": other.name(), "prop": name}
1493 )
1494 other.setProp(name, value)
1495 # If not, and other property also not set, propagate the default
1496 elif not other.isPropertySet(name):
1497 if isinstance(other, ConfigurableUser):
1498 otherType = type(other._properties[name].getDefault())
1499 other._properties[name].setDefault(value)
1500 if otherType in (list, dict, set):
1501 # Special case for list and dictionaries:
1502 # also set the property to the same value of the default (copy)
1503 other.setProp(name, otherType(value))
1504 else:
1505 other.setProp(name, value)
1506 # If not set and other set, do nothing
1507
1508 def propagateProperties(self, names=None, others=None, force=True):
1509 """
1510 Call propagateProperty for each property listed in 'names'.
1511 If 'names' is None, all the properties are propagated.
1512 """
1513 if names is None:
1514 # use all the non-private slots
1515 names = [p for p in self.__slots__ if not p.startswith("_")]
1516 for n in names:
1517 self.propagateProperty(n, others, force)
1518
1520 """
1521 Function to be overridden to convert the high level configuration into a
1522 low level one.
1523 The default implementation calls applyConf, which is the method defined
1524 in some ConfigurableUser implementations.
1525 """
1526 return self.applyConf()
1527
1528 def applyConf(self):
1529 """
1530 Function to be overridden to convert the high level configuration into a
1531 low level one.
1532 """
1533 pass
1534
1535 def _instanceName(self, cls):
1536 """
1537 Function used to define the name of the private instance of a given class
1538 name.
1539 This method is used when the __used_configurables_property__ declares the
1540 need of a private used configurable without specifying the name.
1541 """
1542 if isinstance(cls, str):
1543 clName = cls
1544 else:
1545 clName = cls.__name__
1546 return "%s_%s" % (self.name(), clName)
1547
1548 def getUsedInstance(self, name):
1549 """
1550 Return the used instance with a given name.
1551 """
1552 for i in self.__used_instances__:
1553 if i.name() == name:
1554 if hasattr(i, "_enabled"):
1555 # ensure that the instances retrieved through the method are
1556 # enabled
1557 i._enabled = True
1558 return i
1559 raise KeyError(name)
1560
1561 def isApplicable(self):
1562 """
1563 Return True is the instance can be "applied".
1564 """
1565 return (not self.__users__) and (not self._applied)
1566
1567
1568# list of callables to be called after all the __apply_configuration__ are called.
1569postConfigActions = []
1570
1571
1573 """
1574 Add a new callable ('function') to the list of post-configuration actions.
1575 If the callable is already in the list, it is moved to the end of the list.
1576 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
1577 """
1578 try:
1579 postConfigActions.remove(function)
1580 except Exception:
1581 pass
1582 postConfigActions.append(function)
1583
1584
1586 """
1587 Remove a callable from the list of post-config actions.
1588 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
1589 """
1590 postConfigActions.remove(function)
1591
1592
1593_appliedConfigurableUsers_ = False
1594
1595
1597 """
1598 Call the apply method of all the ConfigurableUser instances respecting the
1599 dependencies. First the C.U.s that are not used by anybody, then the used
1600 ones, when they are not used anymore.
1601 """
1602 # Avoid double calls
1603 global _appliedConfigurableUsers_, postConfigActions
1604 if _appliedConfigurableUsers_:
1605 return
1606 _appliedConfigurableUsers_ = True
1607
1608 def applicableConfUsers():
1609 """
1610 Generator returning all the configurables that can be applied in the
1611 order in which they can be applied.
1612 """
1613 # This is tricky...
1614 # We keep on looking for the first configurable that can be applied.
1615 # When we cannot find any, 'next()' raises a StopIteration that is
1616 # propagated outside of the infinite loop and the function, then handled
1617 # correctly from outside (it is the exception that is raised when you
1618 # exit from a generator).
1619 # Checking every time the full list is inefficient, but it is the
1620 # easiest way to fix bug #103803.
1621 # <https://savannah.cern.ch/bugs/?103803>
1622 while True:
1623 try:
1624 yield next(
1625 c
1626 for c in Configurable.allConfigurables.values()
1627 if c.isApplicable()
1628 )
1629 except StopIteration:
1630 break
1631
1632 debugApplyOrder = "GAUDI_DUBUG_CONF_USER" in os.environ
1633 for c in applicableConfUsers():
1634 if c._enabled:
1635 log.info("applying configuration of %s", c.name())
1636 if debugApplyOrder:
1637 sys.stderr.write("applying %r" % c)
1638 c.__apply_configuration__()
1639 log.info(c)
1640 else:
1641 log.info("skipping configuration of %s", c.name())
1642 c._applied = True # flag the instance as already applied
1643 if hasattr(c, "__detach_used__"):
1644 # tells the used configurables that they are not needed anymore
1645 c.__detach_used__()
1646
1647 # check for dependency loops
1648 leftConfUsers = [
1649 c
1650 for c in Configurable.allConfigurables.values()
1651 if hasattr(c, "__apply_configuration__") and c._enabled and not c._applied
1652 ]
1653 # if an enabled configurable has not been applied, there must be a dependency loop
1654 if leftConfUsers:
1655 raise Error(
1656 "Detected loop in the ConfigurableUser"
1657 " dependencies: %r" % [c.name() for c in leftConfUsers]
1658 )
1659 # ensure that all the Handles have been triggered
1660 known = set()
1661 unknown = set(Configurable.allConfigurables)
1662 while unknown:
1663 for k in unknown:
1664 if not known: # do not print during the first iteration
1665 log.debug("new configurable created automatically: %s", k)
1666 # this trigger the instantiation from handles
1667 Configurable.allConfigurables[k].properties()
1668 known.add(k)
1669 unknown -= known
1670 # Call post-config actions
1671 for action in postConfigActions:
1672 action()
1673
1674
1676 """
1677 Obsolete (buggy) implementation of applyConfigurableUsers(), kept to provide
1678 backward compatibility for configurations that where relying (implicitly) on
1679 bug #103803, or on a specific (non guaranteed) order of execution.
1680
1681 @see applyConfigurableUsers()
1682 """
1683 # Avoid double calls
1684 global _appliedConfigurableUsers_, postConfigActions
1685 if _appliedConfigurableUsers_:
1686 return
1687 _appliedConfigurableUsers_ = True
1688
1689 debugApplyOrder = "GAUDI_DUBUG_CONF_USER" in os.environ
1690 confUsers = [
1691 c
1692 for c in Configurable.allConfigurables.values()
1693 if hasattr(c, "__apply_configuration__")
1694 ]
1695 applied = True # needed to detect dependency loops
1696 while applied and confUsers:
1697 newConfUsers = [] # list of conf users that cannot be applied yet
1698 applied = False
1699 for c in confUsers:
1700 if hasattr(c, "__users__") and c.__users__:
1701 newConfUsers.append(c) # cannot use this one yet
1702 else: # it does not have users or the list is empty
1703 applied = True
1704 # the ConfigurableUser is enabled if it doesn't have an _enabled
1705 # property or its value is True
1706 enabled = (not hasattr(c, "_enabled")) or c._enabled
1707 if enabled:
1708 log.info("applying configuration of %s", c.name())
1709 if debugApplyOrder:
1710 sys.stderr.write("applying %r" % c)
1711 c.__apply_configuration__()
1712 log.info(c)
1713 else:
1714 log.info("skipping configuration of %s", c.name())
1715 if hasattr(c, "__detach_used__"):
1716 # tells the used configurables that they are not needed anymore
1717 c.__detach_used__()
1718 confUsers = newConfUsers # list of C.U.s still to go
1719 if confUsers:
1720 # this means that some C.U.s could not be applied because of a dependency loop
1721 raise Error(
1722 "Detected loop in the ConfigurableUser "
1723 " dependencies: %r" % [c.name() for c in confUsers]
1724 )
1725 # ensure that all the Handles have been triggered
1726 known = set()
1727 unknown = set(Configurable.allConfigurables)
1728 while unknown:
1729 for k in unknown:
1730 if not known: # do not print during the first iteration
1731 log.debug("new configurable created automatically: %s", k)
1732 # this trigger the instantiation from handles
1733 Configurable.allConfigurables[k].properties()
1734 known.add(k)
1735 unknown -= known
1736 # Call post-config actions
1737 for action in postConfigActions:
1738 action()
1739
1740
1742 """
1743 Function to select all and only the configurables that have to be used in
1744 GaudiPython.AppMgr constructor.
1745 This is needed because in Athena the implementation have to be different (the
1746 configuration is used in a different moment).
1747 """
1748 return sorted(
1749 k
1750 for k, v in Configurable.allConfigurables.items()
1751 if v.getGaudiType() != "User"
1752 ) # Exclude ConfigurableUser instances
1753
1754
1755def purge():
1756 """
1757 Clean up all configurations and configurables.
1758 """
1759 for c in Configurable.allConfigurables.values():
1760 c.__class__.configurables.clear()
1761 Configurable.allConfigurables.clear()
1762 # FIXME: (MCl) this is needed because instances of ConfigurableGeneric are not
1763 # migrated to the correct class when this is known.
1764 ConfigurableGeneric.configurables.clear()
1765 import os.path
1766 import sys
1767
1768 from .ProcessJobOptions import _included_files
1769
1770 for file in _included_files:
1771 dirname, basname = os.path.split(file)
1772 basname, ext = os.path.splitext(basname)
1773 if basname in sys.modules:
1774 del sys.modules[basname]
1775 _included_files.clear()
1776
1777
1779 def __init__(self):
1780 self.stack = []
1781
1782 @property
1783 def sequence(self):
1784 return self.stack[-1]
1785
1786 def enter(self, visitee):
1787 pass
1788
1789 def _getUniqueName(self, prefix):
1790 from Gaudi.Configuration import allConfigurables
1791
1792 cnt = 0
1793 name = prefix + str(cnt)
1794 while name in allConfigurables:
1795 cnt += 1
1796 name = prefix + str(cnt)
1797 return name
1798
1799 def _newSeq(self, prefix="seq_", **kwargs):
1800 from Configurables import Gaudi__Sequencer
1801
1802 return Gaudi__Sequencer(self._getUniqueName("seq_"), **kwargs)
1803
1804 def leave(self, visitee):
1805 stack = self.stack
1806 if visitee in (CFTrue, CFFalse):
1807 stack.append(self._newSeq(Invert=visitee is CFFalse))
1808 elif isinstance(visitee, (ControlFlowLeaf, ConfigurableAlgorithm)):
1809 stack.append(visitee)
1810 elif isinstance(visitee, (OrNode, AndNode, OrderedNode)):
1811 b = stack.pop()
1812 a = stack.pop()
1813 seq = self._newSeq(
1814 Members=[a, b],
1815 ModeOR=isinstance(visitee, OrNode),
1816 ShortCircuit=not isinstance(visitee, OrderedNode),
1817 )
1818 stack.append(seq)
1819 elif isinstance(visitee, ignore):
1820 if hasattr(stack[-1], "IgnoreFilterPassed"):
1821 stack[-1].IgnoreFilterPassed = True
1822 else:
1823 stack.append(
1824 self._newSeq(Members=[stack.pop()], IgnoreFilterPassed=True)
1825 )
1826 elif isinstance(visitee, InvertNode):
1827 if hasattr(stack[-1], "Invert"):
1828 stack[-1].Invert = True
1829 else:
1830 stack.append(self._newSeq(Members=[stack.pop()], Invert=True))
1831
1832
1833def makeSequences(expression):
1834 """
1835 Convert a control flow expression to nested GaudiSequencers.
1836 """
1837 if not isinstance(expression, ControlFlowNode):
1838 raise ValueError(
1839 "ControlFlowNode instance expected, got %s" % type(expression).__name__
1840 )
1841 visitor = CreateSequencesVisitor()
1842 expression.visitNode(visitor)
1843 return visitor.sequence
1844
1845
1846class SuperAlgorithm(ControlFlowNode):
1847 """
1848 Helper class to use a ControlFlowNode as an algorithm configurable
1849 instance.
1850 """
1851
1852 def __new__(cls, name=None, **kwargs):
1853 if name is None:
1854 name = cls.__name__
1855 if name in Configurable.allConfigurables:
1856 instance = Configurable.allConfigurables[name]
1857 assert type(instance) is cls, (
1858 "trying to reuse {0!r} as name of a {1} instance while it"
1859 "s "
1860 "already used for an instance of {2}"
1861 ).format(name, cls.__name__, type(instance).__name__)
1862 return instance
1863 else:
1864 instance = super(SuperAlgorithm, cls).__new__(cls)
1865 Configurable.allConfigurables[name] = instance
1866 return instance
1867
1868 def __init__(self, name=None, **kwargs):
1869 self._name = name or self.getType()
1870 self.graph = self._initGraph()
1871 for key in kwargs:
1872 setattr(self, key, kwargs[key])
1873
1874 @property
1875 def name(self): # name is a read only property
1876 return self._name
1877
1878 @classmethod
1879 def getType(cls):
1880 return cls.__name__
1881
1882 # required to be registered in allConfigurables
1883 def properties(self):
1884 pass
1885
1886 # required to be registered in allConfigurables
1887 def isApplicable(self):
1888 return False
1889
1890 # required to be registered in allConfigurables
1891 @classmethod
1893 return "User"
1894
1895 def _makeAlg(self, typ, **kwargs):
1896 """
1897 Instantiate and algorithm of type 'typ' with a name suitable for use
1898 inside a SuperAlgorithm.
1899 """
1900 name = "{0}_{1}".format(self.name, kwargs.pop("name", typ.getType()))
1901 return typ(name, **kwargs)
1902
1903 def _initGraph(self):
1904 raise NotImplementedError()
1905
1906 def __repr__(self):
1907 return "{0}({1!r})".format(self.getType(), self.name)
1908
1909 def _visitSubNodes(self, visitor):
1910 if self.graph:
1911 self.graph.visitNode(visitor)
1912
1913 def __setattr__(self, name, value):
1914 super(SuperAlgorithm, self).__setattr__(name, value)
1915 if name in ("_name", "graph"):
1916 # do not propagate internal data members
1917 return
1918
1919 class PropSetter(object):
1920 def enter(self, node):
1921 try:
1922 setattr(node, name, value)
1923 except (ValueError, AttributeError):
1924 # ignore type and name mismatch
1925 pass
1926
1927 def leave(self, node):
1928 pass
1929
1930 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)