Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask cache memoize not working with flask restful resources

flask_cache.Cache.memoize not working with flask_restful.Resource

Here is sample code:

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 DebugResource(MyResource):
    PATH = '/debug'
    ENDPOINT = 'debug'

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

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


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

Notice that in get() I added print so I can see when the code is actually called and when cached value is used.

I start server then in browser i go to http://localhost:5000/debug?a=1 and press f5 repeatetely. I expect that my function get is called once and then cached value is used. But in server console I see my print each time I press f5. So memoize is not working. What am I doing wrong?

edit:

I moved my cached function outside from Resource class

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

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

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

and that worked. As far as I can see, the issue was self argument that was actually unique in each call. The question is still, how to make it work without extracting additional function for each method i want to cache? Current solution looks like a workaround.

like image 845
Rugnar Avatar asked Mar 10 '23 16:03

Rugnar


2 Answers

The cache doesn't work because you use memoize method. In this case it will cache the result of a function. Decorator doesn't know anything about route(view, path).

To fix it you should use cached method. @cached decorator has argument key_prefix with default value = view/request.path.

So, just change @cache.memoize(timeout=30) to @cache.cached(timeout=30)

like image 188
Danila Ganchar Avatar answered Mar 12 '23 06:03

Danila Ganchar


Thank @Rugnar, this decision came in handy.

solution

The only point, I had to change it a bit, so that I would not exclude the first element (self), but use it, in order to store more unique keys in a situation where the cached method is defined in the base class, and in the children they are customized.

Method _extract_self_arg updated.

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

Maybe it will also be useful to someone.

like image 27
Artem Skliar Avatar answered Mar 12 '23 06:03

Artem Skliar