开发者

Python ArgParse Subparsers and linking to the correct function

开发者 https://www.devze.com 2023-03-10 18:08 出处:网络
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'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?


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.


You could just use the objects themselves in the dict.

#!/usr/bin/python

class Server:
    def __init__(self):
        pass

    def identify(self):
        print self.__class__.__name__

    def check(self):
        raise SomeErrorBecauseThisIsAbstract

class HttpServer(Server):

    def check(self, args):
        if self.verify_http_things():
           return True
        else:
           raise SomeErrorBecauseTheCheckFailed
    pass

class FtpServer(Server):

    def check(self, args):
        if self.verify_ftp_things():
           return True
        else:
           raise SomeErrorBecauseTheCheckFailed
    pass     


if __name__ == '__main__':


    # Hopefully this edit will make my intent clear:

    import argparse
    parser = argparse.ArgumentParser(description='Process some server commands')
    parser.add_argument('-c', dest='command')
    parser.add_argument('-t', dest='server_type')
    args = parser.parse_args()

    servers = {
        'http': HttpServer,
        'ftp': FtpServer
    }

    try:
        o = servers[args.server_type]()
        o.__call__(args.command)
    except Exception, e:
        print e


This should work (but a manual mapping would be more straight forward in my opinion):

import argparse

class Proxy:
    def __getattr__(thing):
        def caller (type):
            if type:
                server_object = # get instance of server with right type
                return getattr(server_object, thing)()
        return caller

parser = argparse.ArgumentParser()

entry_parser.add_argument('--server_type', dest='server_type', required=True,choices=['http', 'ftp', 'ssh'],)

subparser = parser.add_subparsers(dest='operation')
for operation in ['check', 'build', 'deploy', 'configure', 'verify']:
    entry_parser = subparser.add_parser(operation)
    entry_parser.set_defaults(func=getattr(Proxy, command))

options = parser.parse_args()

# this will call proxy function caller with type argument
options.func(options.server_type)
0

精彩评论

暂无评论...
验证码 换一张
取 消