ATLAS Offline Software
AthArgumentParser.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
2 
3 """ Helper wrapper to argparse.ArgumentParser for use in athena
4 
5  The user's options are separated from athena's command line arguments by a
6  single '-' character when athena is called.
7 
8  Public classes:
9  AthArgumentParser
10 
11  Usage:
12  from AthenaCommon.AthArgumentParser import AthArgumentParser
13  parser = AthArgumentParser()
14  parser.add_argument("-a", "--arg", action="store", help="Argument")
15 
16  args = parser.parse_args()
17  print(args.arg) # etc
18 """
19 
20 import argparse
21 import sys
22 import inspect
23 import os
24 from subprocess import Popen, PIPE
25 
26 # import the command line options back from the main process.
27 # This means that this code only runs in something like an athena call.
28 from __main__ import opts
29 
30 
31 class AthArgumentParser(argparse.ArgumentParser):
32  """ Parse user commandline arguments in a job option. """
33 
34  def __init__(self, jo_name=None, prog=None, print_caller_help=True, **kwargs):
35  """ Create the parser
36  ---------
37  Arguments
38  ---------
39  jo_name:
40  The name of the calling job option, used to format the help
41  text. Will attempt to deduce it if possible
42  prog:
43  The start of the usage message, usually can be deduced
44  print_caller_help:
45  Whether or not to print the help text from the calling program
46  (usually athena)
47 
48  All other keyword arguments are forwarded to the base class
49  constructor
50  """
51 
52  caller = sys.argv[0]
53  if prog is None:
54  if jo_name is None:
55  # Use inspect to find which job option we're in. This isn't
56  # completely foolproof but it's just a cosmetic detail so
57  # doesn't need to be
58  for frame in inspect.stack():
59  # For each frame in the stack, check the file name and keep
60  # it if it matches one of the scripts supplied
61  fname = inspect.getfile(frame[0])
62  try:
63  jo_name = next(
64  script for script in opts.scripts if fname.endswith(script)
65  )
66  break
67  except StopIteration:
68  pass
69  else:
70  # fallback name
71  jo_name = "User job option"
72  prog = "{0} {1} [{0} options] -".format(os.path.basename(caller), jo_name)
73  if print_caller_help:
74  # Get the help options for the caller
75  caller_help, _ = Popen(
76  [caller, "--help"], stdout=PIPE, stderr=PIPE
77  ).communicate()
78  # decode from bytes
79  caller_help = caller_help.decode("utf-8")
80  if caller.endswith("athena.py"):
81  # If this is being called from athena, we know that there are
82  # some lines to strip off
83  caller_help = caller_help.split("\n", 3)[3]
84  self.caller_help = "{} options:\n{}".format(
85  os.path.basename(caller), caller_help
86  )
87  else:
88  self.caller_help = ""
89  super().__init__(prog=prog, **kwargs)
90  super().add_argument(
91  "remaining scripts",
92  nargs="*",
93  help="Any remaining scripts to be run by athena after this job option. Mainly used to run the 'post' script on the grid.",
94  )
95 
96  def add_argument(self, *args, **kwargs):
97  """ Add a new argument
98 
99  The difference between this and the base class is that it disallows
100  adding positional arguments which are reserved by athena for job
101  options. In particular, even if user code uses only one job option
102  (which is common) pathena will add more when running on the grid
103  """
104  if not args or len(args) and args[0][0] not in self.prefix_chars:
105  raise ValueError(
106  "Positional arguments are not allowed! Defining them could mess up grid running!"
107  )
108  return super().add_argument(*args, **kwargs)
109 
110  def parse_args(self, args=None, namespace=None):
111  """ Override the base class to use the leftover athena argumnets by
112  default
113  """
114  if args is None:
115  args = opts.user_opts
116  return super().parse_args(args, namespace)
117 
118  def parse_known_args(self, args=None, namespace=None):
119  """ Override the base class to use the leftover athena argumnets by
120  default
121  """
122  if args is None:
123  args = opts.user_opts
124  return super().parse_known_args(args, namespace)
125 
126  def format_help(self):
127  text = super().format_help()
128  return "\n".join([text, self.caller_help])
python.AthArgumentParser.AthArgumentParser.caller_help
caller_help
Definition: AthArgumentParser.py:84
vtune_athena.format
format
Definition: vtune_athena.py:14
python.AthArgumentParser.AthArgumentParser.parse_args
def parse_args(self, args=None, namespace=None)
Definition: AthArgumentParser.py:110
python.AthArgumentParser.AthArgumentParser.parse_known_args
def parse_known_args(self, args=None, namespace=None)
Definition: AthArgumentParser.py:118
python.AthArgumentParser.AthArgumentParser
Definition: AthArgumentParser.py:31
python.AthArgumentParser.AthArgumentParser.format_help
def format_help(self)
Definition: AthArgumentParser.py:126
fillPileUpNoiseLumi.next
next
Definition: fillPileUpNoiseLumi.py:52
python.AthArgumentParser.AthArgumentParser.__init__
def __init__(self, jo_name=None, prog=None, print_caller_help=True, **kwargs)
Definition: AthArgumentParser.py:34
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
python.AthArgumentParser.AthArgumentParser.add_argument
def add_argument(self, *args, **kwargs)
Definition: AthArgumentParser.py:96