正确的模式可以创建类似Choice的选项,而无需提供参数名称?

时间:2019-06-23 15:05:52

标签: python click python-decorators

对于一个应用程序,我想创建一个类似Choice的选项,该选项应该是更改脚本行为的单个确定切换。示例:我想让用户通过制表符补全选择当前文件夹中脚本将以某种方式操作的文件。

选择是我可以在文档中找到的最接近的匹配项,但我想忽略用户需要知道--file之类的参数名称。我想出了一个基于命令/组的实现。这是一个最小的工作示例:

# -*- coding: utf-8 -*-
"""Minimal working example for dynamic commands used as choices."""

import click


# A typical click command group without any other arguments or options
@click.group(name='minimal', help=__doc__)
def main():
    """Script's main entry point."""
    pass  # The following implementation makes the main method somewhat useless


def _convert_allowed_values_to_commands(
        group_function, allowed_values, callback):
    """Take each allowed value and create a command under the main group."""
    [
        _create_command_for_value(group_function, allowed_value, callback)
        for allowed_value in allowed_values
    ]


def _create_command_for_value(group_function, allowed_value, callback):
    """This is the pattern currently used to add commands on the fly."""
    def invoke_callback():
        callback(allowed_value)
    invoke_callback.__name__ = allowed_value
    group_function.command(allowed_value)(invoke_callback)


def _callback_function(selected_command):
    """A callback that will do something with the selected command."""
    click.echo('do_something(' + selected_command + ')')


# List will later be generated somehow specific for the actual use case
allowed_input_values = ['foo', 'bar', 'foobar']

# Take the input values and create a command each under the main group
_convert_allowed_values_to_commands(
    main, allowed_input_values, _callback_function)

# if __main__ pattern only used for demonstration. Later setuptools is used
if __name__ == "__main__":
    main()

它解决了用户可以在更复杂的CLI中使用此命令的子命令来完成制表符的操作,并且将在另一个选项卡上提供所有选择。 但是它感觉很不顺手,并不是非常符合点击要求,并且存在一个问题,即它会使main()函数或多或少地变得无用,并且可能无法与其他选项/参数很好地集成。

也许有人可以向我指出文档或示例,以使其更接近于Click的典型界面/装饰器吗?

谢谢!


编辑:

将所有内容记下来之后,我用click.MultiCommand找到了另一个变体。

# -*- coding: utf-8 -*-
"""Minimal working example for dynamic commands used as choices."""

import click


# List will later be generated somehow specific for the actual use case
allowed_input_values = ['foo', 'bar', 'foobar']


def _setup_dynamic_multicommand(allowed_input_values, _callback_function):
    """Use click.MultiCommand to achieve more or less the same."""
    class DynamicCLI(click.MultiCommand):

        def list_commands(self, ctx):
            return allowed_input_values

        def get_command(self, ctx, name):
            @click.command(name)
            @click.pass_context
            def invoke_callback(ctx):
                _callback_function(ctx)
            return invoke_callback

    return DynamicCLI


def _callback_function(ctx):
    """A callback that will do something with the selected command."""
    sub_command = ctx.parent.invoked_subcommand
    verbose = ctx.parent.params['verbose']
    click.echo(f'invoked: {sub_command}')
    click.echo(f'verbose: {verbose}')


# Take the input values and create a command each under the main group
dyn_multi_command = _setup_dynamic_multicommand(
    allowed_input_values, _callback_function)


# A typical click command group without any other arguments or options
@click.group('minimal', cls=dyn_multi_command)
@click.option('--verbose', is_flag=True)
def main(verbose):
    pass  # Will be forwarded to subcommands


if __name__ == "__main__":
    main()  # pylint: disable=E1120

看起来更好,但不确定这是否是最好的解决方案。

0 个答案:

没有答案