Как проверить конкретные аргументы в определенном порядке - PullRequest
0 голосов
/ 28 апреля 2018

Я хотел бы иметь возможность проверять аргументы, переданные в сценарий, чтобы у меня были следующие опции:

test.py user info <user name>
test.py user create <user name> <last name> <first name> <email>
test.py user create batch <file name>
test.py user delete <user name>

test.py room info <room number>
test.py room create <room number> <room name>
test.py room delete <room number

В настоящее время у меня очень пушистый каскад if, elif, else:

if len(sys.argv) >= 2:
    if (sys.argv[1]).lower() == "user":
        if len(sys.argv) >= 3:
            if (sys.argv[2]).lower() == "info":
                if len(sys.argv) >= 4:
                    username = sys.argv[3]

                    get_user_info(username)
                else:
                    print("username not specified")

и я даже не буду беспокоиться о добавлении операторов elif и else.

Я читал, что использование argparse - лучший способ сделать это, но я просто не вижу, как реализовать это так, чтобы это отвечало моим потребностям.

Как и сейчас, если кто-то выполняет только файл test.py, он выводит действительные аргументы. Затем, если они выполняют test.py user самостоятельно, это дает им следующий набор допустимых аргументов, и так далее, и так далее. Как только вы доберетесь до, их нет.

Пожалуйста, помогите! И заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 29 апреля 2018

Вы можете использовать argparse с вариантами выбора, чтобы ограничить ввод для первых 2 строк. Остальные строки являются более переменными, но тесты if могут использовать общие черты.

import argparse

def lower(astr):
    return astr.lower()

parser = argparse.ArgumentParser()
parser.add_argument('first', type=lower, choices=['user', 'room'])
parser.add_argument('second', type=lower, choices=['info','create','delete'])
parser.add_argument('rest', nargs='+')
parser.set_defaults(file=[], user=[], room=[])  # defaults

args = parser.parse_args()
print(args)

# partial parse of the `rest` list of strings:

if args.first in ['user']:
    if args.second in ['create']:
         if args.rest[0] in ['batch']:
             args.files=args.rest[1:]
         else:
              args.user = args.rest
    else:
        args.user = args.rest[0]
else:
    args.room = args.rest
args.rest=[]    
print(args)

помощь:

1553:~/mypy$ python stack50071515.py -h
usage: stack50071515.py [-h] {user,room} {info,create,delete} rest [rest ...]

positional arguments:
  {user,room}
  {info,create,delete}
  rest

optional arguments:
  -h, --help            show this help message and exit

тестовые входы:

1545:~/mypy$ python stack50071515.py user info name
Namespace(file=[], first='user', rest=['name'], room=[], second='info', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='info', user='name')

1549:~/mypy$ python stack50071515.py user create a b c
Namespace(file=[], first='user', rest=['a', 'b', 'c'], room=[], second='create', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='create', user=['a', 'b', 'c'])

1549:~/mypy$ python stack50071515.py user create batch file
Namespace(file=[], first='user', rest=['batch', 'file'], room=[], second='create', user=[])
Namespace(file=[], files=['file'], first='user', rest=[], room=[], second='create', user=[])

1550:~/mypy$ python stack50071515.py user delete name
Namespace(file=[], first='user', rest=['name'], room=[], second='delete', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='delete', user='name')

1550:~/mypy$ python stack50071515.py room info number
Namespace(file=[], first='room', rest=['number'], room=[], second='info', user=[])
Namespace(file=[], first='room', rest=[], room=['number'], second='info', user=[])

1550:~/mypy$ python stack50071515.py room create a b
Namespace(file=[], first='room', rest=['a', 'b'], room=[], second='create', user=[])
Namespace(file=[], first='room', rest=[], room=['a', 'b'], second='create', user=[])

1550:~/mypy$ python stack50071515.py room delete 23
Namespace(file=[], first='room', rest=['23'], room=[], second='delete', user=[])
Namespace(file=[], first='room', rest=[], room=['23'], second='delete', user=[])
0 голосов
/ 29 апреля 2018

Вот решение, использующее argparse . Он вдохновлен тем, как _catkin_tools_ делает это.

подход

  1. Используйте parse_config словарь словаря для создания записей для <category> <verb>, таких как user info.
  2. parse_config для user info можно найти в parse_config['user']['info'] и содержит поля desc(ription), main и prepare_parser.
  3. main - это вызываемый объект (то есть функция), который вызывается в последней строке скрипта следующим образом: sys.exit(args.main(args)). Игнорируйте sys.exit(..) для этого. args.main - это функция, которая после анализа аргументов, заданных из командной строки, будет указывать на правильную точку входа в зависимости от заданных аргументов. Как это работает, используя subparser.set_defaults(main=main_for_this_subparser). Это означает, что если этот конкретный подпарсер активируется, то args.main указывает на main_for_this_subparser, который может быть любой функцией.
  4. prepare_parser также является функцией, которую можно настроить для каждой команды, чтобы добавить больше аргументов. Поскольку user всегда требует в качестве первого аргумента <user name>, я использовал def parser_user_default(parser): parser.add_argument('user_name') для всех трех глаголов ( info , create , удалить ). Так как create требуется больше аргументов, то получается специальная функция parser_user_create, в которой сначала вызывается parser_user_default, но затем добавляется больше аргументов.

Дальнейшее чтение

Дополнительные примечания

Мое решение до сих пор не обрабатывает вызов test.py user create batch <batch file>. С одной стороны, я не думаю, что это на самом деле хороший CLI, потому что пользователь никогда не может называться «пакетным». Возможно использование как user create --batch <file> было бы лучше. С другой стороны, этого я не мог заставить работать напрямую с argparse. Если вы поэкспериментируете с этим, рассмотрите parse_known_args(), который позволяет смешивать argparse с ручной обработкой аргументов.

Я не совсем доволен своей реализацией. Оглядываясь назад, рекурсивная реализация, которая обрабатывает вложенные словари любого уровня, будет чище. В настоящее время реализация обрабатывает ровно <category> <verb>.

Полный код

"""
test.py user info <user name>
test.py user create <user name> <last name> <first name> <email>
test.py user create batch <file name>
test.py user delete <user name>

test.py room info <room number>
test.py room create <room number> <room name>
test.py room delete <room number
"""

from argparse import ArgumentParser
import sys


def main_user_info(args):
    print('You called "user info" for user {}'.format(args.user_name))

def main_user_create(args):
    print('You called "user create" for : {u}, {last} {first} {mail}'.format(u=args.user_name,
                                                                             last=args.last_name,
                                                                             first=args.first_name,
                                                                             mail=args.email))
def main_user_create_batch(args):
    print('User batch creation using file {}'.format(args.filename))

def main_user_delete(args):
    pass

def main_room_info(args):
    print('Room info called for room number {}'.format(args.room_number))

def main_room_create(args):
    print('Creating room number {nr} with name {name}'.format(nr=args.room_number,
                                                              name=args.room_name))

def main_room_delete(args):
    pass

def parser_user_default(parser):
    parser.add_argument('user_name', metavar='<user name>')
    return parser

def parser_user_create(parser):
    parser = parser_user_default(parser)  # argument <user name>
    parser.add_argument('last_name', metavar='<last name>')
    parser.add_argument('first_name', metavar='<first name>')
    parser.add_argument('email', metavar='<email>')
    return parser

def parser_room_default(parser):
    parser.add_argument('room_number', metavar='<room number>')
    # Above: restrict room number to ints: 'type=int'
    # Restrict room number to known rooms: 'choices=list_of_known_rooms'
    # Restrict room numbers to 1 to 1000: 'choices=range(1, 1001)'
    return parser

def parser_room_create(parser):
    parser = parser_room_default(parser)
    parser.add_argument('room_name', metavar='<room name>')
    return parser

parse_config = {
    'user': {'info': {'desc': 'Show user info', 'main': main_user_info,
                      'prepare_parser': parser_user_default},
             'create': {'desc': 'Create new user', 'main': main_user_create,
                        'prepare_parser': parser_user_create},
             'delete': {'desc': 'Delete user', 'main': main_user_delete,
                        'prepare_parser': parser_user_default}
             },
    'room': {'info': {'desc': 'Show room info', 'main': main_room_info,
                      'prepare_parser': parser_room_default},
             'create': {'desc': 'Show room info', 'main': main_room_create,
                      'prepare_parser': parser_room_create},
             'delete': {'desc': 'Delete a room', 'main': main_room_delete,
                      'prepare_parser': parser_room_default}
             },
}


def prepare_verb_args(parser, category):
    subparsers = parser.add_subparsers(title='verb')

    for verb, config in parse_config[category].items():
        subparser = subparsers.add_parser(verb, description=config['desc'])
        subparser = config['prepare_parser'](subparser)

        # Reading 'set_defaults' is helpful. Notably, this overwrites default of args.main
        # when given this particular category on command line.
        subparser.set_defaults(main=config['main'])
    return parser

if __name__ == '__main__':
    parser = ArgumentParser()
    parser.set_defaults(main=lambda _: parser.print_usage())

    # Inspired by 'catkin_tools'.
    subparsers = parser.add_subparsers(dest='category', title='category')

    for c in parse_config.keys():
        subparser = subparsers.add_parser(c, description='Act on {}'.format(c))
        subparser = prepare_verb_args(subparser, category=c)
        # NOTE: Somehow the lambda binds to the last 'subparser' in the loop.
        # This is a bug but I haven't got the time to fix it.
        subparser.set_defaults(main=lambda x: subparser.print_usage())

    args = parser.parse_args()
    sys.exit(args.main(args))
...