#python #parameters #command-line-arguments #argparse
#python #параметры #аргументы командной строки #argparse
Вопрос:
Я создаю небольшой скрипт на Python для управления различными классами серверов (FTP, HTTP, SSH и т.д.)
На каждом типе сервера мы можем выполнять различные типы действий (развертывание, настройка, проверка и т.д.)
У меня есть базовый Server
класс, затем отдельный класс для каждого типа сервера, который наследуется от этого:
class Server:
...
def check():
...
class HTTPServer(Server):
def check():
super(HTTPServer, self).check()
...
class FTPServer(Server):
def check():
super(FTPServer, self).check()
...
Пример командной строки может быть:
my_program deploy http
Из командной строки мне нужны два обязательных аргумента:
- Операция для выполнения
- Тип сервера для создания / управления
Ранее я использовал argparse
и store
операцию, а также использовал dict
для сопоставления параметра командной строки с фактическим именем класса и функции. Например:
types_of_servers = {
'http': 'HTTPServer',
'ftp': 'FTPServer',
...
}
valid_operations = {
'check': 'check',
'build': 'build',
'deploy': 'deploy',
'configure': 'configure',
'verify': 'verify',
}
(В моем реальном коде valid_operations не был совсем наивным отображением 1: 1.)
А затем использование довольно ужасного кода для создания нужного типа объекта и вызова нужного класса.
Тогда я подумал, что вместо этого я бы использовал subparsers
функцию argparse. Итак, я сделал каждую операцию (проверку, сборку, развертывание и т.д.) подразделом.
Обычно я мог бы связать каждую подкоманду с определенной функцией и заставить ее вызывать ее. Однако я не хочу просто вызывать универсальную check()
функцию — сначала мне нужно создать правильный тип объекта, а затем вызвать соответствующую функцию внутри этого объекта.
Есть ли хороший или pythonic способ сделать это? Предпочтительно такой, который не требует большого количества жесткого кодирования или плохо спроектированных циклов if / else?
Комментарии:
1. Пробовали ли вы Fabric — инструмент командной строки для оптимизации использования SSH для развертывания приложений или задач системного администрирования?
Ответ №1:
Если вы настроены на использование подраздела для каждой команды, я бы сделал что-то вроде этого. Используйте поддержку типов argparse для вызова функции, которая ищет класс, который вы хотите создать, и возвращает его.
Затем вызовите метод для этого экземпляра динамически с помощью 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)()
Вывод выглядит примерно так:
$ 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:
Вы могли бы просто использовать сами объекты в 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
Комментарии:
1. Спасибо =). Однако мой вопрос больше касается argparse / субкоманды — как мне связать данную командную строку с правильной функцией в правильном типе объекта. Кроме того, если бы это был всего лишь один класс, это было бы легко, однако, мне нужно сначала создать соответствующий тип класса, а затем связать с функцией внутри него, все с использованием подкоманд argparse .
Ответ №3:
Это должно сработать (но, на мой взгляд, ручное сопоставление было бы более простым):
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)