Skip to content

Commit df022e5

Browse files
authored
Merge pull request #71 from eadwinCode/cache_attributes_update
Cache attributes update
2 parents f597099 + 8625c11 commit df022e5

22 files changed

+707
-246
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ doc-deploy: ## Run Deploy Documentation
3838
make clean
3939
mkdocs gh-deploy --force --ignore-version
4040

41+
doc-serve: ## Launch doc local server
42+
make mkdoc serve
4143

4244
pre-commit-lint: ## Runs Requires commands during pre-commit
4345
make clean

docs/caching.md

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Memcached runs as a daemon and is allotted a specified amount of RAM. All it doe
3131
After installing Memcached itself, you’ll need to install a Memcached binding.
3232
There are several Python Memcached bindings available;
3333

34-
Ellar supports are [pylibmc](https://pypi.org/project/pylibmc/), [pymemcache](https://pypi.org/project/pymemcache/) and [aiomcache](https://github.com/aio-libs/aiomcache)
34+
Ellar supports are [pylibmc](https://pypi.org/project/pylibmc/) and [pymemcache](https://pypi.org/project/pymemcache/)
3535

3636
For an example, lets assume you have a Memcached is running on localhost (127.0.0.1) port 11211, using the `pymemcache` or `pylibmc` binding:
3737

@@ -59,18 +59,6 @@ For an example, lets assume you have a Memcached is running on localhost (127.0.
5959
'default': PyLibMCCacheBackend(servers=['127.0.0.1:11211'])
6060
}
6161
```
62-
=== "aiomcache"
63-
```python
64-
# project_name/config.py
65-
66-
from ellar.core import ConfigDefaultTypesMixin
67-
from ellar.cache.backends.aio_cache import AioMemCacheBackend
68-
69-
class DevelopmentConfig(ConfigDefaultTypesMixin):
70-
CACHES = {
71-
'default': AioMemCacheBackend(host='127.0.0.1', port=11211)
72-
}
73-
```
7462
If Memcached is available through a local Unix socket file /tmp/memcached.sock using the `pymemcache` or `pylibmc` binding:
7563

7664

@@ -333,6 +321,10 @@ The CacheService class provides methods like:
333321
- **has_key_async**_**(key: str, version: str = None, backend: str = None)**_: asynchronous version of `has_key` action
334322
- **touch**_**(key: str, timeout: t.Union[float, int] = None, version: str = None, backend: str = None)**_: sets a new expiration for a key
335323
- **touch_async**_**(key: str, timeout: t.Union[float, int] = None, version: str = None, backend: str = None)**_: asynchronous version of `touch` action
324+
- **incr**_**(key: str, delta: int = 1, version: str = None, backend: str = None)**_: increments a value for a key by delta
325+
- **incr_async**_**(key: str, delta: int = 1, version: str = None, backend: str = None)**_: asynchronous version of `incr` action
326+
- **decr**_**(key: str, delta: int = 1, version: str = None, backend: str = None)**_: decrement a value for a key by delta
327+
- **decr_async**_**(key: str, delta: int = 1, version: str = None, backend: str = None)**_: asynchronous version of `decr` action
336328

337329
!!! note
338330
If `backend=None`, `default` backend configuration is used.

ellar/cache/backends/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .serializer import ICacheSerializer, RedisSerializer
2+
3+
__all__ = ["ICacheSerializer", "RedisSerializer"]

ellar/cache/backends/_aio_cache.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# import pickle
2+
# import typing as t
3+
# from abc import ABC
4+
#
5+
# from ellar.helper.event_loop import get_or_create_eventloop
6+
#
7+
# try:
8+
# from aiomcache import Client
9+
# except ImportError as e: # pragma: no cover
10+
# raise RuntimeError(
11+
# "To use `AioMemCacheBackend`, you have to install 'aiomcache' package e.g. `pip install aiomcache`"
12+
# ) from e
13+
#
14+
# from ..interface import IBaseCacheBackendAsync
15+
# from ..make_key_decorator import make_key_decorator, make_key_decorator_and_validate
16+
# from ..model import BaseCacheBackend
17+
# from .serializer import AioCacheSerializer, ICacheSerializer
18+
#
19+
#
20+
# class _AioMemCacheBackendSync(IBaseCacheBackendAsync, ABC):
21+
# def _async_executor(self, func: t.Awaitable) -> t.Any:
22+
# return get_or_create_eventloop().run_until_complete(func)
23+
#
24+
# def get(self, key: str, version: str = None) -> t.Any:
25+
# return self._async_executor(self.get_async(key, version=version))
26+
#
27+
# def delete(self, key: str, version: str = None) -> bool:
28+
# res = self._async_executor(self.delete_async(key, version=version))
29+
# return bool(res)
30+
#
31+
# def set(
32+
# self,
33+
# key: str,
34+
# value: t.Any,
35+
# timeout: t.Union[float, int] = None,
36+
# version: str = None,
37+
# ) -> bool:
38+
# res = self._async_executor(
39+
# self.set_async(key, value, version=version, timeout=timeout)
40+
# )
41+
# return bool(res)
42+
#
43+
# def touch(
44+
# self, key: str, timeout: t.Union[float, int] = None, version: str = None
45+
# ) -> bool:
46+
# res = self._async_executor(
47+
# self.touch_async(key, version=version, timeout=timeout)
48+
# )
49+
# return bool(res)
50+
#
51+
# def incr(self, key: str, delta: int = 1, version: str = None) -> int:
52+
# res = self._async_executor(self.incr_async(key, delta=delta, version=version))
53+
# return t.cast(int, res)
54+
#
55+
# def decr(self, key: str, delta: int = 1, version: str = None) -> int:
56+
# res = self._async_executor(self.decr_async(key, delta=delta, version=version))
57+
# return t.cast(int, res)
58+
#
59+
#
60+
# class AioMemCacheBackend(_AioMemCacheBackendSync, BaseCacheBackend):
61+
# """Memcached-based cache backend."""
62+
#
63+
# pickle_protocol = pickle.HIGHEST_PROTOCOL
64+
# MEMCACHE_CLIENT: t.Type = Client
65+
#
66+
# def __init__(
67+
# self,
68+
# host: str,
69+
# port: int = 11211,
70+
# pool_size: int = 2,
71+
# pool_minsize: int = None,
72+
# serializer: ICacheSerializer = None,
73+
# **kwargs: t.Any
74+
# ) -> None:
75+
# super().__init__(**kwargs)
76+
# self._client: Client = None # type: ignore[assignment]
77+
# self._client_options = dict(
78+
# host=host, port=port, pool_size=pool_size, pool_minsize=pool_minsize
79+
# )
80+
# self._serializer = serializer or AioCacheSerializer()
81+
#
82+
# def get_backend_timeout(self, timeout: t.Union[float, int] = None) -> int:
83+
# return int(super().get_backend_timeout(timeout))
84+
#
85+
# @property
86+
# def _cache_client(self) -> Client:
87+
# if self._client is None:
88+
# self._client = self.MEMCACHE_CLIENT(**self._client_options)
89+
# return self._client
90+
#
91+
# @make_key_decorator
92+
# async def get_async(self, key: str, version: str = None) -> t.Optional[t.Any]:
93+
# value = await self._cache_client.get(key.encode("utf-8"))
94+
# if value:
95+
# return self._serializer.load(value)
96+
# return None # pragma: no cover
97+
#
98+
# @make_key_decorator_and_validate
99+
# async def set_async(
100+
# self,
101+
# key: str,
102+
# value: t.Any,
103+
# timeout: t.Union[float, int] = None,
104+
# version: str = None,
105+
# ) -> bool:
106+
# return await self._cache_client.set(
107+
# key.encode("utf-8"),
108+
# self._serializer.dumps(value),
109+
# exptime=self.get_backend_timeout(timeout),
110+
# )
111+
#
112+
# @make_key_decorator
113+
# async def delete_async(self, key: str, version: str = None) -> bool:
114+
# return await self._cache_client.delete(key.encode("utf-8"))
115+
#
116+
# @make_key_decorator
117+
# async def touch_async(
118+
# self, key: str, timeout: t.Union[float, int] = None, version: str = None
119+
# ) -> bool:
120+
# return await self._cache_client.touch(
121+
# key.encode("utf-8"), exptime=self.get_backend_timeout(timeout)
122+
# )
123+
#
124+
# @make_key_decorator
125+
# async def incr_async(self, key: str, delta: int = 1, version: str = None) -> int:
126+
# res = await self._cache_client.incr(key.encode("utf-8"), increment=delta)
127+
# return t.cast(int, res)
128+
#
129+
# @make_key_decorator
130+
# async def decr_async(self, key: str, delta: int = 1, version: str = None) -> int:
131+
# res = await self._cache_client.decr(key.encode("utf-8"), decrement=delta)
132+
# return t.cast(int, res)

ellar/cache/backends/aio_cache.py

Lines changed: 0 additions & 116 deletions
This file was deleted.

ellar/cache/backends/base.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,18 @@ def touch(
4444
result = self._cache_client.touch(key, self.get_backend_timeout(timeout))
4545
return bool(result)
4646

47+
@make_key_decorator
48+
def incr(self, key: str, delta: int = 1, version: str = None) -> int:
49+
result = self._cache_client.incr(key, abs(delta))
50+
return t.cast(int, result)
51+
52+
@make_key_decorator
53+
def decr(self, key: str, delta: int = 1, version: str = None) -> int:
54+
result = self._cache_client.decr(key, abs(delta))
55+
return t.cast(int, result)
56+
4757
def close(self, **kwargs: t.Any) -> None:
48-
# Many clients don't clean up connections properly.
49-
self._cache_client.disconnect_all()
58+
"""Many clients don't clean up connections properly."""
5059

5160
def clear(self) -> None:
5261
self._cache_client.flush_all()
@@ -111,3 +120,11 @@ async def clear_async(self) -> None:
111120
def validate_key(self, key: str) -> None:
112121
super().validate_key(key)
113122
self._memcache_key_warnings(key)
123+
124+
async def incr_async(self, key: str, delta: int = 1, version: str = None) -> int:
125+
res = await self.executor(self.incr, key, delta=delta, version=version)
126+
return t.cast(int, res)
127+
128+
async def decr_async(self, key: str, delta: int = 1, version: str = None) -> int:
129+
res = await self.executor(self.decr, key, delta=delta, version=version)
130+
return t.cast(int, res)

ellar/cache/backends/local_cache.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ def touch(
4444
)
4545
return bool(res)
4646

47+
def incr(self, key: str, delta: int = 1, version: str = None) -> int:
48+
res = self._async_executor(self.incr_async(key, delta=delta, version=version))
49+
return t.cast(int, res)
50+
51+
def decr(self, key: str, delta: int = 1, version: str = None) -> int:
52+
res = self._async_executor(self.decr_async(key, delta=delta, version=version))
53+
return t.cast(int, res)
54+
4755

4856
class LocalMemCacheBackend(_LocalMemCacheBackendSync, BaseCacheBackend):
4957
pickle_protocol = pickle.HIGHEST_PROTOCOL
@@ -116,3 +124,30 @@ async def touch_async(
116124
def has_key(self, key: str, version: str = None) -> bool:
117125
res = self._async_executor(self.has_key_async(key, version=version))
118126
return bool(res)
127+
128+
def _incr_decr_action(self, key: str, delta: int) -> int:
129+
pickled = self._cache[key]
130+
value = t.cast(int, pickle.loads(pickled))
131+
new_value = value + delta
132+
pickled = pickle.dumps(new_value, self.pickle_protocol)
133+
self._cache[key] = pickled
134+
return new_value
135+
136+
@make_key_decorator
137+
async def incr_async(self, key: str, delta: int = 1, version: str = None) -> int:
138+
async with self._lock:
139+
if self._has_expired(key):
140+
await self._delete(key)
141+
raise ValueError("Key '%s' not found" % key)
142+
return self._incr_decr_action(key, delta)
143+
144+
@make_key_decorator
145+
async def decr_async(self, key: str, delta: int = 1, version: str = None) -> int:
146+
async with self._lock:
147+
if self._has_expired(key):
148+
await self._delete(key)
149+
raise ValueError("Key '%s' not found" % key)
150+
res = self._incr_decr_action(key, delta * -1)
151+
if res < 0:
152+
return self._incr_decr_action(key, res * -1)
153+
return res

0 commit comments

Comments
 (0)