"""
:mod:`zsl.utils.cache_helper`
-----------------------------
.. moduleauthor:: Martin Babka <babka@atteq.com>
"""
import abc
import json
import logging
from zsl import inject
from zsl.cache.id_helper import (IdHelper, app_model_decoder_fn, app_model_encoder_fn, create_key_object_prefix,
model_key_generator)
from zsl.db.model.app_model_json_encoder import AppModelJSONEncoder
from zsl.task.job_context import JobContext
[docs]
class CacheDecorator:
@inject(id_helper=IdHelper)
def __init__(self, id_helper):
self._id_helper = id_helper
logging.debug("Initializing CacheDecorator.")
[docs]
def decorate(self, key_params, timeout, fn):
self._key_params = key_params
self._fn = fn
self._timeout = timeout
return self.get_wrapped_fn()
[docs]
@staticmethod
def is_caching(*args):
jc = JobContext.get_current_context()
if 'cache' not in jc.job.data:
cs = True # TODO: Default
else:
cs = bool(jc.job.data['cache'])
logging.debug("Cache status: '%s'.", cs)
return cs
[docs]
@abc.abstractmethod
def get_wrapped_fn(self):
pass
[docs]
def get_data_key(self, *args):
prefix = create_key_object_prefix(args[0])
key = create_key_for_data(prefix, args[1], self._key_params)
return key
[docs]
@staticmethod
def get_decoder():
return app_model_decoder_fn
[docs]
@staticmethod
def get_encoder():
return app_model_encoder_fn
[docs]
class CacheOutputDecorator(CacheDecorator):
[docs]
def get_wrapped_fn(self):
def wrapped_fn(*args):
if not self.is_caching(*args):
return self._fn(*args)
key = self.get_data_key(*args)
logging.debug("Initializing CacheOutputDecorator - key %s.", key)
if self._id_helper.check_key(key):
logging.debug("Retrieved from cache.")
return self._id_helper.get_key(key)
else:
ret_val = self._fn(*args)
if not isinstance(ret_val, (str, bytes)):
raise Exception("Can not cache non-string value. Is the serialization, json_output, already done?")
self._id_helper.set_key(key, ret_val, self._timeout)
logging.debug("Newly fetched into the cache.")
return ret_val
return wrapped_fn
[docs]
def cache_output(key_params, timeout='default'):
def decorator_fn(fn):
return CacheOutputDecorator().decorate(key_params, timeout, fn)
return decorator_fn
[docs]
class CacheModelDecorator(CacheDecorator):
[docs]
def get_wrapped_fn(self):
def wrapped_fn(*args):
if not self.is_caching(*args):
return self._fn(*args)
key = self.get_data_key(*args)
logging.debug("Initializing CacheModelDecorator - key %s.", key)
if self._id_helper.check_key(key):
model_key = self._id_helper.get_key(key)
logging.debug("Retrieved from cache %s.", model_key)
if self._id_helper.check_key(model_key):
return self.get_decoder()(model_key, self._id_helper.get_key(model_key))
model = self._fn(*args)
if model is None:
return model
encoded_model = json.dumps(model, cls=AppModelJSONEncoder)
model_key = model_key_generator(model)
self._id_helper.set_key(key, model_key, self._timeout)
self._id_helper.set_key(model_key, encoded_model, self._timeout)
logging.debug("Newly fetched into the cache.")
return model
return wrapped_fn
[docs]
def cache_model(key_params, timeout='default'):
"""
Caching decorator for app models in task.perform
"""
def decorator_fn(fn):
return CacheModelDecorator().decorate(key_params, timeout, fn)
return decorator_fn
[docs]
class CachePageDecorator(CacheDecorator):
[docs]
def get_wrapped_fn(self):
def wrapped_fn(*args):
if not self.is_caching(*args):
return self._fn(*args)
page_key = self.get_data_key(*args)
logging.debug("Initializing CachePageDecorator - key %s.", page_key)
if self._id_helper.check_page(page_key):
logging.debug("Retrieved from cache %s.", page_key)
return self._id_helper.gather_page(page_key, self.get_decoder())
page = self._fn(*args)
self._id_helper.fill_page(page_key, page, self._timeout, self.get_encoder())
logging.debug("Newly fetched into the cache.")
return page
return wrapped_fn
[docs]
def cache_page(key_params, timeout='default'):
"""
Cache a page (slice) of a list of AppModels
"""
def decorator_fn(fn):
d = CachePageDecorator()
return d.decorate(key_params, timeout, fn)
return decorator_fn
# alias
cache_list = cache_page
[docs]
def cache_filtered_page(filter_param='filter', pagination_param='pagination', sorter_param='sorter', timeout='default'):
def decorator_fn(fn):
d = CachePageDecorator()
return d.decorate([filter_param, sorter_param, pagination_param], timeout, fn)
return decorator_fn
[docs]
def create_key_for_data(prefix, data, key_params):
"""
From ``data`` params in task create corresponding key with help of ``key_params`` (defined in decorator)
"""
d = data.payload
values = []
for k in key_params:
if k in d and type(d[k]) is list:
values.append("{0}:{1}".format(k, " -".join(d[k])))
else:
value = d[k] if k in d else ''
values.append("{0}:{1}".format(k, value))
return "{0}-{1}".format(prefix, "-".join(values))