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