I'm writing a simple script that will be used parse the contents of a JSON file stored on AWS CloudWatch. I've added an argument parser to the script that will accept user input, and allow the user to either print the output of the file to the screen (in a predetermined fashion), or allow them to output the contents to a local JSON file. Here's a snippet of what's stumping me:
import argparse
parser = argparse.ArgumentParser(description="Process a log file")
parser.add_argument('-o', '--output', choices=[???, 'print'],
default='print', help='Specify logfile output path or print to screen')
args = parser.parse_args()
My question stems from the parser.add_argument line, specifically the choices argument. I'd like to allow two inputs for this flag, those being either print or some valid path on their local machine. I'd like the choice that's currently marked by question marks to be a PATH that Python can recognize.
Is there a way, using argparse, to specify that one of of the arguments to a flag must be a PATH? Search results have been, so far, inconclusive.
Thanks in advance!
Use the type keyword argument to add_argument, instead of choices. As the documentation says:
The type keyword argument of add_argument() allows any necessary
type-checking and type conversions to be performed.
def myfiletype(arg):
if arg == 'print' or os.path.isdir(arg):
return arg
else:
raise ValueError('Invalid path specification')
parser.add_argument('-o', '--output', type=myfiletype,
default='print',
help='Specify logfile output path or print to screen')
Define a custom type (as in https://stackoverflow.com/a/14117511/1093967), where the value can be a valid path or 'print'. choices isn't the right choice here.
Related
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--selection', '-s')
parser.add_argument('--choice', '-c', default = argparse.SUPPRESS)
args = parser.parse_args()
def main(selection, choice):
print(selection)
print(choice)
if __name__=='__main__':
main(args.selection, args.choice)
The example provided is just to provide something simple and short that accurately articulates the actual problem I am facing in my project. My goal is to be able to ignore an argument within the code body when it is NOT typed into the terminal. I would like to be able to do this through passing the argument as a parameter for a function. I based my code off of searching 'suppress' in the following link: https://docs.python.org/3/library/argparse.html
When I run the code as is with the terminal input looking like so: python3 stackquestion.py -s cheese, I receive the following error on the line where the function is called:
AttributeError: 'Namespace' object has no attribute 'choice'
I've tried adding the following parameter into parser like so:
parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
I've also tried the above with
parser.add_argument('--choice', '-c')
But I get the same issue on the same line.
#Barmar answered this question in the comments. Using 'default = None' in parser.add_argument works fine; The code runs without any errors. I selected the anser from #BorrajaX because it's a simple solution to my problem.
According to the docs:
Providing default=argparse.SUPPRESS causes no attribute to be added if the command-line argument was not present:
But you're still assuming it will be there by using it in the call to main:
main(args.selection, args.choice)
A suppressed argument won't be there (i.e. there won't be an args.choice in the arguments) unless the caller specifically called your script adding --choice="something". If this doesn't happen, args.choice doesn't exist.
If you really want to use SUPPRESS, you're going to have to check whether the argument is in the args Namespace by doing if 'choice' in args: and operate accordingly.
Another option (probably more common) can be using a specific... thing (normally the value None, which is what argparse uses by default, anyway) to be used as a default, and if args.choice is None, then assume it hasn't been provided by the user.
Maybe you could look at this the other way around: You want to ensure selection is provided and leave choice as optional?
You can try to set up the arguments like this:
parser = argparse.ArgumentParser()
parser.add_argument('--selection', '-s', required=True)
parser.add_argument('--choice', '-c')
args = parser.parse_args()
if __name__ == '__main__':
if args.choice is None:
print("No choice provided")
else:
print(f"Oh, the user provided choice and it's: {args.choice}")
print(f"And selection HAS TO BE THERE, right? {args.selection}")
After looking at about a dozen questions, I can't seem to find an answer.
I have a python CLI i've written using argparse. I have a main command that does nothing but regurgitate help text and then 4 subcommands. My boss wants a very specific output for the help text. He has me write it out as a text file and then we use that text file to display the help text.
However, in some circumstances, it STILL outputs parts of the argparse help text.
For example, if I run my program with no subcommands, it just outputs our help text from the file. But if I use "-h" or "--help" it will output our help text, followed by the list of positional and optional arguments and other argparse stuff. We don't want that.
I could use "add_help=False" but we want the user to be able to type -h and still get our help text. If we set add help to false, it will display our help text followed by the error "-h not recognized".
Also doing this does nothing for when the user uses -h after a subcommand. I set help=None and usage is set to my custom help text for each subcommand, but it still shows the boilerplate argparse info at the end.
This is what I want to happen: user types in the main command with no subcommands prints my custom help text and nothing else. The user types the main command, no subcommand, followed by -h/--help and it prints my custom help text and nothing else. User types in the main command, one of the subcommands, followed by -h/--help and it outputs my help text and nothing else. User types the main command, a subcommand, and then wrong arguments or too many/ too few arguments displays my help text and nothing else. Basically I only ever want it to print nothing, or print just my help text.
how do I do that? here is my main function where the parsers and subparsers are all configured:
def main():
# Import help text from file
p = Path(__file__).with_name("help.txt")
with p.open() as file:
help_text = file.read()
# Configure the top level Parser
parser = argparse.ArgumentParser(prog='myprog', description='My program', usage=help_text)
subparsers = parser.add_subparsers()
# Create Subparsers to give subcommands
parser_one = subparsers.add_parser('subcommandone', prog='subcommandone', usage=help_text, help=None)
parser_one.add_argument('arg1', type=str)
parser_one.add_argument('-o', '--option1', default='mydefault', type=str)
parser_two= subparsers.add_parser('subcommandtwo', usage=help_text, help=None, prog='subcommandtwo')
parser_three= subparsers.add_parser('subcommandthree', usage=help_text, help=None, prog='subcommandthree')
parser_four= subparsers.add_parser('subcommandfour', usage=help_text, help=None, prog='subcommandfour')
# Assign subparsers to their respective functions
parser_one.set_defaults(func=functionone)
parser_two.set_defaults(func=functiontwo)
parser_three.set_defaults(func=functionthree)
parser_four.set_defaults(func=functionfour)
parser.set_defaults(func=base_case)
# Parse the arguments and call appropriate functions
args = parser.parse_args()
if len(sys.argv) == 1:
args.func(args, parser)
else:
args.func(args)
Any thoughts?
You can use sys.exit() after the help text has been displayed, and before the parsing has begun, to avoid problems with "-h not recognized".
So anywhere before the line
# Parse the arguments and call appropriate functions
add
if len(sys.argv) == 1 or '-h' in sys.argv or '--help' in sys.argv:
print(help_text)
sys.exit(1)
In situations where that is not good enough you can subclass argparse.HelpFormatter like so
usage_help_str = 'myscript command [options]'
epilog_str = "More info can be found at https://..."
class Formatter(argparse.HelpFormatter):
# override methods and stuff
def formatter(prog):
return Formatter(prog)
parser = argparse.ArgumentParser(formatter_class=formatter, epilog=epilog_str, usage=usage_help_str, add_help=False)
I tried looking around for documentation on subclassing the helpFormatter, but I couldn't find anything. It looks like people are just looking at the source code to figure out how to subclass it.
I'm trying to parse commands with arguments using the python 3 Built-in argparse module.
I have read the argparse documentation partially, however, I could not find anything that meets my requirements.
I parse the arguments as input (I have my reasons).
I have multiple commands, for each, there are both essential and optional arguments.
For example:
restart --name (the name is replaced)
restart is the command and name is the essential argument.
Currently my code would count the "--" in the input and call the function with corresponding booleans (if --all given, is_all boolean parameter will be true)
I can also add an optional argument --all (all is not replaced).
Sounds like you are looking for something like this
def get_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("--arg1", required=False, default=None)
parser.add_argument("--arg2", required=False, default=None)
return parser.parse_args()
args = get_arguments()
if args.arg1:
# do something
Really hard to answer this without seeing your code or example of what you want.
I'm assuming you're doing something like a shell of sorts. I'm going to also assume that each line entered has a command, each with their own arguments.
from argparse import ArgumentParser
def get_parser(cmd):
'''Returns a parser object for a given command'''
# Instantiate the parser object, add the appropriate arguments for the command
return parser # This is an example -- you need to instantiate it
def main():
while True:
try:
in_line = input('> ')
if not in_line.strip(): # Quit if empty
break
args = in_line.split()
parser = get_parser(args[0])
opts = parser.parser_args(args)
# Do stuff with opts depending on command
except EOFError:
break
except SystemExit:
pass # Prevent failures from killing the program by trapping sys.exit()
using argparse:
parser.add_argument("-o", "--output", help="Log to file")
I want to achieve the following behavior:
The user doesn't specify the -o flag - no logging should be done.
User specifies the -o with nothing - I should log to a default location,
defined within my program.
User specifies -o and a string(path) - I should log there.
Does anyone know the best way to use add_argument to achieve that? I saw a similar example with int values, but in my case, it doesn't get my default value.
You can use nargs='?' for this:
parser.add_argument('-o', '--output',
nargs='?', default=None, const='my_default_location')
If not present, it will produce the default value, if present but without a value it'll use const, otherwise it'll use the supplied value.
Also read through the other examples in the docs, there's a sample for an optional output file which could be useful.
I was wondering if there was a way to use the metavar from argparse be grabbed from elsewhere. Example, there is a -f FILE option, and a -d DIR option. Can I make the help of -d grab the file.metavar or some such?
Maybe:
parser.add_argument("-f", "--file",
metavar = 'FILE',
action="store", dest="file_name", default="foo.bar",
help="Name of the {} to be loaded".format(metavar))
parser.add_argument("-d", "--dir",
metavar = 'DIR',
action = 'store', dest='file_dir', default=".",
help="Directory of {} if it is not current directory".format(option.file_name.metavar)
I know this code is wrong (string doesn't have metavar and options doesn't get set until the parser.parse_args() is run), but I have a few other times I want to just grab the metavar without having a bunch of:
meta_file = 'FILE'
meta_dir = 'DIR'
meta_inquisition = 'SURPRISE'
floating around.
Thanks.
EDIT: s/add_option/add_argument/
In argparse it is add_argument(). This method returns an Action object (actually a subclass of that depending the action parameter). You can access various parameters of that object, either to use, or even change. For example:
action1 = parser.add_argument(
"-f",
"--file",
metavar = "FILE",
dest="file_name",
default="foo.bar",
help="Name of the %(metavar)s to be loaded"
)
action2 = parser.add_argument(
"-d",
"--dir",
metavar="DIR",
dest="file_dir",
default=".",
help="Directory of %s if it is not current directory" % action1.metavar
)
print(action1.metavar) # read the metavar attribute
action2.metvar = "DIRECTORY" # change the metavar attribute
The help reads:
usage: ipython [-h] [-f FILE] [-d DIR]
optional arguments:
-h, --help show this help message and exit
-f FILE, --file FILE Name of the FILE to be loaded
-d DIR, --dir DIR Directory of FILE if it is not current directory
I removed action="store" since that is the default value (no big deal though).
I changed the help values to use %(metavar)s. This is used to incorporate various action attributes. Most commonly it is used for the default.
From the argparse docs:
The help strings can include various format specifiers to avoid repetition of things like the program name or the argument default. The available specifiers include the program name, %(prog)s and most keyword arguments to add_argument(), e.g. %(default)s, %(type)s, etc.:
I am using action1.metavar to place FILE in the help line for action2. It's not a common usage, but I don't see anything wrong with it.
Note that action1.metavar is used once when setting up the parser (to create the action2 help line), and then later when the help is formatted.
In [17]: action2.help
Out[17]: 'Directory of FILE if it is not current directory'
In [18]: action1.help
Out[18]: 'Name of the %(metavar)s to be loaded'
py3 style formatting can be use for action2:
help2 = "Directory of {} if it is not current directory".format(action2.metavar)
action2.help = help2
but py2 style has to be used for action1. Unless you did:
action1.help = "Name of the {} to be loaded".format(action1.metavar)
After creating both actions you even could use:
action1.help = "Name of the {} to be loaded from {}".format(action1.metavar, action2.metavar)
But that's just ordinary Python coding.
According to the docs for the help argument, the help text can contain format specifiers like %(default)s, %(type)s, etc. You can write %(metavar)s and it will be expanded to the value of the metavar (or None if it's not specified).
I don't think there's a way to grab another's argument metavar.