Retry-декоратор Python: преобразование исключений после исчерпания попыток

Напишите решение на Python и проверьте его на странице.

Другие упражнения по этой теме: Декораторы Python: задачи на retry, rate limiter, кэш и Circuit Breaker.

Трансляция сбоев в декораторе повторов

Почтовый шлюз отправляет служебные уведомления через сеть, в которой случаются кратковременные помехи связи. Чтобы единичные сбои не срывали доставку, отправку защищают параметризованным декоратором. Он настраивается лимитом попыток, кортежем ожидаемых временных сетевых исключений и специальной функцией-транслятором критической ошибки.

Если операция завершается исключением из заданного кортежа, декоратор повторяет вызов в пределах установленного лимита. Любое неперечисленное стороннее исключение сразу прерывает работу и выходит наружу без изменений.

Когда лимит попыток исчерпан, а вызов так и не завершился успешно, декоратор не пробрасывает исходную ошибку сети напрямую. Он ровно один раз обращается к транслятору, передавая ему последнее перехваченное исключение, и возбуждает полученный от него объект критической ошибки через from exc, сохраняя контекст сбоя.

Реализуйте декоратор, выполняющий повторные попытки отправки и эскалацию финальной ошибки.

Что уже дано

В редакторе представлена заготовка функции translated_retry(max_attempts, retry_exceptions, translate).

Вспомогательные классы и функции для тестирования работы декоратора доступны во время выполнения тестов, реализовывать их заново не требуется.

Что нужно сделать

Реализуйте параметризованный декоратор translated_retry(max_attempts, retry_exceptions, translate).

Параметры фабрики:

  • max_attempts — положительное целое число от 1 до 20 (включая пользовательские подклассы int), задающее общее количество попыток вызова целевой функции (не дополнительных повторов, а именно суммарных попыток);
  • retry_exceptions — кортеж типов исключений (подклассов BaseException, от 0 до 10 типов), при возникновении которых выполняется повторная попытка. Сопоставление с типами должно работать по стандартным правилам проверки принадлежности подклассам (как в блоке except или через isinstance);
  • translate — синхронная функция, принимающая объект последнего пойманного отслеживаемого исключения и возвращающая объект исключения (экземпляр BaseException). Если сама функция translate возбуждает исключение, оно должно сразу всплывать наружу без повторных попыток.

Возвращаемый декоратор принимает синхронную целевую функцию function и возвращает функцию-обёртку.

Правила работы функции-обёртки:

  • Принимает произвольные позиционные и именованные аргументы *args, **kwargs и передаёт их в целевую функцию без изменений.
  • Учёт попыток ведётся независимо для каждого отдельного вызова обёртки (от 0 до 1000 вызовов на одну обёртку). Обёртка не должна рекурсивно вызывать саму себя.
  • При успешном завершении целевой функции обёртка немедленно возвращает полученный объект результата (включая ложные значения False, None, пустые коллекции и их подклассы) без повторных вызовов.
  • Если целевая функция выбрасывает исключение, не относящееся к типам из retry_exceptions, оно сразу беспрепятственно всплывает наружу тем же самым объектом исключения без повторных вызовов и без вызова translate.
  • Если возникает отслеживаемое исключение из retry_exceptions, попытка повторяется вплоть до достижения max_attempts.
  • Если все max_attempts попыток завершились отслеживаемыми исключениями, декоратор ровно один раз вызывает translate, передавая туда в точности тот объект исключения, который возник при последней попытке. Полученный от translate объект исключения должен быть возбуждён через оператор raise ... from last_exception. Атрибут __cause__ итоговой ошибки обязан указывать на это последнее исключение.
  • Никаких пауз, задержек между попытками, значений по умолчанию при сбое или обязательного сохранения метаданных исходной функции не требуется.

Ввод и вывод

Стандартные потоки ввода stdin и вывода stdout в решении не используются: считывать ввод и вызывать print() не нужно. После выполнения кода учащегося проверяющая система считывает из пространства имён модуля функцию верхнего уровня translated_retry и вызывает её с проверочными аргументами.

Пример использования

attempts=[]
@translated_retry(2,(ConnectionError,),lambda error: RuntimeError("delivery failed"))
def deliver(message):
    attempts.append(message)
    raise ConnectionError("offline")
try:
    deliver("notice")
except RuntimeError as error:
    print(str(error))
    print(type(error.__cause__).__name__)
print(attempts)

Вывод:

delivery failed
ConnectionError
['notice', 'notice']
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи