Using argparse argument names as variable names - python

I want to use name of arguments in argparse as variable names. Now I'm doing something like:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('a')
parser.add_argument('b')
parser.add_argument('c')
parser.add_argument('--d')
parser.add_argument('--e')
args = parser.parse_args()
a = args.a
b = args.b
c = args.c
d = args.d
e = args.e
But this is so inefficient and I may end up with over 10 arguments in total. Is there an efficient way to do this?

This is best I have found to reduce lines of code when renaming arg vars:
a,b,c,d,e = args.a, args.b, args.c, args.d, args.e
For me it's an acceptable solution, where no additional processing is required on an arg var.

Note that this is a very discouraged behavior, but if you really need to do it,
globals().update(args.__dict__)
will update your global namespace.

Related

How to create multiple tests for a function using sys.argv in pytest

I have a function that looks like this:
import argparse
import sys
def execute():
parser = argparse.ArgumentParser()
if (total_args := len(sys.argv)) == 1:
do_stuff()
if total_args == 2:
first = sys.argv[1]
do_stuff2()
if total_args == 3:
first, second = sys.argv[1:3]
do_stuff3()
if total_args > 3:
first, second = sys.argv[1:3]
del sys.argv[1:3]
add_args(parser)
parser.parse_args()
do_stuff4()
Which should have a test function test_execute that will try different given args, the question: is there a clean way to do it without manually modifying sys.argv using sys.argv.extend(some_test_args) and delete the args later?
Note: I can't use argparse optional positional arguments by setting nargs=? in parser.add_argument() because the first 2 arguments are optional and each case (1, 2, 3, > 3 arguments) executes different functions. To understand further, please check the example below ...
parser = argparse.ArgumentParser()
parser.add_argument('arg1', nargs='?')
parser.add_argument('arg2', nargs='?')
args = parser.parse_known_args()
print(args)
which if called like the following, will result to the wrong variable saved in the second position:
>>> python my_script.py --unknown-arg 999
Will print:
(Namespace(arg1='999', arg2=None), ['--unknown-arg'])
which is totally not what I need. I'm expecting arg1 to have a None value. The reason sometimes there will be unknown arguments is that argparse does not support parsing arguments by specifying a group. Let's say I have argument group A and argument group B and I need to parse only group A, I can't do parser.parse_group('A') I will have to create parser_a = argparse.ArgumentParser() and add group A arguments and parse them and repeat for parser_b.
Therefore the best solution I have so far is using sys.argv despite the fact this is inconvenient for testing. Also adding all options without grouping, will create another problem because group B arguments depend on values parsed from group A.
One workaround is to specify using --unknown-arg=999 but this will create inconsistencies in the documentation and usage of the script and is also not what I need.
Could you pass in sys.argv into execute()?
Something like this:
import argparse
import sys
def execute(argv):
parser = argparse.ArgumentParser()
if (total_args := len(argv)) == 1:
do_stuff()
if total_args == 2:
first = argv[1]
do_stuff2()
if total_args == 3:
first, second = argv[1:3]
do_stuff3()
if total_args > 3:
first, second = argv[1:3]
del argv[1:3]
add_args(parser)
parser.parse_args(argv)
do_stuff4()
if __name__ == "__main__":
execute(sys.argv)
In your tests you could then do something along the lines of:
def test_execute():
test_argv = ["some", "args", "list"]
execute(test_argv)
# assert something

How to get subset of argparse arguments in code

I would like to get subset of parsed arguments and send them to another function in python. I found this argument_group idea but I couldn't find to reach argument groups. This is what I want to try to do:
import argparse
def some_function(args2):
x = args2.bar_this
print(x)
def main():
parser = argparse.ArgumentParser(description='Simple example')
parser.add_argument('--name', help='Who to greet', default='World')
# Create two argument groups
foo_group = parser.add_argument_group(title='Foo options')
bar_group = parser.add_argument_group(title='Bar options')
# Add arguments to those groups
foo_group.add_argument('--bar_this')
foo_group.add_argument('--bar_that')
bar_group.add_argument('--foo_this')
bar_group.add_argument('--foo_that')
args = parser.parse_args()
# How can I get the foo_group arguments for example only ?
args2 = args.foo_group
some_function(args2)
I don't know whether a simpler solution exists, but you can create a custom "namespace" object selecting only the keys arguments you need from the parsed arguments.
args2 = argparse.Namespace(**{k: v for k, v in args._get_kwargs()
if k.startswith("foo_")})
You can customize the if clause to your needs and possibly change the argument names k, e.g. removing the foo_ prefix.

Nested options with argparse

I'd like to have my python script take variable number of arguments depending on a particular choice. For example:
python run.py foo
python run.py bar X Y
where choosing the option bar requires two additional arguments, say integer inputs, but foo requires no additional arguments.
import argparse
argparser = argparse.ArgumentParser()
# Allow user to choose test to run
argparser.add_argument("test", choices=['foo', 'bar'], help="You may choose foo or bar.")
...
But how can I specify additional arguments required by bar? P.S. I'm working with Python 2.7, so if a solution requires Python 3, it won't be much help in my case.
You should use subparsers:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands')
parser_foo = subparsers.add_parser('foo')
parser_foo.set_defaults(target='foo')
parser_bar = subparsers.add_parser('bar')
parser_bar.add_argument('more')
parser_bar.set_defaults(target='bar')
Usage:
>>> parser.parse_args(['foo'])
Namespace(target='foo')
>>> parser.parse_args(['bar', '123'])
Namespace(target='bar', more='123')
Note that you could set the target to e.g. a function and call it directly. Here's some sample code that does this (extracted from Cactus' CLI, but that's a rather common pattern):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title = 'subcommands')
parser_create = subparsers.add_parser('create')
parser_create.add_argument('path')
parser_create.add_argument('-s', '--skeleton')
parser_create.set_defaults(target=create)
parser_build = subparsers.add_parser('build')
parser_build.set_defaults(target = build)
args = parser.parse_args()
args.target(**{k: v for k, v in vars(args).items() if k != 'target'})

Is it possible to reconstruct a command line with Python's argparse?

I have a Python script that reads a file containing a command line invocation of some other tool. I'd like to modify the options of this invocation before calling the tool. For example, I might transform:
my_util --input file1.txt --option1 red --option2 blue
...to this:
my_util --input file1_001.txt --option1 red --option3 green
(More accurately, I'd be working on the arguments as lists.)
I figured that using the argparse module would be the easiest way to do this: I could parse the args, change, add or remove the options as I need to, and then reconstruct the command line.
But how do I do the last step? Given the Namespace object returned by parse_args(), can I easily reconstruct a list of command line options, such as could be passed to subprocess.Popen()?
A Namespace object is just a simple object subclass, so you can get the values out as a dict with vars:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo')
>>> args = parser.parse_args(['--foo', 'BAR'])
>>> vars(args)
{'foo': 'BAR'}
Or you can assign to a class directly and get the arguments out as class variables:
>>> class C(object):
... pass
...
>>> c = C()
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo')
>>> parser.parse_args(args=['--foo', 'BAR'], namespace=c)
>>> c.foo
'BAR'
It would be fairly easy to use either of these structures to test/replace arguments and pass the results to Popen.
I know this is an old question, but I've just encountered the same problem. I realized that all I need is a way to iterate over the Action objects. Unfortunately, the internal list is not exposed by ArgParser itself. However, these objects are returned by add_argument(), so I can construct my own list. Well, putting actions.append() around each call looked like too much typing to me, so I store all options in a tuple:
def add_argument(*args, **kwargs):
return (args, kwargs)
parser = argparse.ArgumentParser()
options = (
add_argument('--verbose', action='store_true'),
add_argument('--author'),
add_argument('--subject', required=True),
add_argument('--cache', nargs='?'),
add_argument('files', nargs=''),
)
actions = []
for (args, kwargs) in options:
actions.append(parser.add_argument(*args, **kwargs))
args = parser.parse_args()
At this point, the options are parsed in args, and all argparse.Action objects are stored in the actions list. I can then iterate over this list and reconstruct the options like this:
cmdline = []
for action in actions:
value = getattr(args, action.dest)
if action.required or value != action.default:
if action.option_strings:
cmdline.append(action.option_strings[0])
if action.nargs is None:
cmdline.append(value)
elif action.nargs == '?':
if value != action.const:
cmdline.append(value)
elif action.nargs != 0:
cmdline += value
In my specific case, I also wanted to remove some options from the command line. To do that I simply added them separately with a call to parser.add_argument() and not through the options tuple.

How do I constrain my python script to only accepting one argument? (argparse)

I would like a script that has (for example) three arguments:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--a",help="Argument a")
parser.add_argument("--b",help="Argument b")
parser.add_argument("--c",help="Argument c")
args= parser.parse_args()
But make it so that it is only possible to specify only either 'a','b', or 'c' at any give time e.g. you can specify 'a' but not 'b' or 'c' Is this possible and how would I achieve it?
argparse lets you specify this by using the add_mutually_exclusive_group() method.
import argparse
parser = argparse.ArgumentParser()
g = parser.add_mutually_exclusive_group()
g.add_argument("--a",help="Argument a")
g.add_argument("--b",help="Argument b")
g.add_argument("--c",help="Argument c")
args= parser.parse_args()
Use the add_mutually_exclusive_group() mentioned above to check this on the argparse level already.
If you like to have more control about error message and the like, you can of course check the results later:
if len([x for x in args.a, args.b, args.c if x is not None]) > 1:
raise Exception("Not allowed!")

Categories

Resources