Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ doc-deploy: ## Run Deploy Documentation
make clean
mkdocs gh-deploy --force --ignore-version

doc-serve: ## Launch doc local server
make mkdoc serve

pre-commit-lint: ## Runs Requires commands during pre-commit
make clean
Expand Down
18 changes: 5 additions & 13 deletions docs/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Memcached runs as a daemon and is allotted a specified amount of RAM. All it doe
After installing Memcached itself, you’ll need to install a Memcached binding.
There are several Python Memcached bindings available;

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

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:

Expand Down Expand Up @@ -59,18 +59,6 @@ For an example, lets assume you have a Memcached is running on localhost (127.0.
'default': PyLibMCCacheBackend(servers=['127.0.0.1:11211'])
}
```
=== "aiomcache"
```python
# project_name/config.py

from ellar.core import ConfigDefaultTypesMixin
from ellar.cache.backends.aio_cache import AioMemCacheBackend

class DevelopmentConfig(ConfigDefaultTypesMixin):
CACHES = {
'default': AioMemCacheBackend(host='127.0.0.1', port=11211)
}
```
If Memcached is available through a local Unix socket file /tmp/memcached.sock using the `pymemcache` or `pylibmc` binding:


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

!!! note
If `backend=None`, `default` backend configuration is used.
Expand Down
3 changes: 3 additions & 0 deletions ellar/cache/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .serializer import ICacheSerializer, RedisSerializer

__all__ = ["ICacheSerializer", "RedisSerializer"]
132 changes: 132 additions & 0 deletions ellar/cache/backends/_aio_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# import pickle
# import typing as t
# from abc import ABC
#
# from ellar.helper.event_loop import get_or_create_eventloop
#
# try:
# from aiomcache import Client
# except ImportError as e: # pragma: no cover
# raise RuntimeError(
# "To use `AioMemCacheBackend`, you have to install 'aiomcache' package e.g. `pip install aiomcache`"
# ) from e
#
# from ..interface import IBaseCacheBackendAsync
# from ..make_key_decorator import make_key_decorator, make_key_decorator_and_validate
# from ..model import BaseCacheBackend
# from .serializer import AioCacheSerializer, ICacheSerializer
#
#
# class _AioMemCacheBackendSync(IBaseCacheBackendAsync, ABC):
# def _async_executor(self, func: t.Awaitable) -> t.Any:
# return get_or_create_eventloop().run_until_complete(func)
#
# def get(self, key: str, version: str = None) -> t.Any:
# return self._async_executor(self.get_async(key, version=version))
#
# def delete(self, key: str, version: str = None) -> bool:
# res = self._async_executor(self.delete_async(key, version=version))
# return bool(res)
#
# def set(
# self,
# key: str,
# value: t.Any,
# timeout: t.Union[float, int] = None,
# version: str = None,
# ) -> bool:
# res = self._async_executor(
# self.set_async(key, value, version=version, timeout=timeout)
# )
# return bool(res)
#
# def touch(
# self, key: str, timeout: t.Union[float, int] = None, version: str = None
# ) -> bool:
# res = self._async_executor(
# self.touch_async(key, version=version, timeout=timeout)
# )
# return bool(res)
#
# def incr(self, key: str, delta: int = 1, version: str = None) -> int:
# res = self._async_executor(self.incr_async(key, delta=delta, version=version))
# return t.cast(int, res)
#
# def decr(self, key: str, delta: int = 1, version: str = None) -> int:
# res = self._async_executor(self.decr_async(key, delta=delta, version=version))
# return t.cast(int, res)
#
#
# class AioMemCacheBackend(_AioMemCacheBackendSync, BaseCacheBackend):
# """Memcached-based cache backend."""
#
# pickle_protocol = pickle.HIGHEST_PROTOCOL
# MEMCACHE_CLIENT: t.Type = Client
#
# def __init__(
# self,
# host: str,
# port: int = 11211,
# pool_size: int = 2,
# pool_minsize: int = None,
# serializer: ICacheSerializer = None,
# **kwargs: t.Any
# ) -> None:
# super().__init__(**kwargs)
# self._client: Client = None # type: ignore[assignment]
# self._client_options = dict(
# host=host, port=port, pool_size=pool_size, pool_minsize=pool_minsize
# )
# self._serializer = serializer or AioCacheSerializer()
#
# def get_backend_timeout(self, timeout: t.Union[float, int] = None) -> int:
# return int(super().get_backend_timeout(timeout))
#
# @property
# def _cache_client(self) -> Client:
# if self._client is None:
# self._client = self.MEMCACHE_CLIENT(**self._client_options)
# return self._client
#
# @make_key_decorator
# async def get_async(self, key: str, version: str = None) -> t.Optional[t.Any]:
# value = await self._cache_client.get(key.encode("utf-8"))
# if value:
# return self._serializer.load(value)
# return None # pragma: no cover
#
# @make_key_decorator_and_validate
# async def set_async(
# self,
# key: str,
# value: t.Any,
# timeout: t.Union[float, int] = None,
# version: str = None,
# ) -> bool:
# return await self._cache_client.set(
# key.encode("utf-8"),
# self._serializer.dumps(value),
# exptime=self.get_backend_timeout(timeout),
# )
#
# @make_key_decorator
# async def delete_async(self, key: str, version: str = None) -> bool:
# return await self._cache_client.delete(key.encode("utf-8"))
#
# @make_key_decorator
# async def touch_async(
# self, key: str, timeout: t.Union[float, int] = None, version: str = None
# ) -> bool:
# return await self._cache_client.touch(
# key.encode("utf-8"), exptime=self.get_backend_timeout(timeout)
# )
#
# @make_key_decorator
# async def incr_async(self, key: str, delta: int = 1, version: str = None) -> int:
# res = await self._cache_client.incr(key.encode("utf-8"), increment=delta)
# return t.cast(int, res)
#
# @make_key_decorator
# async def decr_async(self, key: str, delta: int = 1, version: str = None) -> int:
# res = await self._cache_client.decr(key.encode("utf-8"), decrement=delta)
# return t.cast(int, res)
116 changes: 0 additions & 116 deletions ellar/cache/backends/aio_cache.py

This file was deleted.

21 changes: 19 additions & 2 deletions ellar/cache/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,18 @@ def touch(
result = self._cache_client.touch(key, self.get_backend_timeout(timeout))
return bool(result)

@make_key_decorator
def incr(self, key: str, delta: int = 1, version: str = None) -> int:
result = self._cache_client.incr(key, abs(delta))
return t.cast(int, result)

@make_key_decorator
def decr(self, key: str, delta: int = 1, version: str = None) -> int:
result = self._cache_client.decr(key, abs(delta))
return t.cast(int, result)

def close(self, **kwargs: t.Any) -> None:
# Many clients don't clean up connections properly.
self._cache_client.disconnect_all()
"""Many clients don't clean up connections properly."""

def clear(self) -> None:
self._cache_client.flush_all()
Expand Down Expand Up @@ -111,3 +120,11 @@ async def clear_async(self) -> None:
def validate_key(self, key: str) -> None:
super().validate_key(key)
self._memcache_key_warnings(key)

async def incr_async(self, key: str, delta: int = 1, version: str = None) -> int:
res = await self.executor(self.incr, key, delta=delta, version=version)
return t.cast(int, res)

async def decr_async(self, key: str, delta: int = 1, version: str = None) -> int:
res = await self.executor(self.decr, key, delta=delta, version=version)
return t.cast(int, res)
35 changes: 35 additions & 0 deletions ellar/cache/backends/local_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ def touch(
)
return bool(res)

def incr(self, key: str, delta: int = 1, version: str = None) -> int:
res = self._async_executor(self.incr_async(key, delta=delta, version=version))
return t.cast(int, res)

def decr(self, key: str, delta: int = 1, version: str = None) -> int:
res = self._async_executor(self.decr_async(key, delta=delta, version=version))
return t.cast(int, res)


class LocalMemCacheBackend(_LocalMemCacheBackendSync, BaseCacheBackend):
pickle_protocol = pickle.HIGHEST_PROTOCOL
Expand Down Expand Up @@ -116,3 +124,30 @@ async def touch_async(
def has_key(self, key: str, version: str = None) -> bool:
res = self._async_executor(self.has_key_async(key, version=version))
return bool(res)

def _incr_decr_action(self, key: str, delta: int) -> int:
pickled = self._cache[key]
value = t.cast(int, pickle.loads(pickled))
new_value = value + delta
pickled = pickle.dumps(new_value, self.pickle_protocol)
self._cache[key] = pickled
return new_value

@make_key_decorator
async def incr_async(self, key: str, delta: int = 1, version: str = None) -> int:
async with self._lock:
if self._has_expired(key):
await self._delete(key)
raise ValueError("Key '%s' not found" % key)
return self._incr_decr_action(key, delta)

@make_key_decorator
async def decr_async(self, key: str, delta: int = 1, version: str = None) -> int:
async with self._lock:
if self._has_expired(key):
await self._delete(key)
raise ValueError("Key '%s' not found" % key)
res = self._incr_decr_action(key, delta * -1)
if res < 0:
return self._incr_decr_action(key, res * -1)
return res
Loading