Source code for chrysalio.includes.cache

"""Cache functionality."""

from time import time

from beaker.cache import CacheManager, cache_regions
from beaker.util import parse_cache_config_options

from pyramid.config import Configurator


CACHE_DEFAULT_TTL = 43200
CREATED_KEY = '__created__'


# =============================================================================
[docs] def includeme(configurator): """Function to include cache functionnality based on registry for global cache and session for user cache. :type configurator: pyramid.config.Configurator :param configurator: Object used to do configuration declaration within the application. """ if isinstance(configurator, Configurator): if 'cache_user' not in configurator.registry: configurator.registry['cache_user'] = CacheUser( configurator.get_settings()) if 'cache_global' not in configurator.registry: configurator.registry['cache_global'] = CacheGlobal( configurator.get_settings())
# =============================================================================
[docs] class CacheUser(object): """Class to manage a private cache for a user based on session.""" # ------------------------------------------------------------------------- def __init__(self, settings): """Constructor method.""" prefix = '' regions = settings.get('cache.regions') if not regions: prefix = 'beaker.' regions = settings.get('beaker.cache.regions', '') self.regions = {} for region in regions.split(','): region = region.strip() expire = settings.get( '{0}cache.{1}.expire'.format(prefix, region)) self.regions[region] = int(expire) if expire and expire.isdigit() \ else CACHE_DEFAULT_TTL # -------------------------------------------------------------------------
[docs] def set(self, request, key, value, namespace='', region=None, expire=None): """Set value into the cache. :type request: pyramid.request.Request :param request: Current request. :param str key: Key of value to retrieve. :param value: The value associated with the key or ``None``. :param str namespace: (default='') Cache namespace. :param str region: (optional) Beaker cache region. :param int expire: (optional) Special expiration. """ # pylint: disable = too-many-arguments, too-many-positional-arguments if 'cache' not in request.session: request.session['cache'] = {} cache = request.session['cache'] if namespace not in cache: cache[namespace] = {} if not expire: expire = self.regions.get(region, CACHE_DEFAULT_TTL) cache[namespace][key] = (value, time() + expire)
# -------------------------------------------------------------------------
[docs] def get(self, request, key, namespace=''): """Get value from the cache. :type request: pyramid.request.Request :param request: Current request. :param str key: Key of value to retrieve. :param str namespace: (default='') Cache namespace. :return: The value associated with the key or ``None``. """ if 'cache' not in request.session or \ namespace not in request.session['cache'] or \ key not in request.session['cache'][namespace]: return None if request.session['cache'][namespace][key][1] < time(): del request.session['cache'][namespace][key] self.purge(request, namespace) return None return request.session['cache'][namespace][key][0]
# -------------------------------------------------------------------------
[docs] def clear(self, request, key=None, namespace=''): """Clear a key/value or the entire namespace ``namespace``. :type request: pyramid.request.Request :param request: Current request. :param str key: (optional) Key of value to remove. :param str namespace: (default='') Cache namespace. """ if 'cache' not in request.session or \ namespace not in request.session['cache']: return if key is not None: if key in request.session['cache'][namespace]: del request.session['cache'][namespace][key] self.purge(request, namespace) return del request.session['cache'][namespace] self.purge(request)
# -------------------------------------------------------------------------
[docs] @classmethod def purge(cls, request, namespace=None): """Purge expired entries. :type request: pyramid.request.Request :param request: Current request. :param str namespace: (optional) Cache namespace. """ now = time() namespaces = (namespace,) if namespace is not None \ else tuple(request.session['cache']) for nspace in namespaces: for key in tuple(request.session['cache'][nspace]): if request.session['cache'][nspace][key][1] < now: del request.session['cache'][nspace][key] if not request.session['cache'][nspace]: del request.session['cache'][nspace] if not request.session['cache']: del request.session['cache']
# =============================================================================
[docs] def cache_user_access(namespace_prefix, region=None): """A decorator to retrieve in the user cache the result of an access method whose the two first arguments are ``request`` and ``item``. ``item`` must have a ``uid`` attribute. :param str namespace_prefix: Prefix of the cache namespace. :param str region: (optional) Name of region. """ def _decorated(method): """Decoration of the method `method`.""" def _wrapper(class_, request, item, *args, **kwargs): """Use of user cache.""" access_method = method.__func__ \ if isinstance(method, classmethod) else method if item is None: return access_method(class_, request, item, *args, **kwargs) if not hasattr(item, 'uid'): raise AttributeError('Class must have a "uid" attribute!') namespace = cache_namespace(namespace_prefix, item.uid) access = request.registry['cache_user'].get( request, 'access', namespace) if access is not None: return access access = access_method(class_, request, item, *args, **kwargs) request.registry['cache_user'].set( request, 'access', access, namespace, region) return access return _wrapper return _decorated
# =============================================================================
[docs] class CacheGlobal(object): """Class to manage a global cache based on Beaker cache. :param dict settings: Pyramid settings. """ # ------------------------------------------------------------------------- def __init__(self, settings): """Constructor method.""" self._cache_manager = CacheManager( **parse_cache_config_options(settings)) self.regions = cache_regions self._caches = {} # -------------------------------------------------------------------------
[docs] def set(self, key, value, namespace='', region=None, **kwargs): """Set value into the cache. :param str key: Key of value to retrieve. :param value: The value associated with the key or ``None``. :param str namespace: (default='') Cache namespace. :param str region: (optional) Beaker cache region. :param dict kwargs: Keyworded arguments. """ cache = self._get_cache(namespace, region, **kwargs) if cache is not None: cache.put(key, value)
# -------------------------------------------------------------------------
[docs] def get(self, key, namespace='', region=None, **kwargs): """Get value from the cache. :param str key: Key of value to retrieve. :param str namespace: (default='') Cache namespace. :param str region: (optional) Beaker cache region. :param dict kwargs: Keyworded arguments. :return: The value associated with the key or ``None``. """ cache = self._get_cache(namespace, region, **kwargs) try: return cache.get(key) except KeyError: return None
# -------------------------------------------------------------------------
[docs] def clear(self, key=None, namespace='', region=None): """Clear a key/value or the entire namespace ``namespace``. :param str key: (optional) Key of value to remove. :param str namespace: (default='') Cache namespace. :param str region: (optional) Beaker cache region. """ cache = self._get_cache(namespace, region) if key is not None: cache.remove_value(key=key) else: cache.clear() del self._caches[namespace]
# -------------------------------------------------------------------------
[docs] def initialize(self, namespace='', region=None): """Clear a key/value or the entire namespace ``namespace`` and set the time of creation. :param str namespace: (default='') Cache namespace. :param str region: (optional) Beaker cache region. """ cache = self._get_cache(namespace, region) cache.clear() cache.put(CREATED_KEY, time())
# ------------------------------------------------------------------------- def _get_cache(self, namespace, region, **kwargs): """Return a Cache object. :param str namespace: Cache namespace. :param str region: Name of region. :param dict kwargs: Keyworded arguments. :rtype: beaker.cache.Cache """ if namespace in self._caches: return self._caches[namespace] if region is None or region not in self.regions: if 'expire' not in kwargs: kwargs['expire'] = CACHE_DEFAULT_TTL self._caches[namespace] = self._cache_manager.get_cache( namespace, **kwargs) else: if not self.regions[region]['expire']: self.regions[region]['expire'] = CACHE_DEFAULT_TTL region_kwargs = dict(self.regions[region]) region_kwargs.update(kwargs) self._caches[namespace] = self._cache_manager.get_cache( namespace, **region_kwargs) return self._caches[namespace]
# =============================================================================
[docs] def cache_global_item(namespace_prefix, region=None, access_function=None): """A decorator to retrieve in the global cache the result of a creation method whose the two first arguments are ``request`` and ``item_id``. The calling class must have a ``_<item>s`` attribute where `<item>` is the class name in lower case. If ``access_function`` is not ``None``, the access rights are checked. :param str namespace_prefix: Prefix of the cache namespace. :param tuple region: (optional) Name of region. :param access_function: (optional) A function to retrieve the access tuple. """ def _decorated(method): """Decoration of the method `method`.""" def _wrapper(class_, request, item_id, *args, **kwargs): """Use of global cache.""" create_method = method.__func__ \ if isinstance(method, classmethod) else method if not hasattr(class_, '_{0}s'.format(create_method.__name__)): raise AttributeError( 'Class must have a "_{0}s" attribute!'.format( create_method.__name__)) items = getattr(class_, '_{0}s'.format(create_method.__name__)) namespace = cache_namespace(namespace_prefix, item_id) cache_time = request.registry['cache_global'].get( CREATED_KEY, namespace, region) if cache_time is None: cache_time = time() request.registry['cache_global'].set( CREATED_KEY, cache_time, namespace, region) # Get the object if item_id not in items or items[item_id].created < cache_time: if item_id in items: del items[item_id] item = create_method(class_, request, item_id, *args, **kwargs) if item is None: return None items[item_id] = item # Check access rights if access_function is None: return items[item_id] get_access = access_function.__func__ \ if isinstance(access_function, classmethod) \ else access_function access = get_access(class_, request, items[item_id]) return items[item_id] if access[0] else None return _wrapper return _decorated
# =============================================================================
[docs] def cache_global_value(key, namespace_prefix, region=None): """A decorator to retrieve in the global cache the result of a computing method whose the first argument is ``request``. The class of the method must have an ``uid`` attribute. :param str key: Key of the value to retrieve. :param str namespace_prefix: Prefix of the cache namespace. :param str region: (optional) Name of region. """ def _decorated(method): """Decoration of the method `method`.""" def _wrapper(class_, request, *args, **kwargs): """Use of global cache.""" if not hasattr(class_, 'uid'): raise AttributeError('Class must have a "uid" attribute!') namespace = cache_namespace(namespace_prefix, class_.uid) value = request.registry['cache_global'].get( key, namespace, region) if value is not None and not kwargs.get('refresh'): return value computing_method = method.__func__ \ if isinstance(method, classmethod) else method value = computing_method(class_, request, *args, **kwargs) request.registry['cache_global'].set(key, value, namespace, region) return value return _wrapper return _decorated
# =============================================================================
[docs] def cache_namespace(namespace_prefix, item_id): """Return a namespace for the item ``item_id``. :param str namespace: Cache namespace. :param str item_id: ID of the item. :rtype: str """ return f'{namespace_prefix}-{item_id}'