单击:如何将操作应用于所有命令和子命令,但允许命令进行optout(part duex)?

2024-06-16 11:58:21 发布

您现在位置:Python中文网/ 问答频道 /正文

my initial question的基础上,我希望能够在运行回调之前运行父组的主体。在

I have a case where I'd like to automatically run a common function, check_upgrade(), for most of my click commands and sub-commands, but there are a few cases where I don't want to run it. I was thinking I could have a decorator that one can add (e.g. @bypass_upgrade_check) for commands where check_upgrade() should not run.

例如:

def do_upgrade():
    print("Performing upgrade")

bypass_upgrade_check = make_exclude_hook_group(do_upgrade)

@click.group(cls=bypass_upgrade_check())
@click.option('--arg1', default=DFLT_ARG1)
@click.option('--arg2', default=DFLT_ARG2)
@click.pass_context
def cli(ctx, arg1, arg2):
    config.call_me_before_upgrade_check(arg1, arg2)

@bypass_upgrade_check
@cli.command()
def top_cmd1():
    click.echo('cmd1')

@cli.command()
def top_cmd2():
    click.echo('cmd2')

@cli.group()
def sub_cmd_group():
    click.echo('sub_cmd_group')

@bypass_upgrade_check
@sub_cmd_group.command()
def sub_cmd1():
    click.echo('sub_cmd1')

@sub_cmd_group.command()
def sub_cmd2():
    click.echo('sub_cmd2')

我希望事情像the initial question中解释的那样运行,但是在执行cli()的主体之前执行do_upgrade(),我希望它调用:

^{pr2}$

例如。或者对于嵌套命令:

cli() --> sub_cmd_group() --> do_upgrade() --> sub_cmd1()

所以我想用另一种方式来表达这个问题:是否可以从原始问题中获得功能,但是在子命令本身运行之前调用回调,而不是在任何组块运行之前被调用?在

我之所以需要这样做,是因为传入顶级CLI命令的参数指示要检查升级的服务器地址。我需要这些信息来处理do_upgrade()。我无法将此信息直接传递给do_upgrade(),因为此服务器信息也在应用程序的其他地方使用。我可以从do_upgrade()用类似于config.get_server()的方式查询它。在


Tags: runechocmdclidefcheckgroupwhere
1条回答
网友
1楼 · 发布于 2024-06-16 11:58:21

original question类似,解决此问题的一种方法是构建一个与自定义click.Group类成对出现的自定义装饰器。增加的复杂性是钩住Command.invoke()而不是Group.invoke(),这样回调将在Command.invoke()之前立即调用,因此将在任何{}之后调用:

自定义装饰生成器:

import click

def make_exclude_hook_command(callback):
    """ for any command that is not decorated, call the callback """

    hook_attr_name = 'hook_' + callback.__name__

    class HookGroup(click.Group):
        """ group to hook context invoke to see if the callback is needed"""

        def group(self, *args, **kwargs):
            """ new group decorator to make sure sub groups are also hooked """
            if 'cls' not in kwargs:
                kwargs['cls'] = type(self)
            return super(HookGroup, self).group(*args, **kwargs)

        def command(self, *args, **kwargs):
            """ new command decorator to monkey patch command invoke """

            cmd = super(HookGroup, self).command(*args, **kwargs)

            def hook_command_decorate(f):
                # decorate the command
                ret = cmd(f)

                # grab the original command invoke
                orig_invoke = ret.invoke

                def invoke(ctx):
                    """call the call back right before command invoke"""
                    parent = ctx.parent
                    sub_cmd = parent and parent.command.commands[
                        parent.invoked_subcommand]
                    if not sub_cmd or \
                            not isinstance(sub_cmd, click.Group) and \
                            getattr(sub_cmd, hook_attr_name, True):
                        # invoke the callback
                        callback()
                    return orig_invoke(ctx)

                # hook our command invoke to command and return cmd
                ret.invoke = invoke
                return ret

            # return hooked command decorator
            return hook_command_decorate

    def decorator(func=None):
        if func is None:
            # if called other than as decorator, return group class
            return HookGroup

        setattr(func, hook_attr_name, False)

    return decorator

使用装饰生成器:

要使用decorator,我们首先需要构建decorator,如下所示:

^{pr2}$

然后我们需要将其用作自定义类click.group(),例如:

@click.group(cls=bypass_upgrade())
...

最后,我们可以将不需要使用回调的任何命令或子命令修饰为组:

@bypass_upgrade
@my_group.command()
def my_click_command_without_upgrade():
     ...

这是怎么回事?

这是因为click是一个设计良好的OO框架。@click.group()修饰符通常实例化一个click.Group对象,但允许使用cls参数覆盖此行为。因此,在我们自己的类中从click.Group继承并超越所需的方法是一件相对容易的事情。

在本例中,我们构建了一个decorator,它为任何不需要调用回调的click函数设置属性。然后在我们的自定义组中,我们覆盖了group()command()修饰符,这样我们就可以在命令上对invoke()进行猴子补丁,如果要执行的命令还没有被修饰,则调用回调。

测试代码:

def do_upgrade():
    click.echo("Performing upgrade")

bypass_upgrade = make_exclude_hook_command(do_upgrade)

@click.group(cls=bypass_upgrade())
@click.pass_context
def cli(ctx):
    click.echo('cli')

@bypass_upgrade
@cli.command()
def top_cmd1():
    click.echo('cmd1')

@cli.command()
def top_cmd2():
    click.echo('cmd2')

@cli.group()
def sub_cmd_group():
    click.echo('sub_cmd_group')

@bypass_upgrade
@sub_cmd_group.command()
def sub_cmd1():
    click.echo('sub_cmd1')

@sub_cmd_group.command()
def sub_cmd2():
    click.echo('sub_cmd2')


if __name__ == "__main__":
    commands = (
        'top_cmd1',
        'top_cmd2',
        'sub_cmd_group sub_cmd1',
        'sub_cmd_group sub_cmd2',
        ' help',
    )

    import sys, time

    time.sleep(1)
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    for cmd in commands:
        try:
            time.sleep(0.1)
            print('     -')
            print('> ' + cmd)
            time.sleep(0.1)
            cli(cmd.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

结果:

Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
     -
> sub_cmd_group sub_cmd2
cli
sub_cmd_group
Performing upgrade
sub_cmd2
     -
> top_cmd1
cli
cmd1
     -
> top_cmd2
cli
Performing upgrade
cmd2
     -
> sub_cmd_group sub_cmd1
cli
sub_cmd_group
sub_cmd1
     -
> sub_cmd_group sub_cmd2
cli
sub_cmd_group
Performing upgrade
sub_cmd2
     -
>  help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
   arg1 TEXT
   arg2 TEXT
   help       Show this message and exit.

Commands:
  sub_cmd_group
  top_cmd1
  top_cmd2

相关问题 更多 >