【问题标题】:mutually exclusive commands in python Clickpython中的互斥命令单击
【发布时间】:2021-03-24 18:29:35
【问题描述】:

我有一个带有三个命令的click 应用程序:

import click

@click.group(chain=True)
def cli():
    print("MAIN")

@cli.command()
def initialize():
    print("INITIALIZING")
    
@cli.command()
def update():
    print("UPDATING")

@cli.command()
def process():
    print("PROCESSING")

这样定义,所有的命令都可以链接起来。

但是,我怎样才能使initializeupdate 互斥? IE:应该是:

合法运行:

initialize -> process

update -> process

不合法运行:

initialize -> update -> process

【问题讨论】:

    标签: python python-3.x command-line-interface python-click


    【解决方案1】:

    您可以通过创建自定义click.Group 类将可链接命令标记为互斥。

    自定义类

    class MutuallyExclusiveCommandGroup(click.Group):
        def __init__(self, *args, **kwargs):
            kwargs['chain'] = True
            self.mutually_exclusive = []
            super().__init__(*args, **kwargs)
    
        def command(self, *args, mutually_exclusive=False, **kwargs):
            """Track the commands marked as mutually exclusive"""
            super_decorator = super().command(*args, **kwargs)
            def decorator(f):
                command = super_decorator(f)
                if mutually_exclusive:
                    self.mutually_exclusive.append(command)
                return command
            return decorator
    
        def resolve_command(self, ctx, args):
            """Hook the command resolving and verify mutual exclusivity"""
            cmd_name, cmd, args = super().resolve_command(ctx, args)
    
            # find the commands which are going to be run
            if not hasattr(ctx, 'resolved_commands'):
                ctx.resolved_commands = set()
            ctx.resolved_commands.add(cmd_name)
    
            # if args is empty we have have found all of the commands to be run
            if not args:
                mutually_exclusive = ctx.resolved_commands & set(
                    cmd.name for cmd in self.mutually_exclusive)
                if len(mutually_exclusive) > 1:
                    raise click.UsageError(
                        "Illegal usage: commands: `{}` are mutually exclusive".format(
                            ', '.join(mutually_exclusive)))
    
            return cmd_name, cmd, args
    
        def get_help(self, ctx):
            """Extend the short help for the mutually exclusive commands"""
            for cmd in self.mutually_exclusive:
                mutually_exclusive = set(self.mutually_exclusive)
                if not cmd.short_help:
                    cmd.short_help = 'mutually exclusive with: {}'.format(', '.join(
                        c.name for c in mutually_exclusive if c.name != cmd.name))
            return super().get_help(ctx)
    

    使用自定义类:

    要使用自定义类,请将其作为 cls 参数传递给 click.group 装饰器,例如:

    @click.group(cls=MutuallyExclusiveCommandGroup)
    @click.pass_context
    def cli(ctx):
        ...
    

    然后使用mutually_exclusive 装饰器的mutually_exclusive 参数来标记命令 作为互斥组的一部分。

    @cli.command(mutually_exclusive=True)
    def update():
        ...
    

    这是如何工作的?

    这是可行的,因为 click 是一个设计良好的 OO 框架。 @click.group() 装饰器 通常实例化 click.Group 对象,但允许使用 cls 覆盖此行为 范围。所以在我们自己的类及以上继承click.Group是一件相对容易的事情 骑所需的方法。

    在这种情况下,我们覆盖了三个方法:command(), resolve_command() & get_help()。被覆盖的 command() 方法允许我们跟踪哪些命令标记有mutually_exclusive 标志。这 overridden resolve_command() 方法用于观察命令解析过程,注意哪个 命令将被运行。如果要运行互斥命令,则会引发错误。这 重写的get_help 方法设置short_help 属性以显示哪些命令是互斥的。

    测试代码:

    import click
    
    @click.group(chain=True, cls=MutuallyExclusiveCommandGroup)
    @click.pass_context
    def cli(ctx):
        print("MAIN")
    
    @cli.command()
    def initialize():
        print("INITIALIZING")
    
    @cli.command(mutually_exclusive=True)
    def update():
        print("UPDATING")
    
    @cli.command(mutually_exclusive=True)
    def process():
        print("PROCESSING")
    
    
    if __name__ == "__main__":
        commands = (
            '',
            'initialize',
            'update',
            'process',
            'initialize process',
            'update process',
            'initialize update process',
            '--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: 7.1.2
    Python Version: 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]
    -----------
    >
    Usage: test_code.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
    
    Options:
      --help  Show this message and exit.
    
    Commands:
      initialize
      process     mutually exclusive with: update
      update      mutually exclusive with: process
    -----------
    > initialize
    MAIN
    INITIALIZING
    -----------
    > update
    MAIN
    UPDATING
    -----------
    > process
    MAIN
    PROCESSING
    -----------
    > initialize process
    MAIN
    INITIALIZING
    PROCESSING
    -----------
    > update process
    MAIN
    Error: Illegal usage: commands: `update, process` are mutually exclusive
    -----------
    > initialize update process
    MAIN
    Error: Illegal usage: commands: `update, process` are mutually exclusive
    -----------
    > --help
    Usage: test_code.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
    
    Options:
      --help  Show this message and exit.
    
    Commands:
      initialize
      process     mutually exclusive with: update
      update      mutually exclusive with: process.
    

    【讨论】:

    • 感谢您提供如此详细的明确答案!一件小事:使用这个实现,“main”命令cli 总是被执行。如果请求 2 个互斥的子命令,有没有办法防止这种情况(或在一开始就失败)?原因是如果下一步将失败,我想避免一些繁重的初始化。
    • 可悲的是,框架处理的方式,排他检查正在分析的解析是在运行cli()之后完成的。
    猜你喜欢
    • 2014-02-26
    • 1970-01-01
    • 2010-10-03
    • 2023-04-09
    • 1970-01-01
    • 2010-11-06
    • 2021-12-20
    • 2011-06-09
    相关资源
    最近更新 更多