Skip to content

Commit

Permalink
update README and add custom encoders to cache decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
turalpb committed Jan 5, 2022
1 parent 911bfb1 commit faf4803
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 26 deletions.
140 changes: 132 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ cache-house is a caching tool for python working with Redis single instance and
### Installation ###

```sh
$ pip install cache-house
$ pip install cache-house or poetry add cache-house
```

*****
### ***Quick Start*** ###
*****

### Quick Start ###
cache decorator work with async and sync functions

```python
from cache_house.backends.redis_backend import RedisCache
Expand All @@ -18,38 +21,116 @@ import asyncio

RedisCache.init()

@cache()
@cache() # default expire time is 180 seconds
async def test_cache(a: int,b: int):
print("cached")
print("async cached")
return [a,b]

@cache()
def test_cache_1(a: int, b: int):
print("cached")
return [a, b]


if __name__ == "__main__":
print(test_cache_1(3,4))
print(asyncio.run(test_cache(1,2)))
```

Check stored cache key
```sh
➜ $ rdcli KEYS "*"
1) cachehouse:main:f665833ea64e4fc32653df794257ca06
1) cachehouse:main:8f65aed1010f0062a783c83eb430aca0
2) cachehouse:main:f665833ea64e4fc32653df794257ca06

```

### Setup Redis cache instance
*****
### ***Setup Redis cache instance***
*****

You can pass all [redis-py](https://github.com/redis/redis-py) arguments to RedisCache.init method and additional arguments :

```python
def RedisCache.init(
host: str = "localhost",
port: int = 6379,
encoder: Callable[..., Any] = ...,
decoder: Callable[..., Any] = ...,
namespace: str = ...,
key_prefix: str = ...,
key_builder: Callable[..., Any] = ...,
password: str = ...,
db: int = ...,
**kwargs
)
```
or you can set your own encoder and decoder functions

```python
from cache_house.backends.redis_backend import RedisCache

RedisCache.init(host="localhost",port=6379)
def custom_encoder(data):
return json.dumps(data)

def custom_decoder(data):
return json.loads(data)

RedisCache.init(encoder=custom_encoder, decoder=custom_decoder)

```

#### ***Default encoder and decoder is pickle module.***

*****
### ***Setup Redis Cluster cache instance***
*****

All manipulation with RedisCache same with a RedisClusterCache

```python

from cache_house.backends.redis_cluster_backend import RedisClusterCache
from cache_house.cache import cache

RedisClusterCache.init()

@cache()
async def test_cache(a: int,b: int):
print("cached")
return [a,b]

```

```python

def RedisClusterCache.init(
cls,
host="localhost",
port=6379,
encoder: Callable[..., Any] = pickle_encoder,
decoder: Callable[..., Any] = pickle_decoder,
startup_nodes=None,
cluster_error_retry_attempts: int = 3,
require_full_coverage: bool = True,
skip_full_coverage_check: bool = False,
reinitialize_steps: int = 10,
read_from_replicas: bool = False,
namespace: str = DEFAULT_NAMESPACE,
key_prefix: str = DEFAULT_PREFIX,
key_builder: Callable[..., Any] = key_builder,
**kwargs,
)
```


*****
### You can set expire time (seconds) , namespace and key prefix in cache decorator ###
*****

```python

@cache(expire=30, namespace="app", key_prefix="test")
@cache(expire=30, namespace="app", key_prefix="test")
async def test_cache(a: int,b: int):
print("cached")
return [a,b]
Expand All @@ -64,10 +145,53 @@ rdcli KEYS "*"
1) test:app:f665833ea64e4fc32653df794257ca06
```

*****
### ***If your function works with non-standard data types, you can pass custom encoder and decoder functions to the *cache* decorator.***
*****

```python

import asyncio
import json
from cache_house.backends.redis_backend import RedisCache
from cache_house.cache import cache

RedisCache.init()

def custom_encoder(data):
return json.dumps(data)

def custom_decoder(data):
return json.loads(data)

@cache(expire=30, encoder=custom_encoder, decoder=custom_decoder, namespace="custom")
async def test_cache(a: int, b: int):
print("async cached")
return {"a": a, "b": b}


@cache(expire=30)
def test_cache_1(a: int, b: int):
print("cached")
return [a, b]


if __name__ == "__main__":
print(asyncio.run(test_cache(1, 2)))
print(test_cache_1(3, 4))

```

Check stored cache
```sh
rdcli KEYS "*"
1) cachehouse:main:8f65aed1010f0062a783c83eb430aca0
2) cachehouse:custom:f665833ea64e4fc32653df794257ca06
```
*****
### ***All examples works fine with Redis Cluster and single Redis instance.***
*****

# Contributing #

#### Free to open issue and send PR ####
Expand Down
3 changes: 0 additions & 3 deletions cache_house/backends/redis_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,10 @@ def __init__(
log.warning("connection refused to Redis server")

def set_key(self, key, val, exp: Union[timedelta, int]):
val = self.encoder(val)
self.redis.set(key, val, ex=exp)

def get_key(self, key: str):
val = self.redis.get(key)
if val:
val = self.decoder(val)
return val

@classmethod
Expand Down
15 changes: 9 additions & 6 deletions cache_house/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def cache(
namespace: str = None,
key_prefix: str = None,
key_builder: Callable[..., Any] = None,
encoder: Callable[..., Any] = None,
decoder: Callable[..., Any] = None
) -> Callable:
"""Decorator for caching results"""

Expand All @@ -28,6 +30,8 @@ def cache(
key_generator = key_builder or cache_instance.key_builder
namespace = namespace or cache_instance.namespace
prefix = key_prefix or cache_instance.key_prefix
encoder = encoder or cache_instance.encoder
decoder = decoder or cache_instance.decoder

def cache_wrap(f: Callable[..., Any]):
@wraps(f)
Expand All @@ -47,9 +51,9 @@ async def async_wrapper(*args, **kwargs):
if cached_data:
log.info("data exist in cache")
log.info("return data from cache")
return cached_data
return decoder(cached_data)
result = await f(*args, **kwargs)
cache_instance.set_key(key, result, expire)
cache_instance.set_key(key, encoder(result), expire)
log.info("set result in cache")
return result

Expand All @@ -67,13 +71,12 @@ def wrapper(*args, **kwargs):
)
cached_data = cache_instance.get_key(key)
if cached_data:
log.info("find data in cache")
log.info("data exist in cache")
log.info("return data from cache")
return cached_data
return decoder(cached_data)
result = f(*args, **kwargs)
cache_instance.set_key(key, result, expire)
cache_instance.set_key(key, encoder(result), expire)
log.info("set result in cache")

return result

return async_wrapper if inspect.iscoroutinefunction(f) else wrapper
Expand Down
17 changes: 9 additions & 8 deletions example.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import asyncio

import json
from cache_house.backends.redis_backend import RedisCache
from cache_house.backends.redis_cluster_backend import RedisClusterCache
from cache_house.cache import cache

RedisCache.init()

def custom_encoder(data):
return json.dumps(data)

def custom_decoder(data):
return json.loads(data)

@cache(expire=30, namespace="app", key_prefix="test")
@cache(expire=30, encoder=custom_encoder, decoder=custom_decoder, namespace="custom")
async def test_cache(a: int, b: int):
print("async cached")
return [a, b]
return {"a": a, "b": b}


@cache(expire=30, key_prefix="test")
@cache(expire=30)
def test_cache_1(a: int, b: int):
print("cached")
return [a, b]


if __name__ == "__main__":
# for i in range(20):
# print(test_print(i, i+1))
# RedisCache.instance.clear_keys("fastcache:main")
print(asyncio.run(test_cache(1, 2)))
print(test_cache_1(3, 4))
2 changes: 1 addition & 1 deletion tests/test_cache_house.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def custom_key_builder():


def test_version():
assert __version__ == "0.1.3"
assert __version__ == "0.1.4"


@patch("cache_house.backends.redis_backend.Redis", FakeRedis)
Expand Down

0 comments on commit faf4803

Please sign in to comment.