python-diskcache icon indicating copy to clipboard operation
python-diskcache copied to clipboard

Add Support for Flask Caching

Open grantjenks opened this issue 5 years ago • 2 comments

Add support for Flask Caching. Details at https://flask-caching.readthedocs.io/en/latest/

grantjenks avatar Jul 05 '20 21:07 grantjenks

Progress started in #154

grantjenks avatar Aug 24 '20 00:08 grantjenks

Thank you for a great library !

I have been using Diskcache with Flask for some time, but not with all the possible Flask-Caching features.

I have just subclassed the Cache class:

import functools as ft
from diskcache.core import args_to_key, full_name
from diskcache import Cache
from pathlib import Path
from flask import Flask

class DiskCache(Cache):
    def __init__(self, *args, **kwargs):
        Cache.__init__(self, *args, **kwargs)

    def init_app(self, app: Flask) -> None:
        directory  = app.config.get('CACHE_DIR')
        if not directory:
            raise ValueError(f"Missing CACHE_DIR setting")
        else:
            cpath = Path( directory )
            if not cpath.is_dir():
                raise ValueError(f"Invalid CACHE_DIR")    
        
        timeout = app.config.get('CACHE_DEFAULT_TIMEOUT', 3600)

        self.__init__(directory, timeout=timeout, statistics=True, tag_index=True)

    def set(self, key, value, expire=None, read=False, tag=None, retry=False, timeout=None):
        if not expire and timeout:
            expire = timeout

        super().set(key, value, expire=expire, read=read, tag=tag, retry=retry)             

    def memoize(self, name=None, typed=False, expire=None, tag=None, ignore=(), timeout=None):
        if not expire and timeout:
            expire = timeout  
  
        def decorator(func):
            base = (full_name(func),) if name is None else (name,)

            @ft.wraps(func)
            def wrapper(*args, **kwargs):
                key = wrapper.__cache_key__(*args, **kwargs)
                result = self.get(key, default='ENOVAL', retry=True)

                if result == 'ENOVAL':
                    result = func(*args, **kwargs)
                    if expire is None or expire > 0:
                        self.set(key, result, expire, tag=tag, retry=True)

                return result

            def __cache_key__(*args, **kwargs):
                return args_to_key(base, args, kwargs, typed, ignore)

            wrapper.__cache_key__ = __cache_key__
            return wrapper

        return decorator

What I miss in your Cache class is support for the Flask app factory pattern - cache.init_app(app) - which can be hacked ie with adding the init_later param:

 cache = DiskCache(init_later=True)
 cache.init_app(app)
class Cache:
    """Disk and file backed cache."""

    def __init__(self, directory=None, timeout=60, disk=Disk, **settings):
        """Initialize cache instance...
        """
        try:
            assert issubclass(disk, Disk)
        except (TypeError, AssertionError):
            raise ValueError('disk must subclass diskcache.Disk') from None

        if settings.get('init_later'):
            return
        ...

filak avatar Jun 22 '22 20:06 filak