Ring Documentation Release 0.9.0

Jeong YunWon

Jul 31, 2021

Guidelines:

1 Quickstart 3

2 Why Ring? 5

3 Documents 7 3.1 Quickstart...... 7 3.2 Why Ring?...... 12 3.3 Factory functions...... 17 3.4 Attributes of Ring object...... 20 3.5 Save and load rich data...... 24 3.6 Extend Ring to meet your own needs...... 26 3.7 The future of Ring...... 27 3.8 Contribution...... 28 3.9 ring ...... 29

4 Indices and tables 49

Python Module Index 51

Index 53

i ii Ring Documentation, Release 0.9.0

Ring provides function-oriented cache interface for various backends. Repository: https://github.com/youknowone/ring/

Guidelines: 1 Ring Documentation, Release 0.9.0

2 Guidelines: CHAPTER 1

Quickstart

See Quickstart to learn the basic concept of Ring with examples. Using it in your code can be done in a single minute.

3 Ring Documentation, Release 0.9.0

4 Chapter 1. Quickstart CHAPTER 2

Why Ring?

Caching is a popular concept widely spread on the broad range of computer science but its interface is not well developed yet. In these days, we still experience common inconvenience. Ring is one of the solutions for humans. Its approach is close integration with the . See Why Ring? for details.

5 Ring Documentation, Release 0.9.0

6 Chapter 2. Why Ring? CHAPTER 3

Documents

3.1 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.

3.1.1 Installation

PyPI is the recommended way.

$ pip install ring

To browse versions and tarballs, visit: https://pypi.python.org/pypi/ring/ Though Ring includes support for many backends, their packages are not included in ring installation due to the following issues: 1. Ring supports many backends but users don’t use all of them. 2. Backends packages not only cost storages and time but also require some non-Python packages to be installed, which cannot be automated by pip. 3. 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 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 https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_ recently_used_(LRU) for details.

7 Ring Documentation, Release 0.9.0

3.1.2 First example

Let’s start with a simple example: function cache with bytes data. 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 functools. lru_cache() in Python standard library. The differences start here. The core feature of Ring is explicit controllers.

# 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 functools.lru_cache() see Attributes of Ring object for sub-functions details. see Why Ring? if this document doesn’t explain what Ring does.

3.1.3 method, classmethod, staticmethod, property

Ring is adaptable for any kind of methods for Python class. 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 (continues on next page)

8 Chapter 3. Documents Ring Documentation, Release 0.9.0

(continued from previous page)

@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 Factory functions for details.

3.1.4 Choosing backend

Let’s consider using external cache storage instead of lru. Ring includes common cache storage supports. 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.

$ pip install pymemcache

Now you are ready to edit the get_url to use Memcached. import ring import requests (continues on next page)

3.1. Quickstart 9 Ring Documentation, Release 0.9.0

(continued from previous page) 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 ring.lru() version. There are many more included factories for various backends. see Factory functions about more factories and backends. see Extend Ring to meet your own needs to create your own factory.

asyncio support

Ring supports asyncio with a few factories which also are included. They follow similar convention but requiring await for IO jobs.

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 asyncio. see Factory functions and search for asyncio to find fit factories.

3.1.5 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.

import ring import 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} (continues on next page)

10 Chapter 3. Documents Ring Documentation, Release 0.9.0

(continued from previous page)

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 Save and load rich data about more backends. see Extend Ring to meet your own needs to create and register your own coders.

3.1.6 Factory parameters

Ring factories share common parameters to control Ring objects’ behavior. • key_prefix • coder • ignorable_keys • user_inferface • storage_interface see Factory functions for details.

3.1.7 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 ring.dict() to look into the storage.

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 Attributes of Ring object for more attributes.

3.1.8 Bulk access

Bulk access API is optionally supported.

3.1. Quickstart 11 Ring Documentation, Release 0.9.0

@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 Attributes of Ring object for more attributes.

3.1.9 Further documents

see Why Ring? see Attributes of Ring object see ring — the full reference of Ring

3.2 Why Ring?

Caching is a popular concept widely spread on the broad range of computer science. But cache interface is not well-developed yet. Ring is one of the solutions for humans. Its approach is close integration with a programming language.

3.2.1 Common problems of cache

note Skip this section if you are familiar with cache and decorator patterns for cache in Python world.

The straightforward approach to storage

Traditionally we considered cache as a storage. In that sense, calling useful actual_function() with an argument for the cached result of cached_function() looks like next series of works:

# psuedo code for rough flow key= create_key() if storage.has(key): result= storage.get(key) else: result= cached_function() storage.(key, result) actual_function(result)

What’s the problem? We are interested in cached_function() and actual_function() instead of storage. But the code is full of storage operations.

Decorated cache function

Lots of cache libraries working with immutable functions share the similar solution. Here is a functools. lru_cache() example:

12 Chapter 3. Documents Ring Documentation, Release 0.9.0

from functools import lru_cache

@lru_cache(maxsize=32) def cached_function(): ... result= cached_function() actual_function(result)

This code is a lot more readable. Now the last 2 lines of code show what the code does. Note that this code even includes the definition of cached_function() which was not included in the prior code. If the programming world was built on immutable functions, this is perfect; But actually not. I really love functools.lru_cache() but couldn’t use it for most of the cases. In the real world, lots of functions are not pure function - but still, need to be cached. Since this also is one of the common problems, there are solutions too. Let’s see Django’s view cache which helps to reuse web page for specific seconds. from django.views.decorators.cache import cache_page

@cache_page(60 * 15) def cached_page(request): ...

It means the view is cached and the cached data is valid for 15 minutes. In this case, actual_function() is inside of the Django. The actual function will generate HTTP response based on the cached_page. It is good enough when cache expiration is not a real-time requirement.

Manual expiration

Unfortunately, websites are often real-time. Suppose it was a list of customer service articles. New articles must be shown up in short time. This is how Django handle it with The per-view cache. request= Request(...) # fake request to create key key= get_cache_key(request) cache.delete(key) # expire get_cache_key and cache are global names from Django framework to control cache. We started from a neat per-view cache decorator - but now it turns into a storage approach which we demonstrated at first section. You can control them at a consistent level with Ring. see Ring controls cache life-cycle with sub-functions section for details. see ring.django.cache_page() which exactly solved the problem.

Methods and descriptors support

This kind of convenient decorators commonly works for plain functions. Then how about class components like methods, classmethod and property? Ring supports them in expected convention with a unique form. see Methods and descriptors support section for details.

3.2. Why Ring? 13 Ring Documentation, Release 0.9.0

Fixed strategy

Sometimes we need more complicated strategy than normal. Suppose we have very heavy and critical layer. We don’t want to lose cache. It must be updated in every 60 seconds, but without losing the cached version even if it wasn’t updated - or even during it is being updated. With common solutions, we needed to drop the provided feature but to implement a new one. You can replace semantics of Ring commands and storage behaviors. see Ring comes with configurable commands and storage actions section for details.

Hidden backend

You might find another glitch. Their backends are concealed. Memory is ok. There are fewer reasons to uncover data from it. For services, the common cache backends are storages and . Working high-level APIs are good. But we need to access the storages outside of the original product, or even not a Python project. Ring has a transparent interface for backends. Moving between high-level ring.ring_base.Ring and low-level storage interfaces are straightforward and smooth. see Ring approaches backend transparent way section for details.

Data encoding

How to save non-binary data? Python supports pickle as a standard library to convert python objects to binary. Some of the storage libraries like python-memcached implicitly run pickle to support saving and loading Python objects.

classA (object): """Custom object with lots of features and data""" ...

client= memcache.Client(...)

original_data= A() client.set(key, original_data) loaded_data= client.get(key)

assert isinstance(loaded_data, A) # True assert original_data == loaded_data # mostly True

Unfortunately, pickle is not compatible out of the Python world and even some complex Python classes also generate massive pickle data for small information. For example, when you have non-Python code which accesses to the same data, pickle doesn’t fit. In Ring, data encoder is not a fixed value. Choose a preferred way to encode data by each ring rope. note To be fair, you can pass pickler parameter to memcached.Client in python-memcached to change the behavior. In Ring, you can reuse the same memcached.Client to use multiple coders. see Ring provides a configurable data-coding layer section for details.

3.2.2 Ring controls cache life-cycle with sub-functions

The basic usage is similar to functools.lru_cache() or Django per-view cache.

14 Chapter 3. Documents Ring Documentation, Release 0.9.0

import ring

@ring.lru() def cached_function(): ... result= cached_function() actual_function(result)

Extra operations are supported as below: cached_function.update() # force update cached_function.delete() # expire cached_function.execute() # this will not generate cache cached_function.get() # get value only when cache exists

Ring provides a common auto-cache approach by default but not only that. Extra controllers provide full functions for cache policy and storages. see Attributes of Ring object for details. Function parameters are also supported in an expected manner:

@ring.lru() def cached_function(a, b, ): ... cached_function(10, 20, 30) # normal call cached_function.delete(10, 20, 30) # delete call

3.2.3 Ring approaches backend transparent way

High-level interface providers like Ring cannot expose full features of the backends. Various storages have their own features by their design. Ring covers common features but does not cover others. ring.func.base.Ring objects serve data extractors instead. client= memcache.Client(...)

@ring.memcache(client) def f(a): ... cache_key=f.key(10) # cache key for 10 assert f.storage.backend is client encoded_data=f.storage.backend.get(cache_key) # get from memcache client actual_data=f.decode(encoded_data) # decode

see Attributes of Ring object for details.

3.2.4 Ring provides a configurable data-coding layer python-memcached supports pickle by default but pylibmc doesn’t. By adding coder='pickle', next code will be cached through pickle even with pylibmc. Of course for other backends too.

3.2. Why Ring? 15 Ring Documentation, Release 0.9.0

client= pylibmc.Client(...)

@ring.memcache(client, coder='pickle') def f(a): ...

note Does it look verbose? functools.partial() is your friend. Try my_cache = functools.partial(ring.memcache, client, coder='pickle'). When you need a special coder for a function, overriding encode/decode for a specific function also is possible. For example, the next code works the same as the above.

client= pylibmc.Client(...)

@ring.memcache(client) def f(a): ...

@f.encode def f_encode(value): return pickle.dumps(value)

@f.decode def f_decode(data): return pickle.loads(data)

see Save and load rich data for more information about coders.

3.2.5 Methods and descriptors support

Ring supports methods and descriptors including classmethod(), staticmethod() and property() in orthogonal interface. Any custom descriptors written in (weak) common convention also work.

classA (object):

v= None

def __ring_key__(self): '''convert self value typed 'A' to ring key component''' return v

@ring.lru() def method(self): '''method support''' ...

@ring.lru() @classmethod def cmethod(self): '''classmethod support''' ...

@ring.lru() @staticmethod def smethod(self): '''staticmethod support''' (continues on next page)

16 Chapter 3. Documents Ring Documentation, Release 0.9.0

(continued from previous page) ...

@ring.lru() @property def property(self): '''property support''' ...

3.2.6 Ring comes with configurable commands and storage actions

see ring.func.base.BaseStorage see ring.func.sync.CacheUserInterface see ring.func.asyncio.CacheUserInterface

3.3 Factory functions

In this document, you will learn: 1. About pre-defined factories included in Ring. 2. About storage backends. 3. About target functions and descriptors. 4. About Django extension. 5. About common tips.

3.3.1 Built-in factory functions and backends

Factory function means the end user interface of Ring, which usually looks like @ring.lru, @ring.dict, @ring.memcache, @ring.django, etc. These factory functions create concrete ring decorators by arguments. Technically the factory functions are not associated with each backend as bijection, but the built-in functions are mostly matching to the backends. So practically each factory function part of this document is including backend descriptions. Ring includes support for common cache storages: • ring.lru() • ring.dict() • ring.memcache() • ring.redis() • ring.shelve() • ring.disk() Which are shortcuts of concrete implementations and tools below:

ring.func.sync.lru([lru, key_prefix, . . . ]) LRU(Least-Recently-Used) cache interface. Continued on next page

3.3. Factory functions 17 Ring Documentation, Release 0.9.0

Table 1 – continued from previous page ring.func.sync.dict(obj[, key_prefix, . . . ]) Basic Python dict based cache. ring.func.sync.memcache(client[, . . . ]) Common Memcached_ interface. ring.func.sync.redis_py(client[, . . . ]) Redis_ interface. ring.func.sync.shelve(shelf[, key_prefix, Python shelve based cache. . . . ]) ring.func.sync.diskcache(obj[, key_prefix, diskcache_ interface. . . . ]) ring.func.asyncio.dict(obj[, key_prefix, . . . ]) dict interface for asyncio. ring.func.asyncio.aiomcache(client[, . . . ]) Memcached_ interface for asyncio. ring.func.asyncio.aioredis(redis[, . . . ]) Redis interface for asyncio. ring.func.asyncio. Create asyncio compatible factory from synchronous create_factory_from(. . . ) storage. ring.func.asyncio. create_asyncio_factory_proxy(. . . )

see ring.func for built-in backends.

3.3.2 Target functions and descriptors

Ring decorators can be adapted to any kind of methods and descriptors. note Ring decorator must be placed at the top-most position. The descriptors themselves are not func- tions. Ring needs to be on the top to look into them to run descriptors in the proper way. classA (object):

v= None

def __ring_key__(self): '''convert self value typed 'A' to ring key component''' return v

@ring.lru() def method(self): '''method support''' ...

@ring.lru() @classmethod def cmethod(self): '''classmethod support''' ...

@ring.lru() @staticmethod def smethod(self): '''staticmethod support''' ...

@ring.lru() @property def property(self): '''property support''' ...

18 Chapter 3. Documents Ring Documentation, Release 0.9.0

Any custom descriptors following common conventions will be supported: • The decorator has the original function as a getter attribute. For example, classmethod has __func__ at- tribute. property has fget attribute. • Any descriptor returning a callable is a method descriptor; Otherwise property. • When a descriptor is a method descriptor, it must be a static, class, object or hybrid method for any kind of parameter input. – A static method descriptor returns a non-method function or method which doesn’t take an object of the type or the class type as the first argument. – A class method descriptor returns a method function which takes the class type as the first argument. – An object method descriptor returns a method function which takes an object of the type. – A hybrid method can be a combination of one of the static, class or object method by each caller type of object or type class. The hybrid method should keep consistency for the same type of the caller. • When a descriptor is a property descriptor, it must return non-callable object. Note that normal python function returning a callable makes sense but not much about Ring. We don’t save python functions in storages. • For advanced descriptor control, see wirerope.wire.descriptor_bind().

3.3.3 Django extension

Though Django itself is not a storage, it has its own cache API. Ring has a factory function for high-level interface cache_page and the other one cache for the low-level interface.

ring.django.cache_page(timeout[, cache, . . . ]) The drop-in-replacement of Django’s per-view cache. ring.django.cache([backend, key_prefix, . . . ]) A typical ring-style cache based on Django’s low-level cache API.

see ring.django for extension.

3.3.4 Common factory parameters

see ring.func.base.factory() for generic factory definition.

3.3.5 Creating factory shortcuts

Usually, each project has common patterns of programming including common cache pattern. Repeatedly passing common arguments must be boring. Python already has an answer - use functools.partial() to create short- cuts.

import functools import ring import pymemcache.client

client= pymemcache.client.Client(('127.0.0.1', 11211))

# Verbose calling @ring.memcache(client, coder='pickle', user_interface=DoubleCacheUserInterface) def f1(): (continues on next page)

3.3. Factory functions 19 Ring Documentation, Release 0.9.0

(continued from previous page) ...

# Shortcut mem_ring= functools.partial( ring.memcache, client, coder='pickle', user_interface=DoubleCacheUserInterface)

@mem_ring() def f2(): ...

The decorators of f1 and f2 work same.

3.3.6 Custom factory

see Extend Ring to meet your own needs

3.4 Attributes of Ring object

A Ring-decorated function is a Ring object. Ring objects have common attributes which give elaborate controlling. Note that object in Ring object doesn’t mean Python object. They are a collection of Ring-interface-injected stuff which shares interface described in this document.

3.4.1 Meta controller run(function_name[, *args, **kwargs ]) Meta sub-function. It calls the given function_name named sub-function with given parameters *args and **kwargs. Parameters function_name (str) – A sub-function name except for run For example:

@ring.lru() def f(a, b): ...

f.run('execute',1,2) # run execute with argument 1 and 2 f.execute(1,2) # same

3.4.2 Building blocks

These components are defined in ring.func.base.RingRope and shared by wires. storage Cache storage where the cached data be saved. This is an instance of BaseStorage of each data. It includes backend attribute which refers actual storage backend - the first argument of the Ring factories for most of the included factories. For example:

20 Chapter 3. Documents Ring Documentation, Release 0.9.0

storage={}

@ring.dict(storage) def f(): ...

assert f.storage.backend is storage

client= memcache.Client(...)

@ring.memcache(client) def f(): ...

assert f.storage.backend is client decode(cache_data) Decode cache data to actual data. When a coder is passed as coder argument of a Ring factory, this function includes coder.decode. Note experimental Though Ring doesn’t have a concrete design of this function yet, the assumption expects:

@ring.dict({}) def f(): ...

f.set('some_value') r1=f.get() # storage.get may vary by actual storage object r2=f.decode(f.storage.get(f.key())) assert r1 == r2 encode(raw_data) Encode raw actual data to cache data. When a coder is passed as coder argument of a Ring factory, this function includes coder.encode. Note experimental Though Ring doesn’t have a concrete design of this function yet, the assumption expects 3 ways below working same.

@ring.dict({}) def f(): ...

# way #1 f.update() # way #2 result=f.execute() f.set(f.encode(result)) # way #3 # storage.set may vary by actual storage object f.storage.set(f.key(), f.encode(result))

3.4. Attributes of Ring object 21 Ring Documentation, Release 0.9.0

3.4.3 Cache behavior controller

Note that behavior controllers are not fixed as the following meaning. This section is written to describe what Ring and its users expect for each function, not to define what these functions actually do. To change behavior, inherit ring.sync.CacheUserInterface or ring.asyncio. CacheUserInterface then passes it to the user_interface parameter of Ring factories. key([*args, **kwargs ]) Create and return a cache key with given arguments. get_or_update([*args, **kwargs ]) Try to get the cached data with the given arguments; otherwise, execute the function and update cache. This is the default behavior of most of Ring objects. The behavior follows next steps: 1. Create a cache key with given parameters. 2. Try to get cached data by the key. 3. If cache data exists, return it. 4. Otherwise, execute the original function to create a result. 5. Set the result as the value of created cache key. execute([*args, **kwargs ]) Execute the original function with given arguments and return the result. This sub-function is exactly the same as calling the original function. get([*args, **kwargs ]) Try to get the cache data; Otherwise, execute the function and update cache. The behavior follows next steps: 1. Create a cache key with given parameters. 2. Try to get cached data by the key. 3. If cache data exists, return it. 4. Otherwise, return miss_value which normally is None. update([*args, **kwargs ]) Execute the function, update cache data and return the result. The behavior follows next steps: 1. Create a cache key with given parameters. 2. Execute the original function to create a result. 3. Set the result as cache data of created cache key. 4. Return the execution result. set(value[, *args, **kwargs ]) Set the given value as cache data for the given arguments. The behavior follows next steps: 1. Create a cache key with given parameters. 2. Set the value as cache data of created cache key.

22 Chapter 3. Documents Ring Documentation, Release 0.9.0

delete([*args, **kwargs ]) Delete cache data for the given arguments. The behavior follows next steps: 1. Create a cache key with given parameters. 2. Delete the value of created cache key. has([*args, **kwargs ]) Check and return existence of cache data for the given arguments. Note Unlike other sub-functions, this feature may not be supported by the backends. The behavior follows next steps: 1. Create a cache key with given parameters. 2. Check the value of created cache key exists. 3. Return the existence. touch([*args, **kwargs ]) Touch cache data of the given arguments. Touch means extending expiration time. Note Unlike other sub-functions, this feature may not be supported by the backends. The behavior follows next steps: 1. Create a cache key with given parameters. 2. Touch the value of created cache key.

3.4.4 The bulk access controller

The bulk access controller is an optional feature. The backends may or may not implements the feature. args_list is the common variable-length positional argument. It is a sequence of arguments of the original function. While args_list is a list of args, each args is typed as Union[tuple,dict]. Each of them is a complete set of positional-only formed or keyword-only formed arguments. When the args is positional-only formed, its type must be always tuple. Any other iterable types like list are not allowed. When any keyword-only argument is required, use keyword-only formed arguments. When the args is keyword-only formed, its type must be always dict. When there is a variable-length positional argument, pass the values them as a tuple of parameters with the corresponding variable-length positional parameter name. get_or_update_many(*args_list) Try to get the cached data with the given arguments list; Otherwise, execute the function and update cache. The basic idea is: 1. Try to retrieve existing data as much as possible. 2. Update missing values.

Note The details of this function may vary by the implementation. execute_many(*args_list) Many version of execute. key_many(*args_list) Many version of key.

3.4. Attributes of Ring object 23 Ring Documentation, Release 0.9.0 get_many(*args_list) Many version of get. update_many(*args_list) Many version of update. set_many(args_list, value_list) Many version of set. Note This function has a little bit different signature to other bulk-access controllers and set. has_many(*args_list) Many version of has. delete_many(*args_list) Many version of delete. touch_many(*args_list) Many version of touch.

3.4.5 Override behaviors

Each ring rope can override their own behaviors. ring.key(...) Override key composer. Parameters are the same as the original function.

>>> @ring.dict({}) >>> def f(a, b): ... return ...... >>> assert f.key(1,2) =='__main__.f:1:2' # default key >>> >>> @f.ring.key ... def f_key(a, b): ... return 'a{}b{}'.format(a, b) ... >>> assert f.key(1,2) =='a1b2' # new key ring.encode(value) Override data encode function. See Save and load rich data ring.decode(data) Override data decode function. See Save and load rich data

3.5 Save and load rich data

3.5.1 The concept of coder

Though the most basic data type in Ring is bytes which is very common among various storages, modern services handle more complicated data types.

24 Chapter 3. Documents Ring Documentation, Release 0.9.0

Ring factory has coder layer - which provides a customizable interface to encode before saving and to decode after loading. For example, let’s say our function f returns float type but we only have bytes storage. This is a demonstration without Ring. def f(): return 3.1415

# straightforward way storage={} # suppose this is bytes-only db result= f() # run function # set encoded= str(result).encode('utf-8') # encoding: to bytes storage.set('key', encoded) # get encoded= storage.get('key') decoded= float(encoded.decode('utf-8')) # decoding: to float assert result == decoded

You see encoding and decoding steps. The pair of them is called coder in Ring.

3.5.2 Pre-registered coders

Ring is distributed with a few pre-registered coders which are common in modern Python world.

ring.coder.bypass_coder Default coder. ring.coder.JsonCoder JSON Coder. ring.coder.pickle_coder Pickle coder. ring.coder.DataclassCoder

see ring.coder for the module including pre-registered coders.

3.5.3 Create a new coder

Users can register new custom coders with aliases. Related coder types: • ring.coder.Coder • ring.coder.CoderTuple Registry: • ring.coder.registry • ring.coder.Registry • ring.coder.Registry.register() For example, the float example above can be written as a coder like below: class FloatCoder(ring.coder.Coder):

def encode(self, value): return str(value).encode('utf-8')

(continues on next page)

3.5. Save and load rich data 25 Ring Documentation, Release 0.9.0

(continued from previous page) def decode(self, data): return float(data.decode('utf-8')) ring.coder.registry.register('float', FloatCoder())

Now FloatCoder is registered as float. Use it in a familiar way.

@ring.lru(coder='float') def f(): return 3.1415

note coder parameter of factories only take one of the registered names of coders and actual ring. coder.Coder objects. On the other hands, ring.coder.Registry.register() take raw materials of ring.coder.Coder or ring.coder.CoderTuple. See ring.coder. coderize() for details.

3.5.4 Override a coder

Sometimes coder is not a reusable part of the code. Do not create coders for single use. Instead of it, you can redefine encode and decode function of a ring object.

@ring.lru() def f(): return 3.1415

@f.ring.encode def f_encode(value): return str(value).encode('utf-8')

@f.ring.decode def f_decode(value): return float(data.decode('utf-8'))

3.6 Extend Ring to meet your own needs

3.6.1 Creating new coders

Users can register new custom coders with aliases. Once a coder is registered to global registry, passing its alias to coder parameter of each factory is identical to passing the coder object to coder parameter. see Save and load rich data for details

3.6.2 Creating new factory functions

To create a new factory, basic understanding of factory bases is required. Let’s see one by one with examples. General factory: ring.func.base.factory()

26 Chapter 3. Documents Ring Documentation, Release 0.9.0

Creating simple shortcuts

see Creating factory shortcuts to create shortcuts of existing factories.

New storage interface

The most important component is the storage_interface parameter, which is expected to inherit ring.func. base.BaseStorage. The abstract class defines common basic operations of storage. (TBD)

New sub-function semantics

3.7 The future of Ring

3.7.1 Flexible controller import ring classA (object):

@ring.dict({}) def f(self, a, b): ...

@f.ring.key # override key creation function def f_key(self, a, b): ... # possible __ring_key__ alternative

@f.ring.on_update # events def f_on_update(self, a, b): ...

@ring.dict({}) def g(self, a): ...

@g.ring.cascade # cascading for subsets (and supersets) def g_cascade(self): return { 'delete': self.f, }

3.7.2 Ring doctor import ring

@ring.dict({},'prefix') def f1(a): pass

@ring.dict({},'prefix') (continues on next page)

3.7. The future of Ring 27 Ring Documentation, Release 0.9.0

(continued from previous page) def f2(a): pass

@ring.dict({},'overload', overload= True) def o1(a): pass

@ring.dict({},'overload', overload=2) def o2(a): pass ring.doctor() # raise error: f1 and f2 has potential key collision

3.8 Contribution

First, install Ring in editable mode. To install tests requirements, use ‘tests’ extra.

$ pip install -e '.[tests]'

Run pytest to check the test is working.

$ pytest -vv

When all tests passed, edit the code and add new tests for the new or changed code.

3.8.1 Tips

pyenv and pyenv-virtualenv will save your time, especially when experiencing compatibility problems between ver- sions. note Can’t install ring[tests] because of compile errors? Don’t forget to install and start memcached and redis locally. Test codes are using memcached & redis to ensure ring is correctly working.

macOS

$ brew install docker-compose $ docker-compose up

Debian/Ubuntu

$ apt install docker-compose $ docker-compose up

28 Chapter 3. Documents Ring Documentation, Release 0.9.0

3.9 ring

3.9.1 ring.func — Factory functions.

Ring object factory functions are aggregated in this module. ring.func.lru(...) See ring.lru() ring.func.dict(...) See ring.dict() ring.func.memcache(...) See ring.memcache() ring.func.redis(...) See ring.redis() ring.func.shelve(...) See ring.shelve() ring.func.disk(...) See ring.disk()

3.9.2 ring.func.sync — collection of synchronous factory functions.

This module includes building blocks and storage implementations of Ring factories. ring.func.sync.lru(lru=None, key_prefix=None, expire=None, coder=None, user_interface=, storage_class=, maxsize=128, **kwargs) LRU(Least-Recently-Used) cache interface. Because the lru backend is following the basic manner of functools.lru_cache(), see also the document if you are not familiar with it.

>>> @ring.lru(maxsize=128) >>> def f(...): ......

The above is very similar to below:

>>> @functools.lru_cache(maxsize=128, typed=False) >>> def f(...): ......

Note that ring basically supports key consistency and manual operation features, unlike functools. lru_cache(). Parameters • lru (ring.func.lru_cache.LruCache) – Cache storage. If the default value None is given, a new LruCache object will be created. (Recommended) • maxsize (int) – The maximum size of the cache storage.

3.9. ring 29 Ring Documentation, Release 0.9.0

See functools.lru_cache() for LRU cache basics. See ring.func.sync.CacheUserInterface() for sub-functions. ring.func.sync.dict(obj, key_prefix=None, expire=None, coder=None, user_interface=, storage_class=None, **kwargs) Basic Python dict based cache. This backend is not designed for real products. Please carefully read the next details to check it fits your demands. • ring.lru() and functools.lru_cache() are the standard way for the most of local cache. • Expired objects will never be removed from the dict. If the function has unlimited input combinations, never use dict. • It is designed to “simulate” cache backends, not to provide an actual cache backend. If a caching function is a fast job, this backend even can drop the performance.

Parameters obj (dict) – Cache storage. Any dict compatible object. See ring.func.sync.CacheUserInterface() for sub-functions. See ring.dict() for asyncio version. ring.func.sync.memcache(client, key_prefix=None, expire=0, coder=None, user_interface=(, ), storage_class=, **kwargs) Common Memcached interface. This backend is shared interface for various memcached client libraries below: • https://pypi.org/project/python-memcached/ • https://pypi.org/project/python3-memcached/ • https://pypi.org/project/pylibmc/ • https://pypi.org/project/pymemcache/ Most of them expect Memcached client or dev package is installed on your machine. If you are new to Mem- cached, check how to install it and the python package on your platform. The expected types for input and output are always bytes for None coder, but you may use different types depending on client libraries. Ring doesn’t guarantee current/future behavior except for bytes. Note touch feature availability depends on Memcached library.

Parameters • client (object) – Memcached client object. See below for details. – pymemcache: pymemcache.client.Client

>>> client= pymemcache.client.Client(('127.0.0.1', 11211)) >>> @ring.memcache(client,...) ......

– python-memcached or python3-memcached: memcache.Client

30 Chapter 3. Documents Ring Documentation, Release 0.9.0

>>> client= memcache.Client(["127.0.0.1:11211"]) >>> @ring.memcache(client,...) ......

– pylibmc: pylibmc.Client

>>> client= pylibmc.Client(['127.0.0.1']) >>> @ring.memcache(client,...) ......

• key_refactor (object) – The default key refactor may hash the cache key when it doesn’t meet Memcached key restriction. See ring.func.sync.CacheUserInterface() for single access sub-functions. See ring.func.sync.BulkInterfaceMixin() for bulk access sub-functions. See ring.aiomcache() for asyncio version. ring.func.sync.redis_py(client, key_prefix=None, expire=None, coder=None, user_interface=(, ), storage_class=, **kwargs) Redis interface. This backend depends on redis-py. The redis package expects Redis client or dev package is installed on your machine. If you are new to Redis, check how to install Redis and the Python package on your platform. Note that redis.StrictRedis is expected, which is different from redis.Redis. Parameters client (redis.StrictRedis) – Redis client object. See redis. StrictRedis

>>> client= redis.StrictRedis() >>> @ring.redis(client,...) ......

See ring.func.sync.CacheUserInterface() for single access sub-functions. See ring.func.sync.BulkInterfaceMixin() for bulk access sub-functions. See ring.aioredis() for asyncio version. See Redis for Redis documentation. ring.func.sync.redis_py_hash(client, hash_key=None, key_prefix=None, coder=None, user_interface=(, ), stor- age_class=, **kwargs) This backend depends on redis-py. This implements HASH commands in Redis. Note that redis.StrictRedis is expected, which is different from redis.Redis. Parameters client (redis.StrictRedis) – Redis client object. See redis. StrictRedis

3.9. ring 31 Ring Documentation, Release 0.9.0

>>> client= redis.StrictRedis() >>> @ring.redis_hash(client,...) ......

See ring.func.sync.CacheUserInterface() for single access sub-functions. See ring.func.sync.BulkInterfaceMixin() for bulk access sub-functions. See Redis for Redis documentation. ring.func.sync.shelve(shelf, key_prefix=None, coder=None, user_interface=, storage_class=, **kwargs) Python shelve based cache. Parameters shelf (shelve.Shelf) – A shelf storage. See shelve to create a shelf.

>>> shelf= shelve.open('shelve') >>> @ring.shelve(shelf,...) ......

See shelve for the backend. See ring.func.sync.CacheUserInterface() for sub-functions. ring.func.sync.diskcache(obj, key_prefix=None, expire=None, coder=None, user_interface=, storage_class=, **kwargs) diskcache interface. Parameters obj (diskcache.Cache) – diskcache Cache object. See diskcache.Cache.

>>> storage= diskcache.Cache('cachedir') >>> @ring.disk(storage,...) ......

See ring.func.sync.CacheUserInterface() for sub-functions. class ring.func.sync.CacheUserInterface(ring) General cache user interface provider. See ring.func.base.BaseUserInterface for class and methods details. class ring.func.sync.BulkInterfaceMixin Bulk access interface mixin. Any corresponding storage class must be a subclass of ring.func.sync.BulkStorageMixin. delete_many(wire, pargs) Delete the storage values of the corresponding keys. See The class documentation for the parameter details. Return type None execute_many(wire, pargs) Execute and return the results of the original function. See The class documentation for the parameter details. Returns A sequence of the results of the original function. Return type Sequence of the return type of the original function

32 Chapter 3. Documents Ring Documentation, Release 0.9.0

get_many(wire, pargs) Try to get and returns the storage values. See The class documentation for the parameter details. Returns A sequence of the storage values or miss_value for the corresponding keys. When a key exists in the storage, the matching value is selected; Otherwise the miss_value of Ring object is. get_or_update_many(wire, pargs) Try to get and returns the storage values. Note The semantics of this function may vary by the implementation. See The class documentation for the parameter details. Returns A sequence of the storage values or the executed result of the original function for the corresponding keys. When a key exists in the storage, the matching value is selected; Otherwise, the result of the original function is. has_many(wire, pargs) Return whether the storage has values of the corresponding keys. See The class documentation for the parameter details. Return type Sequence[bool] set_many(wire, pargs) Set the storage values of the corresponding keys as the given values. See The class documentation for common parameter details. Parameters value_list (Iterable[Any]) – A list of the values to save in the storage. Return type None touch_many(wire, pargs) Touch the storage values of the corresponding keys. See The class documentation for the parameter details. Return type None update_many(wire, pargs) Execute the original function and set the result as the value. See The class documentation for the parameter details. Returns A sequence of the results of the original function. Return type Sequence of the return type of the original function class ring.func.sync.BulkStorageMixin

delete_many(keys) get_many(keys, miss_value) has_many(keys) set_many(keys, values, expire=Ellipsis) touch_many(keys, expire=Ellipsis) class ring.func.sync.LruStorage(ring, backend)

3.9. ring 33 Ring Documentation, Release 0.9.0

delete_value(key) Delete value for the given key. get_value(key) Get and return value for the given key. has_value(key) Check and return data existences for the given key. (optional) in_memory_storage = True set_value(key, value, expire) Set value for the given key, value and expire. touch_value(key, expire) Touch value for the given key. (optional) class ring.func.sync.ExpirableDictStorage(ring, backend)

delete_value(key) Delete value for the given key. get_value(key) Get and return value for the given key. has_value(key) Check and return data existences for the given key. (optional) in_memory_storage = True now() time() -> floating point number Return the current time in seconds since the Epoch. Fractions of a second may be present if the system clock provides them. set_value(key, value, expire) Set value for the given key, value and expire. touch_value(key, expire) Touch value for the given key. (optional) class ring.func.sync.PersistentDictStorage(ring, backend)

delete_value(key) Delete value for the given key. get_value(key) Get and return value for the given key. has_value(key) Check and return data existences for the given key. (optional) in_memory_storage = True set_value(key, value, expire) Set value for the given key, value and expire. class ring.func.sync.ShelveStorage(ring, backend)

delete_value(key) Delete value for the given key.

34 Chapter 3. Documents Ring Documentation, Release 0.9.0

set_value(key, value, expire) Set value for the given key, value and expire. class ring.func.sync.DiskCacheStorage(ring, backend)

delete_value(key) Delete value for the given key. get_value(key) Get and return value for the given key. set_value(key, value, expire) Set value for the given key, value and expire. class ring.func.sync.MemcacheStorage(ring, backend)

delete_many_values(keys) delete_value(key) Delete value for the given key. get_many_values(keys) get_value(key) Get and return value for the given key. set_many_values(keys, values, expire) set_value(key, value, expire) Set value for the given key, value and expire. touch_value(key, expire) Touch value for the given key. (optional) class ring.func.sync.RedisStorage(ring, backend)

delete_value(key) Delete value for the given key. get_many_values(keys) get_value(key) Get and return value for the given key. has_value(key) Check and return data existences for the given key. (optional) set_many_values(keys, values, expire) set_value(key, value, expire) Set value for the given key, value and expire. touch_value(key, expire) Touch value for the given key. (optional) class ring.func.lru_cache.LruCache(maxsize) Created by breaking down functools.lru_cache from CPython 3.7.0. now() time() -> floating point number

3.9. ring 35 Ring Documentation, Release 0.9.0

Return the current time in seconds since the Epoch. Fractions of a second may be present if the system clock provides them.

3.9.3 ring.func.asyncio — collection of asyncio factory functions.

This module includes building blocks and storage implementations of Ring factories for asyncio. ring.func.asyncio.aiomcache(client, key_prefix=None, expire=0, coder=None, user_interface=(, ), stor- age_class=, key_encoding=’utf-8’, **kwargs) Memcached interface for asyncio. Expected client package is aiomcache. aiomcache expect Memcached client or dev package is installed on your machine. If you are new to Memcached, check how to install it and the python package on your platform. Parameters • client (aiomcache.Client) – aiomcache client object. See aiomcache. Client().

>>> client= aiomcache.Client('127.0.0.1', 11211)

• key_refactor (object) – The default key refactor may hash the cache key when it doesn’t meet Memcached key restriction. See ring.func.asyncio.CacheUserInterface() for single access sub-functions. See ring.func.asyncio.BulkInterfaceMixin() for bulk access sub-functions. See ring.func.sync.memcache() for non-asyncio version. ring.func.asyncio.aioredis(redis, key_prefix=None, expire=None, coder=None, user_interface=(, ), stor- age_class=, **kwargs) Redis interface for asyncio. Expected client package is aioredis. aioredis expect Redis client or dev package is installed on your machine. If you are new to Memcached, check how to install it and the python package on your platform. Note that aioredis>=2.0.0 only supported. Parameters client (Union[aioredis.Redis,Callable[..aioredis.Redis]]) – aioredis interface object. See aioredis.create_redis() or aioredis. create_redis_pool(). For convenience, a coroutine returning one of these objects also is proper. It means next 2 examples working almost same:

>>> redis= await aioredis.create_redis(('127.0.0.1', 6379)) >>> @ring.aioredis(redis) >>> async def by_object(...): >>> ...

36 Chapter 3. Documents Ring Documentation, Release 0.9.0

>>> redis_coroutine= aioredis.create_redis(('127.0.0.1', 6379)) >>> @ring.aioredis(redis_coroutine) >>> async def by_coroutine(...): >>> ...

Though they have slightly different behavior for .storage.backend:

>>> assert by_object.storage.backend is by_object

>>> assert by_coroutine.storage.backend is not redis_coroutine >>> assert isinstance( ... await by_coroutine.storage.backend, aioredis.Redis)

See ring.func.asyncio.CacheUserInterface() for single access sub-functions. See ring.func.asyncio.BulkInterfaceMixin() for bulk access sub-functions. See ring.redis() for non-asyncio version. ring.func.asyncio.dict(obj, key_prefix=None, expire=None, coder=None, user_interface=, storage_class=None, **kwargs) dict interface for asyncio. See ring.func.sync.dict() for common description. ring.func.asyncio.create_asyncio_factory_proxy(factory_table, *, support_asyncio) ring.func.asyncio.convert_storage(storage_class) ring.func.asyncio.create_factory_from(sync_factory, _storage_class) Create asyncio compatible factory from synchronous storage. class ring.func.asyncio.CacheUserInterface(ring) General cache user interface provider for asyncio. See ring.func.base.BaseUserInterface for class and methods details. class ring.func.asyncio.BulkInterfaceMixin Bulk access interface mixin. Any corresponding storage class must be a subclass of ring.func.asyncio.BulkStorageMixin. delete_many(wire, pargs) Delete the storage values of the corresponding keys. See The class documentation for the parameter details. Return type None execute_many(wire, pargs) Execute and return the results of the original function. See The class documentation for the parameter details. Returns A sequence of the results of the original function. Return type Sequence of the return type of the original function get_many(wire, pargs) Try to get and returns the storage values. See The class documentation for the parameter details.

3.9. ring 37 Ring Documentation, Release 0.9.0

Returns A sequence of the storage values or miss_value for the corresponding keys. When a key exists in the storage, the matching value is selected; Otherwise the miss_value of Ring object is. get_or_update_many(wire, pargs) Try to get and returns the storage values. Note The semantics of this function may vary by the implementation. See The class documentation for the parameter details. Returns A sequence of the storage values or the executed result of the original function for the corresponding keys. When a key exists in the storage, the matching value is selected; Otherwise, the result of the original function is. has_many(wire, pargs) Return whether the storage has values of the corresponding keys. See The class documentation for the parameter details. Return type Sequence[bool] set_many(wire, pargs) Set the storage values of the corresponding keys as the given values. See The class documentation for common parameter details. Parameters value_list (Iterable[Any]) – A list of the values to save in the storage. Return type None touch_many(wire, pargs) Touch the storage values of the corresponding keys. See The class documentation for the parameter details. Return type None update_many(wire, pargs) Execute the original function and set the result as the value. See The class documentation for the parameter details. Returns A sequence of the results of the original function. Return type Sequence of the return type of the original function class ring.func.asyncio.BulkStorageMixin

delete_many(keys) Delete values for the given keys. get_many(keys, miss_value) Get and return values for the given key. has_many(keys) Check and return existences for the given keys. set_many(keys, values, expire=Ellipsis) Set values for the given keys. touch_many(keys, expire=Ellipsis) Touch values for the given keys.

38 Chapter 3. Documents Ring Documentation, Release 0.9.0 class ring.func.asyncio.AiomcacheStorage(ring, backend) Storage implementation for aiomcache.Client. delete_many_values(keys) delete_value(key) Delete value for the given key. get_many_values(keys) get_value(key) Get and return value for the given key. set_many_values(keys, values, expire) set_value(key, value, expire) Set value for the given key, value and expire. touch_value(key, expire) Touch value for the given key. (optional)

3.9.4 ring.func.base — The building blocks of ring.func.*. ring.func.base.factory(storage_backend, key_prefix, expire_default, coder, miss_value, user_interface, storage_class, default_action=Ellipsis, coder_registry=Ellipsis, on_manufactured=None, wire_slots=Ellipsis, ignorable_keys=None, key_encoding=None, key_refactor=None) Create a decorator which turns a function into ring wire or wire bridge. This is the base factory function that every internal Ring factories are based on. See the source code of ring. func.sync or ring.func.asyncio for actual usages and sample code. Parameters • storage_backend (Any) – Actual storage backend instance. • key_prefix (Optional[str]) – Specify storage key prefix when a str value is given; Otherwise a key prefix is automatically suggested based on the function signature. Note that the suggested key prefix is not compatible between Python 2 and 3. • expire_default (Optional[float]) – Set the duration of seconds to expire the data when a number is given; Otherwise the default behavior depends on the backend. Note that the storage may or may not support expiration or persistent saving. • coder (Union[str,ring.coder.Coder]) – A registered coder name or a coder object. See ring.coder — Auto encode/decode layer for details. • miss_value (Any) – The default value when storage misses a given key. • user_interface (type) – Injective implementation of sub-functions. • storage_class (type) – Injective implementation of storage. • default_action (Optional[str]) – The default action name for __call__ of the wire object. When the given value is None, there is no __call__ method for ring wire. • coder_registry (Optional[ring.coder.Registry]) – The coder registry to load the given coder. The default value is ring.coder.registry when None is given. • on_manufactured (Optional[Callable[[type(Wire),type(Ring)], None]]) – The callback function when a new ring wire or wire bridge is created.

3.9. ring 39 Ring Documentation, Release 0.9.0

• ignorable_keys (List[str]) – (experimental) Parameter names not to use to create storage key. • key_encoding (Optional[str]) – The storage key is usually str typed. When this parameter is given, a key is encoded into bytes using the given encoding. • key_refactor (Optional[Callable[[str],str]]) – Roughly, key = key_refactor(key) will be run when key_refactor is not None; Otherwise it is omitted. Returns The factory decorator to create new ring wire or wire bridge. Return type (Callable)->ring.wire.RopeCore exception ring.func.base.NotFound Internal exception for a cache miss. Ring internally use this exception to indicate a cache miss. Though common convention of the cache miss is None for many implementations, ring.coder allows None to be proper cached value in Ring. class ring.func.base.BaseUserInterface(ring) The base user interface class for single item access. An instance of interface class is bound to a Ring object. They have the one-to-one relationship. Sub- class this class to create a new user interface. This is an abstract class. The methods marked as abc. abstractmethod() are mandatory; Otherwise not. This class provides sub-functions of ring wires. When trying to access any sub-function of a ring wire which doesn’t exist, it looks up the composed user interface object and creates actual sub-function into the ring wire. The parameter transform_args in ring.func.base.interface_attrs() defines the figure of method parameters. For the BaseUserInterface, some methods’ transform_args are ring.func.base. transform_args_prefix() which removes specified count of prefix. Other mix-ins or subclasses may have different transform_args. The first parameter of interface method always is a RingWire object. The other parameters are composed by transform_args. See ring.func.base.transform_args_prefix() for the specific argument transforma- tion rule for each methods. The parameters below describe common methods’ parameters. Parameters • wire (ring.func.base.RingWire) – The corresponding ring wire object. • pargs (ArgPack) – Refined arguments pack. delete(wire, pargs) Delete the storage value of the corresponding key. See ring.func.base.BaseUserInterface.key() for the key. See The class documentation for the parameter details. Return type None execute(wire, pargs) Execute and return the result of the original function. See The class documentation for the parameter details. Returns The result of the original function.

40 Chapter 3. Documents Ring Documentation, Release 0.9.0

get(wire, pargs) Try to get and return the storage value of the corresponding key. See The class documentation for the parameter details. See ring.func.base.BaseUserInterface.key() for the key. Returns The storage value for the corresponding key if it exists; Otherwise, the miss_value of Ring object. get_or_update(wire, pargs) Try to get and return the storage value; Otherwise, update and so. See ring.func.base.BaseUserInterface.get() for get. See ring.func.base.BaseUserInterface.update() for update. See The class documentation for the parameter details. Returns The storage value for the corresponding key if it exists; Otherwise result of the original function. has(wire, pargs) Return whether the storage has a value of the corresponding key. This is an optional function. See ring.func.base.BaseUserInterface.key() for the key. See The class documentation for the parameter details. Returns Whether the storage has a value of the corresponding key. Return type bool key(wire, pargs) Create and return the composed key for storage. See The class documentation for the parameter details. Returns The composed key with given arguments. Return type str set(wire, value, pargs) Set the storage value of the corresponding key as the given value. See ring.func.base.BaseUserInterface.key() for the key. See The class documentation for common parameter details. Parameters value (Any) – The value to save in the storage. Return type None touch(wire, pargs) Touch the storage value of the corresponding key. This is an optional function. Note Touch means resetting the expiration. See ring.func.base.BaseUserInterface.key() for the key. See The class documentation for the parameter details. Return type bool

3.9. ring 41 Ring Documentation, Release 0.9.0

update(wire, pargs) Execute the original function and set the result as the value. This action is comprehensible as a concatenation of ring.func.base.BaseUserInterface. execute() and ring.func.base.BaseUserInterface.set(). See ring.func.base.BaseUserInterface.key() for the key. See ring.func.base.BaseUserInterface.execute() for the execution. See The class documentation for the parameter details. Returns The result of the original function. class ring.func.base.BaseStorage(ring, backend) Base storage interface. To add a new storage interface, regard to use ring.func.base.CommonMixinStorage and a subclass of ring.func.base.StorageMixin. When subclassing this interface, remember get and set methods must include coder works. The methods marked as abc.abstractmethod() are mandatory; Otherwise not. backend delete(key) Delete data by given key. get(key) Get actual data by given key. has(key) Check data exists for given key. rope set(key, value, expire=Ellipsis) Set actual data by given key, value and expire. touch(key, expire=Ellipsis) Touch data by given key. class ring.func.base.CommonMixinStorage(ring, backend) General storage root for StorageMixin. delete(key) Delete data by given key. get(key) Get actual data by given key. has(key) Check data exists for given key. set(key, value, expire=Ellipsis) Set actual data by given key, value and expire. touch(key, expire=Ellipsis) Touch data by given key. class ring.func.base.StorageMixin Abstract storage mixin class. Subclass this class to create a new storage mixin. The methods marked as abc.abstractmethod() are mandatory; Otherwise not.

42 Chapter 3. Documents Ring Documentation, Release 0.9.0

delete_value(key) Delete value for the given key. get_value(key) Get and return value for the given key. has_value(key) Check and return data existences for the given key. (optional) set_value(key, value, expire) Set value for the given key, value and expire. touch_value(key, expire) Touch value for the given key. (optional) class ring.func.base.AbstractBulkUserInterfaceMixin Bulk access interface mixin. Every method in this mixin is optional. The methods have each corresponding function in ring.func.base. BaseUserInterface. The parameters below describe common methods’ parameters. Parameters • wire (ring.func.base.RingWire) – The corresponding ring wire object. • args_list (Iterable[Union[tuple,dict]]) – A sequence of arguments of the original function. While args_list is a list of args, each args (Union[tuple,dict]) is a complete set of positional-only formed or keyword-only formed arguments. When the args (tuple) is positional-only formed, its type must be always tuple. Any other iterable types like list are not allowed. When any keyword-only argument is required, use keyword- only formed arguments. When the args (dict) is keyword-only formed, its type must be always dict. When there is a variable-length positional argument, pass the values them as a tuple of parameters with the corresponding variable-length positional parameter name. The restriction gives the simple and consistent interface for multiple dispatching. Note that it only describes the methods which don’t have transform_args attribute.

3.9.5 ring.coder — Auto encode/decode layer

Coder is a configurable layer that provides ways to encode raw data and decode stored cache data. class ring.coder.Coder Abstract coder interface. See coderize() to create a Coder-compatible object in an easy way. See CoderTuple to create a Coder- compatible object with functions. decode() Abstract decode function. Children must implement this function. encode() Abstract encode function. Children must implement this function. ring.coder.CoderTuple Coder-compatible tuple with encode and decode functions alias of ring.coder.Coder class ring.coder.DataclassCoder

3.9. ring 43 Ring Documentation, Release 0.9.0

static decode(binary) Deserialize json encoded dictionary to dataclass object static encode(data) Serialize dataclass object to json encoded dictionary class ring.coder.JsonCoder JSON Coder. When ujson package is installed, ujson is automatically selected; Otherwise, json will be used. static decode(binary) Decode UTF-8 bytes to JSON string and load it to object static encode(data) Dump data to JSON string and encode it to UTF-8 bytes class ring.coder.Registry Coder registry. See ring.coder.registry() for default registry instance. get(coder_name) Get the registered coder for corresponding coder_name. This method is internally called when coder parameter is passed to ring object factory. register(coder_name, raw_coder) Register raw_coder as a new coder with alias coder_name. Coder can be one of next types: •A Coder subclass. •A CoderTuple object. • A tuple of encode and decode functions. • An object which has encode and decode methods.

Parameters • coder_name (str) – A new coder name to register. • raw_coder (object) – A new coder object. ring.coder.bypass_coder = (, ) Default coder. encode and decode functions bypass the given parameter. ring.coder.pickle_coder = (, ) Pickle coder. encode is pickle.dumps() and decode is pickle.loads(). cpickle will be automatically loaded for CPython2. ring.coder.registry = The default coder registry with pre-registered coders. Built-in coders are registered by default. See ring.coder.Registry for the class definition.

44 Chapter 3. Documents Ring Documentation, Release 0.9.0

3.9.6 ring.django — Django support ring.django.cache(backend=, key_prefix=None, expire=None, coder=None, user_interface=, storage_class=) A typical ring-style cache based on Django’s low-level cache API. Parameters backend (Union[str,object]) – Django’s cache config key for django. core.cache.caches or Django cache object. See Django’s cache framework: Setting up the cache to configure django cache. See Django’s cache framework: The low-level cache API for the backend. ring.django.cache_page(timeout, cache=None, key_prefix=None, user_interface=, storage_class=) The drop-in-replacement of Django’s per-view cache. Use this decorator instead of django.views.decorators.cache.cache_page(). The decorated view function itself is compatible. Ring decorated function additionally have ring-styled sub-functions. In the common cases, delete and update are helpful. Parameters • timeout (float) – Same as timeout of Django’s cache_page. • cache (Optional[str]) – Same as cache of Django’s cache_page. • key_prefix (str) – Same as key_prefix of Django’s cache_page. Here is an example of delete sub-function.

@ring.django.cache_page(timeout=60) def article_list(request): articles=... return HttpResponse( template.render({'articles': articles}, request))

def article_post(request): article=... # create a new article article_list.delete((request,'article_list')) # DELETE! return ...

Compare to how Django originally invalidate it.

def article_post_django(request): articles=...

from django.core.cache import cache from django.utils.cache import get_cache_key fake_request= HttpRequest() # a fake request fake_request.__dict__.update(request.__dict__) # not mandatory by env fake_request.method='GET' fake_request.path= reverse('article_list') key= get_cache_key(request) cache.delete(key)

return ...

3.9. ring 45 Ring Documentation, Release 0.9.0

Note that the first parameter of every sub-function originally is a django.request.HttpRequest object but a tuple here. The second item of the tuple provides a hint for the request path of article_list. Because Django expects the cache key varies by request path, it is required to find the corresponding cache key. See Django’s cache framework: The per-view cache See django.views.decorators.cache.cache_page(). class ring.django.LowLevelCacheStorage(ring, backend) Storage implementation for django.core.cache.caches. class ring.django.CachePageUserInterface(ring) Django per-view cache interface. Note This interface doesn’t require any storage backend. The interface imitates the behavior of django.views.decorators.cache.cache_page(). The code is mostly parts of fragmented django.utils.decorators.make_middleware_decorator() ex- cept for key, delete and has.

3.9.7 ring - Top-level interfaces for end users.

Common ring decorators are aliased in this level as shortcuts. ring.lru(...) Proxy to select synchronous or asyncio versions of Ring factory. See ring.func.sync.lru() for synchronous version. Note asyncio version is based on synchronous version. It is composed using ring.func. asyncio.convert_storage(). ring.dict(...) Proxy to select synchronous or asyncio versions of Ring factory. See ring.func.sync.dict() for synchronous version. Note asyncio version is based on synchronous version. It is composed using ring.func. asyncio.convert_storage(). ring.memcache(...) Proxy to select synchronous or asyncio versions of Ring factory. See ring.func.sync.memcache() for synchronous version. See ring.func.asyncio.aiomcache() for asyncio version. ring.redis(...) Proxy to select synchronous or asyncio versions of Ring factory. See ring.func.sync.redis_py() for synchronous version. See ring.func.asyncio.aioredis() for asyncio version. ring.shelve(...) Proxy to select synchronous or asyncio versions of Ring factory. See ring.func.sync.shelve() for synchronous version. Note asyncio version is based on synchronous version. It is composed using ring.func. asyncio.create_asyncio_factory_proxy(). Warning The backend storage of this factory doesn’t support asyncio. To enable asyncio support at your own risk, pass force_asyncio=True as a keyword parameter. parameter.

46 Chapter 3. Documents Ring Documentation, Release 0.9.0

ring.disk(...) Proxy to select synchronous or asyncio versions of Ring factory. See ring.func.sync.diskcache() for synchronous version. Note asyncio version is based on synchronous version. It is composed using ring.func. asyncio.create_asyncio_factory_proxy(). Warning The backend storage of this factory doesn’t support asyncio. To enable asyncio support at your own risk, pass force_asyncio=True as a keyword parameter. parameter. ring.aiomcache(client, key_prefix=None, expire=0, coder=None, user_interface=(, ), storage_class=, key_encoding=’utf-8’, **kwargs) Memcached interface for asyncio. Expected client package is aiomcache. aiomcache expect Memcached client or dev package is installed on your machine. If you are new to Memcached, check how to install it and the python package on your platform. Parameters • client (aiomcache.Client) – aiomcache client object. See aiomcache. Client().

>>> client= aiomcache.Client('127.0.0.1', 11211)

• key_refactor (object) – The default key refactor may hash the cache key when it doesn’t meet Memcached key restriction. See ring.func.asyncio.CacheUserInterface() for single access sub-functions. See ring.func.asyncio.BulkInterfaceMixin() for bulk access sub-functions. See ring.func.sync.memcache() for non-asyncio version. ring.aioredis(redis, key_prefix=None, expire=None, coder=None, user_interface=(, ), storage_class=, **kwargs) Redis interface for asyncio. Expected client package is aioredis. aioredis expect Redis client or dev package is installed on your machine. If you are new to Memcached, check how to install it and the python package on your platform. Note that aioredis>=2.0.0 only supported. Parameters client (Union[aioredis.Redis,Callable[..aioredis.Redis]]) – aioredis interface object. See aioredis.create_redis() or aioredis. create_redis_pool(). For convenience, a coroutine returning one of these objects also is proper. It means next 2 examples working almost same:

>>> redis= await aioredis.create_redis(('127.0.0.1', 6379)) >>> @ring.aioredis(redis) >>> async def by_object(...): >>> ...

3.9. ring 47 Ring Documentation, Release 0.9.0

>>> redis_coroutine= aioredis.create_redis(('127.0.0.1', 6379)) >>> @ring.aioredis(redis_coroutine) >>> async def by_coroutine(...): >>> ...

Though they have slightly different behavior for .storage.backend:

>>> assert by_object.storage.backend is by_object

>>> assert by_coroutine.storage.backend is not redis_coroutine >>> assert isinstance( ... await by_coroutine.storage.backend, aioredis.Redis)

See ring.func.asyncio.CacheUserInterface() for single access sub-functions. See ring.func.asyncio.BulkInterfaceMixin() for bulk access sub-functions. See ring.redis() for non-asyncio version.

48 Chapter 3. Documents CHAPTER 4

Indices and tables

• genindex • modindex • search

49 Ring Documentation, Release 0.9.0

50 Chapter 4. Indices and tables Python Module Index

ring, 46 ring.coder, 43 ring.django, 44 ring.func, 29 ring.func.asyncio, 36 ring.func.base, 39 ring.func.sync, 29

51 Ring Documentation, Release 0.9.0

52 Python Module Index Index

A decode() (built-in function), 21 AbstractBulkUserInterfaceMixin (class in decode() (ring.coder.Coder method), 43 ring.func.base), 43 decode() (ring.coder.DataclassCoder static method), aiomcache() (in module ring), 47 43 aiomcache() (in module ring.func.asyncio), 36 decode() (ring.coder.JsonCoder static method), 44 AiomcacheStorage (class in ring.func.asyncio), 38 delete() (built-in function), 22 aioredis() (in module ring), 47 delete() (ring.func.base.BaseStorage method), 42 aioredis() (in module ring.func.asyncio), 36 delete() (ring.func.base.BaseUserInterface method), 40 B delete() (ring.func.base.CommonMixinStorage backend (ring.func.base.BaseStorage attribute), 42 method), 42 BaseStorage (class in ring.func.base), 42 delete_many() (built-in function), 24 BaseUserInterface (class in ring.func.base), 40 delete_many() (ring.func.asyncio.BulkInterfaceMixin BulkInterfaceMixin (class in ring.func.asyncio), method), 37 37 delete_many() (ring.func.asyncio.BulkStorageMixin BulkInterfaceMixin (class in ring.func.sync), 32 method), 38 BulkStorageMixin (class in ring.func.asyncio), 38 delete_many() (ring.func.sync.BulkInterfaceMixin BulkStorageMixin (class in ring.func.sync), 33 method), 32 bypass_coder (in module ring.coder), 44 delete_many() (ring.func.sync.BulkStorageMixin method), 33 C delete_many_values() cache() (in module ring.django), 45 (ring.func.asyncio.AiomcacheStorage method), cache_page() (in module ring.django), 45 39 CachePageUserInterface (class in ring.django), delete_many_values() 46 (ring.func.sync.MemcacheStorage method), 35 CacheUserInterface (class in ring.func.asyncio), delete_value() (ring.func.asyncio.AiomcacheStorage 37 method), 39 CacheUserInterface (class in ring.func.sync), 32 delete_value() (ring.func.base.StorageMixin Coder (class in ring.coder), 43 method), 42 CoderTuple (in module ring.coder), 43 delete_value() (ring.func.sync.DiskCacheStorage CommonMixinStorage (class in ring.func.base), 42 method), 35 convert_storage() (in module ring.func.asyncio), delete_value() (ring.func.sync.ExpirableDictStorage 37 method), 34 create_asyncio_factory_proxy() (in module delete_value() (ring.func.sync.LruStorage ring.func.asyncio), 37 method), 33 create_factory_from() (in module delete_value() (ring.func.sync.MemcacheStorage ring.func.asyncio), 37 method), 35 delete_value() (ring.func.sync.PersistentDictStorage method), 34 DataclassCoder (class in ring.coder), 43 delete_value() (ring.func.sync.RedisStorage

53 Ring Documentation, Release 0.9.0

method), 35 get_many_values() (ring.func.sync.RedisStorage delete_value() (ring.func.sync.ShelveStorage method), 35 method), 34 get_or_update() (built-in function), 22 dict() (in module ring), 46 get_or_update() (ring.func.base.BaseUserInterface dict() (in module ring.func), 29 method), 41 dict() (in module ring.func.asyncio), 37 get_or_update_many() (built-in function), 23 dict() (in module ring.func.sync), 30 get_or_update_many() disk() (in module ring), 46 (ring.func.asyncio.BulkInterfaceMixin disk() (in module ring.func), 29 method), 38 diskcache() (in module ring.func.sync), 32 get_or_update_many() DiskCacheStorage (class in ring.func.sync), 35 (ring.func.sync.BulkInterfaceMixin method), 33 E get_value() (ring.func.asyncio.AiomcacheStorage encode() (built-in function), 21 method), 39 encode() (ring.coder.Coder method), 43 get_value() (ring.func.base.StorageMixin method), encode() (ring.coder.DataclassCoder static method), 43 44 get_value() (ring.func.sync.DiskCacheStorage encode() (ring.coder.JsonCoder static method), 44 method), 35 execute() (built-in function), 22 get_value() (ring.func.sync.ExpirableDictStorage execute() (ring.func.base.BaseUserInterface method), 34 method), 40 get_value() (ring.func.sync.LruStorage method), 34 execute_many() (built-in function), 23 get_value() (ring.func.sync.MemcacheStorage execute_many() (ring.func.asyncio.BulkInterfaceMixin method), 35 method), 37 get_value() (ring.func.sync.PersistentDictStorage execute_many() (ring.func.sync.BulkInterfaceMixin method), 34 method), 32 get_value() (ring.func.sync.RedisStorage method), ExpirableDictStorage (class in ring.func.sync), 35 34 H F has() (built-in function), 23 factory() (in module ring.func.base), 39 has() (ring.func.base.BaseStorage method), 42 has() (ring.func.base.BaseUserInterface method), 41 G has() (ring.func.base.CommonMixinStorage method), get() (built-in function), 22 42 has_many() get() (ring.coder.Registry method), 44 (built-in function), 24 has_many() get() (ring.func.base.BaseStorage method), 42 (ring.func.asyncio.BulkInterfaceMixin get() (ring.func.base.BaseUserInterface method), 40 method), 38 has_many() get() (ring.func.base.CommonMixinStorage method), (ring.func.asyncio.BulkStorageMixin 42 method), 38 has_many() get_many() (built-in function), 23 (ring.func.sync.BulkInterfaceMixin get_many() (ring.func.asyncio.BulkInterfaceMixin method), 33 has_many() method), 37 (ring.func.sync.BulkStorageMixin get_many() (ring.func.asyncio.BulkStorageMixin method), 33 has_value() method), 38 (ring.func.base.StorageMixin method), get_many() (ring.func.sync.BulkInterfaceMixin 43 has_value() method), 32 (ring.func.sync.ExpirableDictStorage get_many() (ring.func.sync.BulkStorageMixin method), 34 has_value() method), 33 (ring.func.sync.LruStorage method), 34 get_many_values() has_value() (ring.func.sync.PersistentDictStorage (ring.func.asyncio.AiomcacheStorage method), method), 34 has_value() 39 (ring.func.sync.RedisStorage method), get_many_values() 35 (ring.func.sync.MemcacheStorage method), 35

54 Index Ring Documentation, Release 0.9.0

I ring.coder (module), 43 in_memory_storage ring.decode() (built-in function), 24 (ring.func.sync.ExpirableDictStorage at- ring.django (module), 44 tribute), 34 ring.encode() (built-in function), 24 in_memory_storage (ring.func.sync.LruStorage at- ring.func (module), 29 tribute), 34 ring.func.asyncio (module), 36 in_memory_storage ring.func.base (module), 39 (ring.func.sync.PersistentDictStorage at- ring.func.sync (module), 29 tribute), 34 ring.key() (built-in function), 24 rope (ring.func.base.BaseStorage attribute), 42 J run() (built-in function), 20 JsonCoder (class in ring.coder), 44 S K set() (built-in function), 22 set() key() (built-in function), 22 (ring.func.base.BaseStorage method), 42 set() key() (ring.func.base.BaseUserInterface method), 41 (ring.func.base.BaseUserInterface method), 41 set() key_many() (built-in function), 23 (ring.func.base.CommonMixinStorage method), 42 L set_many() (built-in function), 24 set_many() (ring.func.asyncio.BulkInterfaceMixin LowLevelCacheStorage (class in ring.django), 46 method), 38 lru() (in module ring), 46 set_many() (ring.func.asyncio.BulkStorageMixin lru() (in module ring.func), 29 method), 38 lru() (in module ring.func.sync), 29 set_many() (ring.func.sync.BulkInterfaceMixin LruCache (class in ring.func.lru_cache), 35 method), 33 LruStorage (class in ring.func.sync), 33 set_many() (ring.func.sync.BulkStorageMixin method), 33 M set_many_values() memcache() (in module ring), 46 (ring.func.asyncio.AiomcacheStorage method), memcache() (in module ring.func), 29 39 memcache() (in module ring.func.sync), 30 set_many_values() MemcacheStorage (class in ring.func.sync), 35 (ring.func.sync.MemcacheStorage method), 35 set_many_values() (ring.func.sync.RedisStorage N method), 35 NotFound, 40 set_value() (ring.func.asyncio.AiomcacheStorage now() (ring.func.lru_cache.LruCache method), 35 method), 39 now() (ring.func.sync.ExpirableDictStorage method), set_value() (ring.func.base.StorageMixin method), 34 43 set_value() (ring.func.sync.DiskCacheStorage P method), 35 PersistentDictStorage (class in ring.func.sync), set_value() (ring.func.sync.ExpirableDictStorage 34 method), 34 pickle_coder (in module ring.coder), 44 set_value() (ring.func.sync.LruStorage method), 34 set_value() (ring.func.sync.MemcacheStorage R method), 35 redis() (in module ring), 46 set_value() (ring.func.sync.PersistentDictStorage redis() (in module ring.func), 29 method), 34 redis_py() (in module ring.func.sync), 31 set_value() (ring.func.sync.RedisStorage method), redis_py_hash() (in module ring.func.sync), 31 35 RedisStorage (class in ring.func.sync), 35 set_value() (ring.func.sync.ShelveStorage method), register() (ring.coder.Registry method), 44 34 Registry (class in ring.coder), 44 shelve() (in module ring), 46 registry (in module ring.coder), 44 shelve() (in module ring.func), 29 ring (module), 46 shelve() (in module ring.func.sync), 32

Index 55 Ring Documentation, Release 0.9.0

ShelveStorage (class in ring.func.sync), 34 storage (built-in variable), 20 StorageMixin (class in ring.func.base), 42 T touch() (built-in function), 23 touch() (ring.func.base.BaseStorage method), 42 touch() (ring.func.base.BaseUserInterface method), 41 touch() (ring.func.base.CommonMixinStorage method), 42 touch_many() (built-in function), 24 touch_many() (ring.func.asyncio.BulkInterfaceMixin method), 38 touch_many() (ring.func.asyncio.BulkStorageMixin method), 38 touch_many() (ring.func.sync.BulkInterfaceMixin method), 33 touch_many() (ring.func.sync.BulkStorageMixin method), 33 touch_value() (ring.func.asyncio.AiomcacheStorage method), 39 touch_value() (ring.func.base.StorageMixin method), 43 touch_value() (ring.func.sync.ExpirableDictStorage method), 34 touch_value() (ring.func.sync.LruStorage method), 34 touch_value() (ring.func.sync.MemcacheStorage method), 35 touch_value() (ring.func.sync.RedisStorage method), 35 U update() (built-in function), 22 update() (ring.func.base.BaseUserInterface method), 41 update_many() (built-in function), 24 update_many() (ring.func.asyncio.BulkInterfaceMixin method), 38 update_many() (ring.func.sync.BulkInterfaceMixin method), 33

56 Index