ATLAS Offline Software
Loading...
Searching...
No Matches
ConfigPropertySubstitution.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3# Property-value substitution across configurables
4#
5# This file provides helpers that walk a configurable component's
6# user-set properties and rewrite string references inside them,
7# recursing into private tools, tool-handle arrays, data handles, and
8# nested containers. Used by `ConfigAccumulator.renameFinalContainers`
9# to strip the auto-generated `_STEP<n>` suffix from each container's
10# final name everywhere it appears in the configuration.
11#
12# Works with both EventLoop (`PythonConfig` / `PrivateToolConfig`) and
13# Athena (`GaudiConfig2.Configurable`, `PrivateToolHandleArray`,
14# `DataHandle`).
15
16
17import collections.abc
18import re
19
20
21# Private-tool leaf types — resolved once at import time. Either flavor
22# may be absent in a given build:
23# * `AnaAlgorithm.PythonConfig` is the EventLoop side; absent in Athena.
24# * `GaudiConfig2` is the Athena side; absent in AnalysisBase.
25try :
26 from AnaAlgorithm.PythonConfig import PrivateToolConfig as _ELPrivateToolConfig
27except ImportError :
28 _ELPrivateToolConfig = None
29
30try :
31 from GaudiConfig2 import Configurable as _CAConfigurable
32except ImportError :
33 _CAConfigurable = None
34
35
36def _anchoredReplace (text, search, replace) :
37 """substring-replace `search` with `replace` in `text`, anchored so the
38 match must start at the beginning of the string or immediately after a
39 non-identifier character (anything not in [A-Za-z0-9_]).
40
41 This avoids accidentally rewriting e.g. `MyJets_STEP3` when replacing
42 `Jets_STEP3`.
43 """
44 pattern = re.compile (r'(?:^|(?<=[^A-Za-z0-9_]))' + re.escape (search))
45 return pattern.sub (replace, text)
46
47
48def substituteValue (value, substitutions) :
49 """recursively rewrite container references inside a property value
50
51 Returns the (possibly new) value; for private-tool values (EventLoop
52 `PrivateToolConfig`, Athena `GaudiConfig2.Configurable`) and for
53 `PrivateToolHandleArray` the tool(s) are mutated in place and the
54 same instance is returned.
55 """
56 if isinstance (value, str) :
57 new = value
58 for search, replace in substitutions :
59 new = _anchoredReplace (new, search, replace)
60 return new
61 # EventLoop private tool
62 if _ELPrivateToolConfig is not None and isinstance (value, _ELPrivateToolConfig) :
63 substituteComponentProperties (value, substitutions)
64 return value
65 # Athena private tool held via single `ToolHandle`
66 if _CAConfigurable is not None and isinstance (value, _CAConfigurable) :
67 substituteComponentProperties (value, substitutions)
68 return value
69 # Athena `ToolHandleArray` (private tools). Detect by class name to
70 # avoid importing GaudiHandles; this is the pattern used elsewhere
71 # in the codebase (e.g. HypoToolAnalyser).
72 if value.__class__.__name__ == 'PrivateToolHandleArray' :
73 for tool in value :
74 substituteComponentProperties (tool, substitutions)
75 return value
76 # Athena `DataHandle` (read/write event-store handle). The container
77 # name lives in `.Path`. Returning the substituted string lets the
78 # property setter reconstruct the handle via its semantics.
79 if value.__class__.__name__ == 'DataHandle' :
80 newPath = substituteValue (value.Path, substitutions)
81 if newPath == value.Path :
82 return value
83 return newPath
84 # Use abstract base classes rather than `list`/`set`/`dict` so we
85 # also catch Athena GaudiConfig2 container wrappers (`_ListHelper`,
86 # `_SetHelper`, `_DictHelper`), which are MutableSequence/Set/Mapping
87 # but not the builtin types.
88 if isinstance (value, collections.abc.MutableMapping) :
89 return {substituteValue (k, substitutions) : substituteValue (v, substitutions)
90 for k, v in value.items()}
91 if isinstance (value, collections.abc.MutableSet) :
92 return {substituteValue (v, substitutions) for v in value}
93 if isinstance (value, collections.abc.MutableSequence) :
94 return [substituteValue (v, substitutions) for v in value]
95 if isinstance (value, tuple) :
96 return tuple (substituteValue (v, substitutions) for v in value)
97 return value
98
99
100def substituteComponentProperties (component, substitutions) :
101 """walk a component's user-set properties, substituting container
102 references in every value, and write the result back through
103 `setattr` so the underlying configuration stays in sync."""
104 if hasattr (component, '_props') :
105 propDict = component._props
106 elif hasattr (component, '_properties') :
107 propDict = component._properties
108 else :
109 return
110 for propName in list (propDict.keys()) :
111 old = propDict[propName]
112 new = substituteValue (old, substitutions)
113 # Private tools and tool arrays are mutated in place — same
114 # identity, skip the write-back; otherwise re-assign only if the
115 # value actually changed.
116 if new is old :
117 continue
118 if new != old :
119 setattr (component, propName, new)
substituteComponentProperties(component, substitutions)