Skip to content

Commit 218e9ea

Browse files
committed
ellar.cache test-cov 100%
1 parent 94a5c56 commit 218e9ea

13 files changed

+282
-69
lines changed

ellar/cache/backends/aio_cache.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414

1515
from ..interface import IBaseCacheBackendAsync
16-
from ..make_key_decorator import make_key_decorator
16+
from ..make_key_decorator import make_key_decorator, make_key_decorator_and_validate
1717
from ..model import BaseCacheBackend
1818

1919

@@ -53,6 +53,7 @@ class AioMemCacheBackend(AioMemCacheBackendSync, BaseCacheBackend):
5353
"""Memcached-based cache backend."""
5454

5555
pickle_protocol = pickle.HIGHEST_PROTOCOL
56+
MEMCACHE_CLIENT: t.Type = Client
5657

5758
def __init__(
5859
self,
@@ -78,7 +79,7 @@ def get_backend_timeout(self, timeout: t.Union[float, int] = None) -> int:
7879
@property
7980
def _cache_client(self) -> Client:
8081
if self._client is None:
81-
self._client = Client(**self._client_options)
82+
self._client = self.MEMCACHE_CLIENT(**self._client_options)
8283
return self._client
8384

8485
@make_key_decorator
@@ -88,7 +89,7 @@ async def get_async(self, key: str, version: str = None) -> t.Optional[t.Any]:
8889
return self._deserializer(value)
8990
return None # pragma: no cover
9091

91-
@make_key_decorator
92+
@make_key_decorator_and_validate
9293
async def set_async(
9394
self,
9495
key: str,

ellar/cache/backends/base.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,32 +53,23 @@ def clear(self) -> None:
5353

5454

5555
class BasePylibMemcachedCache(BasePylibMemcachedCacheSync):
56-
def __init__(
57-
self,
58-
server: t.List[str],
59-
library_client_type: t.Type,
60-
options: t.Dict = None,
61-
**kwargs: t.Any
62-
):
56+
MEMCACHE_CLIENT: t.Type
57+
58+
def __init__(self, servers: t.List[str], options: t.Dict = None, **kwargs: t.Any):
6359
super().__init__(**kwargs)
64-
self._servers = server
60+
self._servers = servers
6561

66-
self._cache_client_class: t.Type = library_client_type
6762
self._cache_client_init: t.Any = None
6863
self._options = options or {}
6964

70-
@property
71-
def client_servers(self) -> t.List[str]:
72-
return self._servers
73-
7465
@property
7566
def _cache_client(self) -> t.Any:
7667
"""
7768
Implement transparent thread-safe access to a memcached client.
7869
"""
7970
if self._cache_client_init is None:
80-
self._cache_client_init = self._cache_client_class(
81-
self.client_servers, **self._options
71+
self._cache_client_init = self.MEMCACHE_CLIENT(
72+
self._servers, **self._options
8273
)
8374
return self._cache_client_init
8475

ellar/cache/backends/pylib_cache.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,10 @@
1616
class PyLibMCCacheBackend(BasePylibMemcachedCache):
1717
"""An implementation of a cache binding using pylibmc"""
1818

19-
def __init__(self, server: t.List[str], options: t.Dict = None, **kwargs: t.Any):
20-
super().__init__(server, library_client_type=Client, options=options, **kwargs)
21-
22-
@property
23-
def client_servers(self) -> t.List[str]:
24-
output = []
25-
for server in self._servers:
26-
output.append(server.replace("unix:", ""))
27-
return output
19+
MEMCACHE_CLIENT = Client
20+
21+
def __init__(self, servers: t.List[str], options: t.Dict = None, **kwargs: t.Any):
22+
super().__init__(servers, options=options, **kwargs)
2823

2924
async def close_async(self, **kwargs: t.Any) -> None:
3025
# libmemcached manages its own connections. Don't call disconnect_all()

ellar/cache/backends/pymem_cache.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
class PyMemcacheCacheBackend(BasePylibMemcachedCache):
1717
"""An implementation of a cache binding using pymemcache."""
1818

19-
def __init__(self, server: t.List[str], options: t.Dict = None, **kwargs: t.Any):
19+
MEMCACHE_CLIENT = HashClient
20+
21+
def __init__(self, servers: t.List[str], options: t.Dict = None, **kwargs: t.Any):
2022

2123
_default_options = options or {}
2224

@@ -27,6 +29,4 @@ def __init__(self, server: t.List[str], options: t.Dict = None, **kwargs: t.Any)
2729
**_default_options,
2830
}
2931

30-
super().__init__(
31-
server, library_client_type=HashClient, options=_options, **kwargs
32-
)
32+
super().__init__(servers, options=_options, **kwargs)

ellar/cache/backends/redis/backend.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"To use `RedisCacheBackend`, you have to install 'redis' package e.g. `pip install redis`"
1313
) from e
1414
from ...interface import IBaseCacheBackendAsync
15-
from ...make_key_decorator import make_key_decorator
15+
from ...make_key_decorator import make_key_decorator, make_key_decorator_and_validate
1616
from ...model import BaseCacheBackend
1717
from .serializer import IRedisSerializer, RedisSerializer
1818

@@ -50,6 +50,7 @@ def touch(
5050

5151

5252
class RedisCacheBackend(RedisCacheBackendSync, BaseCacheBackend):
53+
MEMCACHE_CLIENT: t.Any = Redis
5354
"""Redis-based cache backend.
5455
5556
Redis Server Construct example::
@@ -101,7 +102,7 @@ def _get_client(self, *, write: bool = False) -> Redis:
101102
# cache client can be implemented which might require the key to select
102103
# the server, e.g. sharding.
103104
pool = self._get_connection_pool(write)
104-
return Redis(connection_pool=pool)
105+
return self.MEMCACHE_CLIENT(connection_pool=pool)
105106

106107
def get_backend_timeout(
107108
self, timeout: t.Union[float, int] = None
@@ -120,7 +121,7 @@ async def get_async(self, key: str, version: str = None) -> t.Any:
120121
return self._serializer.load(value)
121122
return None
122123

123-
@make_key_decorator
124+
@make_key_decorator_and_validate
124125
async def set_async(
125126
self,
126127
key: str,

ellar/cache/backends/redis/serializer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ class IRedisSerializer(ABC):
77
default_protocol: int = pickle.HIGHEST_PROTOCOL
88

99
@abstractmethod
10-
def load(self, data: t.Any) -> t.Any:
10+
def load(self, data: t.Any) -> t.Any: # pragma: no cover
1111
...
1212

1313
@abstractmethod
14-
def dumps(self, data: t.Any) -> t.Any:
14+
def dumps(self, data: t.Any) -> t.Any: # pragma: no cover
1515
...
1616

1717

@@ -26,7 +26,7 @@ def load(self, data: t.Any) -> t.Any:
2626
return pickle.loads(data)
2727

2828
def dumps(self, data: t.Any) -> t.Any:
29-
# Only skip pickling for integers, a int subclasses as bool should be
29+
# Only skip pickling for integers, an int subclasses as bool should be
3030
# pickled.
3131
if type(data) is int:
3232
return data

ellar/cache/make_key_decorator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ async def _wrap(
2929
version: str = None,
3030
**kwargs: t.Any
3131
) -> t.Any:
32-
_key = instance.make_key(key, version=version)
3332
if self._validate:
34-
instance.validate_key(_key)
33+
instance.validate_key(key)
34+
_key = instance.make_key(key, version=version)
3535
return await self._func(instance, _key, *args, version=version, **kwargs)
3636

3737
return _wrap
@@ -45,9 +45,9 @@ def _wrap(
4545
version: str = None,
4646
**kwargs: t.Any
4747
) -> t.Any:
48-
_key = instance.make_key(key, version=version)
4948
if self._validate:
50-
instance.validate_key(_key)
49+
instance.validate_key(key)
50+
_key = instance.make_key(key, version=version)
5151
return self._func(instance, _key, *args, version=version, **kwargs)
5252

5353
return _wrap

tests/test_cache/pylib_mock.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,13 @@ def delete(self, *args, **kwargs):
4545

4646
def disconnect_all(self, *args, **kwargs):
4747
return None
48+
49+
def flush_all(self):
50+
self._cache.clear()
51+
52+
53+
class MockSetFailureClient(MockClient):
54+
def set(self, *args, **kwargs):
55+
key, value, _time = args
56+
self._cache[key] = (value, _time)
57+
return False

tests/test_cache/test_memcache.py renamed to tests/test_cache/test_aio_memcache.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@
33
import pytest
44

55
from ellar.cache.backends.aio_cache import AioMemCacheBackend
6+
from ellar.cache.model import CacheKeyWarning
67

78
from .redis_mock import MockAsyncMemCacheClient
89

910

1011
class AioMemCacheBackendMock(AioMemCacheBackend):
11-
@property
12-
def _cache_client(self):
13-
if self._client is None:
14-
self._client = MockAsyncMemCacheClient(
15-
**self._client_options, time_lookup="exptime"
16-
)
17-
return self._client
12+
MEMCACHE_CLIENT = MockAsyncMemCacheClient
13+
14+
def __init__(self, *args, **kwargs):
15+
super().__init__(*args, **kwargs)
16+
self._client_options.update(time_lookup="exptime")
1817

1918

2019
@pytest.mark.asyncio
@@ -28,7 +27,7 @@ async def test_aio_memcache_backend() -> None:
2827
assert not value
2928

3029

31-
class TestRedisCacheBackend:
30+
class TestAioMemCacheBackend:
3231
def setup(self):
3332
self.backend = AioMemCacheBackendMock(host="localhost", port=11211, pool_size=4)
3433

@@ -54,8 +53,19 @@ def test_touch(self):
5453
sleep(0.22)
5554
assert self.backend.get("test-touch") == "1"
5655

56+
def test_invalid_key_length(self):
57+
# memcached limits key length to 250.
58+
key = ("a" * 250) + "清"
59+
expected_warning = (
60+
"Cache key will cause errors if used with memcached: "
61+
"%r (longer than %s)" % (key, self.backend.MEMCACHE_MAX_KEY_LENGTH)
62+
)
63+
with pytest.warns(CacheKeyWarning) as wa:
64+
self.backend.set(key, "value")
65+
assert str(wa.list[0].message) == str(CacheKeyWarning(expected_warning))
66+
5767

58-
class TestRedisCacheBackendAsync:
68+
class TestAioMemCacheBackendAsync:
5969
def setup(self):
6070
self.backend = AioMemCacheBackendMock(host="localhost", port=11211, pool_size=4)
6171

@@ -68,9 +78,7 @@ async def test_set_async(
6878
assert value == "1"
6979

7080
@pytest.mark.asyncio
71-
async def test_has_key_async(
72-
self,
73-
):
81+
async def test_has_key_async(self):
7482
assert not await self.backend.has_key_async("test-has-key")
7583
assert await self.backend.set_async("test-has-key", "1", 1)
7684
assert await self.backend.has_key_async("test-has-key")
@@ -84,9 +92,7 @@ async def test_delete_async(
8492
assert await self.backend.delete_async("test-delete")
8593

8694
@pytest.mark.asyncio
87-
async def test_touch_async(
88-
self,
89-
):
95+
async def test_touch_async(self):
9096
assert not await self.backend.touch_async("test-touch")
9197
assert await self.backend.set_async("test-touch", "1", 0.1)
9298
assert await self.backend.touch_async("test-touch", 30)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from time import sleep
2+
3+
from ellar.cache.backends.local_cache import LocalMemCacheBackend
4+
from ellar.cache.service import CacheService
5+
6+
7+
class TestCacheService:
8+
def setup(self):
9+
self.cache_service = CacheService(dict(default=LocalMemCacheBackend()))
10+
11+
def test_set(self):
12+
assert self.cache_service.set("test", "1", 0.1)
13+
value = self.cache_service.get("test")
14+
assert value == "1"
15+
16+
def test_has_key(self):
17+
assert not self.cache_service.has_key("test-has-key")
18+
assert self.cache_service.set("test-has-key", "1", 0.1)
19+
assert self.cache_service.has_key("test-has-key")
20+
21+
def test_delete(self):
22+
assert not self.cache_service.delete("test-delete")
23+
assert self.cache_service.set("test-delete", "1", 0.1)
24+
assert self.cache_service.delete("test-delete")
25+
26+
def test_touch(self):
27+
assert not self.cache_service.touch("test-touch")
28+
assert self.cache_service.set("test-touch", "1", 0.1)
29+
assert self.cache_service.touch("test-touch", 30)
30+
sleep(0.22)
31+
assert self.cache_service.get("test-touch") == "1"
32+
33+
34+
class TestCacheServiceAsync:
35+
def setup(self):
36+
self.cache_service = CacheService(dict(default=LocalMemCacheBackend()))
37+
38+
async def test_set_async(self, anyio_backend):
39+
assert await self.cache_service.set_async("test", "1", 0.1)
40+
value = await self.cache_service.get_async("test")
41+
assert value == "1"
42+
43+
async def test_has_key_async(self, anyio_backend):
44+
assert not await self.cache_service.has_key_async("test-has-key")
45+
assert await self.cache_service.set_async("test-has-key", "1", 0.1)
46+
assert await self.cache_service.has_key_async("test-has-key")
47+
48+
async def test_delete_async(self, anyio_backend):
49+
assert not await self.cache_service.delete_async("test-delete")
50+
assert await self.cache_service.set_async("test-delete", "1", 0.1)
51+
assert await self.cache_service.delete_async("test-delete")
52+
53+
async def test_touch_async(self, anyio_backend):
54+
assert not await self.cache_service.touch_async("test-touch")
55+
assert await self.cache_service.set_async("test-touch", "1", 0.1)
56+
assert await self.cache_service.touch_async("test-touch", 30)
57+
sleep(0.22)
58+
assert await self.cache_service.get_async("test-touch") == "1"

0 commit comments

Comments
 (0)