Игнорирование кэша в функции Django cache_page при ошибке подключения Redis

Я использую Redis для кэширования некоторых представлений в моем Django Rest Ramework API. Допустим, у меня есть следующее представление:

from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from rest_framework.response import Response
from rest_framework.views import APIView

class MyView(APIView):
    @method_decorator(cache_page(60 * 15))
    def get(self, request):
        return Response({"timestamp": timezone.now()})

Все работает отлично, когда Redis запущен и работает. Однако в последнее время наш экземпляр Redis периодически выходит из строя (крик Heroku Redis), из-за чего все конечные точки, использующие кэш Redis, и, следовательно, все конечные точки, использующие декоратор cache_page, падают и возвращают 500 Internal Server Error.

Я хочу реализовать механизм обхода отказа, который будет просто игнорировать кэш и успешно возвращать ответ, когда возникает ошибка redis.ConnectionError. Вот что у меня есть на данный момент:

def cache_page_with_failover(timeout, *, cache=None, key_prefix=None):
    def decorator(view_func):
        @wraps(view_func)
        def wrapper(*args, **kwargs):
            try:
                return cache_page(timeout, cache=cache, key_prefix=key_prefix)(
                    view_func
                )(*args, **kwargs)
            except redis.ConnectionError:
                return view_func(*args, **kwargs)

        return wrapper

    return decorator

Использование:

class MyView(APIView):
    @method_decorator(cache_page_with_failover(60 * 15))
    def get(self, request):
        return Response({"timestamp": timezone.now()})

Это работает, но это ужасно многословно, и мне кажется, что я копирую большую часть подписи cache_page. Есть ли более чистый и элегантный способ написать это?

Прямой прием декоратора, возвращаемого cache_page:

def with_redis_failover(cache_decorator):
    def decorator(view_func):
        cache_decorated_view_func = cache_decorator(view_func)

        @wraps(view_func)
        def wrapper(*args, **kwargs):
            try:
                return cache_decorated_view_func(*args, **kwargs)
            except redis.ConnectionError:
                return view_func(*args, **kwargs)

        return wrapper

    return decorator

Использование:

# @method_decorator(cache_page_with_failover(60 * 15))
@method_decorator(with_redis_failover(cache_page(60 * 15)))

Это можно обобщить до:

def with_failover(decorator, exception_type):
    def _decorator(view_func):
        decorated_view_func = decorator(view_func)

        @wraps(view_func)
        def wrapper(*args, **kwargs):
            try:
                return decorated_view_func(*args, **kwargs)
            except exception_type:
                return view_func(*args, **kwargs)

        return wrapper

    return _decorator

Использование:

@method_decorator(with_failover(cache_page(60 * 15), redis.ConnectionError))
with_redis_failover = partial(with_failover, exception_type=redis.ConnectionError)

@method_decorator(with_redis_failover(cache_page(60 * 15)))
Вернуться на верх