Skip to content

Commit

Permalink
Support lower case options for Django Redis cache backend
Browse files Browse the repository at this point in the history
Closes joke2k#517.
  • Loading branch information
eht16 committed Jan 19, 2025
1 parent 993b36c commit e093405
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 39 deletions.
17 changes: 13 additions & 4 deletions environ/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
103 changes: 79 additions & 24 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(
Expand Down Expand Up @@ -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():
Expand Down
37 changes: 26 additions & 11 deletions tests/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import os
import tempfile
from unittest import mock
from urllib.parse import quote

import pytest
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
Expand Down

0 comments on commit e093405

Please sign in to comment.