python argparse - either both optional arguments or else neither one - python

I have a program that uses a default name and password. I'm using argparse to allow the user to specify command line options, and I would like to enable the user to provide the program with a different name and password to use. So I have the following:
parser.add_argument(
'-n',
'--name',
help='the login name that you wish the program to use'
)
parser.add_argument(
'-p',
'--password',
help='the password to log in with.'
)
But it doesn't make any sense to specify only the name or only the password, but it would make sense to specify neither one. I noticed that argparse does have the ability to specify that two arguments are mutually exclusive. But what I have are two arguments that must appear together. How do I get this behavior? (I found "argument groups" mentioned in the docs, but they don't appear to solve my problem http://docs.python.org/2/library/argparse.html#argument-groups)

I believe that the best way to handle this is to post-process the returned namespace. The reason that argparse doesn't support this is because it parses arguments 1 at a time. It's easy for argparse to check to see if something was already parsed (which is why mutually-exclusive arguments work), but it isn't easy to see if something will be parsed in the future.
A simple:
parser.add_argument('-n','--name',...,default=None)
parser.add_argument('-p','--password',...,default=None)
ns = parser.parse_args()
if len([x for x in (ns.name,ns.password) if x is not None]) == 1:
parser.error('--name and --password must be given together')
name = ns.name if ns.name is not None else "default_name"
password = ns.password if ns.password is not None else "default_password"
seems like it would suffice.

I know this is more than two years late, but I found a nice and concise way to do it:
if bool(ns.username) ^ bool(ns.password):
parser.error('--username and --password must be given together')
^ is the XOR operator in Python. To require both arguments given at the command line is essentially an XOR test.

This is probably how I'd do it. Since you have existing defaults with the option to change, define the defaults, but don't use them as your argument defaults:
default_name = "x"
default_pass = "y"
parser.add_argument(
'-n',
'--name',
default=None,
help='the login name that you wish the program to use'
)
parser.add_argument(
'-p',
'--password',
default=None,
help='the password to log in with.'
)
args = parser.parse_args()
if all(i is not None for i in [args.name, args.password]):
name = args.name
passwd = args.password
elif any(i is not None for i in [args.name, args.password]):
parser.error("Both Name and Password are Required!")
else:
name = default_name
passwd = default_pass

Related

How do I set a default value for flag in argparse if the flag is given alone [duplicate]

This question already has answers here:
Argparse optional argument with different default if specified without a value
(2 answers)
Closed 3 months ago.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-c',
'--cookies',
nargs='?',
default=5,
type=int,
)
args = parser.parse_args()
if args.cookies:
print('cookies flag is set: ' + args.cookies)
else:
print('cookies flag not set: ' + str(args.cookies))
I want it to work so that if the user gives -c then we know they want cookies, but we don't know how many cookies they want so we give them 5 by default (-c == 5 :).
If the user types -c 25 then we know they want 25 cookies.
If the user does not give a -c flag then we know they do not want cookies and cookies flag should not be set.
The way it works as above is that -c == 5 only when -c is not set by the user. But we do not want to give them cookies if they do not ask for it!
If they ask for a specific amount of cookies (ex: -c 10), then the code above works fine.
I fixed this problem by using a short custom action that checks if the flag is set and if no value is passed in I give it the default value.
This seems a bit convoluted and there must be an easier way. I've searched the argparse docs (looked at nargs, default, and const) but couldn't figure out a solution.
Any ideas? Thank you for your time.
You're looking for the const parameter, which the docs don't do a very good job of explaining.
default always sets the value, even if the flag is not provided, unless it is overridden by a user input.
const only sets the value if the flag is provided and no overriding value is provided.
The nargs section has an example of how to use the const parameter:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', nargs='?', const='c', default='d')
>>> parser.add_argument('bar', nargs='?', default='d')
>>> parser.parse_args(['XX', '--foo', 'YY'])
Namespace(bar='XX', foo='YY')
>>> parser.parse_args(['XX', '--foo'])
Namespace(bar='XX', foo='c')
Although the default keyword isn't necessary in your case, since you want the value to be None if the user does not provide the flag.

Argparse module with multiple command and arguments

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()

Python argparse with Generic Subparser Commands

I have a python script that I want to use as a wrapper for another commandline tool. I want to intercept any subcommands that I have defined, but pass through all other subcommands and arguments. I have tried using a subparser, which seems ideal, but it doesn't seem to allow accepting a generic undefined command, something similar to what parse_known_args does for a regular ArgumentParser.
What I currently have:
ap = argparse.ArgumentParser()
subparsers = ap.add_subparsers(
title="My Subparser",
)
upload_parser = subparsers.add_parser('upload', help='upload help')
upload_parser.add_argument(
'path',
help="Path to file for upload"
)
upload_parser.add_argument(
'--recursive',
'-r',
action='store_true',
)
What I would like to add:
generic_parser = subparser.add_parser('*', help='generic help') # "*" to indicate any other value
generic_parser.add_argument(
'args',
nargs='*',
help='generic command arguments for passthru'
)
This does not work, as it simply expects either upload or a literal asterisk *.
More precisely, I want there to be a subcommand, I just don't know before hand what all the subcommands will be (or really I don't want to list out every subcommand of the tool I'm trying to wrap).
Upon further thought I have realized that this approach is somewhat flawed for my use in a few ways, though I think that this functionality might have its uses elsewhere, so I will leave the question up.
For my case, there is a conflict between seeing the help for my tool versus the one it wraps. That is, I can't distinguish between when the user wants to see the help for the wrapper or see the help for the tool it wraps.
I think you can try Click, this is really powerful and easy to use!
just check this example
import click
#click.command()
#click.option('--count', default=1, help='Number of greetings.')
#click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
hello()

Argparse and mutually exclusive command line arguments

I have created a pastebin terminal client in python. It can take some command line arguments, like -o to open a file, -n to set a paste name etc. It also has option -l which lists the pastes and allows you to delete or view pastes. The problem is that I don't know how to do it in a nice way (using argparse) - it should not allow to use -l with any other options.
I added a simple logic:
if args.name:
if args.list:
print('The -l should be used alone. Check "pb -h" for help.')
sys.exit()
Can it be done using just argparse?
I know about mutually exclusive groups, I even have one (to set paste privacy) but I haven't figured this one yet.
Full code is available here: https://github.com/lkorba/pbstc/blob/master/pb
I don't think you can use argparse to achieve your goal in "a nice way" as you say.
I see 2 options here:
1) The simpler solution as I get it would be to just check your arguments after parsing them. Nothing fancy just:
args = parser.parse_args()
if args.list is not None:
if not (args.name is None and args.open is None and
args.public is None and args.format is None and args.exp is None):
parser.error('Cannot use list with name, open, public, format or exp argument')
2) On the other hand you could revise a bit your program and use
subparsers like:
subparsers = parser.add_subparsers(title="commands", dest="command")
parser_a = subparsers.add_parser('list', help='list help')
parser_b = subparsers.add_parser('action', help='Any action here')
parser_b.add_argument('-f', action="store", help='Choose paste format/syntax: text=None, '
'mysql=MYSQL, perl=Perl, python=Python etc...')
parser_b.add_argument('-n', '--name', action="store")
parser_b.add_argument('-o', '--open', action="store", help='Open file')
...
args = parser.parse_args()
if args.command == 'list':
...
elif args.command == 'action':
...
So, for example if you pass list -n='Name' as arguments in the latter case you will get an error:
usage: subparser_example.py [-h] {list,action} ...
subparser_example.py: error: unrecognized arguments: -n='Name'
Of course you also get (as overhead) one extra parameter action here...

Python argparse: command-line argument that can be either named or positional

I am trying to make a Python program that uses the argparse module to parse command-line options.
I want to make an optional argument that can either be named or positional. For example, I want myScript --username=batman to do the same thing as myScript batman. I also want myScript without a username to be valid. Is this possible? If so, how can it be done?
I tried various things similar to the code below without any success.
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-u", "--user-name", default="admin")
group.add_argument("user-name", default="admin")
args = parser.parse_args()
EDIT: The above code throws an exception saying ValueError: mutually exclusive arguments must be optional.
I am using Python 2.7.2 on OS X 10.8.4.
EDIT: I tried Gabriel Jacobsohn's suggestion but I couldn't get it working correctly in all cases.
I tried this:
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-u", "--user-name", default="admin", nargs="?")
group.add_argument("user_name", default="admin", nargs="?")
args = parser.parse_args()
print(args)
and running myScript batman would print Namespace(user_name='batman'), but myScript -u batman and myScript --user-name=batman would print Namespace(user_name='admin').
I tried changing the name user-name to user_name in the 1st add_argument line and that sometimes resulted in both batman and admin in the namespace or an error, depending on how I ran the program.
I tried changing the name user_name to user-name in the 2nd add_argument line but that would print either Namespace(user-name='batman', user_name='admin') or Namespace(user-name='admin', user_name='batman'), depending on how I ran the program.
The way the ArgumentParser works, it always checks for any trailing positional arguments after it has parsed the optional arguments. So if you have a positional argument with the same name as an optional argument, and it doesn't appear anywhere on the command line, it's guaranteed to override the optional argument (either with its default value or None).
Frankly this seems like a bug to me, at least when used in a mutually exclusive group, since if you had specified the parameter explicitly it would have been an error.
That said, my suggested solution, is to give the positional argument a different name.
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-u','--username')
group.add_argument('static_username',nargs='?',default='admin')
Then when parsing, you use the optional username if present, otherwise fallback to the positional static_username.
results = parser.parse_args()
username = results.username or results.static_username
I realise this isn't a particularly neat solution, but I don't think any of the answers will be.
Here is a solution that I think does everything you want:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--user-name", default="admin")
# Gather all extra args into a list named "args.extra"
parser.add_argument("extra", nargs='*')
args = parser.parse_args()
# Set args.user_name to first extra arg if it is not given and len(args.extra) > 0
if args.user_name == parser.get_default("user_name") and args.extra:
args.user_name = args.extra.pop(0)
print args
If you run myScript -u batman or myScript --user-name=batman, args.user_name is set to 'batman'. If you do myScript batman, args.user_name is again set to 'batman'. And finally, if you just do myScript, args.user_name is set to 'admin'.
Also, as an added bonus, you now have all of the extra arguments that were passed to the script stored in args.extra. Hope this helps.
Try to use the "nargs" parameter of the add_argument method.
This way it works for me.
Now you can add the username twice,
so you have to check it and raise an error, if you want.
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--user-name", default="admin")
parser.add_argument("user_name", default="admin", nargs="?")
args = parser.parse_args()
print(args)

Categories

Resources