=======
Cachual
=======
.. currentmodule:: cachual

Cachual is a library that makes it easy to cache the return values from your
Python functions with a simple decorator::

    from cachual import RedisCache
    cache = RedisCache()

    @cache.cached(ttl=360)
    def get_user_email(user_id):
        ...

Cachual currently supports Redis as the backing cache, but is very easy to
extend to other caching backends.

.. contents::
    :local:
    :backlinks: none

Installation
============

Install the library with pip::

    $ pip install cachual

Usage
=====

Initialize a cache using the desired implementation, providing the connection
information in the constructor if different from the defaults::

    from cachual import RedisCache
    cache = RedisCache(host='localhost', port=1234, db=5)

The cache object gives you access to the :meth:`~CachualCache.cached`
decorator, which you can apply to any function whose return value you want to
cache::

    @cache.cached()
    def get_user_email(user_id):
        ...

If you don't specify a TTL then Cachual will use the server default, which is
probably forever. In most cases you will want to specify a TTL, which is given
in seconds::

    @cache.cached(ttl=300) # 5 minutes
    def get_user_email(user_id):
        ...

How it Works
============

When you decorate a function with the :meth:`~CachualCache.cached` decorator,
before the function is executed, the cache will be checked for a prior return
value for the **same function call**. This means that the cache key will be
different if the function is called with different argument values.

If the value is found it is immediately returned. Otherwise, the function is
executed and the return value is stored in the cache before returning it.

Any cache failures (get or put) are ignored; if the cache becomes
nonfunctional and starts raising exceptions, your function will execute
normally as if there was no cache.

Key Generation
==============

Keys are generated by first appending the function name to the module where the
function resides. After that one of four strings is appended:

1. If the function is called without arguments, then ``()`` is appended.
2. If the function is called only with positional arguments, then the unicode
   value of each argument is joined with ``', '`` and surrounded by
   parentheses. For example::

       'my.module.get_user_email(abc)'

3. If the function is called only with keyword arguments, they will be included
   in ``key=value`` form, where value is the unicode value of the argument, in
   alphabetical order of the keys. For example::

       'my.module.get_user_emails(active=True, age=10, location=US)'

4. If the function is called with both, the positional argument values will be
   included first, and the keyword arguments will be included in alphabetical
   order of the keys after the positional argument values.

Finally, the entire (unicode) key is encoded as UTF-8 and hashed using MD5.
This is to ensure that key format is uniform and consistent, because some
backends (such as Memcached) have restrictions around cache keys, such as
disallowing certain characters and size limits.

For Python 3, all strings are unicode and the default encoding is UTF-8; thus,
the value for each argument will be coerced to unicode using the builtin
``str`` function. Python 2 is a bit more complicated; strings are bytestrings
by default. If you pass a unicode value, that will be the value used for the
cache key. If you pass a string literal, it will be converted to unicode
(assuming UTF-8 encoding). Anything else will be converted to a bytestring
(using the builtin ``str`` function) and then converted to unicode assuming
UTF-8 encoding.

Arguments **should** be able to take on any value, but as a best practice, I
highly recommend you only pass the basic types to your functions e.g. string,
integer, float, etc. Even better, stick to unicode values for your strings
regardless of what version of Python you're using.

.. note:: Because the unicode value of each argument is used to generate the
   cache key, you need to be careful that you are consistent in your function
   calls with respect to the types of your arguments. For example, if you have
   a function that takes a single argument like so::

       def test(a):
           ...

   Then these two calls will result in the same cache key::

       test(5)
       test("5")

   Thus, you should not call your cached function with values for an argument
   that are different types but have the same unicode value. This is good
   practice anyway; mixing types for the same argument value can lead to
   unmaintainable code and unexpected bugs.

Caching Different Data Types
============================

Technically the types of values that your cached functions can return depends
on the underlying cache. For example, Redis's ``SET`` only stores strings;
thus, if you wanted to return something other than a string from your cached
function, you would have to convert the value into the desired datatype if it
comes from a cache hit. To get around this limitation, you can provide two
additional optional arguments to the :meth:`~CachualCache.cached` decorator.

Unpack
------

The ``unpack`` argument specifies a function which will be called with the
value from the cache in the case of a cache hit. The result will be passed
back to the caller. 

In this way you can make sure you get back consistent values regardless of
whether the value came from the cache (which may store every value as a string)
or from the actual function call::

    def get_int(value):
        return int(value)

    @cache.cached(ttl=300, unpack=get_int)
    def get_user_id(email):
        ...

Pack
----

What if you want to cache a more complex data type, like a list or a
dictionary? You can provide a function to the ``pack`` argument, which will be
applied before the function's return value is put into the cache::

    @cache.cached(ttl=300, pack=json.dumps, unpack=json.loads)
    def get_user_json(user_id):
        ...

While the exact way to pack or unpack a cached value depends on the underlying
cache being used, Cachual provides a number of functions to unpack strings into
common Python data types (since most common cache providers store all data as
strings) and pack JSON values (such as dictionaries and lists that only contain
basic types) so that you don't have to write these functions yourself. This
allows you to rewrite the examples above::

    from cachual import unpack_int, pack_json, unpack_json

    @cache.cached(ttl=300, unpack=unpack_int)
    def get_user_id(email):
        ...

    @cache.cached(ttl=300, pack=pack_json, unpack=unpack_json)
    def get_user_json(user_id):
        ...

Note that for Python 3, you will want to use :data:`unpack_json_python3` for
JSON data and :data:`unpack_bytes` for unicode string data because the value
returned from the cache will be bytes (as opposed to the unicode string that
is the default string type in Python 3).

For a complete list of these helper functions see :ref:`packinghelpers`.

.. note:: Be careful with packing/unpacking. If your pack/unpack functions have
   unintended side effects (such as changing the encoding of the value) you may
   get different results when you retrieve values from the cache. Generally it
   is best to keep things as simple as possible - don't try to cache complex
   Python datatypes (such as custom objects), and keep your pack/unpack
   functions very simple (or use the helpers!). Make sure you understand how
   the underlying caching system and library deals with your data,
   particularly when it comes to encoding. For example, Redis will encode your
   value as a bytestring if possible, falling back to the unicode
   representation otherwise (which is why you need to use the pack/unpack
   functions as above for dictionaries).

API Documentation
=================

Caching
-------

.. module:: cachual

.. autoclass:: CachualCache

   .. automethod:: cached

.. autoclass:: RedisCache

   .. automethod:: get

   .. automethod:: put

.. autoclass:: MemcachedCache

   .. automethod:: get

   .. automethod:: put

.. _packinghelpers:

Packing and Unpacking Helpers
-----------------------------

These functions are helpers for packing/unpacking common Python data types for
a cache which stores everything as a string.

.. autofunction:: pack_json

.. autofunction:: unpack_json

.. autofunction:: unpack_json_python3

.. autofunction:: unpack_bytes

.. autofunction:: unpack_int

.. autofunction:: unpack_long

.. autofunction:: unpack_float

.. autofunction:: unpack_bool
