Класс-декоратор Python: переключение между основной и резервной репликой

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

Другие упражнения по этой теме: Классы-декораторы Python: задачи на call, методы и состояние.

Маршрутизатор реплик

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

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

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

Что уже дано

В редакторе объявлен класс ReplicaRouter с заготовками методов __init__, __call__ и force_replica.

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

Реализуйте класс ReplicaRouter(function, quota) со следующим поведением:

  • Конструктор принимает синхронную вызываемую функцию function, ожидающую именованный параметр source, и целое положительное число quota от до — квоту успешных вызовов к основному узлу в рамках одного цикла.
  • Метод __call__(*args, **kwargs) вызывает сохранённую функцию, передавая все позиционные и именованные аргументы без изменений, но подставляя или переопределяя именованный аргумент source значением 'primary' либо 'replica':
  • Первые успешных вызовов цикла выполняются с source='primary'.
  • После успешных основных вызовов следующий обычный вызов выполняется с source='replica'.
  • Только успешное завершение вызова с 'replica' начинает отсчёт следующего цикла основных вызовов заново (счётчик успешных основных вызовов сбрасывается в ).
  • Любое исключение (включая прямые подклассы BaseException), возникшее внутри целевой функции, не изменяет и не обнуляет накопленный прогресс и беспрепятственно пробрасывается наружу как тот же самый объект ошибки. При успешном выполнении метод возвращает исходный объект результата целевой функции.
  • Метод force_replica() возвращает None, не вызывает целевую функцию и выставляет флаг принудительной маршрутизации ровно на один следующий вызов __call__:
  • Повторные вызовы force_replica() до следующего обращения к декоратору имеют такой же эффект, как и один вызов.
  • Следующий вызов __call__ выполняется с source='replica' независимо от текущего прогресса цикла, а флаг принудительного переключения немедленно снимается (в том числе при ошибке вызова).
  • Если принудительный вызов завершился успешно, счётчик основных вызовов цикла сбрасывается в . Если же принудительный вызов завершился ошибкой, накопленный ранее прогресс сохраняется без изменений.
  • Если флаг был выставлен в момент, когда квота уже была полностью набрана, и этот принудительный вызов завершился ошибкой, следующий обычный вызов всё равно автоматически направляется к 'replica' по общему правилу исчерпания квоты.
  • Состояние полностью изолировано для каждого экземпляра ReplicaRouter. Каждый экземпляр может обрабатывать от до вызовов, а система может использовать от до независимых экземпляров одновременно.

Ввод и вывод

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

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

def fetch(report, *, source):
    return (report, source)
router = ReplicaRouter(fetch, 2)
print(router("a"))
print(router("b"))
print(router("c"))
router.force_replica()
print(router("d"))
print(router("e"))

Вывод:

('a', 'primary')
('b', 'primary')
('c', 'replica')
('d', 'replica')
('e', 'primary')
РешениеPython

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

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

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