python argparse if argument selected then another argument required =True - python-3.x

Is there a way to make an argument required to be true if another specific argument choice is present otherwise argument required is false?
For example the following code if argument -act choice select is 'copy' then the argument dp required is true otherwise required is false:
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-act", "--act", required=True, choices=['tidy','copy'],type=str.lower,
help="Action Type")
ap.add_argument("-sp", "--sp", required=True,
help="Source Path")
args = vars(ap.parse_args())
if args["act"] == 'copy':
ap.add_argument("-dp", "--dp", required=True,
help="Destination Path")
else:
ap.add_argument("-dp", "--dp", required=False,
help="Destination Path")
args = vars(ap.parse_args())
### Tidy Function
def tidy():
print("tidy Function")
### Copy Function
def copy():
print("copy Function")
### Call Functions
if args["act"] == 'tidy':
tidy()
if args["act"] == 'copy':
copy()
I am currently getting an error unrecognized arguments: -dp with the above code.
The expected result would be to move on to call function. Thanks

I would use ArgumentParser.add_subparsers to define the action type {tidy, copy} and give the command specific arguments. Using a base parser with parents allows you to define arguments that are shared by both (or all) your sub-commands.
import argparse
parser = argparse.ArgumentParser(
prog='PROG',
epilog="See '<command> --help' to read about a specific sub-command."
)
base_parser = argparse.ArgumentParser(add_help=False)
base_parser.add_argument("--sp", required=True, help="source")
subparsers = parser.add_subparsers(dest='act', help='Sub-commands')
A_parser = subparsers.add_parser('copy', help='copy command', parents=[base_parser])
A_parser.add_argument('--dp', required=True, help="dest, required")
B_parser = subparsers.add_parser('tidy', help='tidy command', parents=[base_parser])
B_parser.add_argument('--dp', required=False, help="dest, optional")
args = parser.parse_args()
if args.act == 'copy':
pass
elif args.act == 'tidy':
pass
print(args)
Which produces the following help pages, note that instead of needing to use -act the command is given as a positional parameter.
~ python args.py -h
usage: PROG [-h] {tidy,copy} ...
positional arguments:
{tidy,copy} Sub-commands
tidy tidy command
copy copy command
optional arguments:
-h, --help show this help message and exit
See '<command> --help' to read about a specific sub-command.
~ python args.py copy -h
usage: PROG copy [-h] --sp SP [--dp DP]
optional arguments:
-h, --help show this help message and exit
--sp SP source
--dp DP dest, optional
~ python args.py tidy -h
usage: PROG tidy [-h] --sp SP --dp DP
optional arguments:
-h, --help show this help message and exit
--sp SP source
--dp DP dest, required

ap.add_argument("-dp", "--dp", help="Destination Path")
args = parser.parse_args()
if args.copy is in ['copy']:
if args.dp is None:
parser.error('With copy, a dp value is required')
Since a user can't set a value to None, is None is a good test for arguments that haven't been used.
The parser has a list of defined arguments, parser._actions. Each has a required attribute. During parsing it maintains a set of seen-actions. At the end of parsing it just checks this set against the actions for which required is True, and issues the error message if there are any.
I would argue that testing after parsing is simpler than trying to set the required parameter before hand.
An alternative is to provide dp with a reasonable default. Then you won't care whether the user provides a value or not.
I can imagine defining a custom Action class for copy that would set the required attribute of dp, but overall that would be more work.

Related

Argparse multiple values with options

I am using argparse for argument parsing in Python script. In this case I have two values where I got from using narg=2.
But the problem is when printing help. Below is part of the script.
#!/usr/bin/python3
import argparse
parser = argparse.ArgumentParser(description='My App')
parser.add_argument('--add',default=None, action='store',help='Add new value', nargs=2,metavar='name')
args = parser.parse_args()
Output for above code is :
$ python3 sshconfig.py list --help usage: sshconfig.py [-h] [--add NAME NAME]
My App
optional arguments: -h, --help show this help message and exit
--add NAME NAME Add new value
I need to differentiate between argument of --add like NAME and VALUE, like below.
$ python3 sshconfig.py list --help usage: sshconfig.py [-h] [--add NAME VALUE]
My App
optional arguments: -h, --help show this help message and exit
--add NAME VALUE Add new value
To add multiple labels, set metavar to a tuple that has a value for each argument.
parser = argparse.ArgumentParser(description='My App')
parser.add_argument('--add', help='Add new value', nargs=2, metavar=('name', 'VALUE'))
args = parser.parse_args()
output:
usage: test.py [-h] [--add name VALUE]
My App
optional arguments:
-h, --help show this help message and exit
--add name VALUE Add new value

In argparse, how can I make positional arguments depend on the optional arguments?

How can I make positional arguments depend on the optional arguments?
For example:
parser.add_argument("-A", action="store_true", help = "add")
parser.add_argument("-U", action="store_true", help = "update")
parser.add_argument("-D", action="store_true", help = "delete")
parser.add_argument("-L", action="store_true", help = "list")
If I choose -A , I want it to require arguments "name , address, cp number"
But if I choose -L, I don't want it to require anything or when I choose -U it requires another set of arguments.
My end goal is to create a contact book where I can add new contacts, update existing contacts, delete contacts and list contacts. I can do this if I use if else statements but I want to try using argparse.
If I'm using argparse incorrectly, please give me some advice!
This is a good opportunity to use subparsers:
This flips (I think) what you're trying to acheive.
The help text looks like this:
😊 /tmp python3 test.py --help
usage: PROG [-h] {add,update,delete,list} ...
positional arguments:
{add,update,delete,list}
Sub-commands
add ADD help text
update UPDATE help text
delete DELETE help text
list LIST help text
optional arguments:
-h, --help show this help message and exit
See '<command> --help' to read about a specific sub-command.
😊 /tmp python3 test.py add -h
usage: PROG add [-h] [--foo FOO]
optional arguments:
-h, --help show this help message and exit
--foo FOO
This skeleton should get you started:
import argparse
def run_command(parser, args):
if args.command == 'add':
print(args)
elif args.command == 'update':
print(args)
elif args.command == 'delete':
print(args)
elif args.command == 'list':
print(args)
parser = argparse.ArgumentParser(
prog='PROG',
epilog="See '<command> --help' to read about a specific sub-command."
)
subparsers = parser.add_subparsers(dest='command', help='Sub-commands')
add_parser = subparsers.add_parser('add', help='ADD help text')
add_parser.add_argument("--foo")
add_parser.set_defaults(func=run_command)
update_parser = subparsers.add_parser('update', help='UPDATE help text')
update_parser.add_argument('--bar')
update_parser.set_defaults(func=run_command)
delete_parser = subparsers.add_parser('delete', help='DELETE help text')
delete_parser.add_argument('--baz')
delete_parser.set_defaults(func=run_command)
list_parser = subparsers.add_parser('list', help='LIST help text')
list_parser.add_argument('--qux')
list_parser.set_defaults(func=run_command)
args = parser.parse_args()
if args.command is not None:
args.func(parser, args)
else:
parser.print_help()
What the code above does is:
Creates an argparse.ArgumentParser
Adds a subparser called command
Then we create 4 parsers (one for each action) that we can add arguments to
Finally we set a func to run_command which we pass the parser and the args.

Is it possible to inherit required options in argparse subparsers?

I am trying to write a command line application that has several modes in which it can run (similar to git having clone, pull, etc.). Each of my subcommands have their own options, but I also wanted them to share a set of required options, so I tried using a parent parser to implement this. However, it seems that inheriting a required option is causing the subparser to keep asking for it. Here is an example recreating the behavior:
import argparse
parent_parser = argparse.ArgumentParser(description="The parent parser")
parent_parser.add_argument("-p", type=int, required=True,
help="set the parent required parameter")
subparsers = parent_parser.add_subparsers(title="actions", required=True, dest='command')
parser_command1 = subparsers.add_parser("command1", parents=[parent_parser],
add_help=False,
description="The command1 parser",
help="Do command1")
parser_command1.add_argument("--option1", help="Run with option1")
parser_command2 = subparsers.add_parser("command2", parents=[parent_parser],
add_help=False,
description="The command2 parser",
help="Do command2")
args = parent_parser.parse_args()
So now if I run python test.py I get:
usage: test.py [-h] -p P {command1,command2} ...
test.py: error: the following arguments are required: -p, command
Ok, so far so good. Then if I try to specify just the -p option with python test.py -p 3 I get:
usage: test.py [-h] -p P {command1,command2} ...
test.py: error: the following arguments are required: command
But then if I run python test.py -p 3 command1
I get:
usage: test.py command1 [-h] -p P [--option1 OPTION1] {command1,command2} ...
test.py command1: error: the following arguments are required: -p, command
If I add another -p 3 it still asks to specify command again, and then if I add that again it asks for another -p 3 etc.
If I don't make the -p option required the problem is fixed, but is there a way to share required options among multiple subparsers without just copy pasting them within each subparser? Or am I going about this entirely the wrong way?
Separate the main parser and parent parser functionality:
import argparse
parent_parser = argparse.ArgumentParser(description="The parent parser", add_help=False)
parent_parser.add_argument("-p", type=int, required=True,
help="set the parent required parameter")
main_parser = argparse.ArgumentParser()
subparsers = main_parser.add_subparsers(title="actions", required=True, dest='command')
parser_command1 = subparsers.add_parser("command1", parents=[parent_parser],
description="The command1 parser",
help="Do command1")
parser_command1.add_argument("--option1", help="Run with option1")
parser_command2 = subparsers.add_parser("command2", parents=[parent_parser],
description="The command2 parser",
help="Do command2")
args = main_parser.parse_args()
The main parser and subparser both write to the same args namespace. If both define a 'p' argument, the subparser's value (or default) will overwrite any value set by the main. So it's possible to use the same 'dest' in both, but it is generally not a good idea. And each parser has to meet its own 'required' specifications. There's no 'sharing'.
The parents mechanism copies arguments from the parent to the child. So it saves typing or copy-n-paste, but little else. It's most useful when the parent is defined elsewhere and imported. Actually it copies by reference, which sometimes raises problems. In general it isn't a good idea to run both the parent and child. Use a 'dummy' parent.

How do I know if add_subparsers() has already been called on a parser

I'd like to create a function that will add a subcommand to a parser.
def add_subparser(parser, command):
sub_parsers = parser.add_subparsers('more commands')
new_parser = sub_parsers.add_parser('free')
return new_parser
It seems to me that the first line needs to check whether parser already has subparsers. What is a good way to do that check?
(Side note: A nice future feature would be get_subparsers that returns a singleton.)
Look at the argparse.py code. The add_subparsers method starts with:
def add_subparsers(self, **kwargs):
if self._subparsers is not None:
self.error(_('cannot have multiple subparser arguments'))
and a bit later sets self._subparsers to a new value.
But if you don't want to look at parser._subparsers you could just wrap the new add_subparses command in a try/except block.
add_subparsers creates a positional Action with a special subparser subclass. That's what we normally assign to a variable, and use in the next lines. (as a side note, add_argument also returns an Action subclass object, the action that was just created).
It's instructive to set up a parser in an interactive session, and look at the objects that each command returns. Most have a basic str method displaying some of the attributes. As with any Python class object, you can explore the attributes in detail, even change some of them.
In [1]: import argparse
In [2]: p = argparse.ArgumentParser()
In [3]: sp = p.add_subparsers(dest='cmd')
_actions is a list of all Action class objects that were created. Here there are two, the default help and the newly created subparser one.
In [4]: p._actions
Out[4]:
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None),
_SubParsersAction(option_strings=[], dest='cmd', nargs='A...', const=None, default=None, type=None, choices=OrderedDict(), help=None, metavar=None)]
In [5]: type(p._actions[1])
Out[5]: argparse._SubParsersAction
The _subparsers attribute is now set to an ArgumentGroup, in this case the first default one, the 'positionals'.
In [6]: p._subparsers
Out[6]: <argparse._ArgumentGroup at 0x7f3d8ede31d0>
In [7]: p._action_groups
Out[7]:
[<argparse._ArgumentGroup at 0x7f3d8ede31d0>,
<argparse._ArgumentGroup at 0x7f3d8ede3cf8>]
In [9]: p.print_help()
usage: ipython3 [-h] {} ...
positional arguments:
{} # the subparsers argument
optional arguments:
-h, --help show this help message and exit
And the error caused by trying to add another subparsers (caught in this case by ipython):
In [10]: sp = p.add_subparsers(dest='cmd')
usage: ipython3 [-h] {} ...
ipython3: error: cannot have multiple subparser arguments
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2
/usr/local/lib/python3.6/dist-packages/IPython/core/interactiveshell.py:3304: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)

Nature of optional arguments argparse Python

As I have started to learn argparse module, I try to get my head around optional arguments in Python argparse. I would like to add my own arguments, as I present below:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--sense", help = "What you are seeking")
arg = parser.parse_args()
if arg.sense:
print("42")
However, the help doesn't seem to accept the new argument in output:
"""python3 file.py -h"""
usage: file.py [-h]
optional arguments:
-h, --help show this help message and exit
What surprises me is the fact, that when I copied the code from a tutorial:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="increase output verbosity",
action="store_true")
args = parser.parse_args()
if args.verbose:
print("verbosity turned on")
https://docs.python.org/3/howto/argparse.html
the help changed. Is there a list of possible optional arguments? Am I allowed to create new ones?

Resources