将多个参数传递给 retry_on_exception 在 python 中重试的参数

Passing multiple arguments to retry_on_exception argument of retrying in python

我有一个用例,如果出现任何异常,需要重试向 table 添加一行的方法。我正在使用重试的@retry 装饰器来实现这一点。

其中一种情况是更改了数据库的密码。我正在使用 arg retry_on_exception 来捕获任何异常。我面临的问题是,我希望密码获取发生在我传递给 retry_on_exception 的方法中。

到目前为止我的代码,

@retry(stop_max_attempt_number=3, retry_on_exception=retry_if_db_error_or_passwd_change)
def add_request_to_db(self, req_id):
    with make_session(self.session_maker) as session:
        session.merge(RequestHolder(request_id=req_id))
        session.commit()

和retry_if_db_error_or_passwd_change就像

def retry_if_db_error_or_passwd_change(exception, class_reference):
    is_db_exception = isinstance(exception, DBRetryExceededException)
    is_operational_error = isinstance(exception, OperationalError)

    if is_db_exception:
        return True
    elif is_operational_error or 'Access denied ' in exception:
        fetch_password(class_reference)
        return True

我的问题是,在将可调用对象传递给 retry_on_exception 时,如何将 class 引用与 retry_if_db_error_or_passwd_change 一起发送?

您可以将 class 引用保留为使用此附加参数调用 retry_if_db_error_or_passwd_change 的包装函数的默认参数:

# assign cls with the desired class reference first

@retry(
    stop_max_attempt_number=3,
    retry_on_exception=lambda exc, cls=cls: retry_if_db_error_or_passwd_change(exc, cls)
)
def add_request_to_db(self, req_id):
    ...

解决方案 1

不是通过 retry_on_exception 使用引发的异常作为重试的基础,您可以将其更改为基于通过 retry_on_result 的 return。在这里,我们可以传递 self 参数。

  1. 将要重试的整个功能包装在 try-except 块中
  2. 捕获所有异常
  3. 如果发生异常,将异常的详细信息和对对象的 class 引用和 return 对象包装起来。
    • retry_on_result 的已配置接收器随后将接收该对象并可以相应地执行操作。
  4. 如果没有异常发生,照常进行。 Return 任何东西或 none。
    • retry_on_result 的已配置接收器仍会收到响应,但应该忽略并且不会重试。
from dataclasses import dataclass
import random
from typing import Any

from retrying import retry


# This could be a dataclass or just an ordinary class
@dataclass
class RetryInfo:
    exception: Exception
    class_reference: Any


def retry_if_db_error_or_passwd_change(retry_info):
    """Return True if we should retry, False otherwise."""
    if not isinstance(retry_info, RetryInfo):
        # If the response isn't intended for the retry mechanism, ignore and just continue.
        print("No retry needed")
        return False

    print("Evaluate retry for:", type(retry_info.exception), retry_info.class_reference.value)

    # Let's say we will stop the retries if there is a RuntimeError
    return not isinstance(retry_info.exception, RuntimeError)


class MyClass:
    def __init__(self, value):
        self.value = value

    @retry(retry_on_result=retry_if_db_error_or_passwd_change)
    def add_request_to_db(self, raise_exception):
        try:
            # Wrap the whole code inside a try-except block to catch all exceptions.
            print("Called add_request_to_db")
            self.value += 1
            if raise_exception:
                raise random.choice([IndexError, KeyError, RuntimeError, TypeError, ValueError])
        except Exception as error:
            # Instead of raising the exception to trigger a retry, just return the contained values.
            return RetryInfo(error, self)

print("Call 1...")
MyClass(0).add_request_to_db(True)

print("\n==========\n")

print("Call 2...")
MyClass(0).add_request_to_db(False)
$ python3 script.py 
Call 1...
Called add_request_to_db
Evaluate retry for: <class 'ValueError'> 1
Called add_request_to_db
Evaluate retry for: <class 'TypeError'> 2
Called add_request_to_db
Evaluate retry for: <class 'ValueError'> 3
Called add_request_to_db
Evaluate retry for: <class 'TypeError'> 4
Called add_request_to_db
Evaluate retry for: <class 'KeyError'> 5
Called add_request_to_db
Evaluate retry for: <class 'TypeError'> 6
Called add_request_to_db
Evaluate retry for: <class 'RuntimeError'> 7

==========

Call 2...
Called add_request_to_db
No retry needed
  • 对于“调用 1”,retry_if_db_error_or_passwd_change 能够接收到异常和对 self 的引用,因为它正确地打印了 self.value 的当前值(递增的数字)并且能够在发生 RuntimeError 时停止。
  • 对于“调用 2”,如果没有引发异常,则行为不会改变,不会进行重试。

解决方案 2

灵感来自于@blhsing 的回答。在 class 初始化期间包装 class 方法,以便您可以注入 class 引用 self.

import random

from retrying import retry


def retry_if_db_error_or_passwd_change(exception, class_reference):
    """Return True if we should retry, False otherwise."""
    print("Evaluate retry for:", type(exception), class_reference.value)

    # Let's say we will just retry if any kind of exception occurred
    return isinstance(exception, Exception)


class MyClass:
    def __init__(self, value):
        self.value = value

        # Decorate functions to be retried
        retry_decorator = retry(
            retry_on_exception=lambda exc: retry_if_db_error_or_passwd_change(exc, self),
        )
        self.add_request_to_db = retry_decorator(self.add_request_to_db)

    def add_request_to_db(self, anything):
        self.value += 1
        chosen_exc = random.choice([IndexError, KeyError, RuntimeError, TypeError, None])
        if chosen_exc:
            print("Called add_request_to_db failed")
            raise chosen_exc
        else:
            print("Called add_request_to_db successful")


MyClass(0).add_request_to_db(None)
$ python3 script2.py 
Called add_request_to_db failed
Evaluate retry for: <class 'TypeError'> 1
Called add_request_to_db failed
Evaluate retry for: <class 'KeyError'> 2
Called add_request_to_db failed
Evaluate retry for: <class 'TypeError'> 3
Called add_request_to_db failed
Evaluate retry for: <class 'RuntimeError'> 4
Called add_request_to_db failed
Evaluate retry for: <class 'RuntimeError'> 5
Called add_request_to_db successful
  • class 引用被正确传递,因为它能够打印 self.value 的当前值(增加的数字)并且当不再引发异常时重试停止。