【问题标题】:Option parsing issue with python clickpython click的选项解析问题
【发布时间】:2021-09-14 11:54:15
【问题描述】:

我在 python 脚本中使用 click 进行 arg/option 解析。我有一个脚本,它接受两个选项(都是强制性的),我可以运行这个脚本如下。

pgm.py --option1 val1 --option2 val2

现在,如果我们错过了为选项 1 赋值,点击会抱怨选项 2 丢失(具有误导性)

pgm.py --option1 --option2 val2

这里@click 假设 option1 的值为 --option2 并搜索它找不到的 option2 参数。

@click.command()
@click.option("--option1", required=True)
@click.option("--option2", required=True)
def main(option1, option2):
    print(f"option1={option1} option2={option2}")

我在文档中搜索了任何相关选项以正确解析此问题(应该有一个),但找不到任何相关选项。甚至尝试回调来验证无类型/空值,这也无济于事,因为我们将选项名称作为选项值。解决此问题的一种方法是在回调函数中使用自定义逻辑来检查值是否以 -- 开头(不太干净)。任何帮助表示赞赏。

【问题讨论】:

  • 这似乎是预期的行为:--option1 需要一个值,而您编写命令行的方式 --option2 就是该值。这是命令行工具的常见行为。
  • @larsks 有没有办法让点击解析器知道此处缺少 option1 值而不将“--option2”视为值?
  • 没有。如果您想要接受以- 开头的值怎么办?最好的办法是添加一个验证器,如果选项值以 - 开头(curl 引发警告:尝试例如 curl --output --fail https://google.com),则该验证器会引发警告或错误。

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


【解决方案1】:

值得注意的是,即使在这种情况下......

pgm.py --option1 --option2 val2

...脚本仍将(正确)退出并出现错误,因为您将丢失--option2 的值。运行上述命令将导致:

Error: Missing option '--option2'.

对于你想要的行为,你可以添加一个简单的验证器;为了 示例:

import click


def errorOnFlagValue(val):
    if val.startswith('-'):
        raise ValueError()

    return val


@click.command()
@click.option('--option1', required=True, type=errorOnFlagValue)
@click.option('--option2', required=True, type=errorOnFlagValue)
def main(option1, option2):
    print(f'option1={option1}, option2={option2}')


if __name__ == '__main__':
    main()

从这个答案的开头运行命令行会 结果:

Error: Invalid value for '--option1': --option2

【讨论】:

  • 谢谢@larsks。知道自定义回调,但我正在寻找一种方法来做到这一点。类似于 required=True (检查是否存在选项),我相信应该有一个选项来强制 value。
  • 设置required=True 确实使该值成为强制性的。您要求对值的格式设置特定要求,这就是自定义验证器的用途。
【解决方案2】:

您可以尝试使用 nessaid-cli 包对 CLI 令牌进行额外验证。可以使用支持复杂 CLI 选项和组合的丰富语法来指定 CLI。 CLI 可以作为交互式 shell 或独立脚本运行。

该框架可在pypigithub 获得

问题中的基本CLI可以这样实现

import sys
import asyncio
from nessaid_cli.cmd import NessaidCmd
from nessaid_cli.tokens import StringToken;


class TestCli(NessaidCmd):
    r"""
    token OPTION1 StringToken(), OPTION2 StringToken();
    """

    def get_token_classes(self):
        """Method to override.
        It should return the list of token classes being used"""
        return [StringToken]

    def do_command1(self, option1, option2):
        r"""
        "--option1"
        OPTION1
        "--option2"
        OPTION2
        <<
            $option1 = $2;
            $option2 = $4;
        >>
        """
        print(f"option1={option1} option2={option2}")

if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(TestCli.execute_args(*sys.argv[1:]))

这将不接受 pgm.py --option1 --option2 val2,因为我们强制要求所有 4 个 CLI 令牌。

这是输出

C:\Users\Said\Desktop>python clitest.py --option1 A --option2 B
option1=A option2=B

C:\Users\Said\Desktop>python clitest.py --option1 1 --option2 2
option1=1 option2=2

C:\Users\Said\Desktop>python clitest.py --option1 --option1 --option2 2
option1=--option1 option2=2

C:\Users\Said\Desktop>python clitest.py --option1 --option2 --option2 --option1
option1=--option2 option2=--option1

现在下面的pass因为我们有自动完成并且上面CLI中关键字的位置是固定的

C:\Users\Said\Desktop>python clitest.py --option --option2 --opt --option1
option1=--option2 option2=--option1

以下失败,因为它需要 --option2 作为第三个参数

C:\Users\Said\Desktop>python clitest.py --option --option2 option2 2
Result: failure
Error: Could not match any rule for this sequence

以下失败,因为缺少第四个参数。

C:\Users\Said\Desktop>python clitest.py --option --option2 --option2
Result: partial
Error: Input sequence is not complete

下一个示例展示了我们如何定义自定义标记。在此,我们可以验证令牌。在这里,我们阻止“--option1”和“--option2”作为输入值。

import sys
import asyncio
from nessaid_cli.cmd import NessaidCmd
from nessaid_cli.tokens import StringToken, MATCH_FAILURE, MATCH_SUCCESS;


class ValidOptions(StringToken):

    def match(self, token_input):
        if token_input in ["--option1", "--option2"]:
            return MATCH_FAILURE
        return MATCH_SUCCESS

class TestCli(NessaidCmd):
    r"""
    token OPTION1 ValidOptions(), OPTION2 ValidOptions();
    """

    def get_token_classes(self):
        """Method to override.
        It should return the list of token classes being used"""
        return [ValidOptions]

    def do_command1(self, option1, option2):
        r"""
        "--option1"
        OPTION1
        "--option2"
        OPTION2
        <<
            $option1 = $2;
            $option2 = $4;
        >>
        """
        print(f"option1={option1} option2={option2}")

if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(TestCli.execute_args(*sys.argv[1:]))

然后行为就变成这样了

C:\Users\Said\Desktop>python clitest.py --op 1 --op 2
option1=1 option2=2

C:\Users\Said\Desktop>python clitest.py --op --option1 --op 2
Result: failure
Error: Could not match any rule for this sequence

C:\Users\Said\Desktop>python clitest.py --op option1 --op 2
option1=option1 option2=2

C:\Users\Said\Desktop>python clitest.py --op option1 --op --option2
Result: failure
Error: Could not match any rule for this sequence

C:\Users\Said\Desktop>python clitest.py --op option1 --op --option3
option1=option1 option2=--option3

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-20
    • 2015-10-29
    • 2011-09-05
    • 2011-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多