ATLAS Offline Software
PythonConfig.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
2 
3 # Import(s):
4 import ROOT
5 import unittest
6 import copy
7 
8 class PythonConfig( ROOT.EL.PythonConfigBase ):
9  """Standalone Analysis Component Configuration
10 
11  This class is used to describe the configuration of an analysis component
12  (a C++ class inheriting from asg::AsgComponentConfig) in Python. It behaves
13  similar to an Athena configurable, but is implemented in a much simpler
14  way.
15 
16  An example of using it in configuring an EventLoop job could look like:
17 
18  job = ROOT.EL.Job()
19  ...
20  from AnaAlgorithm.PythonConfig import PythonConfig
21  alg = PythonConfig( "EL::UnitTestAlg2/TestAlg",
22  property = 1.23 )
23  alg.setComponentType( "AnaAlgorithm" )
24  alg.string_property = "Foo"
25  job.algsAdd( alg )
26 
27  Note that the python code doesn't know what properties can actually be set
28  on any given C++ algorithm. Any mistake made in the Python configuration
29  (apart from syntax errors) is only discovered while initialising the
30  analysis job.
31  """
32 
33  # Class/static variable(s):
34  printHeaderWidth = 80
35  printHeaderPre = 3
36 
37  def __init__( self, typeAndName, **kwargs ):
38  """Constructor for an algorithm configuration object
39 
40  Keyword arguments:
41  typeAndName -- The type/instance name of the algorithm
42 
43  Note that you can pass (initial) properties to the constructor like:
44 
45  alg = PythonConfig( "EL::UnitTestAlg2/TestAlg",
46  property = 1.23 )
47  """
48 
49  # Call the base class's constructor. Use the default constructor instead
50  # of the one receiving the type and name, to avoid ROOT-10872.
51  super( PythonConfig, self ).__init__()
52  self.setTypeAndName( typeAndName )
53 
54  # Initialise the properties of the algorihm:
55  self._props = {}
56 
57  # Set the properties on the object:
58  for key, value in kwargs.items():
59  self.setPropertyFromString( key, stringPropValue( value ) )
60  self._props[ key ] = copy.deepcopy( value )
61  pass
62 
63  pass
64 
65  def getName( self ):
66  """Get the instance name of the algorithm
67 
68  This is for compatibility with the getName() function of Athena
69  configurables.
70  """
71 
72  return self.name()
73 
74  def getType( self ):
75  """Get the type name of the algorithm
76 
77  This is for compatibility with the getType() function of Athena
78  configurables.
79  """
80 
81  return self.type()
82 
83  def addSelfToJob( self, job ):
84  """add a copy of this config to the EventLoop job object
85 
86  Keyword arguments:
87  job -- The job object to add ourself to
88  """
89  job.algsAdd( self )
90  pass
91 
92  def __getattr__( self, name ):
93  """Get a previously set property value from the configuration
94 
95  This function allows us to retrieve the value of a property that was
96  already set for the algorithm, to possibly use it in some configuration
97  decisions in the Python code itself.
98 
99  Keyword arguments:
100  name -- The name of the property
101  """
102 
103  # Fail if the property was not (yet) set:
104  if not name in self._props:
105  raise AttributeError( 'Property \'%s\' was not set on \'%s/%s\'' %
106  ( name, self.type(), self.name() ) )
107 
108  # Return the property value:
109  return self._props[ name ]
110 
111  def __setattr__( self, key, value ):
112  """Set an algorithm property on an existing configuration object
113 
114  This function allows us to set/override properties on an algorithm
115  configuration object. Allowing for the following syntax:
116 
117  alg = ...
118  alg.IntProperty = 66
119  alg.FloatProperty = 3.141592
120  alg.StringProperty = "Foo"
121 
122  Keyword arguments:
123  key -- The key/name of the property
124  value -- The value to set for the property
125  """
126 
127  # Private variables should be set directly:
128  if key[ 0 ] == '_':
129  return super( PythonConfig, self ).__setattr__( key, value )
130 
131  # Set the property, and remember its value:
132  super( PythonConfig,
133  self ).setPropertyFromString( key, stringPropValue( value ) )
134  self._props[ key ] = copy.deepcopy( value )
135  pass
136 
137  def __eq__( self, other ):
138  """Check for equality with another object
139 
140  The implementation of this is very simple. We only check that the type
141  and the name of the algorithms would match.
142  """
143 
144  # First check that the other object is also an PythonConfig one:
145  if not isinstance( other, PythonConfig ):
146  return False
147 
148  # Now check whether the type and the name of the algorithms agree:
149  return ( ( self.type() == other.type() ) and
150  ( self.name() == other.name() ) )
151 
152  def __ne__( self, other ):
153  """Check for an inequality with another object
154 
155  This is just defined to make the '!=' operator of Python behave
156  consistently with the '==' operator for such objects.
157  """
158  return not self.__eq__( other )
159 
160  def __str__( self ):
161  """Print the algorithm configuration in a user friendly way
162 
163  This is just to help with debugging configurations, allowing
164  the user to get a nice printout of their job configuration.
165  """
166 
167  name = 'PythonConfig %s/%s/%s' % ( self.componentType(),self.type(), self.name() )
168  result = PythonConfig._printHeader( name )
169  result += '\n'
170  for key, value in sorted( self._props.items() ):
171  if isinstance( value, str ):
172  printedValue = "'%s'" % value
173  else:
174  printedValue = value
175  pass
176  result += "|- %s: %s\n" % ( key, indentBy( printedValue, "| " ) )
177  pass
178  result += PythonConfig._printFooter( name )
179  return result
180 
181  def addPrivateTool( self, name, type ):
182  """Create a private tool for the algorithm
183 
184  This function is used in 'standalone' mode to declare a private tool
185  for the algorithm, or a private tool for an already declared private
186  tool.
187 
188  Can be used like:
189  config.addPrivateTool( 'tool1', 'ToolType1' )
190  config.addPrivateTool( 'tool1.tool2', 'ToolType2' )
191 
192  Keyword arguments:
193  name -- The full name of the private tool
194  type -- The C++ type of the private tool
195  """
196 
197  # And now set up the Python object that will take care of setting
198  # properties on this tool.
199 
200  # Tokenize the tool's name. In case it is a subtool of a tool, or
201  # something possibly even deeper.
202  toolNames = name.split( '.' )
203 
204  # Look up the component that we need to set up the private tool on.
205  component = self
206  for tname in toolNames[ 0 : -1 ]:
207  component = getattr( component, tname )
208  pass
209 
210  # Check that the component doesn't have such a (tool) property yet.
211  if hasattr( component, toolNames[ -1 ] ):
212  raise RuntimeError( "Tool with name '%s' already exists" % name )
213  pass
214 
215  # Now set up a smart object as a property on that component.
216  component._props[ toolNames[ -1 ] ] = PrivateToolConfig( self, name,
217  type )
218 
219  # Finally, tell the C++ code what to do.
220  self.createPrivateTool( name, type ).ignore()
221 
222  pass
223 
224  def addPrivateToolInArray( self, name, type ):
225  """Create a private tool in an array for the algorithm
226 
227  This function is used in 'standalone' mode to declare a
228  private tool in a tool array for the algorithm, or a private
229  tool in a tool array for an already declared private tool.
230 
231  Can be used like:
232  tool = config.addPrivateToolInArray( 'tool1', 'ToolType1' )
233  tool = config.addPrivateToolInArray( 'tool1.tool2', 'ToolType2' )
234 
235  Keyword arguments:
236  name -- The full name of the private tool
237  type -- The C++ type of the private tool
238  """
239 
240  # And now set up the Python object that will take care of setting
241  # properties on this tool.
242 
243  # Tokenize the tool's name. In case it is a subtool of a tool, or
244  # something possibly even deeper.
245  toolNames = name.split( '.' )
246 
247  # Look up the component that we need to set up the private tool on.
248  component = self
249  for tname in toolNames[ 0 : -1 ]:
250  component = getattr( component, tname )
251  pass
252 
253  # Finally, tell the C++ code what to do.
254  actualName = self.createPrivateToolInArray( name, type )
255 
256  # Tokenize the actual tool's name. In case it is a subtool of
257  # a tool, or something possibly even deeper.
258  actualToolNames = actualName.split( '.' )
259 
260  # Now set up a smart object as a property on that component.
261  config = PrivateToolConfig( self, actualName, type )
262  component._props[ actualToolNames[ -1 ] ] = config
263  return config
264 
265  @staticmethod
266  def _printHeader( title ):
267  """Produce a nice header when printing the configuration
268 
269  This function is used for printing the header of both algorithms
270  and tools.
271 
272  Keyword arguments:
273  indentString -- String used as indentation
274  title -- The title of the algorithm/tool
275  """
276 
277  preLength = PythonConfig.printHeaderPre
278  postLength = PythonConfig.printHeaderWidth - 3 - preLength - \
279  len( title )
280  return '/%s %s %s' % ( preLength * '*', title, postLength * '*' )
281 
282  @staticmethod
283  def _printFooter( title ):
284  """Produce a nice footer when printing the configuration
285 
286  This function is used for printing the footer of both algorithms
287  and tools.
288 
289  Keyword arguments:
290  indentString -- String used as indentation
291  title -- The title of the algorithm/tool
292  """
293 
294  preLength = PythonConfig.printHeaderPre
295  postLength = PythonConfig.printHeaderWidth - 12 - preLength - \
296  len( title )
297  return '\\%s (End of %s) %s' % ( preLength * '-', title,
298  postLength * '-' )
299 
300  pass
301 
302 
304  """Standalone Private Tool Configuration
305 
306  This class is used to mimic the behaviour of Athena tool configurable
307  classes. To be able to set the properties of private tools used by
308  dual-use algorithms in a way that's valid for both Athena and EventLoop.
309  """
310 
311  def __init__( self, algorithm, prefix, type ):
312  """Constructor for an private tool configuration object
313  """
314 
315  self._algorithm = algorithm
316  self._prefix = prefix
317  self._type = type
318  self._props = {}
319 
320  pass
321 
322  def __getattr__( self, name ):
323  """Get a previously set property value from the configuration
324 
325  This function allows us to retrieve the value of a tool property that
326  was already set for an algorithm's private tool, to possibly use it in
327  some configuration decisions in the Python code itself.
328 
329  Keyword arguments:
330  name -- The name of the property
331  """
332 
333  # Fail if the property was not (yet) set:
334  if not name in self._props:
335  raise AttributeError( 'Property "%s" was not set on "%s/%s.%s"' %
336  ( name, self._algorithm.type(),
337  self._algorithm.name(), self._prefix ) )
338 
339  # Return the property value:
340  return self._props[ name ]
341 
342  def __setattr__( self, key, value ):
343  """Set a tool property on an existing configuration object
344 
345  This function allows us to set/override properties on a private tool
346  of an algorithm configuration object. Allowing for the following syntax:
347 
348  alg = ...
349  alg.Tool.IntProperty = 66
350  alg.Tool.FloatProperty = 3.141592
351  alg.Tool.StringProperty = "Foo"
352 
353  Keyword arguments:
354  key -- The key/name of the property
355  value -- The value to set for the property
356  """
357 
358  # Private variables should be set directly:
359  if key[ 0 ] == '_':
360  return super( PrivateToolConfig, self ).__setattr__( key, value )
361 
362  # Construct the full name, used in the C++ code:
363  fullName = self._prefix + "." + key
364 
365  # Set the property, and remember its value:
366  self._algorithm.setPropertyFromString( fullName,
367  stringPropValue( value ) )
368  self._props[ key ] = copy.deepcopy( value )
369  pass
370 
371  def __str__( self ):
372  """Print the private tool configuration in a user friendly way
373 
374  This is just to help with debugging configurations, allowing
375  the user to get a nice printout of their job configuration.
376  """
377 
378  name = 'Private Tool %s/%s' % ( self._type, self._prefix )
379  result = ' \n'
380  result += PythonConfig._printHeader( name )
381  result += '\n'
382  for key, value in sorted( self._props.items() ):
383  if isinstance( value, str ):
384  printedValue = "'%s'" % value
385  else:
386  printedValue = value
387  pass
388  result += "|- %s: %s\n" % ( key, indentBy( printedValue, "| " ) )
389  pass
390  result += PythonConfig._printFooter( name )
391  return result
392 
393  pass
394 
395 
396 def stringPropValue( value ):
397  """Helper function producing a string property value"""
398 
399  stringValue = str( value )
400  if isinstance( value, bool ):
401  stringValue = str( int( value ) )
402  pass
403  return stringValue
404 
405 
406 def indentBy( propValue, indent ):
407  """Helper function used in the configuration printout"""
408 
409  stringValue = str( propValue )
410  result = ""
411  for stringLine in stringValue.split( '\n' ):
412  if len( result ):
413  result += "\n" + indent
414  pass
415  result += stringLine
416  pass
417  return result
418 
419 
420 #
421 # Declare some unit tests for the code
422 #
423 
424 
425 class TestAlgTypeAndName( unittest.TestCase ):
426 
427 
429  def test_singletypename( self ):
430  config1 = PythonConfig( "TypeName" )
431  self.assertEqual( config1.type(), "TypeName" )
432  self.assertEqual( config1.name(), "TypeName" )
433  config2 = PythonConfig( "NS::SomeType" )
434  self.assertEqual( config2.type(), "NS::SomeType" )
435  self.assertEqual( config2.name(), "NS::SomeType" )
436  pass
437 
438 
440  def test_typeandname( self ):
441  config1 = PythonConfig( "TypeName/InstanceName" )
442  self.assertEqual( config1.type(), "TypeName" )
443  self.assertEqual( config1.name(), "InstanceName" )
444  config2 = PythonConfig( "NS::SomeType/Instance" )
445  self.assertEqual( config2.type(), "NS::SomeType" )
446  self.assertEqual( config2.name(), "Instance" )
447  pass
448 
449 
450 class TestAlgProperties( unittest.TestCase ):
451 
452 
453  def setUp( self ):
454  self.config = PythonConfig( "Type/Name" )
455  pass
456 
457 
458  def test_propaccess( self ):
459  self.config.Prop1 = "Value1"
460  self.config.Prop2 = [ "Value2" ]
461  self.assertEqual( self.config.Prop1, "Value1" )
462  self.assertEqual( self.config.Prop2, [ "Value2" ] )
463  self.assertNotEqual( self.config.Prop1, "Foo" )
464  self.assertNotEqual( self.config.Prop2, "Value2" )
465  pass
466 
467 
468  def test_nonexistentprop( self ):
469  with self.assertRaises( AttributeError ):
470  value = self.config.Prop3
471  pass
472  pass
473 
474 
475 class TestAlgPrivateTool( unittest.TestCase ):
476 
477 
478  def setUp( self ):
479  self.config = PythonConfig( "AlgType/AlgName" )
480  pass
481 
482 
483  def test_privatetool( self ):
484  self.config.addPrivateTool( "Tool1", "ToolType1" )
485  self.config.Tool1.Prop1 = "Value1"
486  self.config.Tool1.Prop2 = [ 1, 2, 3 ]
487  self.assertEqual( self.config.Tool1.Prop1, "Value1" )
488  self.assertEqual( self.config.Tool1.Prop2, [ 1, 2, 3 ] )
489  pass
490 
491 
493  tool = self.config.addPrivateToolInArray( "Tool1", "ToolType1" )
494  tool.Prop1 = "Value1"
495  tool.Prop2 = [ 1, 2, 3 ]
496  self.assertEqual( tool.Prop1, "Value1" )
497  self.assertEqual( tool.Prop2, [ 1, 2, 3 ] )
498  pass
499 
500 
502  self.config.addPrivateTool( "Tool1", "ToolType1" )
503  self.config.addPrivateTool( "Tool1.Tool2", "ToolType2" )
504  self.config.Tool1.Tool2.Prop3 = "Foo"
505  self.config.Tool1.Tool2.Prop4 = [ "Bar" ]
506  self.assertEqual( self.config.Tool1.Tool2.Prop3, "Foo" )
507  self.assertEqual( self.config.Tool1.Tool2.Prop4, [ "Bar" ] )
508  pass
509 
510 
511  def test_nonexistentprop( self ):
512  self.config.addPrivateTool( "Tool1", "ToolType1" )
513  with self.assertRaises( AttributeError ):
514  value = self.config.Tool1.BadProp
515  pass
516  self.config.addPrivateTool( "Tool1.Tool2", "ToolType2" )
517  with self.assertRaises( AttributeError ):
518  value = self.config.Tool1.Tool2.BadProp
519  pass
520  pass
521 
522 
523  def test_nonexistenttool( self ):
524  with self.assertRaises( AttributeError ):
525  self.config.addPrivateTool( "BadTool.Tool4", "BadToolType" )
526  pass
527  pass
python.PythonConfig.PythonConfig.__str__
def __str__(self)
Definition: PythonConfig.py:160
python.PythonConfig.TestAlgPrivateTool.test_privatetoolofprivatetool
def test_privatetoolofprivatetool(self)
Test setting up and using a private tool of a private tool.
Definition: PythonConfig.py:501
python.PythonConfig.PythonConfig.getType
def getType(self)
Definition: PythonConfig.py:74
python.PythonConfig.stringPropValue
def stringPropValue(value)
Definition: PythonConfig.py:396
python.PythonConfig.PrivateToolConfig.__setattr__
def __setattr__(self, key, value)
Definition: PythonConfig.py:342
python.PythonConfig.PrivateToolConfig.__getattr__
def __getattr__(self, name)
Definition: PythonConfig.py:322
python.PythonConfig.TestAlgProperties.setUp
def setUp(self)
Common setup for the tests.
Definition: PythonConfig.py:453
python.PythonConfig.TestAlgPrivateTool.test_nonexistenttool
def test_nonexistenttool(self)
Test that private tools can't be set up on not-yet-declared tools.
Definition: PythonConfig.py:523
python.PythonConfig.PythonConfig.__setattr__
def __setattr__(self, key, value)
Definition: PythonConfig.py:111
python.PythonConfig.PythonConfig._props
_props
Definition: PythonConfig.py:55
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
python.DualUseConfig.addPrivateTool
def addPrivateTool(alg, toolName, typeName)
Definition: DualUseConfig.py:180
python.PythonConfig.PythonConfig.__eq__
def __eq__(self, other)
Definition: PythonConfig.py:137
python.PythonConfig.PythonConfig.__getattr__
def __getattr__(self, name)
Definition: PythonConfig.py:92
python.PythonConfig.TestAlgTypeAndName.test_typeandname
def test_typeandname(self)
Test that specifying the type and name separately in the same string works as expected.
Definition: PythonConfig.py:440
python.PythonConfig.PrivateToolConfig.__str__
def __str__(self)
Definition: PythonConfig.py:371
python.PythonConfig.PythonConfig
Definition: PythonConfig.py:8
python.PythonConfig.TestAlgPrivateTool.test_nonexistentprop
def test_nonexistentprop(self)
Test that unset properties on the tools can't be used.
Definition: PythonConfig.py:511
python.DualUseConfig.addPrivateToolInArray
def addPrivateToolInArray(alg, toolName, typeName)
Definition: DualUseConfig.py:229
python.PythonConfig.PrivateToolConfig
Definition: PythonConfig.py:303
DiTauMassTools::ignore
void ignore(T &&)
Definition: PhysicsAnalysis/TauID/DiTauMassTools/DiTauMassTools/HelperFunctions.h:54
python.PythonConfig.PythonConfig.__init__
def __init__(self, typeAndName, **kwargs)
Definition: PythonConfig.py:37
python.PythonConfig.TestAlgPrivateTool.test_privatetool
def test_privatetool(self)
Test setting up and using one private tool.
Definition: PythonConfig.py:483
python.PythonConfig.PrivateToolConfig.__init__
def __init__(self, algorithm, prefix, type)
Definition: PythonConfig.py:311
python.PythonConfig.TestAlgPrivateTool.setUp
def setUp(self)
Set up the main algorithm object to test.
Definition: PythonConfig.py:478
python.PythonConfig.PythonConfig.addSelfToJob
def addSelfToJob(self, job)
Definition: PythonConfig.py:83
python.PythonConfig.indentBy
def indentBy(propValue, indent)
Definition: PythonConfig.py:406
python.PythonConfig.PythonConfig._printFooter
def _printFooter(title)
Definition: PythonConfig.py:283
python.PythonConfig.TestAlgProperties.test_nonexistentprop
def test_nonexistentprop(self)
Test that an unset property can't be accessed.
Definition: PythonConfig.py:468
python.PythonConfig.TestAlgPrivateTool
Test case for using private tools.
Definition: PythonConfig.py:475
python.PythonConfig.TestAlgProperties
Test case for the algorithm property handling.
Definition: PythonConfig.py:450
python.PythonConfig.PrivateToolConfig._algorithm
_algorithm
Definition: PythonConfig.py:315
DerivationFramework::TriggerMatchingUtils::sorted
std::vector< typename T::value_type > sorted(T begin, T end)
Helper function to create a sorted vector from an unsorted one.
python.PythonConfig.PythonConfig.__ne__
def __ne__(self, other)
Definition: PythonConfig.py:152
python.PythonConfig.TestAlgProperties.test_propaccess
def test_propaccess(self)
Test that properties that got set, can be read back.
Definition: PythonConfig.py:458
python.PythonConfig.PythonConfig.addPrivateToolInArray
def addPrivateToolInArray(self, name, type)
Definition: PythonConfig.py:224
name
std::string name
Definition: Control/AthContainers/Root/debug.cxx:195
python.PythonConfig.TestAlgTypeAndName
Test case for the algorithm type/name handling.
Definition: PythonConfig.py:425
python.PythonConfig.PrivateToolConfig._prefix
_prefix
Definition: PythonConfig.py:316
python.PythonConfig.TestAlgTypeAndName.test_singletypename
def test_singletypename(self)
Test that the type and name are set correctly when using a single argument.
Definition: PythonConfig.py:429
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
python.PythonConfig.PythonConfig._printHeader
def _printHeader(title)
Definition: PythonConfig.py:266
python.PythonConfig.PrivateToolConfig._props
_props
Definition: PythonConfig.py:318
python.PythonConfig.PrivateToolConfig._type
_type
Definition: PythonConfig.py:317
python.CaloScaleNoiseConfig.type
type
Definition: CaloScaleNoiseConfig.py:78
pickleTool.object
object
Definition: pickleTool.py:30
str
Definition: BTagTrackIpAccessor.cxx:11
python.PythonConfig.TestAlgProperties.config
config
Definition: PythonConfig.py:454
python.PythonConfig.PythonConfig.getName
def getName(self)
Definition: PythonConfig.py:65
python.PythonConfig.TestAlgPrivateTool.config
config
Definition: PythonConfig.py:479
python.PythonConfig.PythonConfig.addPrivateTool
def addPrivateTool(self, name, type)
Definition: PythonConfig.py:181
python.PythonConfig.TestAlgPrivateTool.test_privatetoolarray
def test_privatetoolarray(self)
Test setting up and using one private tool.
Definition: PythonConfig.py:492