Source code for fictive.cache.abstract

"""

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)