0

I have a file called functions.py with several functions (func1, func2, func3.. ) I would like to write a parser that allows me to run these functions on a terminal/cluster in this way:

python parser.py -function func1 -inputf1_1 10 inputf1_2 20

and I could also do

python parser.py -function func2 -inputf2 'abc'

I know how to do this with different parser files (parser_func1.py, parser_func2.py) but I would prefer to have only one parser.py file.

This is how my code looks at the moment:

import argparse


def func1(x,y):
    print (x*y)

def func2(text):
    print (text)

ap = argparse.ArgumentParser()


FUNCTION_MAP = {'func1' : func1,
                'func2' : func2}


# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('command', choices=FUNCTION_MAP.keys(), help='choose function to run')
subparsers = parser.add_subparsers(help='sub-command help')

# create the parser for the "a" command
parser_a = subparsers.add_parser('func1', help='func1 help')
parser_a.add_argument("-x", "--x", required=True, help="number 1")
parser_a.add_argument("-y", "--y", required=True, help="number 2")


# create the parser for the "b" command
parser_b = subparsers.add_parser('func2', help='func2 help')
parser_b.add_argument("-text", "--text", required=True, help="text you want to print")


args = parser.parse_args()

func = FUNCTION_MAP[args.command]
#I don't know how to put the correct inputs inside the func()
func()

When I now run:

python functions.py func1 -x 10 -y 2

I get this error: usage: PROG [-h] {func1,func2} {func1,func2} ... PROG: error: invalid choice: '2' (choose from 'func1', 'func2')

I've read these but still could not figure out how to do it:

https://docs.python.org/3/library/argparse.html#module-argparse

Call function based on argparse

How to use argparse subparsers correctly?

Thank you!

Ulises Rey
  • 75
  • 8

1 Answers1

1

add_subparsers automatically adds a positional argument for you, you don't need to add the func argument explicitly.

However, you do need to keep track of which subparser was used. The docs explain that quite well.

After you know which parser was used & which arguments to pass, you can call the specific function with keyword arguments that you construct from args

Here's the complete code:

import argparse


def func1(x,y):
    print (x*y)

def func2(text):
    print (text)

ap = argparse.ArgumentParser()


FUNCTION_MAP = {'func1' : func1,
                'func2' : func2}


# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
# Just add the subparser, no need to add any other argument.
# Pass the "dest" argument so that we can figure out which parser was used.
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')

# create the parser for the "a" command
parser_a = subparsers.add_parser('func1', help='func1 help')
# Keep the names here in sync with the argument names for "func1"
# also make sure the expected type is the same (int in this case)
parser_a.add_argument("-x", "--x", required=True, help="number 1", type=int)
parser_a.add_argument("-y", "--y", required=True, help="number 2", type=int)


# create the parser for the "b" command
parser_b = subparsers.add_parser('func2', help='func2 help')
parser_b.add_argument("-text", "--text", required=True, help="text you want to print")


args = parser.parse_args()

# subparser_name has either "func1" or "func2".
func = FUNCTION_MAP[args.subparser_name]
# the passed arguments can be taken into a dict like this
func_args = vars(args)
# remove "subparser_name" - it's not a valid argument
del func_args['subparser_name']
# now call the function with it's arguments
func(**func_args)

Now if I call the script like this:

PROG func1 -x 1 -y 3

I get the result:

3

And if I call it like:

PROG func2 --text=hello

I get:

hello
rdas
  • 20,604
  • 6
  • 33
  • 46
  • Slightly lower down in the [sub-commands](https://docs.python.org/dev/library/argparse.html#sub-commands) docs there is an example using `set_defaults` to call a specific function for each sub-command, which would be a more appropriate method here. – Alex Jan 19 '21 at 17:04