cachepy 1.2
Caching results of functions in Python
Keywords: caching,python,file-based cache,memory-based cache,encrypted cache

Caching results of functions in Python
======================================

.. image:: https://travis-ci.com/scidam/cachepy.svg?branch=master
    :target: https://travis-ci.com/scidam/cachepy


A caching toolset for Python. It is tested for both
Python 2.7.x and 3.4+ (<3.10).

Features
--------

    * Memory-based and file-based caches;
    * Ability to set the TTL (time-to-live) and NOC (the number of calls) on caches;
    * Encryption of the cached data (symmetric encryption algorithm (RSA) is used);
    * LFU (least frequently used) and MFU (most frequently used) cache-clearing strategies;
    * caches of limited size;

Notes
-----

    - Encryption functionality requires the `PyCryptodome` package to be installed;
    - File-based caches save the cached data in a file; files with cached data should be
      cleaned up manually, if needed.

Examples
--------

.. code-block:: python

    from cachepy import *

    mycache = Cache()
    # save the cached data to memory without encryption

    @mycache
    def my_heavy_function(x):
        '''Performs heavy computations'''

        print('Hi, I was invoked...')
        return x**2

    my_heavy_function(2)
    # "Hi, I was invoked..." will be printed to stdout only once
    # return 4

    my_heavy_function(2)
    # return 4


To store data to a file, one needs to initialize a decorator, as follows:

.. code-block:: python

    # create cache-to-file decorator
    filecache = FileCache('mycache')  # mycache.dat file will be created;
    # `.dat` extension is appended automatically to the filename
    # (depends on the shelve module implementation);

Its behavior is the same as a memory-based one, but all cached data is stored in
the specified file.

One can set up time-to-live (TTL) and/or maximum number of calls (NOC)
for the cached data when the decorator is initialized:

.. code-block:: python

    import time
    from cachepy import *

    cache_with_ttl = Cache(ttl=2)  # ttl given in seconds

    @cache_with_ttl
    def my_heavy_function(x):
        '''Performs heavy computations'''

        print('Hi, I was invoked...')
        return x**2

    my_heavy_function(3)
    # Hi, I was invoked... will be printed
    # return 9
    my_heavy_function(3)
    # 'Hi, I was invoked ...' will not be printed
    # return 9
    time.sleep(2)
    my_heavy_function(3)
    # 'Hi, I was invoked ...' will be printed again
    # return 9


.. code-block:: python

    cache_with_noc = Cache(noc=2)  # the number-of-calls: noc = 2

    @cache_with_noc
    def my_heavy_function(x):
        '''Performs heavy computations'''

        print('Hi, I was invoked...')
        return x**2

    my_heavy_function(3)
    my_heavy_function(3) # 'Hi, I was invoked ...' will not be printed
    my_heavy_function(3) # 'Hi, I was invoked ...' will be printed again


It is easy to use both `NOC` and `TTL` arguments when defining
a caching decorator:

.. code-block:: python

    cache_with_noc_ttl = Cache(noc=2, ttl=1)

    @cache_with_noc_ttl
    def my_heavy_function(x):
        '''Performs heavy computations'''

        print('Hi, I was invoked...')
        return x**2

    my_heavy_function(3)
    my_heavy_function(3)  # 'Hi, I was invoked ...' will not be printed
    my_heavy_function(3)  # 'Hi, I was invoked ...' will be printed (noc is
    # reached, recompute the func value)
    time.sleep(2)  # get ttl expired
    my_heavy_function(3) # 'Hi, I was invoked ...' will be printed again

One can encrypt the cached data by providing a non-empty `key` argument as
a password (RSA encryption algorithm is used):

.. code-block:: python

    cache_to_file_ttl_noc = FileCache('mycache',
                                      noc=2, ttl = 2,
                                      key='mypassword')

    @cache_to_file_ttl_noc
    def my_heavy_function(x):
        '''Performs heavy computations'''

        print('Hi, I was invoked...')
        return x**2

    my_heavy_function(2) # 'Hi, I was invoked...' will be printed
    my_heavy_function(2) # 'Hi, I was invoked...' will not be printed

When `my_heavy_function` is decorated by `cache_to_file_ttl_noc`, as shown
in the example above, the value `2**2 = 4` will be computed and the result of
the computation will be stored in the file named `mycache.dat`. Along
with the result of the computation,  additional information will be stored
in the file `mycache.dat`. The additional information includes:
1) the result's expiration time (computed from the TTL),
2) NOC and 3) the number of already performed calls of the function being
decorated (`my_heavy_function`).

Encryption is available only if `PyCryptodome` package is installed and the
`key` parameter (a non-empty string representing the password) is passed to the
cache constructor. It also could work with the old PyCrypto package.

If you passed the non-empty `key` parameter to the cache constructor
but `PyCryptodome` was not found, a special warning would be raised in this case
("PyCryptodome not installed. Data will not be encrypted") and
the cache would work as usual but without encryption functionality.


Caching with limitations
------------------------

Standard cache constructors are used to initialize caches of unlimited capacity.
There are also caches of limited capacity.
Such caches are initialized by constructors named `LimitedCache` and `LimitedFileCache`.
These constructors have additional
parameters `cache_size` (the maximum number of items stored in the cache) and
`algorithm` (cache-clearing algorithm). Available `algorithm` values are
`lfu` (default, which stands for least frequently used) and `mfu` (most frequently used).
When `algorithm='lfu'`, then the least frequently used item is removed from the cache,
if it is exhausted. In case of `algorithm='mfu'`, everything behaves the same way,
with the only difference being that the most frequently used item is removed.


Testing
-------

.. code-block:: bash

         python -m  cachepy.test


TODO
----

    * Writing backend for redis server


Log list
--------

    * Version 1.2 (testing compatibility with Python 3.8 - 3.9)
    * Version 1.1
    * Version 1.0 (broken installation via pip/pipenv)
    * Version 0.1
        - initial release


Author
------

    Dmitry Kislov <kislov@easydan.com>