# mako/cache.py
# Copyright (C) 2006-2011 the Mako authors and contributors <see AUTHORS file>
#
# This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

from bishop.mako import exceptions

cache = None

class BeakerMissing(object):
    def get_cache(self, name, **kwargs):
        raise exceptions.RuntimeException("the Beaker package is required to use cache functionality.")

class Cache(object):
    """Represents a data content cache made available to the module
    space of a :class:`.Template` object.
 
    :class:`.Cache` is a wrapper on top of a Beaker CacheManager object.
    This object in turn references any number of "containers", each of
    which defines its own backend (i.e. file, memory, memcached, etc.) 
    independently of the rest.
 
    """
 
    def __init__(self, id, starttime):
        self.id = id
        self.starttime = starttime
        self.def_regions = {}
 
    def put(self, key, value, **kwargs):
        """Place a value in the cache.
 
        :param key: the value's key.
        :param value: the value
        :param \**kwargs: cache configuration arguments.  The 
         backend is configured using these arguments upon first request.
         Subsequent requests that use the same series of configuration
         values will use that same backend.
 
        """
 
        defname = kwargs.pop('defname', None)
        expiretime = kwargs.pop('expiretime', None)
        createfunc = kwargs.pop('createfunc', None)
 
        self._get_cache(defname, **kwargs).put_value(key, starttime=self.starttime, expiretime=expiretime)
 
    def get(self, key, **kwargs):
        """Retrieve a value from the cache.
 
        :param key: the value's key.
        :param \**kwargs: cache configuration arguments.  The 
         backend is configured using these arguments upon first request.
         Subsequent requests that use the same series of configuration
         values will use that same backend.
 
        """
 
        defname = kwargs.pop('defname', None)
        expiretime = kwargs.pop('expiretime', None)
        createfunc = kwargs.pop('createfunc', None)
 
        return self._get_cache(defname, **kwargs).get_value(key, starttime=self.starttime, expiretime=expiretime, createfunc=createfunc)
 
    def invalidate(self, key, **kwargs):
        """Invalidate a value in the cache.
 
        :param key: the value's key.
        :param \**kwargs: cache configuration arguments.  The 
         backend is configured using these arguments upon first request.
         Subsequent requests that use the same series of configuration
         values will use that same backend.
 
        """
        defname = kwargs.pop('defname', None)
        expiretime = kwargs.pop('expiretime', None)
        createfunc = kwargs.pop('createfunc', None)
 
        self._get_cache(defname, **kwargs).remove_value(key, starttime=self.starttime, expiretime=expiretime)
 
    def invalidate_body(self):
        """Invalidate the cached content of the "body" method for this template.
 
        """
        self.invalidate('render_body', defname='render_body')
 
    def invalidate_def(self, name):
        """Invalidate the cached content of a particular <%def> within this template."""
 
        self.invalidate('render_%s' % name, defname='render_%s' % name)
 
    def invalidate_closure(self, name):
        """Invalidate a nested <%def> within this template.
 
        Caching of nested defs is a blunt tool as there is no
        management of scope - nested defs that use cache tags
        need to have names unique of all other nested defs in the 
        template, else their content will be overwritten by 
        each other.
 
        """
 
        self.invalidate(name, defname=name)
 
    def _get_cache(self, defname, type=None, **kw):
        global cache
        if not cache:
            try:
                from beaker import cache as beaker_cache
                cache = beaker_cache.CacheManager()
            except ImportError:
                # keep a fake cache around so subsequent 
                # calls don't attempt to re-import
                cache = BeakerMissing()

        if type == 'memcached':
            type = 'ext:memcached'
        if not type:
            (type, kw) = self.def_regions.get(defname, ('memory', {}))
        else:
            self.def_regions[defname] = (type, kw)
        return cache.get_cache(self.id, type=type, **kw)
 
