Quickstart ========== To start, remember the philosophy of **Ring** is a human-friendly high-level interface *with* transparent and concrete low-level access. You probably be able to access most of the levels of **Ring** you want. Installation ------------ PyPI is the recommended way. .. sourcecode:: shell $ pip install ring To browse versions and tarballs, visit: ``_ Though **Ring** includes support for many backends, their packages are not included in ring installation due to the following issues: #. Ring supports many backends but users don't use all of them. #. Backends packages not only cost storages and time but also require some non-Python packages to be installed, which cannot be automated by pip. #. Installing some of them is not easy on some platforms. Check each backend you use and manually add related packages to `setup.py` or `requirements.txt`. If you are new to **Ring** and cache, let's start with :func:`ring.lru`. It doesn't require any dependency. Changing lru to another backend is simple for later. .. note:: If you are new to LRU cache, check ``_ for details. First example ------------- Let's start with a simple example: function cache with bytes data. .. code-block:: python import ring import requests # save in a new lru storage @ring.lru() def get_url(url): return requests.get(url).content # default access - it is cached data = get_url('http://example.com') This flow is what you see in common *smart* cache decorators. Actually, this is very similar to :func:`functools.lru_cache` in Python standard library. The differences start here. The core feature of **Ring** is explicit controllers. .. code-block:: python # delete the cache get_url.delete('http://example.com') # get cached data or None data_or_none = get_url.get('http://example.com') # get internal cache key key = get_url.key('http://example.com') # and access directly to the backend encoded_data = get_url.storage.backend.get(key) cached_data = get_url.decode(encoded_data) Ring will have full control for any layer of caching. Which doesn't exist in :func:`functools.lru_cache` :see: :doc:`control` for sub-functions details. :see: :doc:`why` if this document doesn't explain what **Ring** does. method, classmethod, staticmethod, property ------------------------------------------- **Ring** is adaptable for any kind of methods for Python class. .. code-block:: python import ring import requests class Page(object): base_content = '' def __init__(self, url): self.url = url def __ring_key__(self): return 'page=' + self.url @ring.lru() def content(self): return requests.get(self.url).content @ring.lru() @classmethod def class_content(cls): return cls.base_content @ring.lru() @staticmethod def example_dot_com(): return requests.get('http://example.com').content @ring.lru() @property def url_property(self): return self.url_property Page.example_dot_com() # as expected assert Page.example_dot_com.key().endswith('Page.example_dot_com') # key with function-name Page.class_content() # as expected # key with function-name + class name assert Page.class_content.key().endswith('Page.class_content:Page') p = Page('http://example.com') p.content() # as expected # key with class name + function name + __ring_key__ assert p.content.key().endswith('Page.content:page=http://example.com') assert p.url_property == p.url :see: :doc:`factory` for details. Choosing backend ---------------- Let's consider using external cache storage instead of :class:`lru`. **Ring** includes common cache storage supports. `Memcached` is one of the popular cache storage. `Memcached` is not a Python Project. You must install and run it to let your python code connects there. Fortunately, because `Memcached` is very popular, it is well-packaged on most of the platforms. Check how to install it on your platform. :note: For example, ``apt install memcached`` for Debian/Ubuntu. ``yum install memcached`` for CentOS/RHEL. ``brew install memcache`` for macOS with Homebrew_. Once you installed it, do not forget to start it. In **Ring**, you can choose any compatible Memcached package. If you are new to Memcached, let's try pymemcache_ to install it easily. .. sourcecode:: shell $ pip install pymemcache Now you are ready to edit the ``get_url`` to use Memcached. .. code-block:: python import ring import requests import pymemcache.client #1 import pymemcache client = pymemcache.client.Client(('127.0.0.1', 11211)) #2 create a client # save to memcache client, expire in 60 seconds. @ring.memcache(client, expire=60) #3 lru -> memcache def get_url(url): return requests.get(url).content # default access - it is cached data = get_url('http://example.com') Try and compare what's changed from :func:`ring.lru` version. There are many more included factories for various backends. :see: :doc:`factory` about more factories and backends. :see: :doc:`extend` to create your own factory. .. _Homebrew: https://brew.sh/ .. _pymemcache: https://pypi.org/project/pymemcache/ :mod:`asyncio` support ~~~~~~~~~~~~~~~~~~~~~~ **Ring** supports :mod:`asyncio` with a few factories which also are included. They follow similar convention but requiring `await` for IO jobs. .. code-block:: python import ring @ring.lru(force_asyncio=True) # non-asyncio backends require `force_asyncio` async def f(): ... result = await f() # using `await` for __call__ cached_result = await f.get() # using `await` for get() key = f.key() # NOT using `await` for key() :note: Non-IO sub-functions doesn't require `await`. :note: the sync version factories are not compatible with :mod:`asyncio`. :see: :doc:`factory` and search for `asyncio` to find fit factories. Structured or complex data -------------------------- The modern software handles structured data rather than chunks of bytes. Because the popular cache storages only support raw bytes or string, data needs to be encoded and decoded. The `coder` parameter in Ring factories decides the kind of coding. .. code-block:: python import ring import json import pymemcache.client client = pymemcache.client.Client(('127.0.0.1', 11211)) @ring.memcache(client, expire=60, coder='json') def f(): return {'key': 'data', 'number': 42} f() # create cache data loaded = f.get() assert isinstance(loaded, dict) assert loaded == {'key': 'data', 'number': 42} raw_data = f.storage.backend.get(f.key()) assert isinstance(raw_data, bytes) # `str` for py2 assert raw_data == json.dumps({'key': 'data', 'number': 42}).encode('utf-8') :see: :doc:`coder` about more backends. :see: :doc:`extend` to create and register your own coders. Factory parameters ------------------ Ring factories share common parameters to control Ring objects' behavior. - key_prefix - coder - ignorable_keys - user_inferface - storage_interface :see: :doc:`factory` for details. Low-level access ---------------- Do you wonder how your data is encoded? Which keys are mapped to the functions? You don't need to be suffered by looking inside of **Ring**. At this time, let's use :func:`ring.dict` to look into the storage. .. code-block:: python import ring dict_storage = {} @ring.dict(dict_storage) def f(): ... key = f.key() # retrieving the key raw_data = f.storage.backend.get(key) # getting raw data from storage # look into `dict_storage` by yourself to check how it works. :see: :doc:`control` for more attributes. Bulk access ----------- Bulk access API is optionally supported. .. code-block:: python @ring.memcache(...) def f(a, b): ... # getting data for f(1, 2), f(1, 3), f(a=2, b=2) data = f.get_many((1, 2), (1, 3), {'a': 2, 'b': 2}) :see: :doc:`control` for more attributes. Further documents ----------------- :see: :doc:`why` :see: :doc:`control` :see: :doc:`ring` --- the full reference of **Ring**