Skip to content

Commit 7faa82b

Browse files
authored
Merge pull request #639 from iurisilvio/lazy-settings-itersize
Fix #638 Lazy access to Django settings.
2 parents 65a4349 + e7ee981 commit 7faa82b

File tree

6 files changed

+39
-19
lines changed

6 files changed

+39
-19
lines changed

changelog.d/638.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Access `django_redis.cache.DJANGO_REDIS_SCAN_ITERSIZE` and `django_redis.client.herd.CACHE_HERD_TIMEOUT` in runtime to not read Django settings in import time.

django_redis/cache.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
from .exceptions import ConnectionInterrupted
1111

12-
DJANGO_REDIS_SCAN_ITERSIZE = getattr(settings, "DJANGO_REDIS_SCAN_ITERSIZE", 10)
13-
1412
CONNECTION_INTERRUPTED = object()
1513

1614

@@ -45,6 +43,9 @@ def __init__(self, server: str, params: Dict[str, Any]) -> None:
4543
super().__init__(params)
4644
self._server = server
4745
self._params = params
46+
self._default_scan_itersize = getattr(
47+
settings, "DJANGO_REDIS_SCAN_ITERSIZE", 10
48+
)
4849

4950
options = params.get("OPTIONS", {})
5051
self._client_cls = options.get(
@@ -105,7 +106,7 @@ def delete(self, *args, **kwargs):
105106

106107
@omit_exception
107108
def delete_pattern(self, *args, **kwargs):
108-
kwargs["itersize"] = kwargs.get("itersize", DJANGO_REDIS_SCAN_ITERSIZE)
109+
kwargs.setdefault("itersize", self._default_scan_itersize)
109110
return self.client.delete_pattern(*args, **kwargs)
110111

111112
@omit_exception

django_redis/client/herd.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,20 @@ class Marker:
2121
pass
2222

2323

24-
CACHE_HERD_TIMEOUT = getattr(settings, "CACHE_HERD_TIMEOUT", 60)
25-
26-
27-
def _is_expired(x):
28-
if x >= CACHE_HERD_TIMEOUT:
24+
def _is_expired(x, herd_timeout: int) -> bool:
25+
if x >= herd_timeout:
2926
return True
30-
val = x + random.randint(1, CACHE_HERD_TIMEOUT)
27+
val = x + random.randint(1, herd_timeout)
3128

32-
if val >= CACHE_HERD_TIMEOUT:
29+
if val >= herd_timeout:
3330
return True
3431
return False
3532

3633

3734
class HerdClient(DefaultClient):
3835
def __init__(self, *args, **kwargs):
3936
self._marker = Marker()
37+
self._herd_timeout = getattr(settings, "CACHE_HERD_TIMEOUT", 60)
4038
super().__init__(*args, **kwargs)
4139

4240
def _pack(self, value, timeout):
@@ -55,7 +53,7 @@ def _unpack(self, value):
5553
now = int(time.time())
5654
if herd_timeout < now:
5755
x = now - herd_timeout
58-
return unpacked, _is_expired(x)
56+
return unpacked, _is_expired(x, self._herd_timeout)
5957

6058
return unpacked, False
6159

@@ -84,7 +82,7 @@ def set(
8482
)
8583

8684
packed = self._pack(value, timeout)
87-
real_timeout = timeout + CACHE_HERD_TIMEOUT
85+
real_timeout = timeout + self._herd_timeout
8886

8987
return super().set(
9088
key, packed, timeout=real_timeout, version=version, client=client, nx=nx

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ REDIS =
100100
passenv = CI GITHUB*
101101
commands =
102102
{envpython} -m pytest --cov-report= --ds=settings.sqlite {posargs}
103+
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_herd {posargs}
103104
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_json {posargs}
104105
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_lz4 {posargs}
105106
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_msgpack {posargs}

tests/settings/sqlite_herd.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@
2727
INSTALLED_APPS = ["django.contrib.sessions"]
2828

2929
USE_TZ = False
30+
31+
CACHE_HERD_TIMEOUT = 2

tests/test_backend.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,29 @@
22
import threading
33
import time
44
from datetime import timedelta
5-
from typing import List, Union, cast
5+
from typing import Iterable, List, Union, cast
66
from unittest.mock import patch
77

88
import pytest
99
from django.core.cache import caches
10+
from django.test import override_settings
1011
from pytest_django.fixtures import SettingsWrapper
1112
from pytest_mock import MockerFixture
1213

13-
import django_redis.cache
1414
from django_redis.cache import RedisCache
1515
from django_redis.client import ShardClient, herd
1616
from django_redis.serializers.json import JSONSerializer
1717
from django_redis.serializers.msgpack import MSGPackSerializer
1818

19-
herd.CACHE_HERD_TIMEOUT = 2
19+
20+
@pytest.fixture
21+
def patch_itersize_setting() -> Iterable[None]:
22+
# destroy cache to force recreation with overriden settings
23+
del caches["default"]
24+
with override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30):
25+
yield
26+
# destroy cache to force recreation with original settings
27+
del caches["default"]
2028

2129

2230
class TestDjangoRedisCache:
@@ -199,7 +207,12 @@ def test_set_many(self, cache: RedisCache):
199207
res = cache.get_many(["a", "b", "c"])
200208
assert res == {"a": 1, "b": 2, "c": 3}
201209

202-
def test_set_call_empty_pipeline(self, cache: RedisCache, mocker: MockerFixture):
210+
def test_set_call_empty_pipeline(
211+
self,
212+
cache: RedisCache,
213+
mocker: MockerFixture,
214+
settings: SettingsWrapper,
215+
):
203216
if isinstance(cache.client, ShardClient):
204217
pytest.skip("ShardClient doesn't support get_client")
205218

@@ -212,7 +225,7 @@ def test_set_call_empty_pipeline(self, cache: RedisCache, mocker: MockerFixture)
212225

213226
if isinstance(cache.client, herd.HerdClient):
214227
default_timeout = cache.client._backend.default_timeout
215-
herd_timeout = (default_timeout + herd.CACHE_HERD_TIMEOUT) * 1000 # type: ignore # noqa
228+
herd_timeout = (default_timeout + settings.CACHE_HERD_TIMEOUT) * 1000
216229
herd_pack_value = cache.client._pack(value, default_timeout)
217230
mocked_set.assert_called_once_with(
218231
cache.client.make_key(key, version=None),
@@ -495,11 +508,15 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache):
495508

496509
@patch("django_redis.cache.RedisCache.client")
497510
def test_delete_pattern_with_settings_default_scan_count(
498-
self, client_mock, cache: RedisCache
511+
self,
512+
client_mock,
513+
patch_itersize_setting,
514+
cache: RedisCache,
515+
settings: SettingsWrapper,
499516
):
500517
for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]:
501518
cache.set(key, "foo")
502-
expected_count = django_redis.cache.DJANGO_REDIS_SCAN_ITERSIZE
519+
expected_count = settings.DJANGO_REDIS_SCAN_ITERSIZE
503520

504521
cache.delete_pattern("*foo-a*")
505522

0 commit comments

Comments
 (0)