2011-06-07 1 views
6

Je crée un petit script Python pour gérer les différentes classes de serveurs (FTP, HTTP, SSH, etc.)Python argparse Subparsers et un lien vers la fonction correcte

Sur chaque type de serveur, nous pouvons effectuer différentes types d'actions (déployer, configurer, vérifier, etc.)

J'ai une base Server classe, puis une classe distincte pour chaque type de serveur qui hérite de ceci:

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

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

une ligne de commande exemple pourrait être:

my_program deploy http 

De la ligne de commande, les deux arguments obligatoires dont j'ai besoin sont:

  1. opération à effectuer
  2. Type de serveur pour créer/gérer

Auparavant, j'utilisais argparse et l'opération store et en utilisant un dict pour faire correspondre l'option de ligne de commande au nom de classe et de fonction réel. Par exemple:

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

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

(Dans mon code actuel, valid_operations était pas tout à fait un 1 naïf: 1 cartographie.)

Et puis en utilisant le code plutôt horrible de créer le type d'objet, et appeler le bonne classe.

Ensuite, j'ai pensé que j'utiliserais la fonctionnalité subparsers d'argparse pour le faire à la place. J'ai donc fait chaque opération (vérifier, construire, déployer, etc.) un subparser.

Normalement, je pourrais lier chaque sous-commande à une fonction particulière, et l'appeler. Cependant, je ne veux pas simplement appeler une fonction générique check() - Je dois créer le type d'objet correct d'abord, puis appeler la fonction appropriée dans cet objet.

Existe-t-il une bonne façon de faire cela? De préférence celui qui n'implique pas beaucoup de hardcoding, ou des boucles if/else mal conçues?

+0

Avez-vous essayé [Tissu] (http://fabfile.org/) - outil de ligne de commande pour rationaliser l'utilisation de SSH pour le déploiement d'applications ou les tâches d'administration de systèmes? – jfs

Répondre

2

Si vous définissez sur l'utilisation d'un subparser pour chaque commande que je ferais quelque chose comme ça. Utilisez le support de type argparse pour appeler une fonction qui recherche la classe que vous souhaitez instancier et la renvoie.

appeler ensuite la méthode sur cette instance dynamique avec 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)() 

sortie ressemble à ceci:

$ 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

Vous pouvez simplement utiliser les objets eux-mêmes dans le 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

Merci =). Cependant, ma question est plus sur le côté argparse/sous-commande des choses - comment lier une ligne de commande donnée à la fonction correcte dans le bon type d'objet. De plus, si ce n'était qu'une seule classe, ce serait facile, cependant, je dois d'abord créer le type de classe approprié, puis le lier à la fonction, tout en utilisant les sous-commandes de argparse. – victorhooi

0

Cela devrait fonctionner (mais une cartographie manuelle serait plus direct à mon avis):

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) 
Questions connexes