5from dataclasses
import dataclass
6from functools
import wraps
9from AnaAlgorithm.Logging
import logging
10logCPAlgCfgBlock = logging.getLogger(
'CPAlgCfgBlock')
12from AnalysisAlgorithmsConfig.ConfigAccumulator
import DataType, ExpertModeWarning
16 """check whether the sample being run passes a
17 possible DSID filter on the block"""
18 if len(filterList) == 0:
20 for dsid_filter
in filterList:
22 if any(char
in str(dsid_filter)
for char
in "^$*+?.()|[]{}\\"):
23 pattern = re.compile(dsid_filter)
24 if pattern.match(str(config.dsid())):
28 if str(dsid_filter) == str(config.dsid()):
33 """this wrapper ensures that the 'instanceName' of the various
34 config blocks is cleaned up of any non-alphanumeric characters
35 that may arise from using 'selectionName' in the naming."""
37 def wrapper(*args, **kwargs):
39 orig_name = func(*args, **kwargs)
51 """this meta class enforces the application of 'alphanumeric_block_names()'
52 to 'instanceName()' and will be used in the main ConfigBlock class in order
53 to propagate this rule also to all derived classes (the individual config blocks."""
56 if 'instanceName' in dct
and callable(dct[
'instanceName']):
58 return super().
__new__(cls, name, bases, dct)
62 """the information for a single option on a configuration block"""
66 noneAction: str =
'ignore'
67 required: bool =
False
68 default: object =
None
75 """Class encoding a blocks dependence on other blocks."""
90 return f
'ConfigBlockDependency(blockName="{self.blockName}", required={self.required})'
94 """the base class for classes implementing individual blocks of
97 A configuration block is a sequence of one or more algorithms that
98 should always be scheduled together, e.g. the muon four momentum
99 corrections could be a single block, muon selection could then be
100 another block. The blocks themselves generally have their own
101 configuration options/properties specific to the block, and will
102 perform a dynamic configuration based on those options as well as
105 The actual configuration of the algorithms in the block will
106 depend on what other blocks are scheduled before and afterwards,
107 most importantly some algorithms will introduce shallow copies
108 that subsequent algorithms will need to run on, and some
109 algorithms will add selection decorations that subsequent algorithms
110 should use as preselections.
112 The algorithms get created in a multi-step process (that may be
113 extended in the future): As a first step each block retrieves
114 references to the containers it uses (essentially marking its spot
115 in the processing chain) and also registering any shallow copies
116 that will be made. In the second/last step each block then
117 creates the fully configured algorithms.
119 One goal is that when the algorithms get created they will have
120 their final configuration and there needs to be no
121 meta-configuration data attached to the algorithms, essentially an
122 inversion of the approach in AnaAlgSequence in which the
123 algorithms got created first with associated meta-configuration
124 and then get modified in subsequent configuration steps.
126 For now this is mostly an empty base class, but another goal of
127 this approach is to make it easier to build another configuration
128 layer on top of this one, and this class will likely be extended
129 and get data members at that point.
131 The child class needs to implement the method `makeAlgs` which is
132 given a single `ConfigAccumulator` type argument. This is meant to
133 create the sequence of algorithms that this block configures. This
134 is currently (28 Jul 2025) called twice and should do the same thing
135 during both calls, but the plan is to change that to a single call.
137 The child class should also implement the method `getInstanceName`
138 which should return a string that is used to distinguish between
139 multiple instances of the same block. This is used to append the
140 instance name to the names of all algorithms created by this block,
141 and may in the future also be used to distinguish between multiple
142 instances of the block.
154 self.
addOption(
'groupName',
'', type=str,
155 info=(
'Used to specify this block when setting an'
156 ' option at an arbitrary location.'))
157 self.
addOption(
'skipOnData',
False, type=bool,
158 info=(
'User option to prevent the block from running'
159 ' on data. This only affects blocks that are'
160 ' intended to run on data.'))
161 self.
addOption(
'skipOnMC',
False, type=bool,
162 info=(
'User option to prevent the block from running'
163 ' on MC. This only affects blocks that are'
164 ' intended to run on MC.'))
165 self.
addOption(
'skipWithSystematics',
False, type=bool,
166 info=(
'User option to prevent the block from running with systematics.'))
167 self.
addOption(
'onlyForDSIDs', [], type=list,
168 info=(
'Used to specify which MC DSIDs to allow this'
169 ' block to run on. Each element of the list'
170 ' can be a full DSID (e.g. 410470), or a regex'
171 ' (e.g. 410.* to select all 410xxx DSIDs, or'
172 ' ^(?!410) to veto them). An empty list means no'
173 ' DSID restriction.'))
174 self.
addOption(
'propertyOverrides', {}, type=dict,
175 info=(
'EXPERT USE ONLY: A dictionary of properties to'
176 ' override at the end of configuration. This should'
178 ' {"algName.toolName.propertyName": value, ...},'
179 ' without any automatically applied postfixes for'
180 ' the algorithm name. THIS IS MEANT TO BE EXPERT'
181 ' USAGE ONLY. Properties that need to be set by'
182 ' the user should be declared as options on the'
183 ' block itself. EXPERT USE ONLY!'),
187 if cls
not in ConfigBlock.instance_counts:
188 ConfigBlock.instance_counts[cls] = 0
191 frame = inspect.currentframe()
192 while frame
is not None:
194 parent_cls = frame.f_locals.get(
'self',
None)
195 if parent_cls
is None or not isinstance(parent_cls, ConfigBlock):
197 if frame.f_code.co_name ==
"makeConfig":
198 ConfigBlock.instance_counts[cls] += 1
213 """get the factory name for this block
215 This is mostly to give a reliable means of identifying the type
216 of block we have in error messages. This is meant to be
217 automatically set by the factory based on the requested block
218 name, but there are a number of fallbacks. It is best not to
219 assume a specific format, this is mostly meant to be used as an
220 identifier in output messages.
228 return self.__class__.__name__
231 """set the factory name for this block
233 This is meant to be called automatically by the factory based on
234 the requested block name. If you are creating a block without a factory,
235 you can call this method to set the factory name manually.
240 """Get the name of the instance
242 The name of the instance is used to distinguish between multiple
243 instances of the same block. Most importantly, this will be
244 appended to the names of all algorithms created by this block.
245 This defaults to an empty string, but block implementations
246 should override it with an appropriate name based on identifying
247 options set on this instance. A typical example would be the
248 name of the (main) container, plus potentially the selection or
251 Ideally all blocks should override this method, but for backward
252 compatibility (28 Jul 25) it defaults to an empty string.
258 whether this block should be used for the given configuration
260 This is used by `ConfigSequence` to determine whether this block
261 should be included in the configuration.
263 if self.skipWithSystematics
and not config.noSystematics():
265 if self.skipOnData
and config.dataType()
is DataType.Data:
267 if self.skipOnMC
and config.dataType()
is not DataType.Data:
275 Apply any configuration overrides specified in the block's
276 `propertyOverrides` option. This is meant to be called at the
277 end of the configuration process, after all algorithms have been
278 created and configured.
280 for key, value
in self.propertyOverrides.items():
282 parts = key.split(
'.')
284 raise Exception(f
"Invalid override key format: {key}")
285 alg = config.getAlgorithm(parts[0])
287 raise Exception(f
"Algorithm {parts[0]} not found in config for override: {key}")
288 for name
in parts[1:-1]:
290 if hasattr(alg, name):
291 alg = getattr(alg, name)
293 raise Exception(f
"Tool {name} not found for override: {key}")
295 setattr(alg, parts[-1], value)
299 Add a dependency for the block. Dependency is corresponds to the
300 blockName of another block. If required is True, will throw an
301 error if dependency is not present; otherwise will move this
302 block after the required block. If required is False, will do
303 nothing if required block is not present; otherwise, it will
304 move block after required block.
308 self.
addOption(
'ignoreDependencies', [], type=list,
309 info=
'List of dependencies defined in the ConfigBlock to ignore.')
313 """Return True if there is a dependency."""
317 """Return the list of dependencies. """
321 type, info='', noneAction='ignore', required=False,
322 expertMode=None, meta=None) :
323 """declare the given option on the configuration block
325 This should only be called in the constructor of the
328 NOTE: The backend to option handling is slated to be replaced
329 at some point. This particular function should essentially
330 stay the same, but some behavior may change.
333 raise KeyError (f
'duplicate option: {name}')
334 if type
not in [str, bool, int, float, list, dict,
None] :
335 raise TypeError (f
'unknown option type: {type}')
336 noneActions = [
'error',
'set',
'ignore']
337 if noneAction
not in noneActions :
338 raise ValueError (f
'invalid noneAction: {noneAction} [allowed values: {noneActions}]')
341 if expertMode
is not None:
342 if expertMode
is True:
345 elif not isinstance(expertMode, list):
346 raise TypeError (f
'expertMode must be a list, got {expertMode.__class__}')
352 if not isinstance(meta, dict):
353 raise TypeError(f
'meta must be a dictionary, got {meta.__class__}')
355 unknown =
set(meta) - {
'choices',
'role'}
357 raise ValueError(f
'meta received unknown keys: {unknown}')
359 if 'choices' in meta:
360 choices = meta[
'choices']
361 if (
not isinstance(choices, tuple)
363 or not isinstance(choices[0], list)
364 or not all(isinstance(choice, str)
for choice
in choices[0])
365 or (choices[1]
is not None and not isinstance(choices[1], int))
367 raise TypeError(
"meta['choices'] must be a (list[str], int | None) tuple")
371 if role
not in {
'container',
'containerRef',
'selection',
'region'}:
376 raise ValueError(f
"meta['role'] must be one of 'container', 'containerRef', 'selection', 'region', got '{role}'")
378 setattr (self, name, defaultValue)
380 noneAction=noneAction, required=required,
381 default=defaultValue, meta=meta)
385 """set the given option on the configuration block
387 NOTE: The backend to option handling is slated to be replaced
388 at some point. This particular function should essentially
389 stay the same, but some behavior may change.
393 raise KeyError (f
'unknown option "{name}" in block "{self.__class__.__name__}"')
394 noneAction = self.
_options[name].noneAction
395 if value
is not None or noneAction ==
'set' :
399 if optType
is float
and type(value)
is int:
401 if optType
is not None and optType !=
type(value):
402 raise ValueError(f
'{name} for block {self.__class__.__name__} should '
403 f
'be of type {optType} not {type(value)}')
404 setattr (self, name, value)
405 elif noneAction ==
'ignore' :
407 elif noneAction ==
'error' :
408 raise ValueError (f
'passed None for setting option {name} with noneAction=error')
412 """Returns config option value, if present; otherwise return None"""
414 return getattr(self, name)
418 """Return a copy of the options associated with the block"""
424 Prints options and their values
426 def printWrap(text, width=60, indent=" "):
427 wrapper = textwrap.TextWrapper(width=width, initial_indent=indent,
428 subsequent_indent=indent)
429 for line
in wrapper.wrap(text=text):
430 logCPAlgCfgBlock.info(line)
434 logCPAlgCfgBlock.info(indent + f
"\033[4m{opt}\033[0m: {self.getOptionValue(opt)}")
435 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mtype\033[0m: {vals.type}")
436 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mdefault\033[0m: {vals.default}")
437 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mrequired\033[0m: {vals.required}")
438 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mnoneAction\033[0m: {vals.noneAction}")
439 printWrap(f
"\033[4minfo\033[0m: {vals.info}", indent=indent*2)
441 logCPAlgCfgBlock.info(indent + f
"{ opt}: {self.getOptionValue(opt)}")
445 """whether the configuration block has the given option
447 WARNING: The backend to option handling is slated to be
448 replaced at some point. This particular function may change
449 behavior, interface or be removed/replaced entirely.
456 Implementation of == operator. Used for searching configSeq.
457 E.g. if blockName in configSeq:
469 return ConfigBlock.instance_counts.get(cls, 0)
473 Check whether value matches an expert mode rule.
475 - A literal (compared with ==)
476 - A callable predicate (called with value)
477 - A special marker string (common callable)
482 if isinstance(rule, str):
483 if rule ==
"nonemptystring":
484 return isinstance(value, str)
and value !=
""
485 if rule ==
"nonemptylist":
486 return isinstance(value, list)
and value != []
487 if rule ==
"positiveint":
488 return isinstance(value, int)
and value > 0
495 Check if any settings require expert mode and validate accordingly.
496 If any setting is set to a value that requires expert mode but we're
497 not in expert mode, raise an error.
501 default_value = self.
_options[option_name].default
503 if expert_rule
is True:
505 if current_value != default_value:
507 f
"Block '{self.factoryName()}' option '{option_name}' "
508 f
"set to '{current_value}' (default '{default_value}'), "
509 f
"requires expert mode.",
510 ExpertModeWarning, stacklevel=2
514 for ev
in expert_rule:
517 f
"Block '{self.factoryName()}' option '{option_name}' "
518 f
"set to expert-only value '{current_value}'. "
519 f
"Requires expert mode.",
520 ExpertModeWarning, stacklevel=2
checkExpertSettings(self, config)
applyConfigOverrides(self, config)
addOption(self, name, defaultValue, *, type, info='', noneAction='ignore', required=False, expertMode=None, meta=None)
getOptionValue(self, name)
isUsedForConfig(self, config)
_is_expert_value(self, rule, value)
printOptions(self, verbose=False, width=60, indent=" ")
setOptionValue(self, name, value)
addDependency(self, dependencyName, required=True)
setFactoryName(self, name)
std::string replace(std::string s, const std::string &s2, const std::string &s3)
alphanumeric_block_name(func)
filter_dsids(filterList, config)