Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python ArgParse Subparsers and linking to the correct function

I'm creating a small Python script to manage different classes of servers (FTP, HTTP, SSH, etc.)

On each type of server, we can perform different types of actions (deploy, configure, check, etc.)

I have a base Server class, then a separate class for each type of server that inherits from this:

class Server:
    ...
    def check():
        ...

class HTTPServer(Server):
    def check():
        super(HTTPServer, self).check()
        ...
class FTPServer(Server):
    def check():
        super(FTPServer, self).check()
        ...

A sample command line might be:

my_program deploy http

From the command-line, the two mandatory arguments I need are:

  1. Operation to perform
  2. Type of server to create/manage

Previously, I was using argparse and the store operation, and using a dict to match the command-line option to the actual class and function name. For example:

types_of_servers = {
    'http': 'HTTPServer',
    'ftp': 'FTPServer',
    ...
}

valid_operations = {
    'check': 'check',
    'build': 'build',
    'deploy': 'deploy',
    'configure': 'configure',
    'verify': 'verify',
}

(In my actual code, valid_operations wasn't quite a naive 1:1 mapping.)

And then using rather horrible code to create the right type of object, and call the right class.

Then I thought I'd use argparse's subparsers feature to do it instead. So I've made each operation (check, build, deploy, etc.) a subparser.

Normally, I could link each sub-command to a particular function, and have it call it. However, I don't want to just call a generic check() function - I need to create the correct type of object first, and then call the appropriate function within that object.

Is there a good, or pythonic way to do this? Preferably one that doesn't involve a lot of hardcoding, or badly designed if/else loops?

like image 301
victorhooi Avatar asked Jun 07 '11 08:06

victorhooi


People also ask

What are Subparsers in Python?

A “subparser” is an argument parser bound to a namespace. In other words, it works with everything after a certain positional argument. Argh implements commands by creating a subparser for every function. Again, here's how we create two subparsers for commands foo and bar: parser = ArghParser() parser.

How do I add Argparse optional arguments?

Optional Arguments To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.

How do I make Argparse argument optional in Python?

Python argparse optional argument The example adds one argument having two options: a short -o and a long --ouput . These are optional arguments. The module is imported. An argument is added with add_argument .


1 Answers

If you are set on using a subparser for each command I would do something like this. Use argparse's type support to call a function that lookups the class you want to instantiate and returns it.

Then call the method on that instance dynamically with getattr()

import argparse

class Server:
    def check(self):
        return self.__class__.__name__

class FooServer(Server):
    pass

class BarServer(Server):
    pass


def get_server(server):
    try:
        klass = globals()[server.capitalize()+'Server']
        if not issubclass(klass, Server):
            raise KeyError

        return klass()
    except KeyError:
        raise argparse.ArgumentTypeError("%s is not a valid server." % server)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='command')

    check = subparsers.add_parser('check')
    check.add_argument('server', type=get_server)

    args = parser.parse_args()

    print getattr(args.server, args.command)()

Output looks something like this:

$ python ./a.py check foo
FooServer
$ python ./a.py check bar
BarServer
$ python ./a.py check baz
usage: a.py check [-h] server
a.py check: error: argument server: baz is not a valid server.
like image 111
cnelson Avatar answered Oct 18 '22 06:10

cnelson