Экскурсия по командной строке Python: знакомство с подкомандой click

Python GitHub
Экскурсия по командной строке Python: знакомство с подкомандой click
Автор: HelloGitHub-Prodesire

Образец кода и задействованные исторические статьи были обновлены доРепозиторий HelloGitHub-Team

Введение

В последних двух статьях мы представилиclick«Параметры» и «опции» в этой статье будут продолжать пониматьclick, ориентируясь на его «команды» и «группы».

本系列文章默认使用 Python 3 作为解释器进行讲解。
若你仍在使用 Python 2,请注意两者之间语法和库的使用差异哦~

2. Команды и группы

ClickОчень важной особенностью является концепция произвольно вложенных инструментов командной строки, черезCommandа такжеGroup(в реальностиMultiCommand)реализовать.

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

2.1 Обратный вызов

Для обычной команды обратный вызов происходит при выполнении команды. Если бы в реализации этой программы были только команды, коллбэк срабатывал бы всегда, как и все примеры, которые мы приводили в предыдущей статье. Но, как--helpТакие опции предотвращают вход в обратный вызов.

Ситуация немного отличается для групп и нескольких подкоманд. Обратные вызовы обычно возникают при выполнении подкоманд:

@click.group()
@click.option('--debug/--no-debug', default=False)
def cli(debug):
    click.echo('Debug mode is %s' % ('on' if debug else 'off'))

@cli.command()  # @cli, not @click!
def sync():
    click.echo('Syncing')

Эффект от исполнения следующий:

Usage: tool.py [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --help                Show this message and exit.

Commands:
  sync

$ tool.py --debug sync
Debug mode is on
Syncing

В приведенном выше примере мы поместили функциюcliОпределяемая как группа, функцияsyncОпределяется как подкоманда в этой группе. когда мы звонимtool.py --debug syncкоманда, она будет запущена по очередиcliа такжеsyncЛогика обработки (то есть обратный вызов команды).

2.2 Вложенная обработка и контексты

Как видно из приведенного выше примера, группа командcliПолученные аргументы и подкомандыsyncнезависимы друг от друга. Но иногда мы хотим получить параметры группы команд в подкоманде, которую можно использоватьContextреализовать.

Всякий раз, когда вызывается команда,clickНовый контекст создается и связывается с родительским контекстом. Обычно мы не видим контекстной информации. но мы можем пройтиpass_contextдекоратор, чтобы явно позволитьclickПередайте контекст, эта переменная передается в качестве первого параметра.

@click.group()
@click.option('--debug/--no-debug', default=False)
@click.pass_context
def cli(ctx, debug):
    # 确保 ctx.obj 存在并且是个 dict。 (以防 `cli()` 指定 obj 为其他类型
    ctx.ensure_object(dict)

    ctx.obj['DEBUG'] = debug

@cli.command()
@click.pass_context
def sync(ctx):
    click.echo('Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off'))

if __name__ == '__main__':
    cli(obj={})

В приведенном выше примере:

  • через группу командcliи подкомандыsyncУкажите декораторclick.pass_context, первый параметр обеих функций равенctxконтекст
  • в командной группеcli, для контекстаobjПрисвоение переменной (словарю)
  • в подкомандеsyncпрошедшийctx.obj['DEBUG']Получить параметры предыдущего шага
  • Передача параметров из группы команд в подкоманды осуществляется таким образом

2.3 Вызов групп команд без команд

По умолчанию группы команд вызываются при вызове подкоманд. А иногда вы можете захотеть вызвать группу команд напрямую, указавclick.groupизinvoke_without_command=Trueреализовать:

@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
    if ctx.invoked_subcommand is None:
        click.echo('I was invoked without subcommand')
    else:
        click.echo('I am about to invoke %s' % ctx.invoked_subcommand)

@cli.command()
def sync():
    click.echo('The subcommand')

Команды вызова:

$ tool
I was invoked without subcommand
$ tool sync
I am about to invoke sync
The subcommand

В приведенном выше примере поctx.invoked_subcommandЧтобы определить, вызвана ли она подкомандой, распечатайте журнал для двух случаев.

2.4 Пользовательская группа команд/несколько команд

Помимо использованияclick.groupВ дополнение к определению групп команд вы также можете настраивать группы команд (также называемые несколькими командами), чтобы вы могли отложенно загружать подкоманды, что может быть полезно.

Пользовательская мультикоманда должна быть реализованаlist_commandsа такжеget_commandметод:

import click
import os

plugin_folder = os.path.join(os.path.dirname(__file__), 'commands')

class MyCLI(click.MultiCommand):

    def list_commands(self, ctx):
        rv = []  # 命令名称列表
        for filename in os.listdir(plugin_folder):
            if filename.endswith('.py'):
                rv.append(filename[:-3])
        rv.sort()
        return rv

    def get_command(self, ctx, name):
        ns = {}
        fn = os.path.join(plugin_folder, name + '.py')  # 命令对应的 Python 文件
        with open(fn) as f:
            code = compile(f.read(), fn, 'exec')
            eval(code, ns, ns)
        return ns['cli']

cli = MyCLI(help='This tool\'s subcommands are loaded from a '
            'plugin folder dynamically.')

# 等价方式是通过 click.command 装饰器,指定 cls=MyCLI
# @click.command(cls=MyCLI)
# def cli():
#     pass

if __name__ == '__main__':
    cli()

2.5 Объединение групп команд/несколько команд

Когда есть несколько групп команд с некоторыми командами в каждой группе команд, и вы хотите объединить все команды в один набор,click.CommandCollectionЭто пригодится:


@click.group()
def cli1():
    pass

@cli1.command()
def cmd1():
    """Command on cli1"""

@click.group()
def cli2():
    pass

@cli2.command()
def cmd2():
    """Command on cli2"""

cli = click.CommandCollection(sources=[cli1, cli2])

if __name__ == '__main__':
    cli()

Команды вызова:

$ cli --help
Usage: cli [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  cmd1  Command on cli1
  cmd2  Command on cli2

Как видно из приведенного выше примера,cmd1а такжеcmd2принадлежитcli1а такжеcli2,пройти черезclick.CommandCollectionЭти подкоманды можно комбинировать, чтобы обеспечить их возможности в одной и той же командной программе.

Советы: Если одна и та же подкоманда определена в нескольких группах команд, выберите подкоманду в первой группе команд.

2.6 Связанная группа команд/несколько команд

Иногда одноуровневая подкоманда может не соответствовать вашим потребностям, и вам может даже понадобиться несколько уровней подкоманд. Как правило,setuptoolsВ пакете поддерживаются многоуровневые/связанные подкоманды:setup.py sdist bdist_wheel upload. После клика 3.0 реализация связанных групп команд стала очень простой, простоclick.groupуказано вchain=True:

@click.group(chain=True)
def cli():
    pass


@cli.command('sdist')
def sdist():
    click.echo('sdist called')


@cli.command('bdist_wheel')
def bdist_wheel():
    click.echo('bdist_wheel called')

Команда вызова:

$ setup.py sdist bdist_wheel
sdist called
bdist_wheel called

2.7 Группа команд/мультикомандный конвейер

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

Смысл реализации конвейера групп команд состоит в том, чтобы каждая команда возвращала функцию обработчика, а затем писалась общая диспетчерская функция конвейера (и запускаласьMultiCommand.resultcallback()Украсить):

@click.group(chain=True, invoke_without_command=True)
@click.option('-i', '--input', type=click.File('r'))
def cli(input):
    pass

@cli.resultcallback()
def process_pipeline(processors, input):
    iterator = (x.rstrip('\r\n') for x in input)
    for processor in processors:
        iterator = processor(iterator)
    for item in iterator:
        click.echo(item)

@cli.command('uppercase')
def make_uppercase():
    def processor(iterator):
        for line in iterator:
            yield line.upper()
    return processor

@cli.command('lowercase')
def make_lowercase():
    def processor(iterator):
        for line in iterator:
            yield line.lower()
    return processor

@cli.command('strip')
def make_strip():
    def processor(iterator):
        for line in iterator:
            yield line.strip()
    return processor

В приведенном выше примере:

  • будетcliОпределяется как связанная группа команд, и укажите invoke_without_command=True, что означает, что группа команд может запускаться без передачи подкоманд.
  • Определены три функции обработки команд, соответствующиеuppercase,lowercaseа такжеstripЗаказ
  • Функция расписания в трубопроводеprocess_pipeline, войтиinputСтаньте генератором, а затем вызовите функцию обработки (на самом деле введите несколько команд, есть несколько функций обработки) для обработки

2.8 Переопределение значений по умолчанию

По умолчанию дефолтное значение параметра берется из параметра через декораторdefaultопределение. Мы также можем пройтиContext.default_mapсловарь контекста, чтобы переопределить значение по умолчанию:

@click.group()
def cli():
    pass

@cli.command()
@click.option('--port', default=8000)
def runserver(port):
    click.echo('Serving on http://127.0.0.1:%d/' % port)

if __name__ == '__main__':
    cli(default_map={
        'runserver': {
            'port': 5000
        }
    })

В приведенном выше примере поcliуказано вdefault_mapПеременная может переопределить параметр команды (первичный ключ) (вторичный ключ) по умолчанию (значение вторичного ключа).

мы также можемclick.groupуказано вcontext_settingsдля достижения той же цели:


CONTEXT_SETTINGS = dict(
    default_map={'runserver': {'port': 5000}}
)

@click.group(context_settings=CONTEXT_SETTINGS)
def cli():
    pass

@cli.command()
@click.option('--port', default=8000)
def runserver(port):
    click.echo('Serving on http://127.0.0.1:%d/' % port)

if __name__ == '__main__':
    cli()

Команда вызова:

$ cli runserver
Serving on http://127.0.0.1:5000/

3. Резюме

В этой статье сначала рассматриваются вызов обратного вызова и контекст команд, а затем вводятся дополнительные сведения о настройке, слиянии, связывании, конвейере и других функциях групп команд.clickвласти. И чем более продвинутые способности в командной группе(как возвращаемое значение команды), вы можете обратиться к официальной документации для получения дополнительной информации.

Мы представляемclickПараметры, опции и команды смогли полностью реализовать все функции программы командной строки. а такжеclickЭто также дает нам много вишенки на торте, таких как утилиты, автозаполнение параметров и т. д., которые мы подробно опишем в следующем разделе.

«Объяснение серии проектов с открытым исходным кодом»——Пусть больше не боятся люди, интересующиеся проектами с открытым исходным кодом, и пусть инициаторы проектов с открытым исходным кодом больше не остаются в одиночестве. Следите за нашими статьями, и вы откроете для себя радость программирования, насколько легко им пользоваться, и узнаете, как легко участвовать в проектах с открытым исходным кодом. Добро пожаловать, чтобы оставить сообщение, чтобы связаться с нами, присоединиться к нам, позволить большему количеству людей влюбиться в открытый исходный код и внести свой вклад в открытый исходный код ~