我正在Python 3.7中编写一个ETL框架,该框架使用函数作为带有特殊装饰器的“任务”。这些任务大多数都循环运行。如果在循环中引发异常,我希望该函数通过记录有关故障的数据并继续循环来处理该异常。
这是到目前为止我所拥有的简化示例:
class TaskExecutionError(RuntimeError):
def __init__(self, msg="", context={}):
self.msg = msg
self.context = context
def __str__(self):
return self.msg or "Error executing task."
def task(fn):
@wraps(fn)
def _wrapper(*args, **kwargs):
start_ts = datetime.utcnow()
try:
return fn(*args, **kwargs)
except TaskExecutionError as e:
logger.exception(f"Task execution error will be logged: {e}.")
fail_data = {
"task_name": fn.__name__,
"args": list(args),
"kwargs": kwargs,
"context": e.context,
"fail_message": str(e),
"fail_time": str(datetime.utcnow()),
# etc.
}
)
# Write failure data in an object store
finally:
end_ts = datetime.utcnow()
logger.info(f"*** Wallclock: {end_ts - start_ts}.")
_wrapper.is_task = True
return _wrapper
@task
def test_fail_log(a, b, c, kwa=1, kwb=2):
"""
Test handling failures.
"""
for i in range(10):
if i % 3:
raise TaskExecutionError(context={"i": i})
else:
print("All's well")
就我看到的消息正在打印和保存而言,这很好用,但是,当然,一旦出现第一个异常,执行就会中断。
我应该如何解决这个问题以便继续执行?
似乎我无法使用非常方便的异常机制,因此可能不得不设计一个自定义handle_failure()
函数。但是,我不确定从装饰函数内部调用函数装饰器上下文传递给handle failure()
函数的最佳方法。
由于我将在几个经过@task
装饰的函数中使用此机制,因此,如果可能的话,我希望进行一个轻量级的调用,而无需使用大量参数。
感谢您的任何建议。
答案 0 :(得分:0)
我使用inspect
解决了这个问题,我不想经常使用它,但在这里似乎很有必要:
def task(fn):
@wraps(fn)
def _wrapper(*args, **kwargs):
start_ts = datetime.utcnow()
try:
return fn(*args, **kwargs)
finally:
end_ts = datetime.utcnow()
logger.info(f"*** Wallclock: {end_ts - start_ts}.")
_wrapper.is_task = True
def handle_task_failure(exc, local_ctx={}):
caller_frame = inspect.currentframe().f_back
wrapper_frame = caller_frame.f_back
caller_ctx = wrapper_frame.f_locals
print(f"Context: {caller_ctx}")
logger.exception(f"Task execution error will be logged: {exc}.")
fail_data = {
"start_time": caller_ctx.get("start_ts"),
"runid": caller_ctx.get("runid"),
"task_name": caller_ctx.get("fn").__name__,
"args": list(caller_ctx.get("args")),
"kwargs": caller_ctx.get("kwargs"),
"context": local_ctx,
"fail_message": str(exc),
"fail_time": str(datetime.utcnow()),
"traceback": format_stack(caller_frame),
}
# Save failure data in object store
@task
def test_fail_log(a, b, c, kwa=1, kwb=2):
"""
Test handling failures.
"""
for i in range(10):
try:
if i % 3:
raise RuntimeError("All's borked.")
else:
print("All's well.")
except Exception as exc:
handle_task_failure(exc, {"i": i})
另一方面,我不需要自定义的异常类,并且处理失败的调用非常轻巧。重复执行几次很有趣。