0

I want to be able to run different functions from the command line in this way:

python my_parser.py myfunction1 arg1 arg2

and then again be able to run another function

python my_parser.py myfunction2 arg1 arg2 arg3

How do you do this in a clean manner? At the moment I can do it but in a very cumbersome way:

I have my code organized like this: A python file1 with all my functions

A second python file2 where I define the arguments to parse for the functions in file1.

It seems very inefficient because every time that I write a new function on file1 I have to add it to file2.

file1.py:


def my_func1(arg1, arg2):
    return arg1*arg2

def my_func2(arg1, arg2, arg3):
    return arg1*arg2*arg3

def my_func3(arg1)
    return print(arg1)

file2.py (the parser)

import argparse

FUNCTION_MAP = {'myfunc1': file1.my_func1,
                'myfunc2': file1.my_func2,
                'myfunc3': file1.my_func3}

# 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('my_func1', help='func1 help')
# Keep the names here in sync with the argument names of the FUNCTION_MAP
parser_a.add_argument("-arg1", "--arg1", required=True)
parser_a.add_argument("-arg2", "--arg2", required=True)

# create the parser for the "b" command
parser_b = subparsers.add_parser('my_func2', help='func2 help')
# Keep the names here in sync with the argument names of the FUNCTION_MAP
parser_b.add_argument("-arg1", "--arg1", required=True)
parser_b.add_argument("-arg2", "--arg2", required=True)
parser_b.add_argument("-arg3", "--arg3", required=True)


# create the parser for the "c" command
parser_c = subparsers.add_parser('my_func3', help='func3 help')
# Keep the names here in sync with the argument names of the FUNCTION_MAP
parser_c.add_argument("-arg1", "--arg1", required=True)


#parse args
args = parser.parse_args()

# subparser_name has either "tiff2avif" or "ometiff2bigtiff".
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 its arguments
func(**func_args)

It would be great if I could just write the functions in file1.py and a parser would take arguments for the functions without having to manually add a new parser_x and each argument as parser_x_add_argument(). I bet this is possible, but could not figure out how.

Ideally I would like to have the parser on file1 under

if __name__ == "__main__":

So that I can run my functions directly from there, and I can get rid of file2.

Thank you very much in advance.

Related question: Parse different inputs into different Python functions

Ulises Rey
  • 75
  • 8
  • 2
    It's not really clear what you are asking. Have you looked at `click`? – tripleee Aug 29 '22 at 09:26
  • Have you looked at subparsers? – Mad Physicist Aug 29 '22 at 09:30
  • Hi @Tripleee I did not. But I think what I am asking should be possible with argparse? – Ulises Rey Aug 29 '22 at 09:50
  • Hi @MadPhysicist, I am already using them, right? Is there a smarter way of using them? Thank you – Ulises Rey Aug 29 '22 at 09:51
  • "Possible", yes. Convenient, almost by definition no. – tripleee Aug 29 '22 at 09:52
  • Hi @tripleee Maybe I can try to explain better what I am trying to do? What part is not really clear? – Ulises Rey Aug 29 '22 at 10:04
  • 1
    Are you trying to say you would like to expose the function names in `file1.py` as subcommands you can run from the command line? Again, `click` makes this very easy with a simple decorator. – tripleee Aug 29 '22 at 10:07
  • In `pypi` there's a argparse extension called `plac` that might do what you want. – hpaulj Aug 29 '22 at 14:46
  • Hi @tripleee I have been reading about click and it seems to me that I should decorate every function to look like this: @click.argument('arg1') @click.argument('arg2') def my_func1(arg1, arg2): return arg1*arg2 In this case I do not see what I am improving, because if I have to decorate every function I might as well write the same in the parser file. Am I missing something? – Ulises Rey Aug 29 '22 at 15:45
  • `plac` tries to deduce what the function arguments are using inspect and/or annotations. – hpaulj Aug 30 '22 at 04:15

0 Answers0