"""
Provides a base class for automatically managed read-through cacheing behavior
Subclasses should provide a backing store and methods for fetching a new value
"""
import types
from fictive.transform import Transform
[docs]class AbstractCache(object):
"""
An abstract class representing a single-level cache interface
This interface provides four basic actions (`_fetch_impl`,
`_read_impl`, `_set_impl`, and `_delete_impl`) for implementing
the interaction between the cache interface and a backing store.
Subclasses for specific cache backing store types should generally
override one or more of these methods.
Additionally, each of the four actions has an associated 'private'
dispatch method (`_dispatch_fetch`, `_dispatch_read`,
`_dispatch_set`, and `_dispatch__delete`, respectively) that
controlls when and how the interface methods will be invoked.
Mixins or subclasses that want to supplement or override generic
cache behavior, or alter the ways in which the basic actions
interoperate, would probably want to override one or more of these
methods.
The interface also provides a `value` and `delete` methods by which client
code can access and clear the cached value, and a `fetch` decorator by
which client code can set the callable that will 'refresh' the cache from
an external value
"""
IDENTITY_TRANSFORM = Transform(
function=lambda cache, value, *args, **kwargs: value,
inverse=lambda cache, value, *args, **kwargs: value)
#: `Transform` applied to present values to calling code
VALUE_TRANSFORM: Transform = IDENTITY_TRANSFORM
[docs] class CacheError(Exception):
"""
Generic AbstractCache exception class
"""
[docs] class CacheFetchError(CacheError):
"""
Raised when an external value could nto be obtained
"""
[docs] class CacheMissError(CacheError):
"""
Raised when a value is not present in the cache
"""
[docs] class CacheWriteError(CacheError):
"""
Raised when a value cannot be written to the cache
"""
def _dispatch_read(self, *args, **kwargs):
"""
dispatch a `_read_impl` request
"""
return self._read_impl(*args, **kwargs)
def _read_impl(self, *args, **kwargs):
"""
override this method to read a cached value from a backing store
:raise CacheMissError: if the bacing store value cannot be read
"""
# pylint: disable=unused-argument
# pylint: disable=no-self-use
raise self.CacheMissError()
def _dispatch_fetch(self, *args, **kwargs):
"""
dispatch a `_fetch_impl` request, then cache and return the result
"""
value = self._fetch_impl(*args, **kwargs) # pylint: disable=not-callable
self._dispatch_set(value, *args, **kwargs)
return self._dispatch_read(*args, **kwargs)
@property
def fetch_impl(self):
"""
allow for overriding `_fetch_impl` after initialization
"""
return self._fetch_impl
@fetch_impl.setter
def fetch_impl(self, fetch_impl):
self._fetch_impl = types.MethodType(fetch_impl, self)
@fetch_impl.deleter
def fetch_impl(self):
del self._fetch_impl
def _fetch_impl(self, *args, **kwargs):
"""
override this method to obtain an external value for the cache
:raise CacheMissError: if no external value is available
"""
# pylint: disable=unused-argument
# pylint: disable=no-self-use
# pylint: disable=method-hidden
raise self.CacheFetchError()
def _dispatch_set(self, value, *args, **kwargs):
"""
dispatches a `_set_impl` request
"""
self._set_impl(value, *args, **kwargs)
def _set_impl(self, value, *args, **kwargs):
"""
override this method to store a value in the backing store
:raises CacheWriteError: if the backing store value cannot be written
"""
# pylint: disable=unused-argument
# pylint: disable=no-self-use
raise self.CacheWriteError()
ANY_VALUE = object()
def _dispatch_delete(self, cache_value, *args, **kwargs):
"""
dispatches a `_delete_impl` request
"""
if cache_value is self.ANY_VALUE:
return self._delete_impl(cache_value, *args, **kwargs)
try:
current_value = self._dispatch_read(*args, **kwargs)
except self.CacheMissError:
return False
if cache_value != self.VALUE_TRANSFORM(self, current_value, *args, **kwargs):
return False
return self._delete_impl(cache_value, *args, **kwargs)
def _delete_impl(self, cache_value, *args, **kwargs):
"""
override this method to remove a cached value from the backing store
:return: whether a value was actually deleted
:rtype: bool
:raises CacheWriteError: if the cached value cannot be removed
"""
# pylint: disable=unused-argument
# pylint: disable=no-self-use
raise self.CacheWriteError()
[docs] def __init__(self, *args, fetch_impl=None, **kwargs):
"""
:param callable fetch_impl:
if provided, this callable will be invoked by
`_dispatch_fetch` (rather than the class's `_fetch_impl`
method). The call siganture of `fetch_impl` must accept
this instance as a first positional argument.
"""
if fetch_impl:
self.fetch_impl = fetch_impl
super(AbstractCache, self).__init__(*args, **kwargs)
[docs] def get_value(self, *args, **kwargs):
"""
client interface to access the cache; automatically fetches if needed
"""
try:
return self.VALUE_TRANSFORM(
self, self._dispatch_read(*args, **kwargs), *args, **kwargs)
except self.CacheMissError:
return self.VALUE_TRANSFORM(
self, self._dispatch_fetch(*args, **kwargs), *args, **kwargs)
[docs] def set_value(self, value, *args, **kwargs):
"""
client interface to set the cache
"""
self._dispatch_set(
self.VALUE_TRANSFORM.inverse_operation(
self, value, *args, **kwargs), *args, **kwargs)
[docs] def delete_value(self, cache_value, *args, **kwargs):
"""
interface to delete the stored value if it is equal to `cache_value`
"""
return self._dispatch_delete(cache_value, *args, **kwargs)
@property
def value(self):
"""
simple property-based interface for default cache behavior
"""
return self.get_value()
@value.setter
def value(self, new_value):
self.set_value(new_value)
@value.deleter
def value(self):
self.delete_value(self.ANY_VALUE)