Запоминание кэша фляги, не работающее с исходными ресурсами фляги

flask_cache.Cache.memoize не работает с flask_restful.Resouce

Вот пример кода:

from flask import Flask, request, jsonify
from flask_restful import Resource, Api
from flask_cache import Cache

app = Flask(__name__)
api = Api(app)
cache = Cache(app, config={'CACHE_TYPE': 'simple'})


class MyResource(Resource):
    JSONIFY = True
    PATH = None
    ENDPOINT = None

    def dispatch_request(self, *args, **kwargs):
        kw = dict(**kwargs)
        kw.update(request.args.items())
        r = super().dispatch_request(*args, **kw)
        if self.JSONIFY:
            return jsonify(r)
        else:
            return r


class DebugResouce(MyResource):
    PATH = '/debug'
    ENDPOINT = 'debug'

    @cache.memoize(timeout=30)
    def get(self, **kwargs):
        print('cache is not used!')
        return kwargs

for r in [DebugResouce]:
    api.add_resource(r, r.PATH, endpoint=r.ENDPOINT)


print('running!')
app.run()

Обратите внимание, что в get() Я добавил print, чтобы видеть, когда код на самом деле вызывается и когда используется кэшированное значение.

Я запускаю сервер, затем в браузере, я иду к http://localhost:5000/debug?a=1и нажмите f5 repeatetely. Я ожидаю, что моя функция get вызывается один раз, а затем используется кэшированное значение. Но в консоли сервера я вижу свой отпечаток каждый раз, когда нажимаю f5, Так memoize не работает. Что я делаю неправильно?

редактировать:

Я переместил свою кэшированную функцию снаружи из Resource учебный класс

@cache.memoize(timeout=30)
def my_foo(a):
    print('cache is not used!')
    return dict(kw=a, id=id(a))

class DebugResouce(MyResource):
    PATH = '/debug'
    ENDPOINT = 'debug'

    def get(self, a):
        return my_foo(a)

и это сработало. Насколько я вижу, проблема была self аргумент, который был на самом деле уникальным в каждом вызове. Вопрос все еще, как заставить это работать без извлечения дополнительной функции для каждого метода, который я хочу кэшировать? Текущее решение выглядит как обходной путь.

4 ответа

Решение

Спасибо @Rugnar, это решение пригодилось.

решение

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

метод _extract_self_arg обновлено.

class ResourceCache(Cache):
""" When the class method is being memoized,
    cache key uses the class name from self or cls."""

def _memoize_make_cache_key(self, make_name=None, timeout=None):
    def make_cache_key(f, *args, **kwargs):
        fname, _ = function_namespace(f)
        if callable(make_name):
            altfname = make_name(fname)
        else:
            altfname = fname
        updated = altfname + json.dumps(dict(
            args=self._extract_self_arg(f, args),
            kwargs=kwargs), sort_keys=True)
        return b64encode(
            md5(updated.encode('utf-8')).digest()
        )[:16].decode('utf-8')

    return make_cache_key

@staticmethod
def _extract_self_arg(f, args):
    argspec_args = inspect.getargspec(f).args

    if argspec_args and argspec_args[0] in ('self', 'cls'):
        if hasattr(args[0], '__name__'):
            return (args[0].__name__,) + args[1:]
        return (args[0].__class__.__name__,) + args[1:]
    return args

Может быть, это тоже будет кому-то полезно.

Кеш не работает, потому что вы используете метод memoize. В этом случае он будет кешировать результат функции. Декоратор ничего не знает о маршруте (вид, путь).

Чтобы исправить это, вы должны использовать кэшированный метод. @cached у декоратора есть аргумент key_prefix со значением по умолчанию = view/request.path,

Так что просто поменяй @cache.memoize(timeout=30) в @cache.cached(timeout=30)

Это не работает, потому что memoize учитывает аргументы функции в ключе кеша, и каждый новый запрос становится уникальным kwargs (уникальный результат id функция).

Чтобы увидеть, просто измените код

@cache.memoize(timeout=30)
def get(self, **kwargs):
    print('cache is not used!')
    return id(kwargs)

и каждый новый запрос вы получите другой результат. Так что каждый новый ключ кеша запроса отличается, поэтому вы видите cache is not used! на выходе консоли.

Найденное решение по подклассам Cache и перегрузка логики, которая создает ключ кеша для memoize, Так что все работает отлично.

import json
import inspect
from base64 import b64encode
from hashlib import md5
from flask_cache import Cache, function_namespace

class ResourceCache(Cache):
    def _memoize_make_cache_key(self, make_name=None, timeout=None):
        def make_cache_key(f, *args, **kwargs):
            fname, _ = function_namespace(f)
            if callable(make_name):
                altfname = make_name(fname)
            else:
                altfname = fname

            updated = altfname + json.dumps(dict(
                args=self._extract_self_arg(f, args),
                kwargs=kwargs), sort_keys=True)

            return b64encode(
                md5(updated.encode('utf-8')).digest()
            )[:16].decode('utf-8')

        return make_cache_key

    @staticmethod
    def _extract_self_arg(f, args):
        argspec_args = inspect.getargspec(f).args
        if argspec_args and argspec_args[0] in ('self', 'cls'):
            return args[1:]
        return args

Другими словами, когда метод класса запоминается, кеш игнорирует первый аргумент self или же cls,

Другие вопросы по тегам