5from functools
import wraps
8from AnaAlgorithm.Logging
import logging
9logCPAlgCfgBlock = logging.getLogger(
'CPAlgCfgBlock')
11from AnalysisAlgorithmsConfig.ConfigAccumulator
import DataType, ExpertModeWarning
15 """check whether the sample being run passes a"""
16 """possible DSID filter on the block"""
17 if len(filterList) == 0:
19 for dsid_filter
in filterList:
21 if any(char
in str(dsid_filter)
for char
in "^$*+?.()|[]{}\\"):
22 pattern = re.compile(dsid_filter)
23 if pattern.match(str(config.dsid())):
27 if str(dsid_filter) == str(config.dsid()):
32 """this wrapper ensures that the 'instanceName' of the various """
33 """config blocks is cleaned up of any non-alphanumeric characters """
34 """that may arise from using 'selectionName' in the naming."""
36 def wrapper(*args, **kwargs):
38 orig_name = func(*args, **kwargs)
50 """this meta class enforces the application of 'alphanumeric_block_names()' """
51 """to 'instanceName()' and will be used in the main ConfigBlock class in order """
52 """to propagate this rule also to all derived classes (the individual config blocks."""
55 if 'instanceName' in dct
and callable(dct[
'instanceName']):
57 return super().
__new__(cls, name, bases, dct)
60 """the information for a single option on a configuration block"""
62 def __init__ (self, type=None, info='', noneAction='ignore', required=False,
73 """Class encoding a blocks dependence on other blocks."""
89 return f
'ConfigBlockDependency(blockName="{self.blockName}", required={self.required})'
93 """the base class for classes implementing individual blocks of
96 A configuration block is a sequence of one or more algorithms that
97 should always be scheduled together, e.g. the muon four momentum
98 corrections could be a single block, muon selection could then be
99 another block. The blocks themselves generally have their own
100 configuration options/properties specific to the block, and will
101 perform a dynamic configuration based on those options as well as
104 The actual configuration of the algorithms in the block will
105 depend on what other blocks are scheduled before and afterwards,
106 most importantly some algorithms will introduce shallow copies
107 that subsequent algorithms will need to run on, and some
108 algorithms will add selection decorations that subquent algorithms
109 should use as preselections.
111 The algorithms get created in a multi-step process (that may be
112 extended in the future): As a first step each block retrieves
113 references to the containers it uses (essentially marking its spot
114 in the processing chain) and also registering any shallow copies
115 that will be made. In the second/last step each block then
116 creates the fully configured algorithms.
118 One goal is that when the algorithms get created they will have
119 their final configuration and there needs to be no
120 meta-configuration data attached to the algorithms, essentially an
121 inversion of the approach in AnaAlgSequence in which the
122 algorithms got created first with associated meta-configuration
123 and then get modified in susequent configuration steps.
125 For now this is mostly an empty base class, but another goal of
126 this approach is to make it easier to build another configuration
127 layer on top of this one, and this class will likely be extended
128 and get data members at that point.
130 The child class needs to implement the method `makeAlgs` which is
131 given a single `ConfigAccumulator` type argument. This is meant to
132 create the sequence of algorithms that this block configures. This
133 is currently (28 Jul 2025) called twice and should do the same thing
134 during both calls, but the plan is to change that to a single call.
136 The child class should also implement the method `getInstanceName`
137 which should return a string that is used to distinguish between
138 multiple instances of the same block. This is used to append the
139 instance name to the names of all algorithms created by this block,
140 and may in the future also be used to distinguish between multiple
141 instances of the block.
153 self.
addOption(
'groupName',
'', type=str,
154 info=(
'Used to specify this block when setting an'
155 ' option at an arbitrary location.'))
156 self.
addOption(
'skipOnData',
False, type=bool,
157 info=(
'User option to prevent the block from running'
158 ' on data. This only affects blocks that are'
159 ' intended to run on data.'))
160 self.
addOption(
'skipOnMC',
False, type=bool,
161 info=(
'User option to prevent the block from running'
162 ' on MC. This only affects blocks that are'
163 ' intended to run on MC.'))
164 self.
addOption(
'skipWithSystematics',
False, type=bool,
165 info=(
'User option to prevent the block from running with systematics.'))
166 self.
addOption(
'onlyForDSIDs', [], type=list,
167 info=(
'Used to specify which MC DSIDs to allow this'
168 ' block to run on. Each element of the list'
169 ' can be a full DSID (e.g. 410470), or a regex'
170 ' (e.g. 410.* to select all 410xxx DSIDs, or'
171 ' ^(?!410) to veto them). An empty list means no'
172 ' DSID restriction.'))
173 self.
addOption(
'propertyOverrides', {}, type=dict,
174 info=(
'EXPERT USE ONLY: A dictionary of properties to'
175 ' override at the end of configuration. This should'
177 ' {"algName.toolName.propertyName": value, ...},'
178 ' without any automatically applied postfixes for'
179 ' the algorithm name. THIS IS MEANT TO BE EXPERT'
180 ' USAGE ONLY. Properties that need to be set by'
181 ' the user should be declared as options on the'
182 ' block itself. EXPERT USE ONLY!'),
186 if cls
not in ConfigBlock.instance_counts:
187 ConfigBlock.instance_counts[cls] = 0
190 stack = inspect.stack()
191 for frame_info
in stack:
193 parent_cls = frame_info.frame.f_locals.get(
'self',
None)
194 if parent_cls
is None or not isinstance(parent_cls, ConfigBlock):
196 if frame_info.function ==
"makeConfig":
197 ConfigBlock.instance_counts[cls] += 1
210 """get the factory name for this block
212 This is mostly to give a reliable means of identifying the type
213 of block we have in error messages. This is meant to be
214 automatically set by the factory based on the requested block
215 name, but there are a number of fallbacks. It is best not to
216 assume a specific format, this is mostly meant to be used as an
217 identifier in output messages.
225 return self.__class__.__name__
228 """set the factory name for this block
230 This is meant to be called automatically by the factory based on
231 the requested block name. If you are creating a block without a factory,
232 you can call this method to set the factory name manually.
237 """Get the name of the instance
239 The name of the instance is used to distinguish between multiple
240 instances of the same block. Most importantly, this will be
241 appended to the names of all algorithms created by this block.
242 This defaults to an empty string, but block implementations
243 should override it with an appropriate name based on identifying
244 options set on this instance. A typical example would be the
245 name of the (main) container, plus potentially the selection or
248 Ideally all blocks should override this method, but for backward
249 compatibility (28 Jul 25) it defaults to an empty string.
255 whether this block should be used for the given configuration
257 This is used by `ConfigSequence` to determine whether this block
258 should be included in the configuration.
260 if self.skipWithSystematics
and not config.noSystematics():
262 if self.skipOnData
and config.dataType()
is DataType.Data:
264 if self.skipOnMC
and config.dataType()
is not DataType.Data:
272 Apply any configuration overrides specified in the block's
273 `propertyOverrides` option. This is meant to be called at the
274 end of the configuration process, after all algorithms have been
275 created and configured.
277 for key, value
in self.propertyOverrides.items():
279 parts = key.split(
'.')
281 raise Exception(f
"Invalid override key format: {key}")
282 alg = config.getAlgorithm(parts[0])
284 raise Exception(f
"Algorithm {parts[0]} not found in config for override: {key}")
285 for name
in parts[1:-1]:
287 if hasattr(alg, name):
288 alg = getattr(alg, name)
290 raise Exception(f
"Tool {name} not found for override: {key}")
293 alg.__setattr__(parts[-1], value)
297 Add a dependency for the block. Dependency is corresponds to the
298 blockName of another block. If required is True, will throw an
299 error if dependency is not present; otherwise will move this
300 block after the required block. If required is False, will do
301 nothing if required block is not present; otherwise, it will
302 move block after required block.
306 self.
addOption(
'ignoreDependencies', [], type=list,
307 info=
'List of dependencies defined in the ConfigBlock to ignore.')
311 """Return True if there is a dependency."""
315 """Return the list of dependencies. """
319 type, info='', noneAction='ignore', required=False, expertMode=None) :
320 """declare the given option on the configuration block
322 This should only be called in the constructor of the
325 NOTE: The backend to option handling is slated to be replaced
326 at some point. This particular function should essentially
327 stay the same, but some behavior may change.
330 raise KeyError (f
'duplicate option: {name}')
331 if type
not in [str, bool, int, float, list, dict,
None] :
332 raise TypeError (f
'unknown option type: {type}')
333 noneActions = [
'error',
'set',
'ignore']
334 if noneAction
not in noneActions :
335 raise ValueError (f
'invalid noneAction: {noneAction} [allowed values: {noneActions}]')
338 if expertMode
is not None:
339 if expertMode
is True:
342 elif not isinstance(expertMode, list):
343 raise TypeError (f
'expertMode must be a list, got {type(expertMode)}')
348 setattr (self, name, defaultValue)
350 noneAction=noneAction, required=required, default=defaultValue)
354 """set the given option on the configuration block
356 NOTE: The backend to option handling is slated to be replaced
357 at some point. This particular function should essentially
358 stay the same, but some behavior may change.
362 raise KeyError (f
'unknown option "{name}" in block "{self.__class__.__name__}"')
363 noneAction = self.
_options[name].noneAction
364 if value
is not None or noneAction ==
'set' :
368 if optType
is float
and type(value)
is int:
370 if optType
is not None and optType !=
type(value):
371 raise ValueError(f
'{name} for block {self.__class__.__name__} should '
372 f
'be of type {optType} not {type(value)}')
373 setattr (self, name, value)
374 elif noneAction ==
'ignore' :
376 elif noneAction ==
'error' :
377 raise ValueError (f
'passed None for setting option {name} with noneAction=error')
381 """Returns config option value, if present; otherwise return None"""
383 return getattr(self, name)
387 """Return a copy of the options associated with the block"""
393 Prints options and their values
395 def printWrap(text, width=60, indent=" "):
396 wrapper = textwrap.TextWrapper(width=width, initial_indent=indent,
397 subsequent_indent=indent)
398 for line
in wrapper.wrap(text=text):
399 logCPAlgCfgBlock.info(line)
403 logCPAlgCfgBlock.info(indent + f
"\033[4m{opt}\033[0m: {self.getOptionValue(opt)}")
404 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mtype\033[0m: {vals.type}")
405 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mdefault\033[0m: {vals.default}")
406 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mrequired\033[0m: {vals.required}")
407 logCPAlgCfgBlock.info(indent*2 + f
"\033[4mnoneAction\033[0m: {vals.noneAction}")
408 printWrap(f
"\033[4minfo\033[0m: {vals.info}", indent=indent*2)
410 logCPAlgCfgBlock.info(indent + f
"{ opt}: {self.getOptionValue(opt)}")
414 """whether the configuration block has the given option
416 WARNING: The backend to option handling is slated to be
417 replaced at some point. This particular function may change
418 behavior, interface or be removed/replaced entirely.
425 Implementation of == operator. Used for seaching configSeque.
426 E.g. if blockName in configSeq:
438 return ConfigBlock.instance_counts.get(cls, 0)
442 Check whether value matches an expert mode rule.
444 - A literal (compared with ==)
445 - A callable predicate (called with value)
446 - A special marker string (common callable)
451 if isinstance(rule, str):
452 if rule ==
"nonemptystring":
453 return isinstance(value, str)
and value !=
""
454 if rule ==
"nonemptylist":
455 return isinstance(value, list)
and value != []
456 if rule ==
"positiveint":
457 return isinstance(value, int)
and value > 0
464 Check if any settings require expert mode and validate accordingly.
465 If any setting is set to a value that requires expert mode but we're
466 not in expert mode, raise an error.
470 default_value = self.
_options[option_name].default
472 if expert_rule
is True:
474 if current_value != default_value:
476 f
"Block '{self.factoryName()}' option '{option_name}' "
477 f
"set to '{current_value}' (default '{default_value}'), "
478 f
"requires expert mode.",
479 ExpertModeWarning, stacklevel=2
483 for ev
in expert_rule:
486 f
"Block '{self.factoryName()}' option '{option_name}' "
487 f
"set to expert-only value '{current_value}'. "
488 f
"Requires expert mode.",
489 ExpertModeWarning, stacklevel=2
__init__(self, blockName, required=True)
__init__(self, type=None, info='', noneAction='ignore', required=False, default=None)
checkExpertSettings(self, config)
applyConfigOverrides(self, config)
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)
addOption(self, name, defaultValue, *, type, info='', noneAction='ignore', required=False, expertMode=None)
std::string replace(std::string s, const std::string &s2, const std::string &s3)
alphanumeric_block_name(func)
filter_dsids(filterList, config)