forked from shanbay/sea
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request shanbay#23 from yandy/add-cache
add cache
- Loading branch information
Showing
22 changed files
with
415 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
grpcio>=1.4.0,<1.5.0 | ||
orator | ||
mysqlclient | ||
redis | ||
celery | ||
python-consul | ||
pytest | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import functools | ||
import logging | ||
|
||
from sea.extensions import AbstractExtension | ||
from . import backends | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
DEFAULT_KEY_TYPES = (str, int, float, bool) | ||
|
||
|
||
def _trans_key(v): | ||
if isinstance(v, bytes): | ||
return v.decode() | ||
if v is None or isinstance(v, DEFAULT_KEY_TYPES): | ||
return str(v) | ||
else: | ||
raise ValueError('only str, int, float, bool can be key') | ||
|
||
|
||
def default_key(f, *args, **kwargs): | ||
keys = [_trans_key(v) for v in args] | ||
keys += sorted( | ||
['{}={}'.format(k, _trans_key(v)) for k, v in kwargs.items()]) | ||
return '{}.{}.{}'.format(f.__module__, f.__name__, '.'.join(keys)) | ||
|
||
|
||
class Cache(AbstractExtension): | ||
|
||
PROTO_METHODS = ('get', 'get_many', 'set', 'set_many', 'delete', | ||
'delete_many', 'expire', 'expireat', 'clear') | ||
|
||
def __init__(self): | ||
self._backend = None | ||
|
||
def init_app(self, app): | ||
self.app = app | ||
opts = app.config.get_namespace('CACHE_') | ||
backend_cls = getattr(backends, opts.pop('backend')) | ||
# default ttl: 60 * 60 * 48 | ||
self.default_ttl = opts.pop('default_ttl', 172800) | ||
self._backend = backend_cls(**opts) | ||
|
||
def cached(self, ttl=None, cache_key=default_key, unless=None): | ||
if ttl is None: | ||
ttl = self.default_ttl | ||
|
||
def decorator(f): | ||
def make_cache_key(*args, **kwargs): | ||
if callable(cache_key): | ||
key = cache_key(f, *args, **kwargs) | ||
else: | ||
key = cache_key | ||
return '{}.{}'.format(self.app.name, key) | ||
|
||
@functools.wraps(f) | ||
def wrapper(*args, **kwargs): | ||
if callable(unless) and unless(*args, **kwargs): | ||
return f(*args, **kwargs) | ||
key = make_cache_key(*args, **kwargs) | ||
rv = self._backend.get(key) | ||
if rv is None: | ||
rv = f(*args, **kwargs) | ||
self._backend.set(key, rv, ttl=ttl) | ||
return rv | ||
|
||
wrapper.uncached = f | ||
|
||
return wrapper | ||
return decorator | ||
|
||
def __getattr__(self, name): | ||
if name in self.PROTO_METHODS: | ||
return getattr(self._backend, name) | ||
return super().__getattr__(name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import abc | ||
import pickle | ||
import time | ||
|
||
|
||
class BaseBackend(metaclass=abc.ABCMeta): | ||
|
||
@abc.abstractmethod | ||
def get(self, key): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def get_many(self, keys): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def set(self, key, value, ttl=None): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def set_many(self, mapping): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def delete(self, key): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def delete_many(self, keys): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def expire(self, key, seconds): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def expireat(self, key, timestamp): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def clear(self): | ||
raise NotImplementedError | ||
|
||
|
||
class Redis(BaseBackend): | ||
|
||
def __init__(self, *args, **kwargs): | ||
import redis | ||
self._client = redis.StrictRedis(*args, **kwargs) | ||
|
||
def get(self, key): | ||
return self._client.get(key) | ||
|
||
def get_many(self, keys): | ||
return self._client.mget(keys) | ||
|
||
def set(self, key, value, ttl=None): | ||
return self._client.set(key, value, ex=ttl) | ||
|
||
def set_many(self, mapping): | ||
return self._client.mset(mapping) | ||
|
||
def delete(self, key): | ||
return self._client.delete(key) | ||
|
||
def delete_many(self, keys): | ||
return self._client.delete(keys) | ||
|
||
def expire(self, key, seconds): | ||
return self._client.expire(key, seconds) | ||
|
||
def expireat(self, key, timestamp): | ||
return self._client.expireat(key, int(timestamp)) | ||
|
||
def clear(self): | ||
return self._client.flushdb() | ||
|
||
|
||
class Simple(BaseBackend): | ||
|
||
def __init__(self, threshold=500, default_ttl=600): | ||
self._cache = {} | ||
self.threshold = threshold | ||
self.default_ttl = default_ttl | ||
|
||
def _ttl2expire(self, ttl): | ||
if ttl is None: | ||
ttl = self.default_ttl | ||
now = int(time.time()) | ||
return now + ttl | ||
|
||
def _expired(self, ts): | ||
now = int(time.time()) | ||
return now > ts | ||
|
||
def _prune(self): | ||
toremove = [] | ||
for k, (exp, v) in self._cache.items(): | ||
if self._expired(exp): | ||
toremove.append(k) | ||
for k in toremove: | ||
self._cache.pop(k, None) | ||
return len(self._cache) | ||
|
||
def get(self, key): | ||
exp, v = self._cache.get(key, (None, None)) | ||
if exp is None: | ||
return None | ||
if self._expired(exp): | ||
self._cache.pop(key) | ||
return None | ||
return pickle.loads(v) | ||
|
||
def get_many(self, keys): | ||
return [self.get(k) for k in keys] | ||
|
||
def set(self, key, value, ttl=None): | ||
if len(self._cache) >= self.threshold \ | ||
and self._prune() >= self.threshold: | ||
return False | ||
self._cache[key] = ( | ||
self._ttl2expire(ttl), pickle.dumps( | ||
value, pickle.HIGHEST_PROTOCOL)) | ||
return True | ||
|
||
def set_many(self, mapping): | ||
for k, v in mapping.items(): | ||
self.set(k, v) | ||
return True | ||
|
||
def delete(self, key): | ||
try: | ||
self._cache.pop(key) | ||
return 1 | ||
except KeyError: | ||
pass | ||
return 0 | ||
|
||
def delete_many(self, keys): | ||
return sum([self.delete(k) for k in keys]) | ||
|
||
def expire(self, key, seconds): | ||
try: | ||
exp, v = self._cache[key] | ||
except KeyError: | ||
return 0 | ||
self._cache[key] = (self._ttl2expire(seconds), v) | ||
return 1 | ||
|
||
def expireat(self, key, timestamp): | ||
try: | ||
exp, v = self._cache[key] | ||
except KeyError: | ||
return 0 | ||
self._cache[key] = (timestamp, v) | ||
return 1 | ||
|
||
def clear(self): | ||
return self._cache.clear() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,12 @@ | ||
{% if not skip_orator -%}from . import orator as ORATOR{%- endif %} | ||
{% if not skip_celery -%}from . import celery as CELERY{%- endif %} | ||
{% if not skip_cache -%} | ||
CACHE_BACKEND = 'Redis' | ||
CACHE_HOST = 'localhost' | ||
CACHE_DB = 0 | ||
CACHE_PORT = 6379 | ||
CACHE_PASSWORD = 'abcdef' | ||
{%- endif %} | ||
|
||
TESTING = False | ||
DEBUG = True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1 @@ | ||
from configs.default import * | ||
|
||
TESTING = False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
from configs.default import * | ||
{% if not skip_orator -%}from . import orator as ORATOR{%- endif %} | ||
{% if not skip_celery -%}from . import celery as CELERY{%- endif %} | ||
|
||
TESTING = True |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.