ATLAS Offline Software
Loading...
Searching...
No Matches
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
20import argparse
21import sys
22import inspect
23import os
24from 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.
28from __main__ import opts
29
30
31class 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])
__init__(self, jo_name=None, prog=None, print_caller_help=True, **kwargs)
parse_args(self, args=None, namespace=None)
parse_known_args(self, args=None, namespace=None)