diff --git a/environ/environ.py b/environ/environ.py index 5536f2c..a72b865 100644 --- a/environ/environ.py +++ b/environ/environ.py @@ -689,19 +689,28 @@ def cache_url_config(cls, url, backend=None): else: config['LOCATION'] = locations + if backend: + config['BACKEND'] = backend + if url.query: config_options = {} + # Django Redis cache backend expects options in lower case + # while "django_redis" expects them in upper case + backend = config['BACKEND'] + if backend == 'django.core.cache.backends.redis.RedisCache': + key_modifier = 'lower' + else: + key_modifier = 'upper' + for k, v in parse_qs(url.query).items(): - opt = {k.upper(): _cast(v[0])} + key = getattr(k, key_modifier)() + opt = {key: _cast(v[0])} if k.upper() in cls._CACHE_BASE_OPTIONS: config.update(opt) else: config_options.update(opt) config['OPTIONS'] = config_options - if backend: - config['BACKEND'] = backend - return config @classmethod diff --git a/tests/test_cache.py b/tests/test_cache.py index 58e57e0..6da49b5 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -19,7 +19,7 @@ ) -def test_base_options_parsing(): +def test_base_options_parsing_memcache(): url = ('memcache://127.0.0.1:11211/?timeout=0&' 'key_prefix=cache_&key_function=foo.get_key&version=1') url = Env.cache_url_config(url) @@ -29,10 +29,29 @@ def test_base_options_parsing(): assert url['TIMEOUT'] == 0 assert url['VERSION'] == 1 - url = 'redis://127.0.0.1:6379/?timeout=None' - url = Env.cache_url_config(url) - assert url['TIMEOUT'] is None +@pytest.mark.parametrize('redis_driver,timeout_key', + [ + ('django.core.cache.backends.redis.RedisCache', 'timeout'), + ('django_redis.cache.RedisCache', 'TIMEOUT'), + ], + ids=[ + 'django', + 'django_redis', + ], +) +def test_base_options_parsing_redis(redis_driver, timeout_key): + mocked_cache_schemes = Env.CACHE_SCHEMES.copy() + mocked_cache_schemes.update({ + 'rediscache': redis_driver, + 'redis': redis_driver, + 'rediss': redis_driver, + }) + with mock.patch.object(Env, 'CACHE_SCHEMES', mocked_cache_schemes): + url = 'redis://127.0.0.1:6379/?timeout=None' + url = Env.cache_url_config(url) + + assert url[timeout_key] is None @pytest.mark.parametrize( @@ -134,27 +153,63 @@ def test_rediscache_compat(django_version, django_redis_installed): else: assert driver == redis_cache -def test_redis_parsing(): - url = ('rediscache://127.0.0.1:6379/1?client_class=' - 'django_redis.client.DefaultClient&password=secret') - url = Env.cache_url_config(url) - - assert url['BACKEND'] == REDIS_DRIVER - assert url['LOCATION'] == 'redis://127.0.0.1:6379/1' - assert url['OPTIONS'] == { - 'CLIENT_CLASS': 'django_redis.client.DefaultClient', - 'PASSWORD': 'secret', - } - -def test_redis_socket_url(): - url = 'redis://:redispass@/path/to/socket.sock?db=0' - url = Env.cache_url_config(url) - assert REDIS_DRIVER == url['BACKEND'] - assert url['LOCATION'] == 'unix://:redispass@/path/to/socket.sock' - assert url['OPTIONS'] == { - 'DB': 0 - } +@pytest.mark.parametrize('redis_driver,client_class_key,password_key', + [ + ('django.core.cache.backends.redis.RedisCache', 'client_class', 'password'), + ('django_redis.cache.RedisCache', 'CLIENT_CLASS', 'PASSWORD'), + ], + ids=[ + 'django', + 'django_redis', + ], +) +def test_redis_parsing(redis_driver, client_class_key, password_key): + mocked_cache_schemes = Env.CACHE_SCHEMES.copy() + mocked_cache_schemes.update({ + 'rediscache': redis_driver, + 'redis': redis_driver, + 'rediss': redis_driver, + }) + with mock.patch.object(Env, 'CACHE_SCHEMES', mocked_cache_schemes): + url = ('rediscache://127.0.0.1:6379/1?client_class=' + 'django_redis.client.DefaultClient&password=secret') + url = Env.cache_url_config(url) + + assert url['BACKEND'] == redis_driver + assert url['LOCATION'] == 'redis://127.0.0.1:6379/1' + assert url['OPTIONS'] == { + client_class_key: 'django_redis.client.DefaultClient', + password_key: 'secret', + } + + +@pytest.mark.parametrize('redis_driver,db_key', + [ + ('django.core.cache.backends.redis.RedisCache', 'db'), + ('django_redis.cache.RedisCache', 'DB'), + ], + ids=[ + 'django', + 'django_redis', + ], +) +def test_redis_socket_url(redis_driver, db_key): + mocked_cache_schemes = Env.CACHE_SCHEMES.copy() + mocked_cache_schemes.update({ + 'rediscache': redis_driver, + 'redis': redis_driver, + 'rediss': redis_driver, + }) + with mock.patch.object(Env, 'CACHE_SCHEMES', mocked_cache_schemes): + url = 'redis://:redispass@/path/to/socket.sock?db=0' + url = Env.cache_url_config(url) + + assert url['BACKEND'] == redis_driver + assert url['LOCATION'] == 'unix://:redispass@/path/to/socket.sock' + assert url['OPTIONS'] == { + db_key: 0 + } def test_options_parsing(): diff --git a/tests/test_env.py b/tests/test_env.py index 5f0f4c9..052c79e 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -8,6 +8,7 @@ import os import tempfile +from unittest import mock from urllib.parse import quote import pytest @@ -352,26 +353,40 @@ def test_db_url_value(self, var, engine, name, host, user, passwd, port): (Env.DEFAULT_CACHE_ENV, 'django.core.cache.backends.memcached.MemcachedCache', '127.0.0.1:11211', None), - ('CACHE_REDIS', REDIS_DRIVER, + ('CACHE_REDIS', + 'django.core.cache.backends.redis.RedisCache', + 'redis://127.0.0.1:6379/1', + {'client_class': 'django_redis.client.DefaultClient', + 'password': 'secret'}), + ('CACHE_REDIS', + 'django_redis.cache.RedisCache', 'redis://127.0.0.1:6379/1', {'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'PASSWORD': 'secret'}), ], ids=[ 'memcached', - 'redis', + 'django', # Django Redis cache backend + 'redis_django', # django_redis backend ], ) def test_cache_url_value(self, var, backend, location, options): - config = self.env.cache_url(var) - - assert config['BACKEND'] == backend - assert config['LOCATION'] == location - - if options is None: - assert 'OPTIONS' not in config - else: - assert config['OPTIONS'] == options + mocked_cache_schemes = Env.CACHE_SCHEMES.copy() + mocked_cache_schemes.update({ + 'rediscache': backend, + 'redis': backend, + 'rediss': backend, + }) + with mock.patch.object(Env, 'CACHE_SCHEMES', mocked_cache_schemes): + config = self.env.cache_url(var) + + assert config['BACKEND'] == backend + assert config['LOCATION'] == location + + if options is None: + assert 'OPTIONS' not in config + else: + assert config['OPTIONS'] == options def test_email_url_value(self): email_config = self.env.email_url() diff --git a/tox.ini b/tox.ini index a0b8dc2..7a08912 100644 --- a/tox.ini +++ b/tox.ini @@ -42,6 +42,8 @@ deps = django40: Django>=4.0,<4.1 django41: Django>=4.1,<4.2 django42: Django>=4.2,<5.0 + django50: Django>=5.0,<5.1 + django51: Django>=5.1,<5.2 commands_pre = python -m pip install --upgrade pip python -m pip install .