5 from functools
import wraps
7 from AnaAlgorithm.Logging
import logging
8 logCPAlgCfgBlock = logging.getLogger(
'CPAlgCfgBlock')
10 from AnalysisAlgorithmsConfig.ConfigAccumulator
import DataType
14 """check whether the sample being run passes a"""
15 """possible DSID filter on the block"""
16 if len(filterList) == 0:
18 for dsid_filter
in filterList:
20 if any(char
in str(dsid_filter)
for char
in "^$*+?.()|[]{}\\"):
21 pattern = re.compile(dsid_filter)
22 if pattern.match(
str(config.dsid())):
26 if str(dsid_filter) ==
str(config.dsid()):
31 """this wrapper ensures that the 'instanceName' of the various """
32 """config blocks is cleaned up of any non-alphanumeric characters """
33 """that may arise from using 'selectionName' in the naming."""
35 def wrapper(*args, **kwargs):
37 orig_name = func(*args, **kwargs)
46 """this meta class enforces the application of 'alphanumeric_block_names()' """
47 """to 'instanceName()' and will be used in the main ConfigBlock class in order """
48 """to propagate this rule also to all derived classes (the individual config blocks."""
51 if 'instanceName' in dct
and callable(dct[
'instanceName']):
53 return super().
__new__(cls, name, bases, dct)
56 """the information for a single option on a configuration block"""
58 def __init__ (self, type=None, info='', noneAction='ignore', required=False,
69 """Class encoding a blocks dependence on other blocks."""
85 return f
'ConfigBlockDependency(blockName="{self.blockName}", required={self.required})'
89 """the base class for classes implementing individual blocks of
92 A configuration block is a sequence of one or more algorithms that
93 should always be scheduled together, e.g. the muon four momentum
94 corrections could be a single block, muon selection could then be
95 another block. The blocks themselves generally have their own
96 configuration options/properties specific to the block, and will
97 perform a dynamic configuration based on those options as well as
100 The actual configuration of the algorithms in the block will
101 depend on what other blocks are scheduled before and afterwards,
102 most importantly some algorithms will introduce shallow copies
103 that subsequent algorithms will need to run on, and some
104 algorithms will add selection decorations that subquent algorithms
105 should use as preselections.
107 The algorithms get created in a multi-step process (that may be
108 extended in the future): As a first step each block retrieves
109 references to the containers it uses (essentially marking its spot
110 in the processing chain) and also registering any shallow copies
111 that will be made. In the second/last step each block then
112 creates the fully configured algorithms.
114 One goal is that when the algorithms get created they will have
115 their final configuration and there needs to be no
116 meta-configuration data attached to the algorithms, essentially an
117 inversion of the approach in AnaAlgSequence in which the
118 algorithms got created first with associated meta-configuration
119 and then get modified in susequent configuration steps.
121 For now this is mostly an empty base class, but another goal of
122 this approach is to make it easier to build another configuration
123 layer on top of this one, and this class will likely be extended
124 and get data members at that point.
126 The child class needs to implement the method `makeAlgs` which is
127 given a single `ConfigAccumulator` type argument. This is meant to
128 create the sequence of algorithms that this block configures. This
129 is currently (28 Jul 2025) called twice and should do the same thing
130 during both calls, but the plan is to change that to a single call.
132 The child class should also implement the method `getInstanceName`
133 which should return a string that is used to distinguish between
134 multiple instances of the same block. This is used to append the
135 instance name to the names of all algorithms created by this block,
136 and may in the future also be used to distinguish between multiple
137 instances of the block.
149 self.
addOption(
'groupName',
'', type=str,
150 info=(
'Used to specify this block when setting an'
151 ' option at an arbitrary location.'))
152 self.
addOption(
'skipOnData',
False, type=bool,
153 info=(
'User option to prevent the block from running'
154 ' on data. This only affects blocks that are'
155 ' intended to run on data.'))
156 self.
addOption(
'skipOnMC',
False, type=bool,
157 info=(
'User option to prevent the block from running'
158 ' on MC. This only affects blocks that are'
159 ' intended to run on MC.'))
160 self.
addOption(
'onlyForDSIDs', [], type=list,
161 info=(
'Used to specify which MC DSIDs to allow this'
162 ' block to run on. Each element of the list'
163 ' can be a full DSID (e.g. 410470), or a regex'
164 ' (e.g. 410.* to select all 410xxx DSIDs, or'
165 ' ^(?!410) to veto them). An empty list means no'
166 ' DSID restriction.'))
167 self.
addOption(
'propertyOverrides', {}, type=
None,
168 info=(
'EXPERT USE ONLY: A dictionary of properties to'
169 ' override at the end of configuration. This should'
171 ' {"algName.toolName.propertyName": value, ...},'
172 ' without any automatically applied postfixes for'
173 ' the algorithm name. THIS IS MEANT TO BE EXPERT'
174 ' USAGE ONLY. Properties that need to be set by'
175 ' the user should be declared as options on the'
176 ' block itself. EXPERT USE ONLY!'))
179 if cls
not in ConfigBlock.instance_counts:
180 ConfigBlock.instance_counts[cls] = 0
183 stack = inspect.stack()
184 for frame_info
in stack:
186 parent_cls = frame_info.frame.f_locals.get(
'self',
None)
187 if parent_cls
is None or not isinstance(parent_cls, ConfigBlock):
189 if frame_info.function ==
"makeConfig":
190 ConfigBlock.instance_counts[cls] += 1
203 """get the factory name for this block
205 This is mostly to give a reliable means of identifying the type
206 of block we have in error messages. This is meant to be
207 automatically set by the factory based on the requested block
208 name, but there are a number of fallbacks. It is best not to
209 assume a specific format, this is mostly meant to be used as an
210 identifier in output messages.
218 return self.__class__.__name__
221 """set the factory name for this block
223 This is meant to be called automatically by the factory based on
224 the requested block name. If you are creating a block without a factory,
225 you can call this method to set the factory name manually.
230 """Get the name of the instance
232 The name of the instance is used to distinguish between multiple
233 instances of the same block. Most importantly, this will be
234 appended to the names of all algorithms created by this block.
235 This defaults to an empty string, but block implementations
236 should override it with an appropriate name based on identifying
237 options set on this instance. A typical example would be the
238 name of the (main) container, plus potentially the selection or
241 Ideally all blocks should override this method, but for backward
242 compatibility (28 Jul 25) it defaults to an empty string.
248 whether this block should be used for the given configuration
250 This is used by `ConfigSequence` to determine whether this block
251 should be included in the configuration.
253 if self.skipOnData
and config.dataType()
is DataType.Data:
255 if self.skipOnMC
and config.dataType()
is not DataType.Data:
263 Apply any configuration overrides specified in the block's
264 `propertyOverrides` option. This is meant to be called at the
265 end of the configuration process, after all algorithms have been
266 created and configured.
268 for key, value
in self.propertyOverrides.
items():
270 parts = key.split(
'.')
272 raise Exception(f
"Invalid override key format: {key}")
273 alg = config.getAlgorithm(parts[0])
275 raise Exception(f
"Algorithm {parts[0]} not found in config for override: {key}")
276 for name
in parts[1:-1]:
278 if hasattr(alg, name):
279 alg = getattr(alg, name)
281 raise Exception(f
"Tool {name} not found for override: {key}")
284 alg.__setattr__(parts[-1], value)
288 Add a dependency for the block. Dependency is corresponds to the
289 blockName of another block. If required is True, will throw an
290 error if dependency is not present; otherwise will move this
291 block after the required block. If required is False, will do
292 nothing if required block is not present; otherwise, it will
293 move block after required block.
297 self.
addOption(
'ignoreDependencies', [], type=list,
298 info=
'List of dependencies defined in the ConfigBlock to ignore.')
302 """Return True if there is a dependency."""
306 """Return the list of dependencies. """
310 type, info='', noneAction='ignore', required=False) :
311 """declare the given option on the configuration block
313 This should only be called in the constructor of the
316 NOTE: The backend to option handling is slated to be replaced
317 at some point. This particular function should essentially
318 stay the same, but some behavior may change.
321 raise KeyError (f
'duplicate option: {name}')
322 if type
not in [str, bool, int, float, list,
None] :
323 raise TypeError (f
'unknown option type: {type}')
324 noneActions = [
'error',
'set',
'ignore']
325 if noneAction
not in noneActions :
326 raise ValueError (f
'invalid noneAction: {noneAction} [allowed values: {noneActions}]')
327 setattr (self, name, defaultValue)
329 noneAction=noneAction, required=required, default=defaultValue)
333 """set the given option on the configuration block
335 NOTE: The backend to option handling is slated to be replaced
336 at some point. This particular function should essentially
337 stay the same, but some behavior may change.
341 raise KeyError (f
'unknown option "{name}" in block "{self.__class__.__name__}"')
342 noneAction = self.
_options[name].noneAction
343 if value
is not None or noneAction ==
'set' :
347 if optType
is float
and type(value)
is int:
349 if optType
is not None and optType !=
type(value):
350 raise ValueError(f
'{name} for block {self.__class__.__name__} should '
351 f
'be of type {optType} not {type(value)}')
352 setattr (self, name, value)
353 elif noneAction ==
'ignore' :
355 elif noneAction ==
'error' :
356 raise ValueError (f
'passed None for setting option {name} with noneAction=error')
360 """Returns config option value, if present; otherwise return None"""
362 return getattr(self, name)
366 """Return a copy of the options associated with the block"""
372 Prints options and their values
374 def printWrap(text, width=60, indent=" "):
375 wrapper = textwrap.TextWrapper(width=width, initial_indent=indent,
376 subsequent_indent=indent)
377 for line
in wrapper.wrap(text=text):
378 logCPAlgCfgBlock.info(line)
382 logCPAlgCfgBlock.info(indent + f
"\033[4m{opt}\033[0m: {self.getOptionValue(opt)}")
383 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mtype\033[0m: {vals.type}")
384 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mdefault\033[0m: {vals.default}")
385 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mrequired\033[0m: {vals.required}")
386 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mnoneAction\033[0m: {vals.noneAction}")
387 printWrap(f
"\033[4minfo\033[0m: {vals.info}", indent=indent*2)
389 logCPAlgCfgBlock.info(indent + f
"{ opt}: {self.getOptionValue(opt)}")
393 """whether the configuration block has the given option
395 WARNING: The backend to option handling is slated to be
396 replaced at some point. This particular function may change
397 behavior, interface or be removed/replaced entirely.
404 Implementation of == operator. Used for seaching configSeque.
405 E.g. if blockName in configSeq:
417 return ConfigBlock.instance_counts.get(cls, 0)