1============
2Usage Guide
3============
4
5Overview
6========
7
8At the time of this writing, popular key/value servers include
9`Memcached <http://memcached.org>`_,  `Redis <http://redis.io/>`_ and many others.
10While these tools all have different usage focuses, they all have in common that the storage model
11is based on the retrieval of a value based on a key; as such, they are all potentially
12suitable for caching, particularly Memcached which is first and foremost designed for
13caching.
14
15With a caching system in mind, dogpile.cache provides an interface to a particular Python API
16targeted at that system.
17
18A dogpile.cache configuration consists of the following components:
19
20* A *region*, which is an instance of :class:`.CacheRegion`, and defines the configuration
21  details for a particular cache backend.  The :class:`.CacheRegion` can be considered
22  the "front end" used by applications.
23* A *backend*, which is an instance of :class:`.CacheBackend`, describing how values
24  are stored and retrieved from a backend.  This interface specifies only
25  :meth:`~.CacheBackend.get`, :meth:`~.CacheBackend.set` and :meth:`~.CacheBackend.delete`.
26  The actual kind of :class:`.CacheBackend` in use for a particular :class:`.CacheRegion`
27  is determined by the underlying Python API being used to talk to the cache, such
28  as Pylibmc.  The :class:`.CacheBackend` is instantiated behind the scenes and
29  not directly accessed by applications under normal circumstances.
30* Value generation functions.   These are user-defined functions that generate
31  new values to be placed in the cache.   While dogpile.cache offers the usual
32  "set" approach of placing data into the cache, the usual mode of usage is to only instruct
33  it to "get" a value, passing it a *creation function* which will be used to
34  generate a new value if and only if one is needed.   This "get-or-create" pattern
35  is the entire key to the "Dogpile" system, which coordinates a single value creation
36  operation among many concurrent get operations for a particular key, eliminating
37  the issue of an expired value being redundantly re-generated by many workers simultaneously.
38
39Rudimentary Usage
40=================
41
42dogpile.cache includes a Pylibmc backend.  A basic configuration looks like::
43
44    from dogpile.cache import make_region
45
46    region = make_region().configure(
47        'dogpile.cache.pylibmc',
48        expiration_time = 3600,
49        arguments = {
50            'url': ["127.0.0.1"],
51        }
52    )
53
54    @region.cache_on_arguments()
55    def load_user_info(user_id):
56        return some_database.lookup_user_by_id(user_id)
57
58.. sidebar:: pylibmc
59
60    In this section, we're illustrating Memcached usage
61    using the `pylibmc <http://pypi.python.org/pypi/pylibmc>`_ backend, which is a high performing
62    Python library for Memcached.  It can be compared to the `python-memcached <http://pypi.python.org/pypi/python-memcached>`_
63    client, which is also an excellent product.  Pylibmc is written against Memcached's native API
64    so is markedly faster, though might be considered to have rougher edges.   The API is actually a bit
65    more verbose to allow for correct multithreaded usage.
66
67
68Above, we create a :class:`.CacheRegion` using the :func:`.make_region` function, then
69apply the backend configuration via the :meth:`.CacheRegion.configure` method, which returns the
70region.  The name of the backend is the only argument required by :meth:`.CacheRegion.configure`
71itself, in this case ``dogpile.cache.pylibmc``.  However, in this specific case, the ``pylibmc``
72backend also requires that the URL of the memcached server be passed within the ``arguments`` dictionary.
73
74The configuration is separated into two sections.  Upon construction via :func:`.make_region`,
75the :class:`.CacheRegion` object is available, typically at module
76import time, for usage in decorating functions.   Additional configuration details passed to
77:meth:`.CacheRegion.configure` are typically loaded from a configuration file and therefore
78not necessarily available until runtime, hence the two-step configurational process.
79
80Key arguments passed to :meth:`.CacheRegion.configure` include *expiration_time*, which is the expiration
81time passed to the Dogpile lock, and *arguments*, which are arguments used directly
82by the backend - in this case we are using arguments that are passed directly
83to the pylibmc module.
84
85Region Configuration
86====================
87
88The :func:`.make_region` function currently calls the :class:`.CacheRegion` constructor directly.
89
90.. autoclass:: dogpile.cache.region.CacheRegion
91    :noindex:
92
93One you have a :class:`.CacheRegion`, the :meth:`.CacheRegion.cache_on_arguments` method can
94be used to decorate functions, but the cache itself can't be used until
95:meth:`.CacheRegion.configure` is called.  The interface for that method is as follows:
96
97.. automethod:: dogpile.cache.region.CacheRegion.configure
98    :noindex:
99
100The :class:`.CacheRegion` can also be configured from a dictionary, using the :meth:`.CacheRegion.configure_from_config`
101method:
102
103.. automethod:: dogpile.cache.region.CacheRegion.configure_from_config
104    :noindex:
105
106
107Using a Region
108==============
109
110The :class:`.CacheRegion` object is our front-end interface to a cache.  It includes
111the following methods:
112
113
114.. automethod:: dogpile.cache.region.CacheRegion.get
115    :noindex:
116
117.. automethod:: dogpile.cache.region.CacheRegion.get_or_create
118    :noindex:
119
120.. automethod:: dogpile.cache.region.CacheRegion.set
121    :noindex:
122
123.. automethod:: dogpile.cache.region.CacheRegion.delete
124    :noindex:
125
126.. automethod:: dogpile.cache.region.CacheRegion.cache_on_arguments
127    :noindex:
128
129.. _creating_backends:
130
131Creating Backends
132=================
133
134Backends are located using the setuptools entrypoint system.  To make life easier
135for writers of ad-hoc backends, a helper function is included which registers any
136backend in the same way as if it were part of the existing sys.path.
137
138For example, to create a backend called ``DictionaryBackend``, we subclass
139:class:`.CacheBackend`::
140
141    from dogpile.cache.api import CacheBackend, NO_VALUE
142
143    class DictionaryBackend(CacheBackend):
144        def __init__(self, arguments):
145            self.cache = {}
146
147        def get(self, key):
148            return self.cache.get(key, NO_VALUE)
149
150        def set(self, key, value):
151            self.cache[key] = value
152
153        def delete(self, key):
154            self.cache.pop(key)
155
156Then make sure the class is available underneath the entrypoint
157``dogpile.cache``.  If we did this in a ``setup.py`` file, it would be
158in ``setup()`` as::
159
160    entry_points="""
161      [dogpile.cache]
162      dictionary = mypackage.mybackend:DictionaryBackend
163      """
164
165Alternatively, if we want to register the plugin in the same process
166space without bothering to install anything, we can use ``register_backend``::
167
168    from dogpile.cache import register_backend
169
170    register_backend("dictionary", "mypackage.mybackend", "DictionaryBackend")
171
172Our new backend would be usable in a region like this::
173
174    from dogpile.cache import make_region
175
176    region = make_region("myregion")
177
178    region.configure("dictionary")
179
180    data = region.set("somekey", "somevalue")
181
182The values we receive for the backend here are instances of
183``CachedValue``.  This is a tuple subclass of length two, of the form::
184
185    (payload, metadata)
186
187Where "payload" is the thing being cached, and "metadata" is information
188we store in the cache - a dictionary which currently has just the "creation time"
189and a "version identifier" as key/values.  If the cache backend requires serialization,
190pickle or similar can be used on the tuple - the "metadata" portion will always
191be a small and easily serializable Python structure.
192
193
194.. _changing_backend_behavior:
195
196Changing Backend Behavior
197=========================
198
199The :class:`.ProxyBackend` is a decorator class provided to easily augment existing
200backend behavior without having to extend the original class. Using a decorator
201class is also adventageous as it allows us to share the altered behavior between
202different backends.
203
204Proxies are added to the :class:`.CacheRegion` object using the :meth:`.CacheRegion.configure`
205method.  Only the overridden methods need to be specified and the real backend can
206be accessed with the ``self.proxied`` object from inside the :class:`.ProxyBackend`.
207
208For example, a simple class to log all calls to ``.set()`` would look like this::
209
210    from dogpile.cache.proxy import ProxyBackend
211
212    import logging
213    log = logging.getLogger(__name__)
214
215    class LoggingProxy(ProxyBackend):
216        def set(self, key, value):
217            log.debug('Setting Cache Key: %s' % key)
218            self.proxied.set(key, value)
219
220
221:class:`.ProxyBackend` can be be configured to optionally take arguments (as long as the
222:meth:`.ProxyBackend.__init__` method is called properly, either directly
223or via ``super()``.  In the example
224below, the ``RetryDeleteProxy`` class accepts a ``retry_count`` parameter
225on initialization.  In the event of an exception on delete(), it will retry
226this many times before returning::
227
228    from dogpile.cache.proxy import ProxyBackend
229
230    class RetryDeleteProxy(ProxyBackend):
231        def __init__(self, retry_count=5):
232            super(RetryDeleteProxy, self).__init__()
233            self.retry_count = retry_count
234
235        def delete(self, key):
236            retries = self.retry_count
237            while retries > 0:
238                retries -= 1
239                try:
240                    self.proxied.delete(key)
241                    return
242
243                except:
244                    pass
245
246The ``wrap`` parameter of the :meth:`.CacheRegion.configure` accepts a list
247which can contain any combination of instantiated proxy objects
248as well as uninstantiated proxy classes.
249Putting the two examples above together would look like this::
250
251    from dogpile.cache import make_region
252
253    retry_proxy = RetryDeleteProxy(5)
254
255    region = make_region().configure(
256        'dogpile.cache.pylibmc',
257        expiration_time = 3600,
258        arguments = {
259            'url':["127.0.0.1"],
260        },
261        wrap = [ LoggingProxy, retry_proxy ]
262    )
263
264In the above example, the ``LoggingProxy`` object would be instantated by the
265:class:`.CacheRegion` and applied to wrap requests on behalf of
266the ``retry_proxy`` instance; that proxy in turn wraps
267requests on behalf of the original dogpile.cache.pylibmc backend.
268
269.. versionadded:: 0.4.4  Added support for the :class:`.ProxyBackend` class.
270
271
272Configuring Logging
273====================
274
275.. versionadded:: 0.9.0
276
277:class:`.CacheRegion` includes logging facilities that will emit debug log
278messages when key cache events occur, including when keys are regenerated as
279well as when hard invalidations occur.   Using the `Python logging
280<https://docs.python.org/3/library/logging.html>`_ module, set the log level to
281``dogpile.cache`` to ``logging.DEBUG``::
282
283    logging.basicConfig()
284    logging.getLogger("dogpile.cache").setLevel(logging.DEBUG)
285
286Debug logging will indicate time spent regenerating keys as well as when
287keys are missing::
288
289    DEBUG:dogpile.cache.region:No value present for key: '__main__:load_user_info|2'
290    DEBUG:dogpile.cache.region:No value present for key: '__main__:load_user_info|1'
291    DEBUG:dogpile.cache.region:Cache value generated in 0.501 seconds for keys: ['__main__:load_user_info|2', '__main__:load_user_info|3', '__main__:load_user_info|4', '__main__:load_user_info|5']
292    DEBUG:dogpile.cache.region:Hard invalidation detected for key: '__main__:load_user_info|3'
293    DEBUG:dogpile.cache.region:Hard invalidation detected for key: '__main__:load_user_info|2'