ATLAS Offline Software
Loading...
Searching...
No Matches
AthConfigFlags.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3from collections import defaultdict
4from copy import copy, deepcopy
5from difflib import get_close_matches
6from enum import EnumMeta
7from operator import attrgetter
8import glob
9import importlib
10import os
11from AthenaCommon.Logging import logging
12from PyUtils.moduleExists import moduleExists
13
14_msg = logging.getLogger('AthConfigFlags')
15
17 """Return whether or not this is a gaudi-based (athena) environment"""
18
19 return moduleExists('Gaudi')
20
21class CfgFlag(object):
22 """The base flag object.
23
24 A flag can be set to either a fixed value or a callable, which computes
25 the value based on other flags.
26 """
27
28 __slots__ = ['_value', '_setDef', '_type', '_help']
29
30 _compatibleTypes = {
31 (int, float), # int can be assigned to float flag
32 }
33
34 def __init__(self, default, type=None, help=None):
35 """Initialise the flag with the default value.
36
37 Optionally set the type of the flag value and the help string.
38 """
39 if default is None:
40 raise ValueError("Default value of a flag must not be None")
41 self._type = type
42 self._help = help
43 self.set(default)
44 return
45
46 def set(self, value):
47 """Set the value of the flag.
48
49 Can be a constant value or a callable.
50 """
51 if callable(value):
52 self._value=None
53 self._setDef=value
54 else:
55 self._value=value
56 self._setDef=None
57 self._validateType(self._value)
58 return
59
60 def get(self, flagdict=None):
61 """Get the value of the flag.
62
63 If the currently set value is a callable, a dictionary of all available
64 flags needs to be provided.
65 """
66
67 if self._value is not None:
68 return deepcopy(self._value)
69
70 # For cases where the value is intended to be None
71 # i.e. _setDef applied this value, we should not progress
72 if self._setDef is None:
73 return None
74
75 if not flagdict:
76 raise RuntimeError("Flag is using a callable but all flags are not available.")
77
78 # Have to call the method to obtain the default value, and then reuse it in all next accesses
79 if flagdict.locked():
80 # optimise future reads, drop possibility to update this flag ever
81 self._value = self._setDef(flagdict)
82 self._setDef = None
83 value = self._value
84 else:
85 # use function for as long as the flags are not locked
86 value = self._setDef(flagdict)
87
88 self._validateType(value)
89 return deepcopy(value)
90
91 def __repr__(self):
92 if self._value is not None:
93 return repr(self._value)
94 else:
95 return "[function]"
96
97 def _validateType(self, value):
98 if (self._type is None or value is None or
99 isinstance(value, self._type) or
100 (type(value), self._type) in self._compatibleTypes):
101 return
102 # Type mismatch
103 raise TypeError(f"Flag is of type '{self._type.__name__}', "
104 f"but value '{value}' of type '{type(value).__name__}' set.")
105
106
107def _asdict(iterator):
108 """Flags to dict converter
109
110 Used by both FlagAddress and AthConfigFlags. The input must be an
111 iterator over flags to be included in the dict.
112 """
113 outdict = {}
114 for key, item in iterator:
115 x = outdict
116 subkeys = key.split('.')
117 for subkey in subkeys[:-1]:
118 x = x.setdefault(subkey,{})
119 x[subkeys[-1]] = item
120 return outdict
121
122
124 """Proxy for a flags category"""
125
126 __slots__ = ('_flags', '_name')
127
128 def __init__(self, flag, name):
129 if type(flag) is not AthConfigFlags:
130 raise TypeError(f"cannot create FlagAddress for object {name} of type {type(flag)}")
131
132 self._flags = flag
133 self._name = name
134
135 # Handle renames
136 self._name = self._flags._renames.get(self._name, self._name)
137 if self._name is None:
138 raise AttributeError(f"accessing category '{name}' has been blocked by cloneAndReplace")
139
140 def __getattr__(self, name):
141 return getattr(self._flags, f"{self._name}.{name}")
142
143 def __setattr__( self, name, value ):
144 if name.startswith("_"):
145 return super().__setattr__(name, value)
146
147 merged = f"{self._name}.{name}"
148 if merged not in self._flags._flagdict: # flag is missing, try loading dynamic ones
149 self._flags._loadDynaFlags( merged )
150
151 return setattr(self._flags, merged, value)
152
153 def __delattr__(self, name):
154 del self[name]
155
156 def __cmp__(self, other):
157 raise TypeError( f"cannot compare flags category '{self._name}' to a value" )
158 __eq__ = __cmp__
159 __ne__ = __cmp__
160 __lt__ = __cmp__
161 __le__ = __cmp__
162 __gt__ = __cmp__
163 __ge__ = __cmp__
164
165 def __bool__(self):
166 raise TypeError( f"cannot convert flags category '{self._name}' to a boolean" )
167
168 def __getitem__(self, name):
169 try:
170 return getattr(self, name)
171 except AttributeError as e:
172 raise KeyError(e) # convert exception to follow Python convention for [] operator
173
174 def __setitem__(self, name, value):
175 try:
176 setattr(self, name, value)
177 except AttributeError as e:
178 raise KeyError(e) # convert exception to follow Python convention for [] operator
179
180 def __delitem__(self, name):
181 del self._flags[f"{self._name}.{name}"]
182
183 def __contains__(self, name):
184 return hasattr(self, name)
185
186 def __iter__(self):
187 self._flags.loadAllDynamicFlags()
188 rmap = self._flags._renamed_map()
189 used = set()
190 prefix = self._name.rstrip('.') + '.'
191 for flag in self._flags._flagdict.keys():
192 if flag.startswith(prefix):
193 for newflag in rmap[flag]:
194 ntrim = len(self._name) + 1
195 n_dots_in = flag[:ntrim].count('.')
196 remaining = newflag.split('.')[n_dots_in]
197 if remaining not in used:
198 yield remaining
199 used.add(remaining)
200
201 def _subflag_itr(self):
202 """Subflag iterator specialized for this address"""
203 self._flags.loadAllDynamicFlags()
204 address = self._name
205 prefix = address.rstrip('.') + '.'
206 rename = self._flags._renamed_map()
207 for key in self._flags._flagdict.keys():
208 if key.startswith(prefix):
209 ntrim = len(address) + 1
210 remaining = key[ntrim:]
211 for r in rename[key]:
212 yield r, getattr(self, remaining)
213
214 def asdict(self):
215 """Convert to a python dictionary
216
217 Recursively convert this flag and all subflags into a
218 structure of nested dictionaries. All dynamic flags are
219 resolved in the process.
220
221 The resulting data structure should be easy to serialize as
222 json or yaml.
223 """
224 d = _asdict(self._subflag_itr())
225 for k in self._name.split('.'):
226 d = d[k]
227 return d
228
229
230class AthConfigFlags(object):
231
232 # A list of all flags instances for which we've returned a hash.
233 # We can't allow them to be deleted; otherwise, we might get new
234 # flags object with the same hash.
235 _hashedFlags = []
236
237 def __init__(self):
238 self._flagdict=dict()
239 self._locked=False
240 self._dynaflags = dict()
241 self._loaded = set() # dynamic flags that were loaded
242 self._categoryCache = set() # cache for already found categories
243 self._hash = None
244 self._parser = None
245 self._args = None # user args from parser
246 self._renames = {}
247
248 def athHash(self):
249 if self._locked is False:
250 raise RuntimeError("Cannot calculate hash of unlocked flag container")
251 elif self._hash is None:
252 self._hash = self._calculateHash()
253 return self._hash
254
255 def _calculateHash(self):
256 # Once we've hashed a flags instance, we need to be sure that
257 # it never goes away. Otherwise, since we base the hash
258 # on just the id of the dictionary, if a flags object is deleted
259 # and a new one created, the hash of the new one could match the
260 # hash of the old, even if contents are different.
261 # See ATLASRECTS-8070.
262 AthConfigFlags._hashedFlags.append (self)
263 return hash( (frozenset({k: v for k, v in self._renames.items() if k != v}), id(self._flagdict)) )
264
265 def __getattr__(self, name):
266 # Avoid infinite recursion looking up our own attributes
267 _flagdict = object.__getattribute__(self, "_flagdict")
268
269 # First try to get an already loaded flag or category.
270 # Note: Check and lookup is faster than try/except here. Because for nested
271 # flags a failure is normal before we descend into the category.
272 if name in _flagdict:
273 return _flagdict[name].get(self)
274
275 # Check (and load if needed) dynamic flags
276 if self.hasCategory(name):
277 return FlagAddress(self, name)
278
279 raise AttributeError(f"No such flag: {name}")
280
281 def __setattr__(self, name, value):
282 if name.startswith("_"):
283 return object.__setattr__(self, name, value)
284
285 self._tryModify()
286 try:
287 self._flagdict[name].set(value)
288 except KeyError:
289 closestMatch = get_close_matches(name,self._flagdict.keys(),1)
290 raise KeyError(f"No flag with name '{name}' found" +
291 (f". Did you mean '{closestMatch[0]}'?" if closestMatch else ""))
292
293 def __delattr__(self, name):
294 del self[name]
295
296 def __getitem__(self, name):
297 try:
298 return getattr(self, name)
299 except AttributeError as e:
300 raise KeyError(e) # convert exception to follow Python convention for [] operator
301
302 def __setitem__(self, name, value):
303 try:
304 setattr(self, name, value)
305 except AttributeError as e:
306 raise KeyError(e) # convert exception to follow Python convention for [] operator
307
308 def __delitem__(self, name):
309 self._tryModify()
311 for key in list(self._flagdict):
312 if key.startswith(name):
313 del self._flagdict[key]
314 self._categoryCache.clear()
315
316 def __contains__(self, name):
317 return hasattr(self, name)
318
319 def __iter__(self):
321 rmap = self._renamed_map()
322 used = set()
323 for flag in self._flagdict:
324 for r in rmap[flag]:
325 first = r.split('.',1)[0]
326 if first not in used:
327 yield first
328 used.add(first)
329
330 def asdict(self):
331 """Convert to a python dictionary
332
333 This is identical to the `asdict` in FlagAddress, but for all
334 the flags.
335
336 """
337 return _asdict(self._subflag_itr())
338
339
340 def _renamed_map(self):
341 """mapping from the old names to the new names
342
343 This is the inverse of _renamed, which maps new names to old
344 names
345
346 Returns a list of the new names corresponding to the old names
347 (since cloneAndReplace may or may not disable access to the old name,
348 it is possible that an old name renames to multiple new names)
349 """
350 revmap = defaultdict(list)
351
352 for new, old in self._renames.items():
353 if old is not None:
354 revmap[old].append(new)
355
356 def rename(key):
357 for old, newlist in revmap.items():
358 if key.startswith(old + '.'):
359 stem = key.removeprefix(old)
360 return [ f'{new}{stem}' if new else '' for new in newlist ]
361 return [ key ]
362
363 return {x:rename(x) for x in self._flagdict.keys()}
364
365 def _subflag_itr(self):
366 """Subflag iterator for all flags
367
368 This is used by the asdict() function.
369 """
371
372 for old, newlist in self._renamed_map().items():
373 for new in newlist:
374 # Lots of modules are missing in analysis releases. I
375 # tried to prevent imports using the _addFlagsCategory
376 # function which checks if some module exists, but this
377 # turned in to quite a rabbit hole. Catching and ignoring
378 # the missing module exception seems to work, even if it's
379 # not pretty.
380 try:
381 yield new, getattr(self, old)
382 except ModuleNotFoundError as err:
383 _msg.debug(f'missing module: {err}')
384 pass
385
386 def addFlag(self, name, setDef, type=None, help=None):
387 self._tryModify()
388 if name in self._flagdict:
389 raise KeyError("Duplicated flag name: {}".format( name ))
390 self._flagdict[name]=CfgFlag(setDef, type, help)
391 return
392
393 def addFlagsCategory(self, path, generator, prefix=False):
394 """
395 The path is the beginning of the flag name (e.g. "X" for flags generated with name "X.*").
396 The generator is a function that returns a flags container, the flags have to start with the same path.
397 When the prefix is True the flags created by the generator are prefixed by "path".
398
399 Supported calls are then:
400 addFlagsCategory("A", g) - where g is function creating flags is f.addFlag("A.x", someValue)
401 addFlagsCategory("A", g, True) - when flags are defined in g like this: f.addFalg("x", somevalue),
402 The latter option allows to share one generator among flags that are later loaded in different paths.
403 """
404 self._tryModify()
405 _msg.debug("Adding flag category %s", path)
406 self._dynaflags[path] = (generator, prefix)
407
408 def needFlagsCategory(self, name):
409 """ public interface for _loadDynaFlags """
410 self._loadDynaFlags( name )
411
412 def _loadDynaFlags(self, name):
413 """
414 loads the flags of the form "A.B.C" first attempting the path "A" then "A.B" and then "A.B.C"
415 """
416
417 def __load_impl( flagBaseName ):
418 if flagBaseName in self._loaded:
419 _msg.debug("Flags %s already loaded",flagBaseName )
420 return
421 if flagBaseName in self._dynaflags:
422 _msg.debug("Dynamically loading the flags under %s", flagBaseName )
423 # Retain locked status and hash
424 isLocked = self._locked
425 myHash = self._hash
426 self._locked = False
427 generator, prefix = self._dynaflags[flagBaseName]
428 self.join( generator(), flagBaseName if prefix else "" )
429 self._locked = isLocked
430 self._hash = myHash
431 del self._dynaflags[flagBaseName]
432 self._loaded.add(flagBaseName)
433
434 pathfrags = name.split('.')
435 for maxf in range(1, len(pathfrags)+1):
436 __load_impl( '.'.join(pathfrags[:maxf]) )
437
439 """Force load all the dynamic flags """
440 while len(self._dynaflags) != 0:
441 # Need to convert to a list since _loadDynaFlags may change the dict.
442 for prefix in list(self._dynaflags.keys()):
443 self._loadDynaFlags( prefix )
444
445 def hasCategory(self, name):
446 """Check if category exists (loads dynamic flags if needed)"""
447 # We cache successfully found categories
448 if name in self._categoryCache:
449 return True
450
451 if (re_name := self._renames.get(name)) is not None and re_name != name:
452 return self.hasCategory(re_name)
453
454 # Load dynamic flags if needed
455 self._loadDynaFlags(name)
456
457 # If not found do search through all keys.
458 # TODO: could be improved by using a trie for _flagdict
459 for f in self._flagdict.keys():
460 if f.startswith(name+'.'):
461 self._categoryCache.add(name)
462 return True
463 for c in self._dynaflags.keys():
464 if c.startswith(name):
465 self._categoryCache.add(name)
466 return True
467
468 return False
469
470 def hasFlag(self, name):
471 """Check if flag exists (loads dynamic flags if needed)"""
472 # Use attrgetter to check if attribute exists. As opposed to getattr,
473 # this also supports nested attributes, which is required to trigger loading
474 # of dynamics flags.
475 try:
476 attrgetter(name)(self)
477 # Now check if flag was found (taking into account renames)
478 return any(name in x for x in self._renamed_map().values())
479 except AttributeError:
480 return False
481
482 def lock(self):
483 if not self._locked:
484 # before locking, parse args if a parser was defined
485 if self._args is None and self._parser is not None: self.fillFromArgs()
486 self._locked = True
487 return
488
489 def locked(self):
490 return self._locked
491
492 def _tryModify(self):
493 if self._locked:
494 raise RuntimeError("Attempt to modify locked flag container")
495 else:
496 # if unlocked then invalidate hash
497 self._hash = None
498
499 def clone(self):
500 """Return an unlocked copy of self (dynamic flags are not loaded)"""
501 cln = AthConfigFlags()
502 cln._flagdict = deepcopy(self._flagdict)
503 cln._dynaflags = copy(self._dynaflags)
504 cln._renames = deepcopy(self._renames)
505 return cln
506
507
508 def cloneAndReplace(self,subsetToReplace,replacementSubset, keepOriginal=False):
509 """
510 This is to replace subsets of configuration flags like
511
512 Example:
513 newflags = flags.cloneAndReplace('Muon', 'Trigger.Offline.Muon')
514 """
515
516 _msg.debug("cloning flags and replacing %s by %s", subsetToReplace, replacementSubset)
517
518 self._loadDynaFlags( subsetToReplace )
519 self._loadDynaFlags( replacementSubset )
520
521 subsetToReplace = subsetToReplace.strip(".")
522 replacementSubset = replacementSubset.strip(".")
523
524 #Sanity check: Don't replace a by a
525 if (subsetToReplace == replacementSubset):
526 raise RuntimeError(f'Can not replace flags {subsetToReplace} with themselves')
527
528 # protect against subsequent remaps within remaps: clone = flags.cloneAndReplace('Y', 'X').cloneAndReplace('X.b', 'X.a')
529 for alias,src in self._renames.items():
530 if src is None: continue
531 if src+"." in subsetToReplace:
532 raise RuntimeError(f'Can not replace flags {subsetToReplace} by {replacementSubset} because of already present replacement of {alias} by {src}')
533
534
535 newFlags = copy(self) # shallow copy
536 newFlags._renames = deepcopy(self._renames) #maintains renames
537
538 if replacementSubset in newFlags._renames: #and newFlags._renames[replacementSubset]:
539 newFlags._renames[subsetToReplace] = newFlags._renames[replacementSubset]
540 else:
541 newFlags._renames[subsetToReplace] = replacementSubset
542
543 if not keepOriginal:
544 if replacementSubset not in newFlags._renames or newFlags._renames[replacementSubset] == replacementSubset:
545 newFlags._renames[replacementSubset] = None # block access to original flags
546 else:
547 del newFlags._renames[replacementSubset]
548 #If replacementSubset was a "pure renaming" of another set of flags,
549 #the original set of flags gets propagated down to its potential further renamings:
550 #no need to worry about maintaining the intermediate steps in the renaming.
551 else:
552 if replacementSubset not in newFlags._renames:
553 newFlags._renames[replacementSubset] = replacementSubset
554 #For _renamed_map to know that these flags still work.
555 newFlags._hash = None
556 return newFlags
557
558
559 def join(self, other, prefix=''):
560 """
561 Merges two flag containers
562 When the prefix is passed each flag from the "other" is prefixed by "prefix."
563 """
564 self._tryModify()
565
566 for (name,flag) in other._flagdict.items():
567 fullName = prefix+"."+name if prefix != "" else name
568 if fullName in self._flagdict:
569 raise KeyError("Duplicated flag name: {}".format( fullName ) )
570 self._flagdict[fullName]=flag
571
572 for (name,loader) in other._dynaflags.items():
573 fullName = prefix+"."+name if prefix != "" else name
574 if fullName in self._dynaflags:
575 raise KeyError("Duplicated dynamic flags name: {}".format( fullName ) )
576 _msg.debug("Joining dynamic flags with %s", fullName)
577 self._dynaflags[fullName] = loader
578 return
579
580 def dump(self, pattern=".*", evaluate=False, formatStr="{:40} : {}", maxLength=None):
581 import re
582 compiled = re.compile(pattern)
583 def truncate(s): return s[:maxLength] + ("..." if maxLength and len(s)>maxLength else "")
584 reverse_renames = {value: key for key, value in self._renames.items() if value is not None} # new name to old
585 for name in sorted(self._flagdict):
586 renamed = name
587 if any([name.startswith(r) for r in reverse_renames.keys() if r is not None]):
588 for oldprefix, newprefix in reverse_renames.items():
589 if name.startswith(oldprefix):
590 renamed = name.replace(oldprefix, newprefix)
591 break
592 if compiled.match(renamed):
593 if evaluate:
594 try:
595 rep = repr(self._flagdict[name] )
596 val = repr(self._flagdict[name].get(self))
597 if val != rep:
598 print(formatStr.format(renamed,truncate("{} {}".format( val, rep )) ))
599 else:
600 print(formatStr.format(renamed, truncate("{}".format(val)) ) )
601 except Exception as e:
602 print(formatStr.format(renamed, truncate("Exception: {}".format( e )) ))
603 else:
604 print(formatStr.format( renamed, truncate("{}".format(repr(self._flagdict[name] ) )) ))
605
606 if len(self._dynaflags) != 0 and any([compiled.match(x) for x in self._dynaflags.keys()]):
607 print("Flag categories that can be loaded dynamically")
608 print("{:25} : {:>30} : {}".format( "Category","Generator name", "Defined in" ) )
609 for name,gen_and_prefix in sorted(self._dynaflags.items()):
610 if compiled.match(name):
611 print("{:25} : {:>30} : {}".format( name, gen_and_prefix[0].__name__, '/'.join(gen_and_prefix[0].__code__.co_filename.split('/')[-2:]) ) )
612 if len(self._renames):
613 print("Flag categories that are redirected by the cloneAndReplace")
614 for alias,src in self._renames.items():
615 print("{:30} points to {:>30} ".format( alias, src if src else "nothing") )
616
617
618 def initAll(self):
619 """
620 Mostly a self-test method
621 """
622 for n,f in list(self._flagdict.items()):
623 f.get(self)
624 return
625
626
627 def getArgumentParser(self, **kwargs):
628 """
629 Scripts calling AthConfigFlags.fillFromArgs can extend this parser, and pass their version to fillFromArgs
630 """
631 import argparse
632 from AthenaCommon.AthOptionsParser import getArgumentParser
633 parser = getArgumentParser(**kwargs)
634 parser.add_argument("---",dest="terminator",action='store_true', help=argparse.SUPPRESS) # special hidden option required to convert option terminator -- for --help calls
635
636 return parser
637
638 def parser(self):
639 if self._parser is None: self._parser = self.getArgumentParser()
640 return self._parser
641
642 def args(self):
643 return self._args
644
645
646 def fillFromString(self, flag_string):
647 """Fill the flags from a string of type key=value"""
648 import ast
649
650 try:
651 key, value = flag_string.split("=")
652 except ValueError:
653 raise ValueError(f"Cannot interpret argument {flag_string}, expected a key=value format")
654
655 key = key.strip()
656 value = value.strip()
657
658 # also allow key+=value to append
659 oper = "="
660 if (key[-1]=="+"):
661 oper = "+="
662 key = key[:-1]
663
664 if key not in self._flagdict:
665 self._loadDynaFlags( '.'.join(key.split('.')[:-1]) ) # for a flag A.B.C dynamic flags from category A.B
666 if key not in self._flagdict:
667 raise KeyError(f"{key} is not a known configuration flag")
668
669 flag_type = self._flagdict[key]._type
670 if flag_type is None:
671 # Regular flag
672 try:
673 ast.literal_eval(value)
674 except Exception: # Can't determine type, assume we got an un-quoted string
675 value=f"\"{value}\""
676
677 elif isinstance(flag_type, EnumMeta):
678 # Flag is an enum, so we need to import the module containing the enum
679 ENUM = importlib.import_module(flag_type.__module__) # noqa: F841 (used in exec)
680 value=f"ENUM.{value}"
681
682 # Set the value (this also does the type checking if needed)
683 exec(f"self.{key}{oper}{value}")
684
685
686 # parser argument must be an ArgumentParser returned from getArgumentParser()
687 def fillFromArgs(self, listOfArgs=None, parser=None, return_unknown=False):
688 """
689 Used to set flags from command-line parameters, like flags.fillFromArgs(sys.argv[1:])
690
691 if return_unknown=False, returns: args
692 otherwise returns: args, uknown_args
693 where unknown_args is the list of arguments that did not correspond to one of the flags
694 """
695 import sys
696
697 self._tryModify()
698
699 if parser is None:
700 parser = self.parser()
701 self._parser = parser # set our parser to given one
702 argList = listOfArgs if listOfArgs is not None else sys.argv[1:]
703 do_help = False
704 # We will now do a pre-parse of the command line arguments to propagate these to the flags
705 # the reason for this is so that we can use the help messaging to display the values of all
706 # flags as they would be *after* any parsing takes place. This is nice to see e.g. the value
707 # that any derived flag (functional flag) will take after, say, the filesInput are set
708 import argparse
709 unrequiredActions = []
710 if "-h" in argList or "--help" in argList:
711 do_help = True
712 if "-h" in argList: argList.remove("-h")
713 if "--help" in argList: argList.remove("--help")
714 # need to unrequire any required arguments in order to do a "pre parse"
715 for a in parser._actions:
716 if a.required:
717 unrequiredActions.append(a)
718 a.required = False
719 (args,leftover)=parser.parse_known_args(argList)
720 for a in unrequiredActions: a.required=True
721
722 # remove the leftovers from the argList ... for later use in the do_help
723 argList = [a for a in argList if a not in leftover]
724
725 # First, handle athena.py-like arguments (if available in parser):
726 def arg_set(dest):
727 """Check if dest is available in parser and has been set"""
728 return vars(args).get(dest, None) is not None
729
730 if arg_set('debug'):
731 self.Exec.DebugStage=args.debug
732
733 if arg_set('evtMax'):
734 self.Exec.MaxEvents=args.evtMax
735
736 if arg_set('interactive'):
737 self.Exec.Interactive=args.interactive
738
739 if arg_set('skipEvents'):
740 self.Exec.SkipEvents=args.skipEvents
741
742 if arg_set('filesInput'):
743 self.Input.Files = [] # remove generic
744 for f in args.filesInput.split(","):
745 found = glob.glob(f)
746 # if not found, add string directly
747 self.Input.Files += found if found else [f]
748
749 if "-l" in argList or "--loglevel" in argList: # different check b.c. has a default value so will always be in args
750 from AthenaCommon import Constants
751 self.Exec.OutputLevel = getattr(Constants, args.loglevel)
752
753 if arg_set('config_only') and args.config_only is not False:
754 from os import environ
755 environ["PICKLECAFILE"] = "" if args.config_only is True else args.config_only
756
757 if arg_set('threads'):
758 self.Concurrency.NumThreads = args.threads
759 #Work-around a possible inconsistency of NumThreads and NumConcurrentEvents that may
760 #occur when these values are set by the transforms and overwritten by --athenaopts ..
761 #See also ATEAM-907
762 if args.concurrent_events is None and self.Concurrency.NumConcurrentEvents==0:
763 self.Concurrency.NumConcurrentEvents = args.threads
764
765 if arg_set('concurrent_events'):
766 self.Concurrency.NumConcurrentEvents = args.concurrent_events
767
768 if arg_set('nprocs'):
769 self.Concurrency.NumProcs = args.nprocs
770
771 if arg_set('perfmon'):
772 from PerfMonComps.PerfMonConfigHelpers import setPerfmonFlagsFromRunArgs
773 setPerfmonFlagsFromRunArgs(self, args)
774
775 if arg_set('mtes'):
776 self.Exec.MTEventService = args.mtes
777
778 if arg_set('mtes_channel'):
779 self.Exec.MTEventServiceChannel = args.mtes_channel
780
781 if arg_set('profile_python'):
782 from AthenaCommon.Debugging import dumpPythonProfile
783 import atexit, cProfile, functools
784 cProfile._athena_python_profiler = cProfile.Profile()
785 cProfile._athena_python_profiler.enable()
786
787 # Save stats to file at exit
788 atexit.register(functools.partial(dumpPythonProfile, args.profile_python))
789
790 if arg_set('mpi'):
791 self.Exec.MPI = args.mpi
792
793 # All remaining arguments are assumed to be key=value pairs to set arbitrary flags:
794 unknown_args = []
795 for arg in leftover:
796 if arg=='--':
797 argList += ["---"]
798 continue # allows for multi-value arguments to be terminated by a " -- "
799 if do_help and '=' not in arg:
800 argList += arg.split(".") # put arg back back for help (but split by sub-categories)
801 continue
802 try:
803 self.fillFromString(arg)
804 except (KeyError,ValueError) as e:
805 if return_unknown:
806 unknown_args += [arg]
807 else:
808 raise e
809
810 if do_help:
811 if parser.epilog is None: parser.epilog=""
812 parser.epilog += " Note: Specify additional flags in form <flagName>=<value>."
813 subparsers = {"":[parser,parser.add_subparsers(help=argparse.SUPPRESS)]} # first is category's parser, second is subparsers (effectively the category's subcategories)
814 # silence logging and ROOT errors while evaluating flags
815 logging.root.setLevel(logging.ERROR)
816 os.environ["TDAQ_ERS_WARNING"] = "null"
817 os.environ["TDAQ_ERS_ERROR"] = "null"
818 import ROOT
819 ignoreLevel = ROOT.gErrorIgnoreLevel
820 ROOT.gErrorIgnoreLevel=ROOT.kFatal
821
822 def getParser(category): # get parser for a given category
823 if category not in subparsers.keys():
824 cat1,cat2 = category.rsplit(".",1) if "." in category else ("",category)
825 p,subp = getParser(cat1)
826 if subp.help==argparse.SUPPRESS:
827 subp.help = "Flag subcategories:"
828 newp = subp.add_parser(cat2,help="{} flags".format(category),
829 formatter_class = argparse.ArgumentDefaultsHelpFormatter,usage=argparse.SUPPRESS)
830 newp._positionals.title = "flags"
831 subparsers[category] = [newp,newp.add_subparsers(help=argparse.SUPPRESS)]
832 return subparsers[category]
834 for name in sorted(self._flagdict):
835 category,flagName = name.rsplit(".",1) if "." in name else ("",name)
836 flag = self._flagdict[name]
837 try:
838 val = repr(flag.get(self))
839 except Exception:
840 val = None
841 if flag._help != argparse.SUPPRESS:
842 helptext = ""
843 if flag._help is not None:
844 helptext = f": {flag._help}"
845 if flag._type is not None:
846 helptext += f' [type: {flag._type.__name__}]'
847 if val is not None and helptext == "":
848 helptext = ": " # ensures default values are displayed even if there's no help text
849 getParser(category)[0].add_argument(name, nargs='?', default=val, help=helptext)
850
851 parser._positionals.title = 'flags and positional arguments'
852 parser.parse_known_args(argList + ["--help"])
853 ROOT.gErrorIgnoreLevel=ignoreLevel # this is quite unnecessary since we are about to exit, but people worry about touching msg levels
854
855 self._args = args
856
857 if return_unknown:
858 return args,unknown_args
859 else:
860 return args
861
862
863
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
void clear()
Empty the pool.
void print(char *figname, TCanvas *c1)
cloneAndReplace(self, subsetToReplace, replacementSubset, keepOriginal=False)
fillFromArgs(self, listOfArgs=None, parser=None, return_unknown=False)
addFlag(self, name, setDef, type=None, help=None)
addFlagsCategory(self, path, generator, prefix=False)
__init__(self, default, type=None, help=None)
STL class.
bool add(const std::string &hname, TKey *tobj)
Definition fastadd.cxx:55
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:132
int count(std::string s, const std::string &regx)
count how many occurances of a regx are in a string
Definition hcg.cxx:148
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:179
-event-from-file