Skip to content

Commit

Permalink
Pivot design and goal of the project
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowone committed Jan 21, 2017
1 parent 1cf30e0 commit 258a214
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 124 deletions.
2 changes: 2 additions & 0 deletions ring/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

import ring.func
22 changes: 20 additions & 2 deletions ring/ring.py → ring/chain.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@

'''
from __future__ import absolute_import
import time
from collections import defaultdict
from prettyexc import PrettyException
from .key import adapt_key
from .tools import hybridmethod
class hybridmethod(object):
def __init__(self, func):
self.func = func
def __get__(self, obj, cls):
context = obj if obj is not None else cls
@wraps(self.func)
def hybrid(*args, **kw):
return self.func(context, *args, **kw)
# optional, mimic methods some more
hybrid.__func__ = hybrid.im_func = self.func
hybrid.__self__ = hybrid.im_self = context
return hybrid
class Link(object):
Expand Down Expand Up @@ -245,3 +262,4 @@ def ring(self, tag):
else:
"""method"""
raise NotImplementedError
'''
192 changes: 192 additions & 0 deletions ring/func.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
"""Collection of cache decorators"""
import functools
import time
from ring.key import CallableKey


def _bypass(x):
return x


def _factory(
context, key_prefix,
get_value, set_value, del_value, touch_value, miss_value, coder,
args_prefix_size=None, ignorable_keys=None):

if coder:
if isinstance(coder, str):
import ring.coder
loaded_coder = getattr(ring.coder, coder, None)
if loaded_coder is None:
raise TypeError(
"Argument 'coder' is an instance of 'str' but built-in coder "
"'{}' does not exist".format(coder))
coder = loaded_coder

if isinstance(coder, tuple):
encode, decode = coder
else:
encode, decode = coder.encode, coder.decode
else:
encode, decode = _bypass, _bypass

def _decorator(f):
ckey = CallableKey(f, format_prefix=key_prefix, args_prefix_size=args_prefix_size or 0, ignorable_keys=ignorable_keys or [])
if args_prefix_size is None:
if f.__code__.co_varnames and f.__code__.co_varnames[0] in ('self', 'cls'):
ckey.args_prefix_size = 1

def build_key(args, kwargs):
full_kwargs = ckey.merge_kwargs(args, kwargs)
key = ckey.build(full_kwargs)
return key

@functools.wraps(f)
def _get_or_update(*args, **kwargs):
key = build_key(args, kwargs)
value = get_value(context, key)
if value == miss_value:
result = f(*args, **kwargs)
value = encode(result)
set_value(context, key, value)
else:
result = decode(value)
return value

def _get(*args, **kwargs):
key = build_key(args, kwargs)
value = get_value(context, key)
if value == miss_value:
return miss_value
else:
return decode(value)

def _update(*args, **kwargs):
key = build_key(args, kwargs)
result = f(*args, **kwargs)
value = encode(result)
set_value(context, key, value)
return result

def _delete(*args, **kwargs):
key = build_key(args, kwargs)
del_value(context, key)

def _touch(*args, **kwargs):
key = build_key(args, kwargs)
touch_value(context, key)

_f = _get_or_update
_f.get = _get
_f.update = _update
_f.get_or_update = _get_or_update
_f.delete = _delete
if touch_value:
_f.touch = _touch

return _f
return _decorator


def dict(obj, key_prefix='', expire=None, coder=None, args_prefix_size=None, ignorable_keys=None, now=None):
miss_value = None

def get_value(obj, key):
if now is None:
_now = time.time()
else:
_now = now
try:
expired_time, value = obj[key]
except KeyError:
return miss_value
if expired_time is not None and expired_time < _now:
return miss_value
return value

def set_value(obj, key, value):
if now is None:
_now = time.time()
else:
_now = now
if expire is None:
expired_time = None
else:
expired_time = _now + expire
obj[key] = expired_time, value

def del_value(obj, key):
try:
del obj[key]
except KeyError:
pass

def touch_value(obj, key):
try:
expired_time, value = obj[key]
except KeyError:
return
if expire is None:
expired_time = None
else:
expired_time = _now + expire
obj[key] = expired_time, value

return _factory(
obj, key_prefix=key_prefix,
get_value=get_value, set_value=set_value, del_value=del_value,
touch_value=touch_value,
miss_value=miss_value, coder=coder,
args_prefix_size=args_prefix_size, ignorable_keys=ignorable_keys)


def memcache(client, key_prefix, time=0, coder=None, args_prefix_size=None, ignorable_keys=None):
miss_value = None

def get_value(client, key):
value = client.get(key)
return value

def set_value(client, key, value):
print(key, value, time)
client.set(key, value, time)

def del_value(client, key):
client.delete(key)

def touch_value(client, key):
client.touch(key, time)

return _factory(
client, key_prefix=key_prefix,
get_value=get_value, set_value=set_value, del_value=del_value,
touch_value=touch_value,
miss_value=miss_value, coder=coder,
args_prefix_size=args_prefix_size, ignorable_keys=ignorable_keys)


def redis_py(client, key_prefix, expire, coder=None, args_prefix_size=None, ignorable_keys=None):
miss_value = None

def get_value(client, key):
value = client.get(key)
return value

def set_value(client, key, value):
client.set(key, value, expire)

def del_value(client, key):
client.delete(key)

def touch_value(client, key):
client.expire(key, expire)

return _factory(
client, key_prefix=key_prefix,
get_value=get_value, set_value=set_value, del_value=del_value,
touch_value=touch_value,
miss_value=miss_value, coder=coder,
args_prefix_size=args_prefix_size, ignorable_keys=ignorable_keys)


redis = redis_py # de facto standard
94 changes: 73 additions & 21 deletions ring/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from __future__ import absolute_import

import re
from .tools import lazy_property
from ring.tools import cached_property


try:
Expand All @@ -13,49 +13,101 @@

class Key(object):

def __init__(self, key, indirect_marker='*'):
self.key = key
def __init__(self, provider, indirect_marker='*'):
self.provider = provider
self.indirect_marker = indirect_marker

def __repr__(self):
return u'<{}.{} key={}>'.format(
return u'<{}.{} provider={}>'.format(
self.__class__.__module__, self.__class__.__name__,
self.key)
self.provider)

def build(self, args):
raise NotImplementedError

@lazy_property
def partial_keys(self):
@cached_property
def ordered_provider_keys(self):
raise NotImplementedError

@cached_property
def provider_keys_set(self):
return frozenset(self.ordered_provider_keys)

def build_indirect_marker(self, args):
full_args = {key: self.indirect_marker for key in self.partial_keys}
full_args = {key: self.indirect_marker for key in self.provider_keys}
full_args.update(args)
return self.build(full_args)


class FormatKey(Key):

def build(self, args):
return self.key.format(**args)
return self.provider.format(**args)

@lazy_property
def partial_keys(self):
keys = re.findall('{([a-zA-Z_][a-zA-Z_0-9]*)}', self.key)
return frozenset(keys)
@cached_property
def ordered_provider_keys(self):
keys = re.findall('{([a-zA-Z_][a-zA-Z_0-9]*)}', self.provider)
return keys


class CallableKey(Key):

def build(self, args):
return self.key(**args)

@lazy_property
def partial_keys(self):
code = self.key.__code__
keys = code.co_varnames
return frozenset(keys)
def __init__(self, provider, indirect_marker='*', format_prefix=None, args_prefix_size=0, ignorable_keys=[]):
assert callable(provider)
super(CallableKey, self).__init__(provider, indirect_marker)
self.args_prefix_size = args_prefix_size
self.ignorable_keys = ignorable_keys
if format_prefix is None:
format_prefix = self.default_format_prefix
if callable(format_prefix):
format_prefix = format_prefix(provider)
self.format = format_prefix + self.default_format_body(self.ordered_provider_keys)

@cached_property
def ordered_provider_keys(self):
varnames = self.provider.__code__.co_varnames
keys = varnames[self.args_prefix_size:]
for key in self.ignorable_keys:
if key not in self.ignorable_keys:
raise KeyError(
"'{}' is not a varname but in ignorable_keys argument.".format(
key))
del keys[keys.index(key)]
return keys

@staticmethod
def default_format_prefix(provider):
return '{}.{}'.format(provider.__module__, provider.__name__)

@staticmethod
def default_format_body(provider_keys):
parts = []
for key in provider_keys:
parts.append(':{')
parts.append(key)
parts.append('}')
return ''.join(parts)

def merge_kwargs(self, args, kwargs):
f_code = self.provider.__code__
kwargs = kwargs.copy()
for i, arg in enumerate(args[self.args_prefix_size:]):
if i >= f_code.co_argcount:
raise TypeError(
'{} takes {} positional arguments but {} were given'.format(
f_code.co_name, f_code.co_argcount, len(args)))
varname = f_code.co_varnames[i]
if varname in kwargs:
raise TypeError(
"{}() got multiple values for argument '{}'".format(
f_code.co_name, varname))
kwargs[varname] = arg
for key in self.ignorable_keys:
del kwargs[key]
return kwargs

def build(self, full_kwargs):
return self.format.format(**full_kwargs)


def adapt_key(key):
Expand Down
7 changes: 0 additions & 7 deletions ring/storage/__init__.py

This file was deleted.

Loading

0 comments on commit 258a214

Please sign in to comment.