Передача нескольких аргументов в аргумент 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
, возможно, вы захотите изменить его, чтобы он основывался на обратном переходе. Здесь мы можем передать аргумент.
- Оберните всю функциональность, которую нужно повторить, в блоке try-except
- Поймать все исключения
- Если произошло исключение, оберните сведения об исключении и ссылку на класс в объект и верните объект.
- После этого настроенный получатель получит объект и сможет действовать соответствующим образом.
- Если никаких исключений не произошло, продолжайте как обычно. Верните что-нибудь или ничего.
- Настроенный приемник
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):
...