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