1

我有一个用例,其中需要重试向表中添加行的方法,以防出现任何异常。我正在使用重试的@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 时,如何使用 retry_if_db_error_or_passwd_change 发送类引用?

4

2 回答 2

1

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):
    ...
于 2021-08-25T07:58:41.343 回答
0

解决方案 1

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

  1. 将要重试的整个功能包装在 try-except 块中
  2. 捕获所有异常
  3. 如果发生异常,则将异常的详细信息和对对象的类引用包装起来并返回该对象。
    • 然后配置的接收器retry_on_result将接收对象并可以采取相应的行动。
  4. 如果没有发生异常,照常进行。返回任何东西或不返回。
    • 配置的接收器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
  • 对于“Call 1”,retry_if_db_error_or_passwd_change能够接收到异常和对的引用,self因为它正确地打印了(递增数字)的当前值self.value并且能够在RuntimeError发生时停止。
  • 对于“调用 2”,如果没有引发异常,则行为不会改变,也不会重试。

解决方案 2

这是受到@blhsing 答案的启发。在类初始化期间包装类方法,以便您可以注入类引用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
  • 类引用被正确传递,因为它能够打印 self.value 的当前值(增加的数字),并且当不再引发异常时重试停止。
于 2021-08-25T09:17:27.113 回答