2011-06-07 8 views
6

tworzę mały skrypt Pythona do zarządzania różnych klas serwerów (HTTP, FTP, SSH, etc.)Python ArgParse Subparsers i linkami do prawidłowego działania

na każdym typie serwera, możemy wykonać inny rodzaje działań (wdrożenie, konfigurację, sprawdź, etc.)

mam baza Server klasę, a następnie oddzielną klasę dla każdego typu serwera, która dziedziczy z tego:

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

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

linia komend próbka może być:

my_program deploy http 

Z linii poleceń, dwa obowiązkowe argumenty muszę to:

  1. Operation wykonywać
  2. typ serwera, aby utworzyć/zarządzać

Wcześniej używałem argparse i operacji store i za pomocą dict dopasować opcję wiersza polecenia do rzeczywistej klasy i nazwy funkcji. Na przykład:

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

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

(W moim rzeczywisty kod, valid_operations nie było dość naiwny mapowanie 1: 1).

A następnie przy użyciu raczej straszny kod stworzyć odpowiedni rodzaj obiektu i wywołać właściwa klasa.

Potem pomyślałem, że zamiast tego użyję funkcji argparse'a subparsers. Dlatego wykonałem każdą operację (sprawdzenie, zbudowanie, wdrożenie, itd.), A następnie subparser.

Normalnie mógłbym połączyć każde pod-polecenie z konkretną funkcją i zlecić jej wywołanie. Jednak nie chcę po prostu wywoływać ogólnej funkcji check() - potrzebuję utworzyć prawidłowy typ obiektu najpierw, a następnie wywołać odpowiednią funkcję w obrębie tego obiektu.

Czy istnieje dobry, czy pythonic sposób to zrobić? Najlepiej taki, który nie wymaga dużo twardego kodu lub źle zaprojektowanego pętli/else?

+0

próbowałeś [tkaniny] (http://fabfile.org/) - narzędzie wiersza polecenia do usprawnienia korzystania z SSH do wdrażania aplikacji lub zadań administracyjnych systemu? – jfs

Odpowiedz

2

Jeśli jesteś ustawiony na użyciu subparser dla każdego polecenia chciałbym zrobić coś takiego. Użyj funkcji argparse, aby wywołać funkcję, która wyszukuje klasę, którą chcesz utworzyć i zwraca ją.

Następnie wywołać metodę na tej instancji dynamicznie 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)() 

Wyjście wygląda mniej więcej tak:

$ 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

Można użyć samych obiektów w dyktafonie.

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

Dzięki =). Jednak moje pytanie jest bardziej po stronie argparse/sub-command - w jaki sposób połączyć dany wiersz polecenia z poprawną funkcją we właściwym typie obiektu. Ponadto, jeśli byłaby to tylko jedna klasa, byłoby to łatwe, jednak najpierw trzeba utworzyć odpowiedni typ klasy, a następnie połączyć z nią funkcję, używając podkomisji argparse. – victorhooi

0

To powinno działać (ale ręcznego mapowania byłoby bardziej proste moim zdaniem):

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) 
Powiązane problemy