2011-06-07 13 views
6

Estoy creando un pequeño script en Python para manejar diferentes tipos de servidores (FTP, HTTP, SSH, etc.)Python argparse Subparsers y ligarse a la función correcta

En cada tipo de servidor, podemos realizar diferentes tipos de acciones (desplegar, configurar, cheque, etc.)

que tienen una clase base Server, a continuación, una clase separada para cada tipo de servidor que se hereda de esto:

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

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

una línea de comandos de ejemplo podría be:

my_program deploy http 

Desde la línea de comandos, los dos argumentos obligatorios que necesito son:

  1. operación a realizar
  2. Tipo de servidor para crear/administrar

Anteriormente, yo estaba usando argparse y la operación store, y usando un dict para hacer coincidir la opción de la línea de comando con la clase real y el nombre de la función. Por ejemplo:

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

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

(En mi código real, valid_operations no era bastante ingenua 1: 1 de mapeo.)

Y a continuación, utilizando código en lugar horrible para crear el tipo de objeto, y llamar a la clase correcta

Luego pensé que usaría la función subparsers de argparse para hacerlo en su lugar. Así que hice que cada operación (verificar, construir, implementar, etc.) fuera subparser.

Normalmente, podría vincular cada subcomando a una función en particular y hacer que lo llame. Sin embargo, no quiero simplemente llamar a una función genérica check() - Necesito crear el tipo correcto de objeto primero, y luego llamar a la función apropiada dentro de ese objeto.

¿Hay alguna manera buena o pítica de hacer esto? Preferiblemente uno que no involucra una gran cantidad de hardcoding, o mal diseñado si/else loops?

+0

Ha intentado [Tela] (http://fabfile.org/) - ¿herramienta de línea de comandos para optimizar el uso de SSH para implementación de aplicaciones o tareas de administración de sistemas? – jfs

Respuesta

2

Si se fijan en el uso de un subparser para cada comando que haría algo como esto. Utilice el soporte de tipo de argparse para llamar a una función que busque la clase que desea crear una instancia y la devuelve.

continuación, llame al método en esa instancia dinámicamente con 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)() 

de salida es como la siguiente:

$ 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. 
0

Podrías usar los objetos en el 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

Gracias =). Sin embargo, mi pregunta es más sobre el lado argparse/sub-comando de las cosas: cómo puedo vincular una línea de comando dada a la función correcta en el tipo de objeto correcto. Además, si fuera solo una clase, sería fácil, sin embargo, primero necesito crear el tipo de clase apropiado, luego enlazar a la función dentro de eso, todo usando los Subcomandos de argparse. – victorhooi

0

Esto debería funcionar (pero un mapeo manual sería más sencillo en mi opinión):

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) 
Cuestiones relacionadas