Одновременный анализ нескольких подкоманд в python или другой способ группировки проанализированных аргументов

Я конвертирую утилиту установки оболочки Bash в Python 2.7, и мне нужно реализовать сложный интерфейс командной строки, чтобы я мог анализировать десятки параметров (потенциально до ~ 150). Это имена переменных класса Puppet в дополнение к дюжине общих параметров развертывания, которые доступны в версии оболочки.

Однако после того, как я начал добавлять больше переменных, я столкнулся с несколькими проблемами: 1. Мне нужно сгруппировать параметры в отдельные словари, чтобы параметры развертывания были отделены от переменных Puppet. Если их бросить в одно ведро, то мне придется написать некоторую логику для их сортировки, потенциально переименовав параметры, и тогда слияние словарей не будет тривиальным. 2. Могут быть переменные с одинаковыми именами, но принадлежащие к разным классам Puppet, поэтому я подумал, что подкоманды позволят мне фильтровать, что и куда следует, и избегать конфликтов имен.

На данный момент я реализовал синтаксический анализ параметров, просто добавив несколько парсеров:

parser = argparse.ArgumentParser(description='deployment parameters.')
env_select = parser.add_argument_group(None, 'Environment selection')
env_select.add_argument('-c', '--client_id',  help='Client name to use.')
env_select.add_argument('-e', '--environment', help='Environment name to use.')
setup_type = parser.add_argument_group(None, 'What kind of setup should be done:')
setup_type.add_argument('-i', '--install', choices=ANSWERS, metavar='', action=StoreBool, help='Yy/Nn Do normal install and configuration')
# MORE setup options
...
args, unk = parser.parse_known_args()
config['deploy_cfg'].update(args.__dict__)

pup_class1_parser = argparse.ArgumentParser(description=None)
pup_class1 = pup_class1_parser.add_argument_group(None, 'Puppet variables')
pup_class1.add_argument('--ad_domain', help='AD/LDAP domain name.')
pup_class1.add_argument('--ad_host', help='AD/LDAP server name.')
# Rest of the parameters

args, unk = pup_class1_parser.parse_known_args()
config['pup_class1'] = dict({})
config['pup_class1'].update(args.__dict__)
# Same for class2, class3 and so on.

Проблема с этим подходом в том, что он не решает проблему 2. Также первый анализатор использует опцию «-h», а остальные параметры не отображаются в справке.

Я попытался использовать пример, выбранный в качестве ответа но я не смог использовать обе команды одновременно.

## This function takes the 'extra' attribute from global namespace and re-parses it to create separate namespaces for all other chained commands.
def parse_extra (parser, namespace):
  namespaces = []
  extra = namespace.extra
  while extra:
    n = parser.parse_args(extra)
    extra = n.extra
    namespaces.append(n)

  return namespaces

pp = pprint.PrettyPrinter(indent=4)

argparser=argparse.ArgumentParser()
subparsers = argparser.add_subparsers(help='sub-command help', dest='subparser_name')

parser_a = subparsers.add_parser('command_a', help = "command_a help")
## Setup options for parser_a
parser_a.add_argument('--opt_a1', help='option a1')
parser_a.add_argument('--opt_a2', help='option a2')

parser_b = subparsers.add_parser('command_b', help = "command_b help")
## Setup options for parser_a
parser_b.add_argument('--opt_b1', help='option b1')
parser_b.add_argument('--opt_b2', help='option b2')


## Add nargs="*" for zero or more other commands
argparser.add_argument('extra', nargs = "*", help = 'Other commands')

namespace = argparser.parse_args()
pp.pprint(namespace)
extra_namespaces = parse_extra( argparser, namespace )
pp.pprint(extra_namespaces)

Результаты меня в:

$ python argtest.py command_b --opt_b1 b1 --opt_b2 b2 command_a --opt_a1 a1
usage: argtest.py [-h] {command_a,command_b} ... [extra [extra ...]]
argtest.py: error: unrecognized arguments: command_a --opt_a1 a1

Тот же результат был, когда я попытался определить родителя с двумя дочерними парсерами.

ВОПРОСЫ

  1. Могу ли я как-то использовать parser.add_argument_group для анализа аргументов или это только для группировки в распечатке справки? Это решило бы проблему 1, не пропуская побочный эффект справки. Передача его как parse_known_args(namespace=argument_group) (если я правильно помню свои эксперименты) получает все переменные (это нормально), но также получает все объекты объекта Python в результирующем dict (это плохо для hieradata YAML)
  2. Чего мне не хватает во втором примере, чтобы разрешить использование нескольких подкоманд? Или это невозможно с argparse?
  3. Любое другое предложение по группировке переменных командной строки? Я посмотрел на Click, но не нашел никаких преимуществ перед стандартным argparse для своей задачи.

Примечание. Я системный администратор, а не программист, поэтому будьте осторожны со мной за кодирование в необъектном стиле. :)

Спасибо

РЕШЕНО Группировка аргументов решена с помощью ответа, предложенного hpaulj.

import argparse
import pprint
parser = argparse.ArgumentParser()

group_list = ['group1', 'group2']

group1 = parser.add_argument_group('group1')
group1.add_argument('--test11', help="test11")
group1.add_argument('--test12', help="test12")

group2 = parser.add_argument_group('group2')
group2.add_argument('--test21', help="test21")
group2.add_argument('--test22', help="test22")

args = parser.parse_args()
pp = pprint.PrettyPrinter(indent=4)

d = dict({})

for group in parser._action_groups:
    if group.title in group_list:
        d[group.title]={a.dest:getattr(args,a.dest,None) for a in group._group_actions}

print "Parsed arguments"
pp.pprint(d)

Это дает мне желаемый результат для проблемы №1. пока у меня не будет нескольких параметров с одинаковым именем. Решение может выглядеть некрасиво, но, по крайней мере, оно работает так, как ожидалось.

python argtest4.py --test22 aa  --test11 yy11 --test21 aaa21
Parsed arguments
{   'group1': {   'test11': 'yy11', 'test12': None},
    'group2': {   'test21': 'aaa21', 'test22': 'aa'}}

person Elvinas    schedule 22.07.2015    source источник


Ответы (2)


Ваш вопрос слишком сложен, чтобы понять и ответить на него с одной попытки. Но я выброшу некоторые предварительные идеи.

Да, argument_groups — это просто способ группировки аргументов в справке. На синтаксический анализ они не влияют.

Другой недавний SO спросил о разборе групп аргументов:

Можно ли анализировать только один аргумент параметры группы с помощью argparse?

Этот плакат изначально хотел использовать группу в качестве парсера, но структура класса argparse не позволяет этого. argparse написано в объектном стиле. parser=ArguementParser... создает один класс объектов, parser.add_arguement... создает другой, add_argument_group... еще один. Вы настраиваете его, создавая подклассы ArgumentParser или HelpFormatter или Action классов и т. д.

Я упомянул механизм parents. Вы определяете один или несколько родительских парсеров и используете их для заполнения своего «основного» парсера. Их можно запускать независимо (с parse_known_args), в то время как «main» используется для обработки справки.

Мы также обсудили группировку аргументов после синтаксического анализа. namespace — это простой объект, в котором каждый аргумент является атрибутом. Его также можно преобразовать в словарь. Легко извлекать группы элементов из словаря.

Есть ТАК вопросы об использовании нескольких подпарсеров. Это неудобное предложение. Возможно, но непросто. Подпарсеры подобны выдаче команды системной программе. Обычно вы выдаете одну команду за вызов. Вы не вкладываете их и не выдаете последовательности. Вы позволяете конвейеру оболочки и сценариям выполнять несколько действий.

IPython использует argparse для анализа своих входных данных. Сначала он перехватывает помощь и выдает собственное сообщение. Большинство аргументов поступают из файлов конфигурации, поэтому можно установить значения с конфигурациями по умолчанию, пользовательскими конфигурациями и в командной строке. Это пример именования очень большого набора аргументов.

Подпарсеры позволяют вам использовать одно и то же имя аргумента, но без возможности вызывать несколько подпарсеров за один вызов, что мало помогает. И даже если бы вы могли вызвать несколько подпарсеров, они все равно поместили бы аргументы в одно и то же пространство имен. Также argparse пытается обрабатывать помеченные аргументы независимо от порядка. Таким образом, --foo в конце командной строки анализируется так же, как если бы оно было в начале.

Был ТАК вопрос, где мы обсуждали использование имен аргументов («dest»), таких как 'group1.argument1', и я даже обсуждал использование вложенных пространств имен. Я мог бы поискать их, если это поможет.


Еще одна мысль - загрузить sys.argv и разбить его перед передачей одному или нескольким парсерам. Вы можете разделить его на какое-то ключевое слово, или на префиксы и т.д.

person hpaulj    schedule 22.07.2015
comment
Спасибо. Ссылки предоставили полезный ответ на задачу №1. Теперь я могу группировать параметры и перемещать их в отдельные словари. - person Elvinas; 23.07.2015

Если у вас так много аргументов, это похоже на проблему дизайна. Это кажется очень неуправляемым. Не можете ли вы реализовать это с помощью файла конфигурации с разумным набором значений по умолчанию? Или значения по умолчанию в коде с разумным (т.е. МАЛЕНЬКИМ) количеством аргументов в командной строке и разрешить переопределение всего или всего остального параметрами в файле конфигурации «ключ: значение»? Я не могу представить, что мне придется использовать CLI с тем количеством переменных, которое вы предлагаете.

person Bill Rosmus    schedule 22.07.2015
comment
Ну у меня УЖЕ есть конфиги и они успешно используются. Идея состоит в том, чтобы иметь возможность переопределять эти значения из CLI. Скорее всего потребуется переопределить 10-15 параметров, но выбор этих 10-15 может быть из всего набора возможных значений конфигурации. Для справки, полное развертывание продукта — это 15-17 хостов, которые используют веб-сервис, 2 разных сервиса БД, 2 бэкенда виртуализации и реализованы на 5 языках программирования. Может быть я что-то упустил. - person Elvinas; 23.07.2015
comment
Как насчет параметра, позволяющего вам выбирать, какой файл конфигурации использовать, а затем уменьшать переопределяемые параметры до наиболее вероятных для изменений. - person Bill Rosmus; 26.07.2015
comment
Как я уже сказал, у меня уже есть параметры для файла конфигурации. На самом деле их было 3 (теперь 2) - файл со значениями по умолчанию и файл конфигурации с переопределениями/переменными среды, в зависимости от среды. Однако можно выполнить несколько развертываний, связанных с одной и той же средой, и они могут иметь несколько или десятки различных параметров, которые необходимо переопределить. Это сложная система, иногда слишком сложная :D - person Elvinas; 28.07.2015