GithubHelp home page GithubHelp logo

youknowone / ring Goto Github PK

View Code? Open in Web Editor NEW
476.0 9.0 39.0 650 KB

Python cache interface with clean API and built-in memcache & redis + asyncio support.

Home Page: http://ring-cache.readthedocs.io/en/latest/

License: Other

Python 100.00%
python asyncio cache memcache redis aiomcache aioredis python3 python2 diskcache

ring's Introduction

Ring

Join the chat at https://gitter.im/ring-cache/community

image

image

Let's concentrate on code, not on storages.

Ring shows a way to control cache in point of view of code - not about storages. Ring's decorator is convenient but also keeps fluency for general scenarios.

asyncio support for Python3.5+!

Take advantage of perfectly explicit and fully automated cache interface. Ring decorators convert your functions to cached version of them, with extra control methods.

Documentation

Full documentation with examples and references: http://ring-cache.readthedocs.io/

  • Function/method support.
  • asyncio support.
  • Django support.
  • Bulk access support.

Function cache

It is a normal smart cache flow.

But ring is different when you want to explicitly control it.

Method cache

Installation

PyPI is the recommended way.

shell

$ pip install ring

To browse versions and tarballs, visit:

https://pypi.python.org/pypi/ring/

To use memcached or redis, don't forget to install related libraries. For example: python-memcached, python3-memcached, pylibmc, redis-py, Django etc

It may require to install and run related services on your system too. Look for memcached and redis for your system.

Contributors

See contributors list on:

https://github.com/youknowone/ring/graphs/contributors

ring's People

Contributors

changgeonlee avatar daxartio avatar dhulchuk avatar dkim010 avatar dtrodrigues avatar earlbread avatar geonu avatar gitter-badger avatar jimjeon avatar jowoojun avatar machenity avatar rscarrera27 avatar tobark avatar vazrupe avatar wy-z avatar yangroro avatar youknowone avatar yunitto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ring's Issues

passing key-generate function

which usually will be useful for self not to create __ring_key__.

In special case, something like flask global context also will be processiable here

Coder registry

The name json of JSON coder is accessible only from namespace of the coder module.

[As-is] For now, the only way to register a new coder name is:

import ring.coder
ring.coder.json = SomeCoder  # to replace existing json coder to other coder
ring.coder.pickle = PickleCoder  # to register a new coder as name of `pickle`

[To-be] To improve user interface, there can be a registry to register coders.

# registration
from ring.coder import registry

registry.register('json', JsonCoder)
registry.register('pickle', PickleCoder)


# selection
@ring.func.memcache(client, coder='json')
def f():
    ....

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

json = JsonCoder in ring.coder must be removed.
https://github.com/youknowone/ring/blob/master/ring/coder.py#L19

unpack_coder in ring/func_base.py also will be simpler when the coder type is str.
registry will provide an interface like:

coder = registry.get('json')

Missing keyword 'action' argument error

import ring
import redis

cache_dict = {}

@ring.func.dict(cache_dict)
def test(a):
    return a + 3

@ring.func.dict(cache_dict)
def test2(a):
    return a +4

print(test(3))
print(test2(3))
Traceback (most recent call last):
  File "/home/spark/.pyenv/plugins/pyenv-virtualenv/ring/keyCollision.py", line 1, in <module>
    import ring
  File "/home/spark/.pyenv/plugins/pyenv-virtualenv/ring/ring/__init__.py", line 2, in <module>
    import ring.func  # noqa
  File "/home/spark/.pyenv/plugins/pyenv-virtualenv/ring/ring/func.py", line 3, in <module>
    from ring.func_sync import dict  # noqa
  File "/home/spark/.pyenv/plugins/pyenv-virtualenv/ring/ring/func_sync.py", line 6, in <module>
    from ring import func_base as fbase
  File "/home/spark/.pyenv/plugins/pyenv-virtualenv/ring/ring/func_base.py", line 199
    raise TypeError("run() missing 1 required keyword argument: 'action'") from exc
                                                                              ^
SyntaxError: invalid syntax

Action trigger like `f.run('execute')`

For now,

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


f()  # ok this is default
f.execute()  # ok this is explicit executing

# but make a wrapper

def w():
     return f()

# and now controlling f out of w is not easy

Suggestion: there must be an action trigger like run

def w():
    return f()  # ok

def w(*, action='get_or_update'):
    return f.run(action)

Allow to use hash() instead of __ring_key__ for in-memory storage

Because hash() returns diffrent values by processes, it should be used only for in-memory storages.
Still, in-memory cache is a big part of use case of ring.

Implementation note:

  • Add in-memory storage marker to storage.
  • Fix coerce() function to receive whether hash() is allowed or not.
  • The priority is:
    • Call ring_key first
    • Fallback to hash() if it is allowed
    • Warning if nothing available

ring.lru() maxsize behaves differently from functools.lru_cache() maxsize

In functools.lru_cache() you can specify maxsize=None if you want to allow the cache to grow indefinitely.

If you try to do this with ring.lru(maxsize=None), however, you will run into a TypeError on line 117 of lru_cache.py when cache_len() is compared with maxsize, since the former is an integer and you have specified None for the latter.

Proposed fix:
Change the code in lru_cache.py to skip this bounds check if maxsize is None.

Fix aiomcache_client working without pytest-lazy-fixture==0.4.2

It is found pytest-lazy-fixture is weirdly making the test running.

TvoroG/pytest-lazy-fixture#38

Let's fix aiomcache support or aiomcache_client fixture to working without this version of pytest-lazy-fixture.

Test file below must work without lazyfixture.

import asyncio
import time
import sys
import shelve

import aiomcache
import diskcache
import ring
from ring.func.lru_cache import LruCache

import pytest


@pytest.fixture()
def aiomcache_client():
    client = aiomcache.Client('127.0.0.1', 11211)
    return client, ring.func.asyncio.aiomcache


@pytest.mark.asyncio
@asyncio.coroutine
def test_aiomcache(aiomcache_client):
    client, _ = aiomcache_client

    @ring.aiomcache(client)
    @asyncio.coroutine
    def f(a):
        return 't{}'.format(a).encode()

    yield from f.delete(1)
    yield from f(1)
    yield from f.touch(1)

    r = yield from f.get_many(
        (1,),
        {'a': 2},
    )
    assert r == [b't1', None]

    with pytest.raises(AttributeError):
        yield from f.has(1)

    with pytest.raises(NotImplementedError):
        yield from f.update_many()

    with pytest.raises(NotImplementedError):
        yield from f.delete_many()

    with pytest.raises(AttributeError):
        yield from f.has_many()

    with pytest.raises(AttributeError):
        yield from f.touch_many()

Follow up aioredis api breaking change

  • Short term fix: Specify last working aioredis version in setup.py
  • Long term fix: Migrate to new aioredis code and specify least version to guarantee not to be broken

[BUG] pytest assertion error

$ python --version
Python 3.6.2

$ pytest -vv

...

=================================== FAILURES ===================================
_____________________________ test_ring_key[bytes] _____________________________

value = b'bytes'

    @pytest.mark.parametrize('value', [
        1,
        0,
        True,
        False,
        u'str',
        b'bytes',
        ['list', 'with', 'values'],
        {'dict': 'also', 'matters': '!'},
        set(['set', 'should', 'be', 'ordered']),
    ])
    def test_ring_key(value):
        # test only with real cache backends. dict doesn't help this
        @ring.func.memcache(memcache_client)
        def simple(key):
            return key
    
>       assert simple(value) == value  # cache miss
E       AssertionError: assert 'bytes' == b'bytes'
E        +  where 'bytes' = <ring.func_sync.wrapper_class.<locals>.Ring object at 0x7f8cc39a2128>(b'bytes')

tests/test_func_sync.py:137: AssertionError
===================== 1 failed, 40 passed in 1.62 seconds ======================
$ python
import ring, memcache

mc = memcache.Client(['127.0.0.1:11211'])

@ring.func.memcache(mc)
def simple(key):
    return key

value = b'bytes'
print(value, simple(value))
# b'bytes' b'bytes

Package factory

package factory generates function set of ring.func.* with different default paramaters.

  • There must be a default configuration provider not to hold every default values functions by functions.
  • A default default configuraiton will be provided as global namespace which will be same as now - @ring.* which is exposed attributes of default package factory
  • Each package factory can be a replacement of global @ring, like @dryrun.lru or @dryrun.memcache

Not sure this is package factory or environment or configuration or something else at this stage.

Implement garbage collector for dict backend

Because dict backend doesn't have any gc machansm, it can be infinitely inflated.
For now, this is warned in documentation but GC will make everyone happy even who doesn't read document.

Decision background: The most important feature of dict backend is transparency. So that user always can access to the bare key/value data of the backend. Due to this limitation, encoding or adding metadata is not a preferred way for dict backend.

Policy:

  • Do not gc unless its size hits maxsize
  • Default value of maxsize is 128. Let's follow @ring.lru interface for maxsize.
  • Once it hits the maxsize, the gc removes at least 25% of maxsize.
  • GC runs 2 strategies one by one.
    • 1st strategy
      • (psuedo) Randomly pick an element and check it is expired.
      • If expired, remove it and run 1st strategy again; otherwise increase missing counter.
      • Once the missing counter hits 4, exit from the strategy.
    • Condition checking: If gc succeeed to remove more than 25% of garbages, stop here.
    • 2nd strategy
      • Randomly pick an element and remove until the size goes less than 75% of maxsize

Expirable LRU cache

For now, @ring.lru ignores expire parameter.

In this case, expiration maybe not very correct.

A suggestion.

  • Put timestamp into 5th paramter of link.
  • For hot items, it will be expired by checking timestamp at next get.
  • For cold items, it will be smoothly expired by LRU logic.

LruCache source: https://github.com/youknowone/ring/blob/master/ring/func/lru_cache.py

  • Will it need a separated class like PersistantDict and ExpirableDict?
    • Probably not.

[WRANING] pytest it is not a function

import ring

cache_dict = {}

@ring.func.dict(cache_dict)
def test(a):
return a + 3

@ring.func.dict(cache_dict)
def test2(a):
return a +4

print(test(3)) # 6
print(test2(3)) # 6 : bug

image

encode/decode needs to be exposed to the users

Interface suggestion:

storage = {}

@ring.func.dict(storage)
def f(a):
    return a

encoded_value = f.encode('value')
decoded_value = f.decode(encoded_value)
assert encoded_value == decoded_value

raw_value = storage.get(f.key('10'))  # raw value
value = f.decode(raw_value)

assert f.get('10') == value

README.rst 의 Method Cache에서 Assertion Error?

README.rst에 있는 Method Cache의 마지막 부분에 의문점이 생겼습니다.

Method Cache

import ring
import redis

rc = redis.StrictRedis()

class User(dict):
    def __ring_key__(self):
        return self['id']

    # working for rc, no expiration
    # using json coder for non-bytes cache data
    @ring.func.redis(rc, coder='json')
    def data(self):
        return self.copy()

    # parameters are also ok!
    @ring.func.redis(rc, coder='json')
    def child(self, child_id):
        return {'user_id': self['id'], 'child_id': child_id}

user = User(id=42, name='Ring')

# create and get cache
user_data = user.data()  # cached
user['name'] = 'Ding'
# still cached
cached_data = user.data()
assert user_data == cached_data
# refresh
updated_data = user.data.update()
assert user_data != updated_data

# id is the cache key so...
user2 = User(id=42)
# still hitting the same cache
assert user_data == user2.data()

마지막 줄 assert user_data == user2.data() 에서
user_data{'Ring', 42} 이고
user2.data(){'Ding', 42} 이라서 Assertion error가 발생합니다.

에러가 안 생기려면 test_readme.py 처럼 마지막 줄의 user_data --> updated_data 로 바꿔야하지 않을까요?

[BUG] `ring.func.dict` key collision bug

cache_dict = {}

@ring.func.dict(cache_dict)
def test(a);
    return a + 3

@ring.func.dict(cache_dict)
def test2(a):
    return a +4

print(test(3)) # 6
print(test2(3)) # 6 : bug

LruCache clear bug

Hi!

Seems like there is a bug in LruCache.clear implementation.
I've created pull request with example test and proposed fix: #142 .

[bug] 'RedisConnection' object has no attribute

image
image
PickleCoder쪽은 pyenv 를 통해 python2와 python3에서 pytest는 에러가 없는데 대신에 test_common과 test_complicated_key에서 에러가 발생했는데 제가 한번도 안 건드린 곳이라서.. 이유를 잘 모르겠습니다...

pickle coder

PIckle: https://docs.python.org/3/library/pickle.html

Add PickleCoder to ring/coder.py

pickle is a binary serialization protocol for python objects.
pickle coder will allow ring cache any kind of python objects.

For example, next code will work:

import datetime
import ring

@ring.func.memcache(client, coder='pickle')
def now():
     return datetime.datetime.now()

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.