2011-06-07 8 views
6

Sto creando un piccolo script Python per gestire diverse classi di server (FTP, HTTP, SSH, ecc)Python ArgParse Subparsers e il collegamento alla funzione corretta

Su ogni tipo di server, siamo in grado di eseguire diversi tipi di azioni (implementare, configurare, controllare, ecc)

ho una classe di base Server, quindi una classe separata per ogni tipo di server che eredita da questo:

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

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

una riga di comando di esempio potrebbe be:

my_program deploy http 

Dalla riga di comando, i due argomenti obbligatori di cui ho bisogno sono:

  1. Operazione per eseguire
  2. tipo di server per creare/gestire

In precedenza, stavo usando argparse e l'operazione store e utilizzando un dict per abbinare l'opzione della riga di comando al nome effettivo della classe e della funzione. Per esempio:

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

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

(Nel mio codice vero e proprio, valid_operations non era del tutto un ingenuo mappatura 1: 1.)

E poi utilizzando il codice piuttosto orribile per creare il giusto tipo di oggetto, e chiamare il classe giusta

Quindi ho pensato di utilizzare la funzione subparsers di argparse per farlo. Quindi ho effettuato ciascuna operazione (controllo, compilazione, distribuzione, ecc.) A subparser.

In genere, potevo collegare ciascun comando secondario a una funzione particolare e chiamarlo. Tuttavia, non voglio chiamare semplicemente una funzione generica check() - Devo creare il tipo corretto di oggetto prima e quindi chiamare la funzione appropriata all'interno dell'oggetto.

Esiste un modo buono o pietoso per farlo? Preferibilmente uno che non coinvolge un sacco di hardcoding, o mal progettato se/else loops?

+0

Hai provato [Tessuto] (http://fabfile.org/) - Strumento da riga di comando per semplificare l'uso di SSH per la distribuzione delle applicazioni o le attività di amministrazione dei sistemi? – jfs

risposta

2

Se si imposta sull'utilizzo di un subparser per ogni comando vorrei fare qualcosa di simile. Usa il supporto del tipo di argparse per chiamare una funzione che ricerca la classe che vuoi istanziare e la restituisce.

Quindi chiamare il metodo su tale istanza in modo dinamico 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)() 

uscita simile a questa:

$ 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

Si potrebbe semplicemente utilizzare gli oggetti stessi nel dett.

#!/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

Grazie =). Tuttavia, la mia domanda è più sul lato argparse/sub-command delle cose - come faccio a collegare una determinata riga di comando alla funzione corretta nel tipo di oggetto corretto. Inoltre, se fosse solo una singola classe sarebbe facile, tuttavia, ho bisogno di prima creare il tipo di classe appropriato, quindi collegare la funzione all'interno di quella, utilizzando tutti i sottocomandi di argparse. – victorhooi

0

questo dovrebbe funzionare (ma una mappatura manuale sarebbe più semplice a mio parere):

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) 
Problemi correlati