单击:在带有上下文对象的链接命令中使用其他功能

科尔德迈耶

我最近一直在使用click包来构建命令行界面,到目前为止,该界面一直运行良好。

现在,在将链接命令与上下文对象结合使用时遇到了麻烦。问题是,当我想从另一个命令中调用另一个命令的函数时,我莫名其妙地出错了。

这可能与单击中装饰器的使用有关,但是我现在看不到错误。

这是我的代码的最小示例:

import click


@click.group(chain=True)
@click.option('--some_common_option', type=float, default=1e-10)
@click.pass_context
def cli(ctx, some_common_option):
    # save shared params within context object for different commands
    for k, v in locals().items():
        if 'ctx' not in k:
            ctx.obj[k] = v

    return True


@cli.command()
@click.argument('some_argument', type=str)
@click.pass_context
def say_something(ctx, some_argument):
    print(some_argument)

    return True


@cli.command()
@click.argument('some_other_argument', type=str)
@click.pass_context
def say_more(ctx, some_other_argument):
    ctx.obj['text'] = some_other_argument
    say_something(ctx, ctx.obj['text'])

    return True


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

这是终端上提供的错误:

$ python test.py say_something 'Hello!'
Hello!
$ python test.py say_more 'How are you?'
Traceback (most recent call last):
  File "test.py", line 36, in <module>
    cli(obj={})
  File "/home/user/.anaconda3/lib/python3.6/site-packages/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/home/user/.anaconda3/lib/python3.6/site-packages/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/home/user/.anaconda3/lib/python3.6/site-packages/click/core.py", line 1092, in invoke
    rv.append(sub_ctx.command.invoke(sub_ctx))
  File "/home/user/.anaconda3/lib/python3.6/site-packages/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/user/.anaconda3/lib/python3.6/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/user/.anaconda3/lib/python3.6/site-packages/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "test.py", line 30, in say_more
    say_something(ctx, ctx.obj['text'])
  File "/home/user/.anaconda3/lib/python3.6/site-packages/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/home/user/.anaconda3/lib/python3.6/site-packages/click/core.py", line 683, in main
    args = list(args)
TypeError: 'Context' object is not iterable
$ 

我想知道为什么在上下文对象上进行迭代以及在何处进行迭代。

有什么提示我如何解决此问题并从另一个命令中使用该功能?

斯蒂芬·劳奇

如果您可以编辑单击命令功能,则可以按以下方式组织它们:

@cli.command()
@click.argument('some_argument', type=str)
@click.pass_context
def say_something(ctx, some_argument):
    return _say_something(ctx, some_argument):

def _say_something(ctx, some_argument):
    print(some_argument)

如果像这样构建,则可以将_say_something()函数作为未修饰的(正常)Python函数进行调用。

如果无法编辑命令

此答案的基础上,您可以使用以下功能将上下文传递给另一个click命令:

码:

def call_click_command_with_ctx(cmd, ctx, *args, **kwargs):
    """ Wrapper to call a click command with a Context object

    :param cmd: click cli command function to call
    :param ctx: click context
    :param args: arguments to pass to the function
    :param kwargs: keyword arguments to pass to the function
    :return: None
    """

    # monkey patch make_context
    def make_context(*some_args, **some_kwargs):
        child_ctx = click.Context(cmd, parent=ctx)
        with child_ctx.scope(cleanup=False):
            cmd.parse_args(child_ctx, list(args))
        return child_ctx

    cmd.make_context = make_context
    prev_make_context = cmd.make_context

    # call the command
    call_click_command(cmd, *args, **kwargs)

    # restore make_context
    cmd.make_context = prev_make_context

这是如何运作的?

之所以可行,是因为click是一个设计良好的OO框架。@click.Command可以对象进行自省以确定其期望的参数。然后可以构造一个命令行,该命令行看起来像单击所期望的命令行。另外,make_context可以重写命令方法以允许命令上下文使用命令。

来自上一个答案的代码

def call_click_command(cmd, *args, **kwargs):
    """ Wrapper to call a click command

    :param cmd: click cli command function to call
    :param args: arguments to pass to the function
    :param kwargs: keywrod arguments to pass to the function
    :return: None
    """

    # Get positional arguments from args
    arg_values = {c.name: a for a, c in zip(args, cmd.params)}
    args_needed = {c.name: c for c in cmd.params
                   if c.name not in arg_values}

    # build and check opts list from kwargs
    opts = {a.name: a for a in cmd.params if isinstance(a, click.Option)}
    for name in kwargs:
        if name in opts:
            arg_values[name] = kwargs[name]
        else:
            if name in args_needed:
                arg_values[name] = kwargs[name]
                del args_needed[name]
            else:
                raise click.BadParameter(
                    "Unknown keyword argument '{}'".format(name))


    # check positional arguments list
    for arg in (a for a in cmd.params if isinstance(a, click.Argument)):
        if arg.name not in arg_values:
            raise click.BadParameter("Missing required positional"
                                     "parameter '{}'".format(arg.name))

    # build parameter lists
    opts_list = sum(
        [[o.opts[0], str(arg_values[n])] for n, o in opts.items()], [])
    args_list = [str(v) for n, v in arg_values.items() if n not in opts]

    # call the command
    cmd(opts_list + args_list)

测试代码:

import click

@click.group(chain=True)
@click.option('--some_common_option', type=float, default=1e-10)
@click.pass_context
def cli(ctx, some_common_option):
    # save shared params within context object for different commands
    for k, v in locals().items():
        if 'ctx' not in k:
            ctx.obj[k] = v


@cli.command()
@click.argument('some_argument', type=str)
@click.pass_context
def say_something(ctx, some_argument):
    print(some_argument)


@cli.command()
@click.argument('some_other_argument', type=str)
@click.pass_context
def say_more(ctx, some_other_argument):
    ctx.obj['text'] = some_other_argument
    call_click_command_with_ctx(say_something, ctx, ctx.obj['text'])


if __name__ == "__main__":
    commands = (
        'say_something something',
        'say_more more',
        '--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(), obj={})

        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)]
-----------
> say_something something
something
-----------
> say_more more
more
-----------
> --help
Usage: test.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...

Options:
  --some_common_option FLOAT
  --help                      Show this message and exit.

Commands:
  say_more
  say_something

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

是否可以在其他上下文中使用带有响应(钩子)的上下文?

在其他窗口的上下文中执行功能?

如何在静态上下文中使用带有上下文参数的类而不引起内存泄漏?

访问其他文件中的Lambda上下文对象

带有上下文本的按钮将不会单击

如何在其他类Android Kotlin中使用它和上下文?

如何在 Handlebars 中使用当前上下文的 @key 从其他数组中获取元素?

如何在C#中使用其他上下文中的变量

React hooks & Context:在带有 useEffect 的子组件中使用上下文时出错

Spark编程:组织上下文导入和其他具有多种功能的最佳方法

不在对象上下文中使用$ this php

使用grepl匹配某些单词,但只能在不得出现其他单词的特定上下文中使用

发送电子邮件“在对象上下文中使用$ this”时,带有伪造的服务器上的Laravel错误

Javascript如何在用户单击其他位置时关闭上下文菜单

具有上下文的全局功能

上下文评估有什么功能

永久取消隐藏Windows 7上下文菜单的其他功能

在其他应用程序的上下文中运行命令

在带有上下文路径的Spring Boot应用中使用API网关时,HATEOAS路径无效

scala:带有类型参数的函数的默认值;在部分应用的上下文中使用

向Spring安全上下文中存储的主体对象添加其他详细信息

我可以从消息上下文中获得什么其他对象

将上下文绑定到其他对象后,如何使VsCode代码建议起作用?

使用带有复杂对象的钩子设置提供程序值时,无法更新上下文状态

在片段中使用上下文

在组件中使用 React 上下文

在AppCompatActivity中使用上下文

在片段中使用上下文

DDD - 验证实体在其他有界上下文中的存在