将argparse与Setuptools entry_points一起使用

时间:2014-03-05 11:41:21

标签: python python-2.7 setuptools argparse

我正在编写一个我想使用Setuptools分发的脚本。我已将此脚本添加到entry_points。{/ p>中的setup.py部分

从setuptools docs:

  

您指定的函数不带参数调用,并且它们的返回值传递给sys.exit(),因此您可以返回错误级别或消息以打印到stderr

由于该方法将返回而不是退出,因此它变得更加可测试。出于可测试性目的,我接受方法中的参数默认为sys.argv。到目前为止一切都很好。

当将argparse添加到混合中时会出现问题。当argparse无法解析args时,它会调用sys.exit。现在我真的更喜欢argparse没有这样做,因为这是由setuptools包装器处理的。我能想到的第一件事就是覆盖argparse.ArgumentParser,但后来我看到了:

# ===============
# Exiting methods
# ===============
def exit(self, status=0, message=None):
    if message:
        self._print_message(message, _sys.stderr)
    _sys.exit(status)

def error(self, message):
    """error(message: string)

    Prints a usage message incorporating the message to stderr and
    exits.

    If you override this in a subclass, it should not return -- it
    should either exit or raise an exception.
    """
    self.print_usage(_sys.stderr)
    self.exit(2, _('%s: error: %s\n') % (self.prog, message))

因此文档字符串指出我不应该返回并坚持提出异常。我该怎么解决这个问题?

如果我没有彻底解释它的主要方法:

def main(args=sys.argv):
    parser = ArgumentParser(prog='spam')

    # parser is configured here

    parsed = parser.parse_args(args)

    # Parsed args are used here

2 个答案:

答案 0 :(得分:2)

您不希望来自return error的原因是解析器将继续解析。在结尾附近会产生一些错误(例如关于未解析的字符串),但其他错误可能会提前发生(例如第一个参数字符串的错误类型)。如果从error方法返回,则parse_args的行为是不可预测的。通常,您希望解析器退出并返回对代码的控制。

您要做的是将parse_args()调用包装在try: except SystemExit:块中。我经常使用这样的测试脚本:

for test in ['-o FILE',
    ...
         ]:
    print(test)
    try:
        print(parser.parse_args(test.split()))
    except SystemExit:
        pass

您可以使用error和/或exit返回其他类型的例外情况。他们还可以绕过使用信息。但是在某种程度上你需要在你的包装器中捕获异常。

答案 1 :(得分:0)

如果您正在开始一个新项目或有时间进行重构,那么您可以考虑使用Click库。点击同时包含setuptools integration和“testability”作为功能以及其他注意事项 - 这里是文档中的示例test-snippet,它们都创建了一个迷你命令行界面,然后立即对其进行测试:

import click
from click.testing import CliRunner

def test_hello_world():
    @click.command()
    @click.argument('name')
    def hello(name):
        click.echo('Hello %s!' % name)

    runner = CliRunner()
    result = runner.invoke(hello, ['Peter'])
    assert result.exit_code == 0
    assert result.output == 'Hello Peter!\n'