diff --git a/django_cache_mock/backends/memcached.py b/django_cache_mock/backends/memcached.py index 3dc1dff..f956818 100644 --- a/django_cache_mock/backends/memcached.py +++ b/django_cache_mock/backends/memcached.py @@ -1,13 +1,17 @@ +import logging from functools import cached_property -import mockcache -from django.core.cache.backends.memcached import BaseMemcachedCache +from django.core.cache.backends.memcached import BaseMemcachedCache, PyMemcacheCache + +logger = logging.getLogger(__name__) class MockcacheCache(BaseMemcachedCache): _dbs = {} def __init__(self, server, params): + import mockcache + super().__init__( server, params, @@ -30,3 +34,20 @@ def _cache(self): client = super()._cache client.dictionary = self._dbs.setdefault(self.location, {}) return client + + +class PyMemcacheMockMemcacheCache(PyMemcacheCache): + _dbs = {} + + def __init__(self, server, params): + from pymemcache.test.utils import MockMemcacheClient + + super().__init__(server, params) + self._class = MockMemcacheClient + self.location = server + + @cached_property + def _cache(self): + client = super()._cache + client._contents = self._dbs.setdefault(self.location, {}) + return client diff --git a/django_cache_mock/backends/redis.py b/django_cache_mock/backends/redis.py index d24e0fe..648c378 100644 --- a/django_cache_mock/backends/redis.py +++ b/django_cache_mock/backends/redis.py @@ -3,14 +3,9 @@ from django.utils.functional import SimpleLazyObject -logger = logging.getLogger(__name__) - +from django_cache_mock.exceptions import LazyLibImportError -class LazyRedisCacheImportError(Exception): - parent_exception = None - - def __init__(self, server, params): - raise self from self.parent_exception +logger = logging.getLogger(__name__) try: @@ -42,7 +37,7 @@ def _cache(self): except ImportError as _import_error: logger.debug("Django built-in redis cache not found.") - class BaseDjangoBuiltInRedisCache(LazyRedisCacheImportError): + class BaseDjangoBuiltInRedisCache(LazyLibImportError): parent_exception = _import_error @@ -83,30 +78,22 @@ def client(self): except ImportError as _import_error: logger.debug("django-redis is not installed.") - class BaseDjangoRedisRedisCache(LazyRedisCacheImportError): + class BaseDjangoRedisRedisCache(LazyLibImportError): parent_exception = _import_error -try: - import redislite - - class RedisLiteMixin: - library = redislite - client_class = redislite.StrictRedis - - def __init__(self, server, params): - self.dbfilename = server or "redislite.db" - super().__init__(server, params) - - @property - def redis_client_cls_kwargs(self): - return {"dbfilename": self.dbfilename} +class RedisLiteMixin: + def __init__(self, server, params): + import redislite -except ImportError as _import_error: - logger.debug("redislite is not installed.") + self.library = redislite + self.client_class = redislite.StrictRedis + self.dbfilename = server or "redislite.db" + super().__init__(server, params) - class RedisLiteMixin(LazyRedisCacheImportError): - parent_exception = _import_error + @property + def redis_client_cls_kwargs(self): + return {"dbfilename": self.dbfilename} try: @@ -127,7 +114,7 @@ def redis_client_cls_kwargs(self): except ImportError as _import_error: logger.debug("fakeredis is not installed.") - class FakeRedisMixin(LazyRedisCacheImportError): + class FakeRedisMixin(LazyLibImportError): parent_exception = _import_error diff --git a/django_cache_mock/exceptions.py b/django_cache_mock/exceptions.py new file mode 100644 index 0000000..a346761 --- /dev/null +++ b/django_cache_mock/exceptions.py @@ -0,0 +1,5 @@ +class LazyLibImportError(Exception): + parent_exception = None + + def __init__(self, server, params): + raise self from self.parent_exception diff --git a/django_cache_mock/mock.py b/django_cache_mock/mock.py index b4b9b6a..d29d48a 100644 --- a/django_cache_mock/mock.py +++ b/django_cache_mock/mock.py @@ -1,6 +1,7 @@ import logging SUPPORTED_BACKENDS = { + "pymemcache": "django_cache_mock.backends.memcached.PyMemcacheMockMemcacheCache", "mockcache": "django_cache_mock.backends.memcached.MockcacheCache", "fakeredis": "django_cache_mock.backends.redis.FakeRedisCache", "redislite": "django_cache_mock.backends.redis.RedisLiteCache", @@ -15,8 +16,8 @@ logger = logging.getLogger(__name__) -def patch(caches, cache_alias, backend, params=None, *, force=False): - current_config = caches[cache_alias] +def patch(caches_config, cache_alias, backend, params=None, *, force=False): + current_config = caches_config[cache_alias] current_location = current_config.get("LOCATION") if params is None: @@ -32,7 +33,7 @@ def patch(caches, cache_alias, backend, params=None, *, force=False): params["BACKEND"] = SUPPORTED_BACKENDS[backend] logger.info(f"Cache {cache_alias} mocked with {backend}.") logger.debug(f"{params=}.") - caches[cache_alias] = params + caches_config[cache_alias] = params return True diff --git a/pyproject.toml b/pyproject.toml index adb479b..b167977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,8 @@ packages = [{include = "django_cache_mock"}] [tool.poetry.dependencies] python = "^3.8" django = "^3 || ^4" -mockcache3 = { version = "*", optional = true } +pymemcache = { version = "*", optional = true } +mockcache3 = { version = "^2", optional = true } fakeredis = { version = "*", optional = true } redislite = { version = "*", optional = true } django-redis = { version = "*", optional = true } @@ -21,6 +22,7 @@ pytest-cov = "*" pytest-ruff = "*" [tool.poetry.extras] +pymemcache = ["pymemcache"] mockcache = ["mockcache3"] fakeredis = ["fakeredis"] redislite = ["redislite"] diff --git a/tests/conftest.py b/tests/conftest.py index d4efc52..4b7242d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,7 +19,7 @@ def cache_alias(request): def _validate_backend_installed(cache_alias): # Import at root level trigger https://github.com/jazzband/django-redis/issues/638. - from django_cache_mock.backends.redis import LazyRedisCacheImportError + from django_cache_mock.exceptions import LazyLibImportError backend_module, backend_class = CACHES[cache_alias]["BACKEND"].rsplit(".", 1) try: @@ -28,7 +28,7 @@ def _validate_backend_installed(cache_alias): return False backend = getattr(module, backend_class) - if issubclass(backend, LazyRedisCacheImportError): + if issubclass(backend, LazyLibImportError): return False return True diff --git a/tests/test_backends.py b/tests/test_backends.py index 669e6b6..46c4089 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -2,6 +2,7 @@ from django.conf import settings from django.core.cache import InvalidCacheBackendError, caches +from django_cache_mock.exceptions import LazyLibImportError from tests.thread_with_exceptions import Thread @@ -38,19 +39,17 @@ def test_server_name(cache_alias_installed, tmp_path): pass cache = caches[cache_alias] assert cache.location == location + cache.set("FOO", "BAR") assert cache.get("FOO") == "BAR" def test_not_implemented_exception(): - # Import at root level trigger https://github.com/jazzband/django-redis/issues/638. - from django_cache_mock.backends.redis import LazyRedisCacheImportError - try: 1 / 0 except ZeroDivisionError as _exc: - class MyError(LazyRedisCacheImportError): + class MyError(LazyLibImportError): exception = _exc with pytest.raises(MyError) as exception_info: @@ -61,13 +60,10 @@ class MyError(LazyRedisCacheImportError): def test_redis_import_error(redis_cache_alias_not_installed): - # Import at root level trigger https://github.com/jazzband/django-redis/issues/638. - from django_cache_mock.backends.redis import LazyRedisCacheImportError - cache_alias = redis_cache_alias_not_installed try: caches[cache_alias] - except LazyRedisCacheImportError: + except LazyLibImportError: pass else: # pragma: no cover pytest.fail("Cache unexpectedly worked.") diff --git a/tests/test_mock.py b/tests/test_mock.py index 3d725bb..f9b5ffd 100644 --- a/tests/test_mock.py +++ b/tests/test_mock.py @@ -39,14 +39,19 @@ def test_custom_params(): def test_use_django_builtin_redis_based_on_backend(redis_backend): caches = {"default": {"BACKEND": "django.core.cache.backends.redis.RedisCache"}} patch(caches, "default", redis_backend) - assert caches["default"]["BACKEND"] == SUPPORTED_BACKENDS[redis_backend] + expected_backend = SUPPORTED_BACKENDS[redis_backend] + assert caches["default"] == { + "BACKEND": expected_backend, + } def test_use_django_redis_based_on_backend(redis_backend): caches = {"default": {"BACKEND": "django_redis.cache.RedisCache"}} patch(caches, "default", redis_backend) expected_backend = SUPPORTED_BACKENDS[f"{redis_backend}[django-redis]"] - assert caches["default"]["BACKEND"] == expected_backend + assert caches["default"] == { + "BACKEND": expected_backend, + } def test_use_django_redis_explicit(redis_backend): @@ -54,4 +59,6 @@ def test_use_django_redis_explicit(redis_backend): explicit_django_redis_backend = f"{redis_backend}[django-redis]" patch(caches, "default", explicit_django_redis_backend) expected_backend = SUPPORTED_BACKENDS[explicit_django_redis_backend] - assert caches["default"]["BACKEND"] == expected_backend + assert caches["default"] == { + "BACKEND": expected_backend, + }