Nature of optional arguments argparse Python - python-3.x

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?

Related

Python3 argparse nargs="+" get number of arguments

I'm now googling for quite a while and I just don't find any solution to my problem.
I am using argparse to parse some command line arguments. I want to be able to parse an arbitrary number of arguments > 1 (therefore nargs="+") and I want to know afterwards how many arguments I have parsed. The arguments are all strings. But I get a problem when I just have one argument, because then the length of the list is the number of characters of the word and not 1 as in 1 argument. And I want to know how many arguments were parsed. Does anyone know how I could solve this problem?
examples:
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--test", type=str, nargs="+", required=False, default="hi")
args = parser.parse_args()
test = args.test
print(test)
print(len(test))
So with python3 example.py --test hello hallo hey the output is:
['hello', 'hallo', 'hey']
3
But with python3 example.py --test servus the output is:
servus
6
What I already know is that I could do print([test]) but I only want to do that if I have 1 argument. Because if I have more than one arguments and use print([test]) I get a double array... So I just want to know the number of parsed arguments for "test".
I cannot imagine that I am the only one with such a problem, but I could not find anything in the internet. Is there a quick and clean solution?
You left off the test=args.test line.
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--test", type=str, nargs="+", required=False, default="hi")
args = parser.parse_args()
print(args)
print(len(args.test))
test cases
0856:~/mypy$ python3 stack68020846.py --test one two three
Namespace(test=['one', 'two', 'three'])
3
0856:~/mypy$ python3 stack68020846.py --test one
Namespace(test=['one'])
1
0856:~/mypy$ python3 stack68020846.py
Namespace(test='hi')
2
change the default to default=[]
0856:~/mypy$ python3 stack68020846.py
Namespace(test=[])
0
Theres probably a better solution, but try this:
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--test", type=str, nargs="+", required=False, default="hi")
args = parser.parse_args()
print("Number of arguments:", len(args._get_kwargs()[-1][-1]))
By the way, i figured this out by checking the content of args with print(dir(args))

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.

python argparse if argument selected then another argument required =True

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.

Argparse add option strings

That question will be strange in some point...
I'm trying to add the option strings in the parser after adding one argument.
For example
import argparse
p = argparse.ArgumentParser()
p.add_argument(dest = 'myvar')
p._actions[1].option_strings = ['-foo']
p.parse_args('-foo 1')
This example doesn't work, it says:
: error: the following arguments are required: -foo
Even though I'm supplying the argument...
Anyone know why this is occurring?
Is there some way to add the option strings after the add_argument method?
After looking at the source code of argparse, I found you also have to register the action by the option string it has:
import argparse
p = argparse.ArgumentParser()
p.add_argument(dest = 'myvar')
p._actions[1].option_strings = ['-foo']
# You also need to do this
p._option_string_actions['-foo'] = p._actions[1]
args = p.parse_args(['-foo', '100'])
print('myvar is', args.myvar) # myvar is 1
Note the change when calling parse_args.
I hope this help!!

How to use argparse in the actual script

How does argparse work? I was told to 'hide' the password from the psycopg2 connection I am building so to be able to run the script automatically every week and being able to share it between departments. This is the beginning of the psycopg2 script where the password is asked:
#Connect to database
conn_string = "host='my_aws_postgresql_database.rds.amazonaws.com' dbname='my_database_name' user='my_username' password='my_password'"
# print the connection string we will use to connect
print ("\"Connecting to database\n ->%s\"" % (conn_string))
Now, how would I use argparse (and getpass) to hide my password? I found this script about this subject a couple of times (I would delete the print statement after getting it to work):
import argparse
import getpass
class Password(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
if values is None:
values = getpass.getpass()
setattr(namespace, self.dest, values)
parser = argparse.ArgumentParser('Test password parser')
parser.add_argument('-p', action=Password, nargs='?', dest='password',
help='Enter your password')
args = parser.parse_args()
print (args.password)
I tried to add the argparse snippet above the #Connect to database code. And replaced the password part on line 2 with
conn_string =
"host='my_aws_postgresql_database.rds.amazonaws.com'
dbname='my_database_name'
user='my_username'
password='" + args + "'"
Then I tried to run the whole script with the command python3 my_script_file.py my_password -p I get asked for the password which I entered, but this rendered the following error
usage: Test password parser [-h] [-p [PASSWORD]]
Test password parser: error: unrecognized arguments: my_password
If I use python3 my_script_file.py my_password I get the same error, but I did not have to enter the password (again).
Am I close to the solution? Is this the standard way of doing this?
The problem was that I used python3 my_script_file.py my_password -p instead of the correct order python3 my_script_file.py -p my_password, see accepted answer below by #hpaulj and the comments to that answer.
This parser is gives the user 2 ways of entering the password, on the commandline, or with a separate getpass prompt:
import argparse
import getpass
class Password(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
if values is None:
values = getpass.getpass()
setattr(namespace, self.dest, values)
parser = argparse.ArgumentParser('Test password parser')
parser.add_argument('-p', action=Password, nargs='?', dest='password',
help='Enter your password')
args = parser.parse_args()
print (args)
Sample runs:
0911:~/mypy$ python3 stack44571340.py
Namespace(password=None)
0912:~/mypy$ python3 stack44571340.py -p test
Namespace(password='test')
0912:~/mypy$ python3 stack44571340.py -p
Password:
Namespace(password='testing')
0912:~/mypy$ python3 stack44571340.py test
usage: Test password parser [-h] [-p [PASSWORD]]
Test password parser: error: unrecognized arguments: test
I tested without any arguments (got the default None)`; with '-p test' which uses the 'test' string; with just '-p', which asks; and without '-p', which produces the error.
I don't know why python3 my_script_file.py -p my_password produced an error; my best guess there's a typo in your parser definition (something wrong with nargs?).
It's not entirely clear how you merged this parser code into the larger script. Done right it shouldn't have changed the behavior of the parser.
The password argument would be used as:
password='" + args.password + "'"
The echo argument, is a positional one, which requires a string. In contrast the -p with nargs='?', is an optional flagged argument, which allows for the three way input I illustrated.
parser.add_argument("echo")
Thank you #CarlShiles, your answer didn't work with that long argparse/getpass snippet from above, but it made me realise that I could just echo the password in there. So I did a simple
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
and then used your suggestion, password='" + args.echo + "'". And then ran the following command python3 my_script_file.py my_password. This worked just fine.

Resources