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