在 Click 中,
Context.with_resource
的 文档 指出它可用于:
[r]注册资源,就像在
语句中使用该资源一样。当上下文弹出时,资源将被清理。with
据此,我了解到我传递给
Context.with_resource
的上下文管理器将在执行根 CLI 组及其任何子组和命令后退出。这似乎与这样的示例配合得很好,我将 stdout
重定向到文件:
import contextlib
import click
@contextlib.contextmanager
def my_redirect_stdout(file):
with open(file, mode="w") as fp:
with contextlib.redirect_stdout(fp):
yield
@click.group()
@click.pass_context
@click.argument("file")
def cli(ctx, file):
ctx.with_resource(my_redirect_stdout(file))
@cli.command()
def hello():
print(f"this goes to a file")
if __name__ == "__main__":
cli()
但是,当我尝试通过以下方式捕获异常回溯时,这不起作用:
import contextlib
import sys
import traceback
import click
@contextlib.contextmanager
def capture_traceback(file):
with open(file, mode="w") as fp:
try:
yield
except:
print(f"exception!")
traceback.print_exc(file=fp)
sys.exit(1)
@click.group()
@click.pass_context
@click.argument("file")
def cli(ctx, file):
ctx.with_resource(capture_traceback(file))
@cli.command()
def hello():
raise ValueError("error!")
if __name__ == "__main__":
cli()
异常似乎没有被
capture_traceback
函数捕获。相反,它像往常一样由解释器打印。似乎 Click 正在捕获错误,关闭上下文管理器,然后重新引发。如何捕获来自任何组或命令的异常,将回溯打印到文件,然后退出程序(不将回溯打印到终端/stderr
)?
单击不会传播这样的错误。
click.BaseCommand
类拥有一个click.Context
,它将这些上下文管理器存储在一个简单的ExitStack
中。当上下文中出现 __exit__
时,它会关闭退出堆栈而不转发错误信息。代码大致为:
class Context:
...
def __exit__(self, exc_type, exc_value, tb):
self._depth -= 1
if self._depth == 0:
self.close()
pop_context()
...
def close(self) -> None:
self._exit_stack.close()
self._exit_stack = ExitStack()
因为click没有转发异常信息,所以无法处理。话虽如此,看起来确实可以覆盖应用程序使用的上下文类。
免责声明以下代码非常脆弱。最好在上游存储库中提出一个问题来请求它。至少,您需要将 click 的版本固定到确切的版本,以便可以确保此补丁继续发挥作用。
...
class MyContext(click.Context):
def __exit__(self, exc_type, exc_value, tb):
self._depth -= 1
if self._depth == 0:
self._exit_stack.__exit__(exc_type, exc_value, tb)
self._exit_stack = contextlib.ExitStack()
click.core.pop_context()
cli.context_class = MyContext
if __name__ == "__main__":
cli()