Передача нескольких аргументов в аргумент retry_on_exception повторной попытки в Python

У меня есть случай использования, когда метод, добавляющий строку в таблицу, необходимо повторить в случае какого-либо исключения. Я использую декоратор @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_if_db_error_or_passwd_change при передаче вызываемого объекта retry_on_exception?

Спасибо !

2 ответа

Решение

Альтернативой является использование возбужденного исключения в качестве основы для повторной попытки через retry_on_exception, возможно, вы захотите изменить его, чтобы он основывался на обратном переходе. Здесь мы можем передать аргумент.

  1. Оберните всю функциональность, которую нужно повторить, в блоке try-except
  2. Поймать все исключения
  3. Если произошло исключение, оберните сведения об исключении и ссылку на класс в объект и верните объект.
    • После этого настроенный получатель получит объект и сможет действовать соответствующим образом.
  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 the 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 'IndexError'> 7
Called add_request_to_db
Evaluate retry for: <class 'TypeError'> 8
Called add_request_to_db
Evaluate retry for: <class 'RuntimeError'> 9

==========

Call 2...
Called add_request_to_db
No retry needed
  • Для "Call 1", как видите, retry_if_db_error_or_passwd_change смог получить как исключение, так и ссылку на self поскольку он правильно напечатал текущее значение self.value (увеличивающееся число) и смог остановиться, когда RuntimeError произошел.
  • Для «Вызов 2» поведение не изменяется, если нет возбужденных исключений, повторные попытки не выполняются.

Вы можете сохранить ссылку на класс в качестве параметра по умолчанию для функции-оболочки, которая вызывает 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):
    ...
Другие вопросы по тегам