Custom Cache¶
The following section outlines the custom cache capabilities of the library, providing a basic guide on best practices and a simple how-to.
The Default Cache¶
By default, coc.py uses an OrderedDict as the cache class.
However, it modifies this by adding in a max size and expiry time for objects. Upon entering the cache, all objects are given a timestamp, and if the distance between retrieval time and when the object was put in the cache is greater than a defined amount, the object is disgarded and None is returned. Similarly, if the cache exceeds the maximum size, the least recently used object is disgarded.
This is a simple, lightweight and effective method of caching. For the average user, it is more than adequete.
Modifying Max Size and Time To Live¶
One of the options for modifying the cache is to change the settings for max size and time to live, on a ‘per group’ basis. The groups are as follows:
The Default Max Size/Time To Live for each group are detailed below:
Cache Group | Max Size | Time To Live |
clan |
1024 |
3600 sec (1hour) |
war |
1024 |
1800 sec (0.5 hr) |
player |
1024 |
3600 sec (1hour) |
static |
1024 |
None (never expires) |
Where each of the groups correspond to the following cached objects:
Cache Name | Group |
Search clans |
clan |
Clan War Logs |
clan |
Clan Wars |
war |
Current Wars |
war |
War League Groups |
war |
League Wars |
war |
War Clans |
war |
War Players |
war |
Search Players |
player |
Locations |
static |
Leagues |
static |
Seasons |
static |
Events Run |
static |
These default max size and time to live can be overidden like follows:
import coc
class CustomCache(coc.Cache):
@property
def clan_config(self):
return coc.CacheConfig(128, 60) # max_size, time to live
@property
def player_config(self):
return coc.CacheConfig(256, 30) # max size = 256, time to live = 30
Where coc.CacheConfig
is a named tuple where max size and time to live can be set.
You would then pass this custom cache class into your coc.login
function.
import coc
client = coc.login('email', 'password', cache=CustomCache)
Where CustomCache
is your custom cache class.
Modifying the Default Cache Instance Type¶
Sometimes you may wish to change the type of cache instance completely. The default cache instance/class is an OrderedDict - an inbuilt dict from the collections module.
The library does, however, provide support for different classes than regular dicts. This provides the user with the opportunity to use an async compatabile cache, redis (or aioredis), through to an in-memory database (sqlite3 is one).
When customising the cache instance, a few important things should be noted:
- By default, the library tries to index the cache object with a key, ie
cache[key]
to get a value. If this fails, it will fallback to a .get(key) method of the cache, which can be a coroutine. If your cache instance does not support either operation, you must override thecache.get()
method. More can be found inCache.get()
- The same applies for
cache.set(key, value)
,cache.pop(key)
,cache.items()
,cache.values()
,cache.keys()
andcache.clear()
. All of these methods are coroutines.- If applicable, checking for maximum size and TTL of objects should be done in this class instance. The library does not handle TTL outside of the default cache classes.
A few examples:
# to use a cache with only a max size; no TTL.
from coc import MaxSizeCache, Cache, login
class CustomCache(Cache):
def create_default_cache(self, max_size, ttl):
return MaxSizeCache(max_size)
client = login('email', 'password', cache=CustomCache)
# simarly, to use a cache with only TTL; no max size:
from coc import TimeToLiveCache, Cache, login
class CustomCache(Cache):
def create_default_cache(self, max_size, ttl):
return TimeToLiveCache(ttl)
client = login('email', 'password', cache=CustomCache)
# using aioredis as cache
import aioredis
from coc import Cache, EventsClient, login
class CustomCache(Cache):
def create_default_cache(self, name, max_size, ttl):
return
def make_key(cache_type, key):
return f'{cache_type}:{key}'
async def get(cache_type, key):
new_key = self.make_key(cache_type, key)
return await self.client.redis.get(new_key)
async def set(cache_type, key, value):
new_key = self.make_key(cache_type, key)
expiry = self.get_ttl(cache_type)
await self.client.redis.set(new_key, value, expire=expiry)
async def pop(cache_type, key):
new_key = self.make_key(cache_type, key)
return await self.client.redis.lpop(new_key)
async def keys(cache_type, limit=0):
cur, keys = await self.client.redis.scan(match=cache_type, count=limit)
return keys
async def values(cache_type):
keys = await self.keys(cache_type)
return (await self.client.redis.get(k) for k in keys)
async def items(cache_type):
keys = await self.keys(cache_type)
return ((k, await self.client.redis.get(k)) for k in keys)
async def clear(self, cache_type):
await self.client.redis.flushdb()
async def get_limit(self, cache_type, limit):
keys = await self.keys(cache_type, limit=limit)
return ((k, await self.client.redis.get(k)) for k in keys
class CustomClient(EventsClient):
def __init__(self, **options):
super().__init__(**options)
self.redis = aioredis.create_redis('redis://localhost')
async def on_client_close(self):
self.redis.close()
await self.redis.wait_closed()
client = coc.login('email', 'password', client=CustomClient, cache=CustomCache)