Retry и fallback на Python: декоратор повторов с резервным вызовом

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

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

Резервный контур при сбоях

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

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

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

Что уже дано

В редакторе представлена заготовка функции retry_backup(max_attempts, retry_exceptions, backup). Вспомогательные классы и тестовые пробы доступны в проверяющем окружении во время выполнения, реализовывать их заново не требуется.

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

Реализуйте функцию-фабрику retry_backup(max_attempts, retry_exceptions, backup).

Функция принимает параметры:

  • max_attempts (int) — целое положительное число основных попыток выполнения (от 1 до 20) для каждого отдельного вызова полученной обёртки;
  • retry_exceptions (tuple) — кортеж классов исключений (подклассов BaseException), при возникновении которых выполняется повторная попытка (кортеж может быть пустым);
  • backup — обязательный синхронный вызываемый объект, принимающий тот же набор аргументов, что и декорируемая функция.

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

Правила работы обёртки:

  • При каждом вызове обёртка принимает произвольные позиционные и именованные аргументы (*args, **kwargs) и передаёт их в целевую функцию без изменений. Счётчик попыток сбрасывается для каждого нового вызова обёртки (лимит попыток расходуется заново).
  • Если вызов основной функции завершается успешно, обёртка немедленно возвращает полученный объект результата.
  • Если целевая функция выбрасывает исключение, типы проверяются стандартным сопоставлением (isinstance / принадлежность классам из retry_exceptions):
  • при возникновении отслеживаемого исключения попытка считается неудачной, и вызов повторяется вплоть до достижения max_attempts;
  • при возникновении любого неотслеживаемого исключения выполнение немедленно прерывается, исключение пробрасывается наружу в неизменном виде, а функция backup не вызывается.
  • Если все max_attempts основных попыток завершились отслеживаемыми сбоями, обёртка ровно один раз вызывает backup(*args, **kwargs) с теми же самыми объектами аргументов:
  • если backup завершается успешно, возвращается его фактический результат;
  • если backup выбрасывает исключение (любого типа), оно пробрасывается наружу без перехвата и без повторных попыток.
  • Никаких искусственных пауз, задержек или дополнительных раундов повторов добавлять не нужно.
  • Обёртка должна сохранять метаданные, сигнатуру интроспекции и идентичность ссылки __wrapped__ исходной функции с помощью functools.wraps.

Ввод и вывод

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

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

def backup(value):
    return "backup:" + value
@retry_backup(2, (OSError,), backup)
def upload(value):
    raise OSError("temporary")
print(upload("batch"))

Вывод:

backup:batch
РешениеPython

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

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

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