Анализирайте множество подкоманди в Python едновременно или по друг начин за групиране на анализирани аргументи

Преобразувам помощната програма за инсталиране на Bash shell в Python 2.7 и трябва да внедря сложен CLI, така че да мога да анализирам десетки параметри (потенциално до ~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 е прост обект, в който всеки аргумент е атрибут. Може да се преобразува и в речник. Лесно е да изтеглите групи от елементи от речник.

Има много въпроси относно използването на множество подпарсери. Това е неудобно предложение. Възможно, но не лесно. Подпарсерите са като издаване на команда към системна програма. Обикновено издавате една команда на повикване. Не ги влагате или издавате последователности. Оставяте shell piping и скриптове да обработват множество действия.

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 различни DB услуги, 2 виртуализационни бекенда и е внедрено на 5 езика за програмиране. Може да съм пропуснал нещо. - person Elvinas; 23.07.2015
comment
Какво ще кажете за параметър, който ви позволява да изберете кой конфигурационен файл да използвате и след това да намалите прекомерно управляваните параметри до тези с най-голяма вероятност за промени. - person Bill Rosmus; 26.07.2015
comment
Както казах, вече имам опции за конфигурационен файл. Всъщност имаше 3 от тях (сега 2) - файл с настройки по подразбиране и конфигурационен файл със замени/специфични за средата променливи, в зависимост от средата. Въпреки това могат да бъдат направени множество внедрявания, свързани с една и съща среда и те могат да имат няколко или десетки различни параметри, които да бъдат заменени. Това е сложна система, понякога твърде сложна :D - person Elvinas; 28.07.2015