Python ArgParse Subparsers and Binding to the Right Function

I am creating a small Python script to manage various classes of servers (FTP, HTTP, SSH, etc.)

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

I have a Server base class, and 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() ... 

An example command line might be:

 my_program deploy http 

Two required arguments are needed from the command line:

  • Operation to perform
  • Type of server to create / manage

I used to use the argparse and store operations and used dict to map the command line option to the actual class name and function name. For instance:

 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 was not a 1: 1 naive mapping.)

And then using pretty awful code to create the desired type of object and call the correct class.

Then I decided to use the argparse subparsers function to do this instead. Therefore, I performed each operation (validation, assembly, deployment, etc.) subparser .

Usually I can associate each subcommand with a specific function and call it. However, I do not want to just call the general function check() - I need to first create the correct type of the object, and then call the corresponding function inside this object.

Is there a good or pythonic way to do this? Preferably one that doesn't require a lot of hard coding or poorly designed if / else loops?

+6
source share
3 answers

If you are configured to use a subparameter for each command, I would do something like this. Use argparse type support to call a function that looks for the class you want to create and returns it.

Then call the method in this instance dynamically using 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)() 

The result 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. 
+2
source

You can 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 
0
source

This should work (but manual matching will be more direct 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
source

Source: https://habr.com/ru/post/889929/


All Articles