Разбор нескольких подкоманд в 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

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

ВОПРОСЫ

  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'}}

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 и разделить его перед передачей одному или нескольким парсерам. Вы можете разделить его на какое-то ключевое слово или на префиксы и т. Д.

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

Другие вопросы по тегам