Использование опции для вызова подкоманды может быть достигнуто с помощью click.MultiCommand
и пользовательского метода parse_args()
, например:
Пользовательский класс
def mode_opts_cmds(mode_opt_name, namespace=None):
class RemoteCommandsAsModeOpts(click.MultiCommand):
def __init__(self, *args, **kwargs):
super(RemoteCommandsAsModeOpts, self).__init__(*args, **kwargs)
self.mode_opt_name = '--{}'.format(mode_opt_name)
opt = next(p for p in self.params if p.name == mode_opt_name)
assert isinstance(opt.type, click.Choice)
choices = set(opt.type.choices)
self.commands = {k: v for k, v in (
namespace or globals()).items() if k in choices}
for command in self.commands.values():
assert isinstance(command, click.Command)
def parse_args(self, ctx, args):
try:
args.remove(self.mode_opt_name)
except ValueError:
pass
super(RemoteCommandsAsModeOpts, self).parse_args(ctx, args)
def list_commands(self, ctx):
return sorted(self.commands)
def get_command(self, ctx, name):
return self.commands[name]
return RemoteCommandsAsModeOpts
Использование пользовательского класса
Чтобы использовать пользовательский класс, вызовите функцию mode_opts_cmds()
для создания пользовательского класса, а затем используйте параметр cls
, чтобы передать этот класс декоратору click.group()
.
@click.group(cls=mode_opts_cmds('mode'))
@click.option('--mode', type=click.Choice(["foo", "bar"]))
def cli(mode):
"""My wonderful cli"""
Как это работает?
Это работает, потому что click - это хорошо спроектированная OO-инфраструктура. Декоратор @click.group()
обычно создает экземпляр объекта click.Group
, но позволяет переопределить это поведение параметром cls
. Так что сравнительно легко унаследовать от click.Group
в нашем собственном классе и перебрать нужные методы.
В этом случае мы перебираем click.Group.parse_args()
, так что когда анализируется командная строка, мы просто удаляем --mode
, а затем командная строка анализируется так же, как подкоманда normal .
Чтобы сделать это библиотечной функцией
Если пользовательский класс будет находиться вбиблиотеку, затем вместо использования значения по умолчанию globals()
из файла библиотеки необходимо будет передать пространство имен. Затем необходимо вызвать метод создания пользовательского класса с чем-то вроде:
@click.group(cls=mode_opts_cmds('mode', namespace=globals()))
TestКод
import click
@click.command()
@click.option('--foo-arg', default='asdf')
def foo(foo_arg):
"""the foo command is ok"""
click.echo(f"Hello from foo with arg {foo_arg}")
@click.command()
@click.option('--bar-arg', default='zxcv')
def bar(bar_arg):
"""bar is my favorite"""
click.echo(f"Hello from bar with arg {bar_arg}")
@click.group(cls=mode_opts_cmds('mode'))
@click.option('--mode', type=click.Choice(["foo", "bar"]))
def cli(mode):
"""My wonderful cli command"""
if __name__ == "__main__":
commands = (
'--mode foo --foo-arg asdf',
'--mode bar --bar-arg zxcv',
'--mode foo --bar-arg qwer',
'--mode foo --help',
'',
)
import sys, time
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
for command in commands:
try:
time.sleep(0.1)
print('-----------')
print('> ' + command)
time.sleep(0.1)
cli(command.split())
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc,
(click.ClickException, SystemExit)):
raise
Результаты теста
Click Version: 7.0
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> --mode foo --foo-arg asdf
Hello from foo with arg asdf
-----------
> --mode bar --bar-arg zxcv
Hello from bar with arg zxcv
-----------
> --mode foo --bar-arg qwer
Usage: test.py foo [OPTIONS]
Try "test.py foo --help" for help.
Error: no such option: --bar-arg
-----------
> --mode foo --help
Usage: test.py foo [OPTIONS]
the foo command is ok
Options:
--foo-arg TEXT
--help Show this message and exit.
-----------
>
Usage: test.py [OPTIONS] COMMAND [ARGS]...
My wonderful cli command
Options:
--mode [foo|bar]
--help Show this message and exit.
Commands:
bar bar is my favorite
foo the foo command is ok