From 5c851f1d28d37108908025a29017497950069414 Mon Sep 17 00:00:00 2001 From: Anas Date: Mon, 18 Apr 2022 11:21:54 +0300 Subject: [PATCH 001/148] Added blocking parameter to lock --- django_redis/client/default.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 1df90a27..ac01a531 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -338,9 +338,10 @@ def lock( self, key, version: Optional[int] = None, - timeout=None, - sleep=0.1, - blocking_timeout=None, + timeout: Optional[float] = None, + sleep: float = 0.1, + blocking: bool = True, + blocking_timeout: Optional[float] = None, client: Optional[Redis] = None, thread_local=True, ): @@ -352,6 +353,7 @@ def lock( key, timeout=timeout, sleep=sleep, + blocking=blocking, blocking_timeout=blocking_timeout, thread_local=thread_local, ) From 44aff8815b3b8ad3195fce0c653f613397f217ae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 10 May 2022 09:38:11 +0300 Subject: [PATCH 002/148] [pre-commit.ci] pre-commit autoupdate (#605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.32.0 → v2.32.1](https://github.com/asottile/pyupgrade/compare/v2.32.0...v2.32.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4215c65d..f0aab0cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v2.32.1 hooks: - id: pyupgrade From 36c87c85fceb8f24bbb504974c6d2f2f9171c03a Mon Sep 17 00:00:00 2001 From: Tomislav Maricevic Date: Thu, 23 Jun 2022 17:14:40 +0200 Subject: [PATCH 003/148] Log traceback if exceptions are ignored --- django_redis/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_redis/cache.py b/django_redis/cache.py index d3e8708d..73d7832f 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -32,7 +32,7 @@ def _decorator(self, *args, **kwargs): except ConnectionInterrupted as e: if self._ignore_exceptions: if self._log_ignored_exceptions: - self.logger.error(str(e)) + self.logger.exception(str(e)) return return_value raise e.__cause__ From 38e630f051d6cc1f54e2e919c5751853282657ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 29 Jun 2022 20:53:57 +0300 Subject: [PATCH 004/148] [pre-commit.ci] pre-commit autoupdate (#612) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0) - [github.com/asottile/pyupgrade: v2.32.1 → v2.34.0](https://github.com/asottile/pyupgrade/compare/v2.32.1...v2.34.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f0aab0cd..8ed7893b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-ast - id: check-case-conflict @@ -10,7 +10,7 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 + rev: v2.34.0 hooks: - id: pyupgrade From 0a9d795ec4f95c6f23f84e1c1cfccdca3b81038f Mon Sep 17 00:00:00 2001 From: Tomislav Maricevic Date: Sat, 2 Jul 2022 20:44:38 +0200 Subject: [PATCH 005/148] Adjust error message --- django_redis/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_redis/cache.py b/django_redis/cache.py index 73d7832f..ebf4d14b 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -32,7 +32,7 @@ def _decorator(self, *args, **kwargs): except ConnectionInterrupted as e: if self._ignore_exceptions: if self._log_ignored_exceptions: - self.logger.exception(str(e)) + self.logger.exception("Exception ignored") return return_value raise e.__cause__ From 8a54fa54842b9cd004b5e75ba56215334b401a65 Mon Sep 17 00:00:00 2001 From: Tomislav Maricevic Date: Sat, 2 Jul 2022 20:52:23 +0200 Subject: [PATCH 006/148] Add towncrier fragment --- changelog.d/611.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/611.misc diff --git a/changelog.d/611.misc b/changelog.d/611.misc new file mode 100644 index 00000000..bd6260c4 --- /dev/null +++ b/changelog.d/611.misc @@ -0,0 +1 @@ +Print full exception traceback when logging ignored exceptions From 52fc78df197a654a73ba57d0f7fa9a59da5dd921 Mon Sep 17 00:00:00 2001 From: Tomislav Maricevic Date: Sat, 16 Jul 2022 17:05:27 +0200 Subject: [PATCH 007/148] Test that exceptions are logged --- tests/test_cache_options.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/test_cache_options.py b/tests/test_cache_options.py index 93259111..652a03d5 100644 --- a/tests/test_cache_options.py +++ b/tests/test_cache_options.py @@ -3,6 +3,7 @@ import pytest from django.core.cache import caches +from pytest import LogCaptureFixture from pytest_django.fixtures import SettingsWrapper from redis.exceptions import ConnectionError @@ -22,8 +23,10 @@ def reverse_key(key: str) -> str: def ignore_exceptions_cache(settings: SettingsWrapper) -> RedisCache: caches_setting = copy.deepcopy(settings.CACHES) caches_setting["doesnotexist"]["OPTIONS"]["IGNORE_EXCEPTIONS"] = True + caches_setting["doesnotexist"]["OPTIONS"]["LOG_IGNORED_EXCEPTIONS"] = True settings.CACHES = caches_setting settings.DJANGO_REDIS_IGNORE_EXCEPTIONS = True + settings.DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True return cast(RedisCache, caches["doesnotexist"]) @@ -34,12 +37,22 @@ def test_get_django_omit_exceptions_many_returns_default_arg( assert ignore_exceptions_cache.get_many(["key1", "key2", "key3"]) == {} -def test_get_django_omit_exceptions(ignore_exceptions_cache: RedisCache): +def test_get_django_omit_exceptions( + caplog: LogCaptureFixture, ignore_exceptions_cache: RedisCache +): assert ignore_exceptions_cache._ignore_exceptions is True + assert ignore_exceptions_cache._log_ignored_exceptions is True + assert ignore_exceptions_cache.get("key") is None assert ignore_exceptions_cache.get("key", "default") == "default" assert ignore_exceptions_cache.get("key", default="default") == "default" + assert len(caplog.records) == 3 + assert all( + record.levelname == "ERROR" and record.msg == "Exception ignored" + for record in caplog.records + ) + def test_get_django_omit_exceptions_priority_1(settings: SettingsWrapper): caches_setting = copy.deepcopy(settings.CACHES) From 326f86eb4be29621dd6c98e40df638665ce28b77 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 18:33:57 +0000 Subject: [PATCH 008/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.34.0 → v3.1.0](https://github.com/asottile/pyupgrade/compare/v2.34.0...v3.1.0) - [github.com/psf/black: 22.3.0 → 22.10.0](https://github.com/psf/black/compare/22.3.0...22.10.0) - [github.com/PyCQA/flake8: 4.0.1 → 5.0.4](https://github.com/PyCQA/flake8/compare/4.0.1...5.0.4) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8ed7893b..0fe576ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,17 +10,17 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v3.1.0 hooks: - id: pyupgrade - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.10.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + rev: 5.0.4 hooks: - id: flake8 From 1e865a3bbacdd507984b44b2c541509cd0af7a3d Mon Sep 17 00:00:00 2001 From: Vasyl Dizhak Date: Fri, 21 Oct 2022 18:45:07 +0200 Subject: [PATCH 009/148] #609, use pipeline for delete_pattern (#617) * #609, use pipeline for delete_pattern * #609, add changelog description --- README.rst | 9 +++++++++ changelog.d/609.misc | 1 + django_redis/client/default.py | 6 +++++- tests/test_client.py | 24 +++++++++++++++++++++++- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 changelog.d/609.misc diff --git a/README.rst b/README.rst index 266780c4..ffa3d482 100644 --- a/README.rst +++ b/README.rst @@ -483,6 +483,15 @@ pattern syntax as the ``keys`` function and returns the number of deleted keys. >>> from django.core.cache import cache >>> cache.delete_pattern("foo_*") +To achieve the best performance while deleting many keys, you should set ``DJANGO_REDIS_SCAN_ITERSIZE`` to a relatively +high number (e.g., 100_000) by default in Django settings or pass it directly to the ``delete_pattern``. + + +.. code-block:: pycon + + >>> from django.core.cache import cache + >>> cache.delete_pattern("foo_*", itersize=100_000) + Redis native commands ~~~~~~~~~~~~~~~~~~~~~ diff --git a/changelog.d/609.misc b/changelog.d/609.misc new file mode 100644 index 00000000..e9f5891b --- /dev/null +++ b/changelog.d/609.misc @@ -0,0 +1 @@ +Speed up deleting multiple keys by a pattern with pipelines and larger itersize \ No newline at end of file diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 1df90a27..6886b46b 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -393,9 +393,13 @@ def delete_pattern( try: count = 0 + pipeline = client.pipeline() + for key in client.scan_iter(match=pattern, count=itersize): - client.delete(key) + pipeline.delete(key) count += 1 + pipeline.execute() + return count except _main_exceptions as e: raise ConnectionInterrupted(connection=client) from e diff --git a/tests/test_client.py b/tests/test_client.py index 807fbf3a..74308657 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,5 +1,5 @@ from typing import Iterable -from unittest.mock import Mock, patch +from unittest.mock import Mock, call, patch import pytest from django.core.cache import DEFAULT_CACHE_ALIAS @@ -104,6 +104,28 @@ def test_delete_pattern_calls_scan_iter_with_count_if_itersize_given( count=90210, match=make_pattern_mock.return_value ) + @patch("test_client.DefaultClient.make_pattern") + @patch("test_client.DefaultClient.get_client", return_value=Mock()) + @patch("test_client.DefaultClient.__init__", return_value=None) + def test_delete_pattern_calls_pipeline_delete_and_execute( + self, init_mock, get_client_mock, make_pattern_mock + ): + client = DefaultClient() + client._backend = Mock() + client._backend.key_prefix = "" + get_client_mock.return_value.scan_iter.return_value = [":1:foo", ":1:foo-a"] + get_client_mock.return_value.pipeline.return_value = Mock() + get_client_mock.return_value.pipeline.return_value.delete = Mock() + get_client_mock.return_value.pipeline.return_value.execute = Mock() + + client.delete_pattern(pattern="foo*") + + assert get_client_mock.return_value.pipeline.return_value.delete.call_count == 2 + get_client_mock.return_value.pipeline.return_value.delete.assert_has_calls( + [call(":1:foo"), call(":1:foo-a")] + ) + get_client_mock.return_value.pipeline.return_value.execute.assert_called_once() + class TestShardClient: @patch("test_client.DefaultClient.make_pattern") From d6cc1d25983e968ba4bd5c83584134ceaa16894f Mon Sep 17 00:00:00 2001 From: Ihor Sychevskyi Date: Tue, 25 Oct 2022 00:34:25 +0300 Subject: [PATCH 010/148] update readme links (#622) --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index ffa3d482..9e403349 100644 --- a/README.rst +++ b/README.rst @@ -811,7 +811,7 @@ Herd client ^^^^^^^^^^^ This pluggable client helps dealing with the thundering herd problem. You can read more about it -on link: `Wikipedia `_ +on link: `Wikipedia `_ Like previous pluggable clients, it inherits all functionality from the default client, adding some additional methods for getting/setting keys. @@ -871,7 +871,7 @@ msgpack library): } } -.. _MsgPack: http://msgpack.org/ +.. _MsgPack: https://msgpack.org/ Pluggable Redis client ~~~~~~~~~~~~~~~~~~~~~~ From 2e40dc64cc708fe25452165e0ab410c4fd9be2be Mon Sep 17 00:00:00 2001 From: Ihor Sychevskyi Date: Fri, 28 Oct 2022 01:06:49 +0300 Subject: [PATCH 011/148] update AUTHORS links (#624) --- AUTHORS.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 12f25c6d..76d317db 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,10 +1,10 @@ Andrei Antoukh / niwibe -Sean Bleier -Matt Dennewitz -Jannis Leidel -S. Angel / Twidi -Noah Kantrowitz / coderanger -Martin Mahner / bartTC +Sean Bleier +Matt Dennewitz +Jannis Leidel +S. Angel / Twidi +Noah Kantrowitz / coderanger +Martin Mahner / bartTC Timothée Peignier / cyberdelia Lior Sion / liorsion Ales Zoulek / aleszoulek From 8cf4b489330d35476d776c3bf0e51927bd8f00af Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 18:43:05 +0000 Subject: [PATCH 012/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0fe576ca..63d3afd3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.2.0 hooks: - id: pyupgrade From f09d592a3eaba5c71e8a340daadb63aa75a932ef Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Sat, 5 Nov 2022 19:18:31 +0100 Subject: [PATCH 013/148] Add Django 4.1 to test matrix and classifiers --- .github/workflows/ci.yml | 7 +++++++ changelog.d/627.feature | 1 + setup.cfg | 5 ++++- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changelog.d/627.feature diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66440eb4..6ec90bdf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,7 @@ jobs: - '3.1' - '3.2' - '4.0' + - '4.1' redis-version: - 'latest' @@ -47,6 +48,12 @@ jobs: # exclude python 3.6 and 3.7 for django 4.x as they are not supported see https://docs.djangoproject.com/en/dev/releases/4.0/#python-compatibility exclude: + - django-version: '4.1' + python-version: '3.6' + + - django-version: '4.1' + python-version: '3.7' + - django-version: '4.0' python-version: '3.6' diff --git a/changelog.d/627.feature b/changelog.d/627.feature new file mode 100644 index 00000000..5657ecf0 --- /dev/null +++ b/changelog.d/627.feature @@ -0,0 +1 @@ +Add support for django 4 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 28c1fc1a..95b3ca8e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,6 +16,7 @@ classifiers = Framework :: Django :: 3.1 Framework :: Django :: 3.2 Framework :: Django :: 4.0 + Framework :: Django :: 4.1 Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent @@ -72,7 +73,7 @@ envlist = isort mypy # tests against released versions - py{36,37,38,39,310}-dj{22,31,32,40}-redislatest + py{36,37,38,39,310}-dj{22,31,32,40,41}-redislatest # tests against unreleased versions py310-dj40-redismaster py310-djmain-redis{latest,master} @@ -91,6 +92,7 @@ DJANGO = 3.1: dj31 3.2: dj32 4.0: dj40 + 4.1: dj41 main: djmain REDIS = latest: redislatest @@ -116,6 +118,7 @@ deps = dj31: Django>=3.1,<3.2 dj32: Django>=3.2,<3.3 dj40: Django>=4.0,<4.1 + dj41: Django>=4.1,<4.2 djmain: https://github.com/django/django/archive/main.tar.gz msgpack>=0.6.0 pytest From 8da08c2f47bb50e87b5983636a1a425d1a2049b5 Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Sun, 6 Nov 2022 21:16:04 +0100 Subject: [PATCH 014/148] mypy: actual type "BaseCache", expected type "RedisCache" --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1b99f0c7..59ea7d8d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,10 +3,10 @@ import pytest from django.core.cache import cache as default_cache -from django_redis.cache import RedisCache +from django_redis.cache import BaseCache @pytest.fixture -def cache() -> Iterable[RedisCache]: +def cache() -> Iterable[BaseCache]: yield default_cache default_cache.clear() From e611a6248478041b29527d035771c032385e9fab Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Sun, 6 Nov 2022 21:16:39 +0100 Subject: [PATCH 015/148] mypy: ignore `default_timeout` can be `None` mypy: ignore `default_timeout` can be `None` --- changelog.d/626.misc | 1 + tests/test_backend.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/626.misc diff --git a/changelog.d/626.misc b/changelog.d/626.misc new file mode 100644 index 00000000..c9da403d --- /dev/null +++ b/changelog.d/626.misc @@ -0,0 +1 @@ +Fix mypy linting \ No newline at end of file diff --git a/tests/test_backend.py b/tests/test_backend.py index 0e8e1fdf..165779ce 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -212,7 +212,7 @@ def test_set_call_empty_pipeline(self, cache: RedisCache, mocker: MockerFixture) if isinstance(cache.client, herd.HerdClient): default_timeout = cache.client._backend.default_timeout - herd_timeout = (default_timeout + herd.CACHE_HERD_TIMEOUT) * 1000 + herd_timeout = (default_timeout + herd.CACHE_HERD_TIMEOUT) * 1000 # type: ignore # noqa herd_pack_value = cache.client._pack(value, default_timeout) mocked_set.assert_called_once_with( cache.client.make_key(key, version=None), From 2ba6b15ee77a4f159f49a7a9098add7c41f5fc19 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Thu, 19 Jan 2023 05:28:36 -0300 Subject: [PATCH 016/148] Fix CI, with Python 3.6 support. (#643) --- .github/workflows/ci.yml | 6 +++--- changelog.d/645.misc | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog.d/645.misc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ec90bdf..b8dc103f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: Django ${{ matrix.django-version }}, Redis.py ${{ matrix.redis-version }} - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: fail-fast: false @@ -86,7 +86,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install --upgrade tox tox-gh-actions + python -m pip install --upgrade "tox<4" tox-gh-actions - name: Tox tests run: | @@ -149,7 +149,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install --upgrade tox + python -m pip install --upgrade "tox<4" - name: Run ${{ matrix.tool }} run: tox -e ${{ matrix.tool }} diff --git a/changelog.d/645.misc b/changelog.d/645.misc new file mode 100644 index 00000000..318edf46 --- /dev/null +++ b/changelog.d/645.misc @@ -0,0 +1 @@ +Fix CI, running tox<4 to still support Python 3.6. \ No newline at end of file From 98871af8a76f8e01a53f1528e73617d4eb8ef57d Mon Sep 17 00:00:00 2001 From: Vasyl Dizhak Date: Fri, 20 Jan 2023 10:41:24 +0100 Subject: [PATCH 017/148] #646, fix skip tests for the django.contrib.sessions.serializers.PickleSerializer for django > 4.2 --- tests/test_session.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_session.py b/tests/test_session.py index 1c179eb7..e24da231 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,4 +1,8 @@ import base64 + +import django +import pytest + import unittest from datetime import timedelta from typing import Optional, Type @@ -300,7 +304,6 @@ def test_decode_failure_logged_to_security(self): self.assertEqual({}, self.session.decode(bad_encode)) # The failed decode is logged. self.assertIn("corrupted", cm.output[0]) - def test_actual_expiry(self): # this doesn't work with JSONSerializer (serializing timedelta) with override_settings( @@ -365,6 +368,10 @@ def test_session_save_does_not_resurrect_session_logged_out_in_other_context(sel class SessionTests(SessionTestsMixin, unittest.TestCase): backend = CacheSession + @pytest.mark.skipif( + django.VERSION > (4, 2), + reason="PickleSerializer is removed as of https://code.djangoproject.com/ticket/29708" + ) def test_actual_expiry(self): if isinstance( caches[DEFAULT_CACHE_ALIAS].client._serializer, MSGPackSerializer From 26009437fecf15c1c3a2a9b1ea4cc418e61aa6a5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 09:43:25 +0000 Subject: [PATCH 018/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_session.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_session.py b/tests/test_session.py index e24da231..8ae6d9b0 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,12 +1,10 @@ import base64 - -import django -import pytest - import unittest from datetime import timedelta from typing import Optional, Type +import django +import pytest from django.conf import settings from django.contrib.sessions.backends.base import SessionBase from django.contrib.sessions.backends.cache import SessionStore as CacheSession @@ -304,6 +302,7 @@ def test_decode_failure_logged_to_security(self): self.assertEqual({}, self.session.decode(bad_encode)) # The failed decode is logged. self.assertIn("corrupted", cm.output[0]) + def test_actual_expiry(self): # this doesn't work with JSONSerializer (serializing timedelta) with override_settings( @@ -370,7 +369,7 @@ class SessionTests(SessionTestsMixin, unittest.TestCase): @pytest.mark.skipif( django.VERSION > (4, 2), - reason="PickleSerializer is removed as of https://code.djangoproject.com/ticket/29708" + reason="PickleSerializer is removed as of https://code.djangoproject.com/ticket/29708", ) def test_actual_expiry(self): if isinstance( From 8a9eb1e1679c2591848bfba45cef23c8bdf4d701 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sat, 21 Jan 2023 11:35:57 +0200 Subject: [PATCH 019/148] Dropped support for django 2.2 and 3.1 --- .github/workflows/ci.yml | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8dc103f..f09f385b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,22 +15,29 @@ jobs: fail-fast: false matrix: python-version: - - '3.6' - - '3.7' - '3.8' - '3.9' - '3.10' django-version: - - '2.2' - - '3.1' - '3.2' - '4.0' - '4.1' redis-version: - 'latest' - # only test pre-release dependencies for the latest Python + # Only test pre-release dependencies for the latest Python. + # And keep testing Django 3.2 with python 3.6 and 3.7 include: + # Django 3.2 and python 3.6 with latest redis + - django-version: '3.2' + redis-version: 'latest' + python-version: '3.6' + + # Django 3.2 and python 3.7 with latest redis + - django-version: '3.2' + redis-version: 'latest' + python-version: '3.7' + # latest Django with pre-release redis - django-version: '4.0' redis-version: 'master' @@ -46,20 +53,6 @@ jobs: redis-version: 'master' python-version: '3.10' - # exclude python 3.6 and 3.7 for django 4.x as they are not supported see https://docs.djangoproject.com/en/dev/releases/4.0/#python-compatibility - exclude: - - django-version: '4.1' - python-version: '3.6' - - - django-version: '4.1' - python-version: '3.7' - - - django-version: '4.0' - python-version: '3.6' - - - django-version: '4.0' - python-version: '3.7' - steps: - uses: actions/checkout@v2 From 48fa2d9e04c16873c8d26cda6c112a604efcf5ec Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sat, 21 Jan 2023 11:41:33 +0200 Subject: [PATCH 020/148] Added changelog --- changelog.d/649.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/649.misc diff --git a/changelog.d/649.misc b/changelog.d/649.misc new file mode 100644 index 00000000..fb1e40d9 --- /dev/null +++ b/changelog.d/649.misc @@ -0,0 +1 @@ +Dropped support for django 2.2 and 3.1 \ No newline at end of file From dd7688fe85a4788d01b8b4409264e7bdfeac4457 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sun, 22 Jan 2023 15:39:55 +0200 Subject: [PATCH 021/148] Added support for python 3.11 and some fixed in setup.cfg --- .github/workflows/ci.yml | 7 ++++--- setup.cfg | 16 ++++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f09f385b..bc191450 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: - '3.8' - '3.9' - '3.10' + - '3.11' django-version: - '3.2' - '4.0' @@ -41,17 +42,17 @@ jobs: # latest Django with pre-release redis - django-version: '4.0' redis-version: 'master' - python-version: '3.10' + python-version: '3.11' # latest redis with pre-release Django - django-version: 'main' redis-version: 'latest' - python-version: '3.10' + python-version: '3.11' # pre-release Django and redis - django-version: 'main' redis-version: 'master' - python-version: '3.10' + python-version: '3.11' steps: - uses: actions/checkout@v2 diff --git a/setup.cfg b/setup.cfg index 95b3ca8e..e0106810 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,8 +12,6 @@ classifiers = Development Status :: 5 - Production/Stable Environment :: Web Environment Framework :: Django - Framework :: Django :: 2.2 - Framework :: Django :: 3.1 Framework :: Django :: 3.2 Framework :: Django :: 4.0 Framework :: Django :: 4.1 @@ -28,6 +26,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Software Development :: Libraries Topic :: Utilities @@ -39,7 +38,7 @@ packages = django_redis.serializers django_redis.compressors install_requires = - Django>=2.2 + Django>=3.2 redis>=3,!=4.0.0,!=4.0.1 [options.extras_require] @@ -73,10 +72,10 @@ envlist = isort mypy # tests against released versions - py{36,37,38,39,310}-dj{22,31,32,40,41}-redislatest + py{36,37,38,39,310,311}-dj{22,31,32,40,41}-redislatest # tests against unreleased versions - py310-dj40-redismaster - py310-djmain-redis{latest,master} + py311-dj41-redismaster + py311-djmain-redis{latest,master} [gh-actions] python = @@ -85,11 +84,10 @@ python = 3.8: py38, black, flake8, isort, mypy 3.9: py39 3.10: py310 + 3.11: py311 [gh-actions:env] DJANGO = - 2.2: dj22 - 3.1: dj31 3.2: dj32 4.0: dj40 4.1: dj41 @@ -114,8 +112,6 @@ commands = {envpython} -m coverage xml deps = - dj22: Django>=2.2,<2.3 - dj31: Django>=3.1,<3.2 dj32: Django>=3.2,<3.3 dj40: Django>=4.0,<4.1 dj41: Django>=4.1,<4.2 From 05ca48ad7211b2531047b8ad4393f4c86fd560b7 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sun, 22 Jan 2023 15:43:50 +0200 Subject: [PATCH 022/148] python 3.11 is supported only by django 4.1 and added changelog --- .github/workflows/ci.yml | 8 ++++++-- changelog.d/633.misc | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelog.d/633.misc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc191450..ced03f92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ jobs: - '3.8' - '3.9' - '3.10' - - '3.11' django-version: - '3.2' - '4.0' @@ -39,8 +38,13 @@ jobs: redis-version: 'latest' python-version: '3.7' + # Django 4.1 and python 3.11 with latest redis + - django-version: '4.1' + redis-version: 'latest' + python-version: '3.11' + # latest Django with pre-release redis - - django-version: '4.0' + - django-version: '4.1' redis-version: 'master' python-version: '3.11' diff --git a/changelog.d/633.misc b/changelog.d/633.misc new file mode 100644 index 00000000..2f4c8c21 --- /dev/null +++ b/changelog.d/633.misc @@ -0,0 +1 @@ +Added support for python 3.11 \ No newline at end of file From 81f89a4dab346234fb4b305af9472ad94a4401b2 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sun, 22 Jan 2023 16:01:42 +0200 Subject: [PATCH 023/148] Added changelog and fixed condition for pickle serializer for sessions --- changelog.d/646.bugfix | 1 + tests/test_session.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/646.bugfix diff --git a/changelog.d/646.bugfix b/changelog.d/646.bugfix new file mode 100644 index 00000000..149b155c --- /dev/null +++ b/changelog.d/646.bugfix @@ -0,0 +1 @@ +Skipping pickle serializer test for django >= 4.2 \ No newline at end of file diff --git a/tests/test_session.py b/tests/test_session.py index 8ae6d9b0..bcea9c39 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -368,7 +368,7 @@ class SessionTests(SessionTestsMixin, unittest.TestCase): backend = CacheSession @pytest.mark.skipif( - django.VERSION > (4, 2), + django.VERSION >= (4, 2), reason="PickleSerializer is removed as of https://code.djangoproject.com/ticket/29708", ) def test_actual_expiry(self): From 1cb16078935b69e982d8f2e11efe8f70891f49c5 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sun, 22 Jan 2023 16:03:27 +0200 Subject: [PATCH 024/148] Added line too long ignore --- tests/test_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_session.py b/tests/test_session.py index bcea9c39..36745035 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -369,7 +369,7 @@ class SessionTests(SessionTestsMixin, unittest.TestCase): @pytest.mark.skipif( django.VERSION >= (4, 2), - reason="PickleSerializer is removed as of https://code.djangoproject.com/ticket/29708", + reason="PickleSerializer is removed as of https://code.djangoproject.com/ticket/29708", # noqa: E501 ) def test_actual_expiry(self): if isinstance( From a2d9d2f1b983a4a81e797419e392c7407a4d4211 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 20:00:05 +0000 Subject: [PATCH 025/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0) - [github.com/asottile/pyupgrade: v3.2.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.3.1) - [github.com/psf/black: 22.10.0 → 23.1.0](https://github.com/psf/black/compare/22.10.0...23.1.0) - [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0) - [github.com/PyCQA/isort: 5.10.1 → 5.12.0](https://github.com/PyCQA/isort/compare/5.10.1...5.12.0) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63d3afd3..a7bfd584 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-ast - id: check-case-conflict @@ -10,21 +10,21 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.3.1 hooks: - id: pyupgrade - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 23.1.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort From 36b76ccf1f3c47a04c7f9b891f5aa0089d87671c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 20:00:21 +0000 Subject: [PATCH 026/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/client/herd.py | 1 - django_redis/client/sharded.py | 1 - django_redis/pool.py | 1 - tests/test_backend.py | 3 --- 4 files changed, 6 deletions(-) diff --git a/django_redis/client/herd.py b/django_redis/client/herd.py index e818a475..d5ab941d 100644 --- a/django_redis/client/herd.py +++ b/django_redis/client/herd.py @@ -69,7 +69,6 @@ def set( nx=False, xx=False, ): - if timeout is DEFAULT_TIMEOUT: timeout = self._backend.default_timeout diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index a1b0edd0..a30e177e 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -205,7 +205,6 @@ def lock( client=None, thread_local=True, ): - if client is None: key = self.make_key(key, version=version) client = self.get_server(key) diff --git a/django_redis/pool.py b/django_redis/pool.py index dda3b945..44afa943 100644 --- a/django_redis/pool.py +++ b/django_redis/pool.py @@ -10,7 +10,6 @@ class ConnectionFactory: - # Store connection pool by cache backend options. # # _pools is a process-global, as otherwise _pools is cleared every time diff --git a/tests/test_backend.py b/tests/test_backend.py index 165779ce..9fdba5a4 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -541,7 +541,6 @@ def test_ttl(self, cache: RedisCache): assert ttl == 0 def test_pttl(self, cache: RedisCache): - # Test pttl cache.set("foo", "bar", 10) ttl = cache.pttl("foo") @@ -599,7 +598,6 @@ def test_pexpire(self, cache: RedisCache): assert cache.pexpire("not-existent-key", 20500) is False def test_pexpire_at(self, cache: RedisCache): - # Test settings expiration time 1 hour ahead by datetime. cache.set("foo", "bar", timeout=None) expiration_time = datetime.datetime.now() + timedelta(hours=1) @@ -625,7 +623,6 @@ def test_pexpire_at(self, cache: RedisCache): assert cache.pexpire_at("not-existent-key", expiration_time) is False def test_expire_at(self, cache: RedisCache): - # Test settings expiration time 1 hour ahead by datetime. cache.set("foo", "bar", timeout=None) expiration_time = datetime.datetime.now() + timedelta(hours=1) From 202018b7e13aa169cc113e9b28393405fdf9f99e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 20:20:38 +0000 Subject: [PATCH 027/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a7bfd584..1ced8962 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: pyupgrade - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black From 9dc3e3029c48b1e1f492c49df38e19aed0714315 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Fri, 6 Jan 2023 20:27:47 -0300 Subject: [PATCH 028/148] Lazy access to Django settings. --- django_redis/cache.py | 5 ++--- django_redis/client/herd.py | 16 +++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/django_redis/cache.py b/django_redis/cache.py index ebf4d14b..795e5011 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -9,8 +9,6 @@ from .exceptions import ConnectionInterrupted -DJANGO_REDIS_SCAN_ITERSIZE = getattr(settings, "DJANGO_REDIS_SCAN_ITERSIZE", 10) - CONNECTION_INTERRUPTED = object() @@ -105,7 +103,8 @@ def delete(self, *args, **kwargs): @omit_exception def delete_pattern(self, *args, **kwargs): - kwargs["itersize"] = kwargs.get("itersize", DJANGO_REDIS_SCAN_ITERSIZE) + if not kwargs.get("itersize"): + kwargs["itersize"] = getattr(settings, "DJANGO_REDIS_SCAN_ITERSIZE", 10) return self.client.delete_pattern(*args, **kwargs) @omit_exception diff --git a/django_redis/client/herd.py b/django_redis/client/herd.py index d5ab941d..555f0d52 100644 --- a/django_redis/client/herd.py +++ b/django_redis/client/herd.py @@ -21,15 +21,12 @@ class Marker: pass -CACHE_HERD_TIMEOUT = getattr(settings, "CACHE_HERD_TIMEOUT", 60) - - -def _is_expired(x): - if x >= CACHE_HERD_TIMEOUT: +def _is_expired(x, herd_timeout): + if x >= herd_timeout: return True - val = x + random.randint(1, CACHE_HERD_TIMEOUT) + val = x + random.randint(1, herd_timeout) - if val >= CACHE_HERD_TIMEOUT: + if val >= herd_timeout: return True return False @@ -37,6 +34,7 @@ def _is_expired(x): class HerdClient(DefaultClient): def __init__(self, *args, **kwargs): self._marker = Marker() + self._herd_timeout = getattr(settings, "CACHE_HERD_TIMEOUT", 60) super().__init__(*args, **kwargs) def _pack(self, value, timeout): @@ -55,7 +53,7 @@ def _unpack(self, value): now = int(time.time()) if herd_timeout < now: x = now - herd_timeout - return unpacked, _is_expired(x) + return unpacked, _is_expired(x, self._herd_timeout) return unpacked, False @@ -84,7 +82,7 @@ def set( ) packed = self._pack(value, timeout) - real_timeout = timeout + CACHE_HERD_TIMEOUT + real_timeout = timeout + self._herd_timeout return super().set( key, packed, timeout=real_timeout, version=version, client=client, nx=nx From 0105fb1e819be269e81f199b911b697d63cfdcff Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Fri, 6 Jan 2023 20:55:17 -0300 Subject: [PATCH 029/148] Fix tests. --- tests/test_backend.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 9fdba5a4..c2ea2551 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -6,6 +6,7 @@ from unittest.mock import patch import pytest +from django.conf import settings from django.core.cache import caches from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture @@ -16,8 +17,6 @@ from django_redis.serializers.json import JSONSerializer from django_redis.serializers.msgpack import MSGPackSerializer -herd.CACHE_HERD_TIMEOUT = 2 - class TestDjangoRedisCache: def test_setnx(self, cache: RedisCache): @@ -199,7 +198,9 @@ def test_set_many(self, cache: RedisCache): res = cache.get_many(["a", "b", "c"]) assert res == {"a": 1, "b": 2, "c": 3} - def test_set_call_empty_pipeline(self, cache: RedisCache, mocker: MockerFixture): + def test_set_call_empty_pipeline(self, cache: RedisCache, mocker: MockerFixture, settings: SettingsWrapper): + settings.CACHE_HERD_TIMEOUT = 2 + if isinstance(cache.client, ShardClient): pytest.skip("ShardClient doesn't support get_client") @@ -212,7 +213,7 @@ def test_set_call_empty_pipeline(self, cache: RedisCache, mocker: MockerFixture) if isinstance(cache.client, herd.HerdClient): default_timeout = cache.client._backend.default_timeout - herd_timeout = (default_timeout + herd.CACHE_HERD_TIMEOUT) * 1000 # type: ignore # noqa + herd_timeout = (default_timeout + settings.CACHE_HERD_TIMEOUT) * 1000 # type: ignore # noqa herd_pack_value = cache.client._pack(value, default_timeout) mocked_set.assert_called_once_with( cache.client.make_key(key, version=None), @@ -495,11 +496,13 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") def test_delete_pattern_with_settings_default_scan_count( - self, client_mock, cache: RedisCache + self, client_mock, cache: RedisCache, settings: SettingsWrapper, ): + settings.DJANGO_REDIS_SCAN_ITERSIZE = 30 + for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]: cache.set(key, "foo") - expected_count = django_redis.cache.DJANGO_REDIS_SCAN_ITERSIZE + expected_count = settings.DJANGO_REDIS_SCAN_ITERSIZE cache.delete_pattern("*foo-a*") From b88a718a3d1c5e126d3cd881759f57b2ba1b1b10 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Fri, 6 Jan 2023 20:55:33 -0300 Subject: [PATCH 030/148] Fix tox passenv. I don't know it changed. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e0106810..2aa38cc1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -97,7 +97,7 @@ REDIS = master: redismaster [testenv] -passenv = CI GITHUB* +passenv = CI,GITHUB* commands = {envpython} -m pytest --cov-report= --ds=settings.sqlite {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_json {posargs} From 7a61ec9db0825d92374a2da3ba5852d3eb8c4177 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Fri, 6 Jan 2023 20:57:45 -0300 Subject: [PATCH 031/148] LInt. --- tests/test_backend.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index c2ea2551..9594e51d 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -6,12 +6,10 @@ from unittest.mock import patch import pytest -from django.conf import settings from django.core.cache import caches from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture -import django_redis.cache from django_redis.cache import RedisCache from django_redis.client import ShardClient, herd from django_redis.serializers.json import JSONSerializer From dde64b73828cedac6e4b4d1145c08c58627b4289 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Fri, 6 Jan 2023 21:22:28 -0300 Subject: [PATCH 032/148] changelog --- changelog.d/638.removal | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/638.removal diff --git a/changelog.d/638.removal b/changelog.d/638.removal new file mode 100644 index 00000000..909ec48e --- /dev/null +++ b/changelog.d/638.removal @@ -0,0 +1 @@ +Removed `django_redis.cache.DJANGO_REDIS_SCAN_ITERSIZE` and `django_redis.client.herd.CACHE_HERD_TIMEOUT` to not access Django settings in import time. \ No newline at end of file From fe99e5c841cfce4b5ac1efefe038c688935d3ea5 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Thu, 19 Jan 2023 11:48:03 -0300 Subject: [PATCH 033/148] revert passenv --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 2aa38cc1..e0106810 100644 --- a/setup.cfg +++ b/setup.cfg @@ -97,7 +97,7 @@ REDIS = master: redismaster [testenv] -passenv = CI,GITHUB* +passenv = CI GITHUB* commands = {envpython} -m pytest --cov-report= --ds=settings.sqlite {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_json {posargs} From 7284dacdd913f0bd4f1707dc2f7a4c83016d16a0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 13:42:13 +0000 Subject: [PATCH 034/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_backend.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 9594e51d..3206bfce 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -196,7 +196,9 @@ def test_set_many(self, cache: RedisCache): res = cache.get_many(["a", "b", "c"]) assert res == {"a": 1, "b": 2, "c": 3} - def test_set_call_empty_pipeline(self, cache: RedisCache, mocker: MockerFixture, settings: SettingsWrapper): + def test_set_call_empty_pipeline( + self, cache: RedisCache, mocker: MockerFixture, settings: SettingsWrapper + ): settings.CACHE_HERD_TIMEOUT = 2 if isinstance(cache.client, ShardClient): @@ -494,7 +496,10 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") def test_delete_pattern_with_settings_default_scan_count( - self, client_mock, cache: RedisCache, settings: SettingsWrapper, + self, + client_mock, + cache: RedisCache, + settings: SettingsWrapper, ): settings.DJANGO_REDIS_SCAN_ITERSIZE = 30 From 0a2ca0d881287114bc77ab91b8607f7b25ad748e Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Thu, 1 Jun 2023 16:20:04 +0100 Subject: [PATCH 035/148] Run herd tests. --- setup.cfg | 1 + tests/conftest.py | 6 ++++++ tests/test_backend.py | 2 -- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index e0106810..3c76f354 100644 --- a/setup.cfg +++ b/setup.cfg @@ -100,6 +100,7 @@ REDIS = passenv = CI GITHUB* commands = {envpython} -m pytest --cov-report= --ds=settings.sqlite {posargs} + {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_herd {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_json {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_lz4 {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_msgpack {posargs} diff --git a/tests/conftest.py b/tests/conftest.py index 59ea7d8d..85e9e57f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,17 @@ from typing import Iterable import pytest +from pytest_django.fixtures import SettingsWrapper from django.core.cache import cache as default_cache from django_redis.cache import BaseCache +@pytest.fixture(autouse=True) +def patch_settings(settings: SettingsWrapper): + settings.CACHE_HERD_TIMEOUT = 2 + + @pytest.fixture def cache() -> Iterable[BaseCache]: yield default_cache diff --git a/tests/test_backend.py b/tests/test_backend.py index 3206bfce..5ddfa1dc 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -199,8 +199,6 @@ def test_set_many(self, cache: RedisCache): def test_set_call_empty_pipeline( self, cache: RedisCache, mocker: MockerFixture, settings: SettingsWrapper ): - settings.CACHE_HERD_TIMEOUT = 2 - if isinstance(cache.client, ShardClient): pytest.skip("ShardClient doesn't support get_client") From dc91afbcf1018c4b55803f1323a9f5903059261d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 15:22:45 +0000 Subject: [PATCH 036/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 85e9e57f..61ce16cf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,8 @@ from typing import Iterable import pytest -from pytest_django.fixtures import SettingsWrapper from django.core.cache import cache as default_cache +from pytest_django.fixtures import SettingsWrapper from django_redis.cache import BaseCache From 3b597f05a2896494e1f2b43b7246599fd54fe335 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Thu, 1 Jun 2023 16:35:11 +0100 Subject: [PATCH 037/148] Fix lint and mypy. --- tests/test_backend.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 5ddfa1dc..4445fd75 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -211,7 +211,7 @@ def test_set_call_empty_pipeline( if isinstance(cache.client, herd.HerdClient): default_timeout = cache.client._backend.default_timeout - herd_timeout = (default_timeout + settings.CACHE_HERD_TIMEOUT) * 1000 # type: ignore # noqa + herd_timeout = (default_timeout + settings.CACHE_HERD_TIMEOUT) * 1000 herd_pack_value = cache.client._pack(value, default_timeout) mocked_set.assert_called_once_with( cache.client.make_key(key, version=None), @@ -494,10 +494,7 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") def test_delete_pattern_with_settings_default_scan_count( - self, - client_mock, - cache: RedisCache, - settings: SettingsWrapper, + self, client_mock, cache: RedisCache, settings: SettingsWrapper, ): settings.DJANGO_REDIS_SCAN_ITERSIZE = 30 From db54da5b7c18d7ce8f9ae271f966e3e28bb8ed50 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 15:35:42 +0000 Subject: [PATCH 038/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_backend.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 4445fd75..e222c7e7 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -494,7 +494,10 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") def test_delete_pattern_with_settings_default_scan_count( - self, client_mock, cache: RedisCache, settings: SettingsWrapper, + self, + client_mock, + cache: RedisCache, + settings: SettingsWrapper, ): settings.DJANGO_REDIS_SCAN_ITERSIZE = 30 From 9c787b56dc44aebc3833b51cac80a04f3d7e08ad Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 12:50:30 +0200 Subject: [PATCH 039/148] Changelog it as bugfix. --- changelog.d/638.bugfix | 1 + changelog.d/638.removal | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/638.bugfix delete mode 100644 changelog.d/638.removal diff --git a/changelog.d/638.bugfix b/changelog.d/638.bugfix new file mode 100644 index 00000000..ade737ce --- /dev/null +++ b/changelog.d/638.bugfix @@ -0,0 +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. \ No newline at end of file diff --git a/changelog.d/638.removal b/changelog.d/638.removal deleted file mode 100644 index 909ec48e..00000000 --- a/changelog.d/638.removal +++ /dev/null @@ -1 +0,0 @@ -Removed `django_redis.cache.DJANGO_REDIS_SCAN_ITERSIZE` and `django_redis.client.herd.CACHE_HERD_TIMEOUT` to not access Django settings in import time. \ No newline at end of file From 113c294da3247e14fe0b47ed698f8c67db63b2d9 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 12:51:10 +0200 Subject: [PATCH 040/148] Persist itersize on RedisCache init. --- django_redis/cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django_redis/cache.py b/django_redis/cache.py index 795e5011..8a0e81b7 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -43,6 +43,7 @@ def __init__(self, server: str, params: Dict[str, Any]) -> None: super().__init__(params) self._server = server self._params = params + self._default_scan_itersize = getattr(settings, "DJANGO_REDIS_SCAN_ITERSIZE", 10) options = params.get("OPTIONS", {}) self._client_cls = options.get( @@ -103,8 +104,7 @@ def delete(self, *args, **kwargs): @omit_exception def delete_pattern(self, *args, **kwargs): - if not kwargs.get("itersize"): - kwargs["itersize"] = getattr(settings, "DJANGO_REDIS_SCAN_ITERSIZE", 10) + kwargs.setdefault("itersize", self._default_scan_itersize) return self.client.delete_pattern(*args, **kwargs) @omit_exception From cfdd96f210e70cf6475596e61a8e901114437a76 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 13:04:46 +0200 Subject: [PATCH 041/148] Use override_settings instead of pytest test settings. --- tests/test_backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index e222c7e7..8fa06e0a 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -7,6 +7,7 @@ import pytest from django.core.cache import caches +from django.test import override_settings from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture @@ -493,14 +494,13 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): client_mock.delete_pattern.assert_called_once_with("*foo-a*", itersize=2) @patch("django_redis.cache.RedisCache.client") + @override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30) def test_delete_pattern_with_settings_default_scan_count( self, client_mock, cache: RedisCache, settings: SettingsWrapper, ): - settings.DJANGO_REDIS_SCAN_ITERSIZE = 30 - for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]: cache.set(key, "foo") expected_count = settings.DJANGO_REDIS_SCAN_ITERSIZE From 5e5d09515a147d5e97b76ef85538de4a6cd01e91 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 11 Jun 2023 11:05:14 +0000 Subject: [PATCH 042/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/cache.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/django_redis/cache.py b/django_redis/cache.py index 8a0e81b7..0b4d5890 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -43,7 +43,9 @@ def __init__(self, server: str, params: Dict[str, Any]) -> None: super().__init__(params) self._server = server self._params = params - self._default_scan_itersize = getattr(settings, "DJANGO_REDIS_SCAN_ITERSIZE", 10) + self._default_scan_itersize = getattr( + settings, "DJANGO_REDIS_SCAN_ITERSIZE", 10 + ) options = params.get("OPTIONS", {}) self._client_cls = options.get( From 06a7cae105b85e5f17fd95de4acd3ac995189dee Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 19:43:44 +0200 Subject: [PATCH 043/148] Use local fixture to patch herd settings, --- tests/conftest.py | 6 ------ tests/test_backend.py | 35 +++++++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 61ce16cf..59ea7d8d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,16 +2,10 @@ import pytest from django.core.cache import cache as default_cache -from pytest_django.fixtures import SettingsWrapper from django_redis.cache import BaseCache -@pytest.fixture(autouse=True) -def patch_settings(settings: SettingsWrapper): - settings.CACHE_HERD_TIMEOUT = 2 - - @pytest.fixture def cache() -> Iterable[BaseCache]: yield default_cache diff --git a/tests/test_backend.py b/tests/test_backend.py index 8fa06e0a..9474f642 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -17,6 +17,20 @@ from django_redis.serializers.msgpack import MSGPackSerializer +@pytest.fixture +def patch_herd_settings(): + default_cache = caches["default"] + if not isinstance(default_cache.client, herd.HerdClient): + yield + + # destroy cache to force recreation with updated settings + del caches["default"] + with override_settings(CACHE_HERD_TIMEOUT=2): + yield + # destroy cache force recreation with original settings + del caches["default"] + + class TestDjangoRedisCache: def test_setnx(self, cache: RedisCache): # we should ensure there is no test_key_nx in redis @@ -36,7 +50,7 @@ def test_setnx(self, cache: RedisCache): res = cache.get("test_key_nx") assert res is None - def test_setnx_timeout(self, cache: RedisCache): + def test_setnx_timeout(self, patch_herd_settings, cache: RedisCache): # test that timeout still works for nx=True res = cache.set("test_key_nx", 1, timeout=2, nx=True) assert res is True @@ -115,7 +129,7 @@ def test_save_float(self, cache: RedisCache): assert isinstance(res, float) assert res == float_val - def test_timeout(self, cache: RedisCache): + def test_timeout(self, patch_herd_settings, cache: RedisCache): cache.set("test_key", 222, timeout=3) time.sleep(4) @@ -127,7 +141,7 @@ def test_timeout_0(self, cache: RedisCache): res = cache.get("test_key") assert res is None - def test_timeout_parameter_as_positional_argument(self, cache: RedisCache): + def test_timeout_parameter_as_positional_argument(self, patch_herd_settings, cache: RedisCache): cache.set("test_key", 222, -1) res = cache.get("test_key") assert res is None @@ -198,7 +212,11 @@ def test_set_many(self, cache: RedisCache): assert res == {"a": 1, "b": 2, "c": 3} def test_set_call_empty_pipeline( - self, cache: RedisCache, mocker: MockerFixture, settings: SettingsWrapper + self, + patch_herd_settings, + cache: RedisCache, + mocker: MockerFixture, + settings: SettingsWrapper, ): if isinstance(cache.client, ShardClient): pytest.skip("ShardClient doesn't support get_client") @@ -496,10 +514,7 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") @override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30) def test_delete_pattern_with_settings_default_scan_count( - self, - client_mock, - cache: RedisCache, - settings: SettingsWrapper, + self, client_mock, patch_herd_settings, cache: RedisCache, settings: SettingsWrapper, ): for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]: cache.set(key, "foo") @@ -521,7 +536,7 @@ def test_close_client(self, cache: RedisCache, mocker: MockerFixture): cache.close() assert mock.called - def test_ttl(self, cache: RedisCache): + def test_ttl(self, patch_herd_settings, cache: RedisCache): cache.set("foo", "bar", 10) ttl = cache.ttl("foo") @@ -728,7 +743,7 @@ def test_touch_zero_timeout(self, cache: RedisCache): res = cache.get("test_key") assert res is None - def test_touch_positive_timeout(self, cache: RedisCache): + def test_touch_positive_timeout(self, patch_herd_settings, cache: RedisCache): cache.set("test_key", 222, timeout=10) assert cache.touch("test_key", 2) is True From 9992d6bf5635ebe8b558a19ca6b9b034b3058204 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 11 Jun 2023 17:44:43 +0000 Subject: [PATCH 044/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_backend.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 9474f642..903b0112 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -141,7 +141,9 @@ def test_timeout_0(self, cache: RedisCache): res = cache.get("test_key") assert res is None - def test_timeout_parameter_as_positional_argument(self, patch_herd_settings, cache: RedisCache): + def test_timeout_parameter_as_positional_argument( + self, patch_herd_settings, cache: RedisCache + ): cache.set("test_key", 222, -1) res = cache.get("test_key") assert res is None @@ -514,7 +516,11 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") @override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30) def test_delete_pattern_with_settings_default_scan_count( - self, client_mock, patch_herd_settings, cache: RedisCache, settings: SettingsWrapper, + self, + client_mock, + patch_herd_settings, + cache: RedisCache, + settings: SettingsWrapper, ): for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]: cache.set(key, "foo") From e486bb6ca5fcac66e9fe39b7d18813c1db33ead2 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 19:48:31 +0200 Subject: [PATCH 045/148] Only one yield. --- tests/test_backend.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 903b0112..85c0735b 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -20,16 +20,16 @@ @pytest.fixture def patch_herd_settings(): default_cache = caches["default"] - if not isinstance(default_cache.client, herd.HerdClient): + if isinstance(default_cache.client, herd.HerdClient): + # destroy cache to force recreation with updated settings + del caches["default"] + with override_settings(CACHE_HERD_TIMEOUT=2): + yield + # destroy cache force recreation with original settings + del caches["default"] + else: yield - # destroy cache to force recreation with updated settings - del caches["default"] - with override_settings(CACHE_HERD_TIMEOUT=2): - yield - # destroy cache force recreation with original settings - del caches["default"] - class TestDjangoRedisCache: def test_setnx(self, cache: RedisCache): From 36a54cc886d93d5486d1ab11dbfa4d0351228398 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 20:31:11 +0200 Subject: [PATCH 046/148] Cleanup. --- tests/settings/sqlite_herd.py | 2 ++ tests/test_backend.py | 33 ++++++--------------------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/tests/settings/sqlite_herd.py b/tests/settings/sqlite_herd.py index 7cfb7af4..9bac3a4c 100644 --- a/tests/settings/sqlite_herd.py +++ b/tests/settings/sqlite_herd.py @@ -27,3 +27,5 @@ INSTALLED_APPS = ["django.contrib.sessions"] USE_TZ = False + +CACHE_HERD_TIMEOUT = 2 diff --git a/tests/test_backend.py b/tests/test_backend.py index 85c0735b..dfedd24d 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -17,20 +17,6 @@ from django_redis.serializers.msgpack import MSGPackSerializer -@pytest.fixture -def patch_herd_settings(): - default_cache = caches["default"] - if isinstance(default_cache.client, herd.HerdClient): - # destroy cache to force recreation with updated settings - del caches["default"] - with override_settings(CACHE_HERD_TIMEOUT=2): - yield - # destroy cache force recreation with original settings - del caches["default"] - else: - yield - - class TestDjangoRedisCache: def test_setnx(self, cache: RedisCache): # we should ensure there is no test_key_nx in redis @@ -50,7 +36,7 @@ def test_setnx(self, cache: RedisCache): res = cache.get("test_key_nx") assert res is None - def test_setnx_timeout(self, patch_herd_settings, cache: RedisCache): + def test_setnx_timeout(self, cache: RedisCache): # test that timeout still works for nx=True res = cache.set("test_key_nx", 1, timeout=2, nx=True) assert res is True @@ -129,7 +115,7 @@ def test_save_float(self, cache: RedisCache): assert isinstance(res, float) assert res == float_val - def test_timeout(self, patch_herd_settings, cache: RedisCache): + def test_timeout(self, cache: RedisCache): cache.set("test_key", 222, timeout=3) time.sleep(4) @@ -141,9 +127,7 @@ def test_timeout_0(self, cache: RedisCache): res = cache.get("test_key") assert res is None - def test_timeout_parameter_as_positional_argument( - self, patch_herd_settings, cache: RedisCache - ): + def test_timeout_parameter_as_positional_argument(self, cache: RedisCache): cache.set("test_key", 222, -1) res = cache.get("test_key") assert res is None @@ -215,7 +199,6 @@ def test_set_many(self, cache: RedisCache): def test_set_call_empty_pipeline( self, - patch_herd_settings, cache: RedisCache, mocker: MockerFixture, settings: SettingsWrapper, @@ -516,11 +499,7 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") @override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30) def test_delete_pattern_with_settings_default_scan_count( - self, - client_mock, - patch_herd_settings, - cache: RedisCache, - settings: SettingsWrapper, + self, client_mock, cache: RedisCache, settings: SettingsWrapper, ): for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]: cache.set(key, "foo") @@ -542,7 +521,7 @@ def test_close_client(self, cache: RedisCache, mocker: MockerFixture): cache.close() assert mock.called - def test_ttl(self, patch_herd_settings, cache: RedisCache): + def test_ttl(self, cache: RedisCache): cache.set("foo", "bar", 10) ttl = cache.ttl("foo") @@ -749,7 +728,7 @@ def test_touch_zero_timeout(self, cache: RedisCache): res = cache.get("test_key") assert res is None - def test_touch_positive_timeout(self, patch_herd_settings, cache: RedisCache): + def test_touch_positive_timeout(self, cache: RedisCache): cache.set("test_key", 222, timeout=10) assert cache.touch("test_key", 2) is True From 60500d11bf774e12e5d60c658cc91cfc8dbc9af9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 11 Jun 2023 18:35:37 +0000 Subject: [PATCH 047/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_backend.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index dfedd24d..440d1fef 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -499,7 +499,10 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") @override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30) def test_delete_pattern_with_settings_default_scan_count( - self, client_mock, cache: RedisCache, settings: SettingsWrapper, + self, + client_mock, + cache: RedisCache, + settings: SettingsWrapper, ): for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]: cache.set(key, "foo") From 418c89ff05a2efbf9c8498959454a6f7c1bd81ac Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 21:14:41 +0200 Subject: [PATCH 048/148] Fixture to patch itersize. --- tests/test_backend.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 440d1fef..35f6f9ea 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -17,6 +17,16 @@ from django_redis.serializers.msgpack import MSGPackSerializer +@pytest.fixture +def patch_itersize(): + # destroy cache to force recreation with overriden settings + del caches["default"] + with override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30): + yield + # destroy cache to force recreation with original settings + del caches["default"] + + class TestDjangoRedisCache: def test_setnx(self, cache: RedisCache): # we should ensure there is no test_key_nx in redis @@ -497,12 +507,8 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): client_mock.delete_pattern.assert_called_once_with("*foo-a*", itersize=2) @patch("django_redis.cache.RedisCache.client") - @override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30) def test_delete_pattern_with_settings_default_scan_count( - self, - client_mock, - cache: RedisCache, - settings: SettingsWrapper, + self, client_mock, patch_itersize, cache: RedisCache, settings: SettingsWrapper, ): for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]: cache.set(key, "foo") From 84209b506c7e79f3334227b759795838e95a3d5b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 11 Jun 2023 19:15:41 +0000 Subject: [PATCH 049/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_backend.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 35f6f9ea..9766b7fa 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -508,7 +508,11 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): @patch("django_redis.cache.RedisCache.client") def test_delete_pattern_with_settings_default_scan_count( - self, client_mock, patch_itersize, cache: RedisCache, settings: SettingsWrapper, + self, + client_mock, + patch_itersize, + cache: RedisCache, + settings: SettingsWrapper, ): for key in ["foo-aa", "foo-ab", "foo-bb", "foo-bc"]: cache.set(key, "foo") From 0af03b47233f580de678961f7ad05a6c84437728 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 21:36:48 +0200 Subject: [PATCH 050/148] Trying to make mypy happy. --- django_redis/client/herd.py | 2 +- tests/test_backend.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/django_redis/client/herd.py b/django_redis/client/herd.py index 555f0d52..0c52480f 100644 --- a/django_redis/client/herd.py +++ b/django_redis/client/herd.py @@ -21,7 +21,7 @@ class Marker: pass -def _is_expired(x, herd_timeout): +def _is_expired(x, herd_timeout: int) -> bool: if x >= herd_timeout: return True val = x + random.randint(1, herd_timeout) diff --git a/tests/test_backend.py b/tests/test_backend.py index 9766b7fa..8c730c76 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -18,7 +18,7 @@ @pytest.fixture -def patch_itersize(): +def patch_itersize_setting() -> Iterable[None]: # destroy cache to force recreation with overriden settings del caches["default"] with override_settings(DJANGO_REDIS_SCAN_ITERSIZE=30): @@ -510,7 +510,7 @@ def test_delete_pattern_with_custom_count(self, client_mock, cache: RedisCache): def test_delete_pattern_with_settings_default_scan_count( self, client_mock, - patch_itersize, + patch_itersize_setting, cache: RedisCache, settings: SettingsWrapper, ): From e7ee9815cad68f449a999bb6da7b8678b801c9c7 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 11 Jun 2023 21:39:54 +0200 Subject: [PATCH 051/148] Import iterable. --- tests/test_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 8c730c76..7f4beb70 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -2,7 +2,7 @@ import threading import time from datetime import timedelta -from typing import List, Union, cast +from typing import Iterable, List, Union, cast from unittest.mock import patch import pytest From 12c697b6675b6c43b04ad2218009df415e577adf Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Thu, 15 Jun 2023 15:07:02 +0200 Subject: [PATCH 052/148] Add Django 4.2 to test matrix and classifiers --- .github/workflows/ci.yml | 8 +++++++- changelog.d/668.misc | 1 + setup.cfg | 7 +++++-- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 changelog.d/668.misc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ced03f92..1b5bee6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: - '3.2' - '4.0' - '4.1' + - '4.2' redis-version: - 'latest' @@ -43,8 +44,13 @@ jobs: redis-version: 'latest' python-version: '3.11' + # Django 4.2 and python 3.11 with latest redis + - django-version: '4.2' + redis-version: 'latest' + python-version: '3.11' + # latest Django with pre-release redis - - django-version: '4.1' + - django-version: '4.2' redis-version: 'master' python-version: '3.11' diff --git a/changelog.d/668.misc b/changelog.d/668.misc new file mode 100644 index 00000000..6c146efa --- /dev/null +++ b/changelog.d/668.misc @@ -0,0 +1 @@ +Run actions & tox against Django 4..2 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 3c76f354..9395f819 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,7 @@ classifiers = Framework :: Django :: 3.2 Framework :: Django :: 4.0 Framework :: Django :: 4.1 + Framework :: Django :: 4.2 Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent @@ -72,9 +73,9 @@ envlist = isort mypy # tests against released versions - py{36,37,38,39,310,311}-dj{22,31,32,40,41}-redislatest + py{36,37,38,39,310,311}-dj{22,31,32,40,41,42}-redislatest # tests against unreleased versions - py311-dj41-redismaster + py311-dj42-redismaster py311-djmain-redis{latest,master} [gh-actions] @@ -91,6 +92,7 @@ DJANGO = 3.2: dj32 4.0: dj40 4.1: dj41 + 4.2: dj42 main: djmain REDIS = latest: redislatest @@ -116,6 +118,7 @@ deps = dj32: Django>=3.2,<3.3 dj40: Django>=4.0,<4.1 dj41: Django>=4.1,<4.2 + dj42: Django>=4.2,<5.0 djmain: https://github.com/django/django/archive/main.tar.gz msgpack>=0.6.0 pytest From c479e955eaa23ee334cec4f9e38ce909617407a4 Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Fri, 16 Jun 2023 13:51:10 +0200 Subject: [PATCH 053/148] Prepare release of django-redis 5.3.0 --- CHANGELOG.rst | 28 ++++++++++++++++++++++++++++ changelog.d/609.misc | 1 - changelog.d/611.misc | 1 - changelog.d/626.misc | 1 - changelog.d/627.feature | 1 - changelog.d/633.misc | 1 - changelog.d/638.bugfix | 1 - changelog.d/645.misc | 1 - changelog.d/646.bugfix | 1 - changelog.d/649.misc | 1 - changelog.d/668.misc | 1 - django_redis/__init__.py | 2 +- 12 files changed, 29 insertions(+), 11 deletions(-) delete mode 100644 changelog.d/609.misc delete mode 100644 changelog.d/611.misc delete mode 100644 changelog.d/626.misc delete mode 100644 changelog.d/627.feature delete mode 100644 changelog.d/633.misc delete mode 100644 changelog.d/638.bugfix delete mode 100644 changelog.d/645.misc delete mode 100644 changelog.d/646.bugfix delete mode 100644 changelog.d/649.misc delete mode 100644 changelog.d/668.misc diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9385e3e4..f802d185 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,34 @@ Changelog .. towncrier release notes start +django-redis 5.3.0 (2023-06-16) +=============================== + +Features +-------- + +- Add support for django 4 (`#627 `_) + + +Bug Fixes +--------- + +- 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. (`#638 `_) +- Skipping pickle serializer test for django >= 4.2 (`#646 `_) + + +Miscellaneous +------------- + +- Speed up deleting multiple keys by a pattern with pipelines and larger itersize (`#609 `_) +- Print full exception traceback when logging ignored exceptions (`#611 `_) +- Fix mypy linting (`#626 `_) +- Added support for python 3.11 (`#633 `_) +- Fix CI, running tox<4 to still support Python 3.6. (`#645 `_) +- Dropped support for django 2.2 and 3.1 (`#649 `_) +- Run actions & tox against Django 4..2 (`#668 `_) + + django-redis 5.2.0 (2021-12-22) =============================== diff --git a/changelog.d/609.misc b/changelog.d/609.misc deleted file mode 100644 index e9f5891b..00000000 --- a/changelog.d/609.misc +++ /dev/null @@ -1 +0,0 @@ -Speed up deleting multiple keys by a pattern with pipelines and larger itersize \ No newline at end of file diff --git a/changelog.d/611.misc b/changelog.d/611.misc deleted file mode 100644 index bd6260c4..00000000 --- a/changelog.d/611.misc +++ /dev/null @@ -1 +0,0 @@ -Print full exception traceback when logging ignored exceptions diff --git a/changelog.d/626.misc b/changelog.d/626.misc deleted file mode 100644 index c9da403d..00000000 --- a/changelog.d/626.misc +++ /dev/null @@ -1 +0,0 @@ -Fix mypy linting \ No newline at end of file diff --git a/changelog.d/627.feature b/changelog.d/627.feature deleted file mode 100644 index 5657ecf0..00000000 --- a/changelog.d/627.feature +++ /dev/null @@ -1 +0,0 @@ -Add support for django 4 \ No newline at end of file diff --git a/changelog.d/633.misc b/changelog.d/633.misc deleted file mode 100644 index 2f4c8c21..00000000 --- a/changelog.d/633.misc +++ /dev/null @@ -1 +0,0 @@ -Added support for python 3.11 \ No newline at end of file diff --git a/changelog.d/638.bugfix b/changelog.d/638.bugfix deleted file mode 100644 index ade737ce..00000000 --- a/changelog.d/638.bugfix +++ /dev/null @@ -1 +0,0 @@ -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. \ No newline at end of file diff --git a/changelog.d/645.misc b/changelog.d/645.misc deleted file mode 100644 index 318edf46..00000000 --- a/changelog.d/645.misc +++ /dev/null @@ -1 +0,0 @@ -Fix CI, running tox<4 to still support Python 3.6. \ No newline at end of file diff --git a/changelog.d/646.bugfix b/changelog.d/646.bugfix deleted file mode 100644 index 149b155c..00000000 --- a/changelog.d/646.bugfix +++ /dev/null @@ -1 +0,0 @@ -Skipping pickle serializer test for django >= 4.2 \ No newline at end of file diff --git a/changelog.d/649.misc b/changelog.d/649.misc deleted file mode 100644 index fb1e40d9..00000000 --- a/changelog.d/649.misc +++ /dev/null @@ -1 +0,0 @@ -Dropped support for django 2.2 and 3.1 \ No newline at end of file diff --git a/changelog.d/668.misc b/changelog.d/668.misc deleted file mode 100644 index 6c146efa..00000000 --- a/changelog.d/668.misc +++ /dev/null @@ -1 +0,0 @@ -Run actions & tox against Django 4..2 \ No newline at end of file diff --git a/django_redis/__init__.py b/django_redis/__init__.py index 108780d6..f83bf322 100644 --- a/django_redis/__init__.py +++ b/django_redis/__init__.py @@ -1,4 +1,4 @@ -VERSION = (5, 2, 0) +VERSION = (5, 3, 0) __version__ = ".".join(map(str, VERSION)) From 78320ad66dfe8e2d394a57ca974ec343483411b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 20:02:42 +0000 Subject: [PATCH 054/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.1 → v3.8.0](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.8.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1ced8962..d3e21bad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.8.0 hooks: - id: pyupgrade From 8f924c7a24a60a83a33630a319f479c6511dc4d7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:28:03 +0000 Subject: [PATCH 055/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.8.0 → v3.9.0](https://github.com/asottile/pyupgrade/compare/v3.8.0...v3.9.0) - [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3e21bad..25b4fb76 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,12 +10,12 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v3.8.0 + rev: v3.9.0 hooks: - id: pyupgrade - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black From 22c77a150f4448a23c140ee2cc97e3abaab7162c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 20:30:36 +0000 Subject: [PATCH 056/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.9.0 → v3.10.1](https://github.com/asottile/pyupgrade/compare/v3.9.0...v3.10.1) - [github.com/PyCQA/flake8: 6.0.0 → 6.1.0](https://github.com/PyCQA/flake8/compare/6.0.0...6.1.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 25b4fb76..a409013f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v3.9.0 + rev: v3.10.1 hooks: - id: pyupgrade @@ -20,7 +20,7 @@ repos: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 From 95d76a428117034d6835d9896340ebc1d17b4bfa Mon Sep 17 00:00:00 2001 From: Jack Linke <73554672+jacklinke@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:28:28 -0400 Subject: [PATCH 057/148] Update README.rst Added note about configuring pluggable parser with hiredis for those who are using redis-py 5, resolving #676 --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 9e403349..40e0831d 100644 --- a/README.rst +++ b/README.rst @@ -754,6 +754,8 @@ used with django-redis. "PARSER_CLASS": "redis.connection.HiredisParser", } +Note: if using version 5 of redis-py, you should use ``"redis.connection._HiredisParser"`` do to an internal rename of classes within that package. + Pluggable clients ~~~~~~~~~~~~~~~~~ From a636791925464dd929eb2acf4ab1cfedbaf19e7f Mon Sep 17 00:00:00 2001 From: Jack Linke <73554672+jacklinke@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:30:32 -0400 Subject: [PATCH 058/148] Update README.rst Fix spelling/grammar --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 40e0831d..ccf323b1 100644 --- a/README.rst +++ b/README.rst @@ -754,7 +754,7 @@ used with django-redis. "PARSER_CLASS": "redis.connection.HiredisParser", } -Note: if using version 5 of redis-py, you should use ``"redis.connection._HiredisParser"`` do to an internal rename of classes within that package. +Note: if using version 5 of redis-py, use ``"redis.connection._HiredisParser"`` for the ``PARSER_CLASS`` due to an internal rename of classes within that package. Pluggable clients ~~~~~~~~~~~~~~~~~ From 2d339711bd632c6bd6edaca103f9b7871d81dc54 Mon Sep 17 00:00:00 2001 From: Jack Linke <73554672+jacklinke@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:39:11 -0400 Subject: [PATCH 059/148] Create 677.doc --- changelog.d/677.doc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/677.doc diff --git a/changelog.d/677.doc b/changelog.d/677.doc new file mode 100644 index 00000000..a0e0d999 --- /dev/null +++ b/changelog.d/677.doc @@ -0,0 +1 @@ +Added note in docs for correctly configuring hiredis parser when using redis-py version 5. From 2e0c1f924892e39c863b83b2a777356731a9aecc Mon Sep 17 00:00:00 2001 From: Jack Linke <73554672+jacklinke@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:39:53 -0400 Subject: [PATCH 060/148] Delete 677.doc Committed to wrong branch --- changelog.d/677.doc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 changelog.d/677.doc diff --git a/changelog.d/677.doc b/changelog.d/677.doc deleted file mode 100644 index a0e0d999..00000000 --- a/changelog.d/677.doc +++ /dev/null @@ -1 +0,0 @@ -Added note in docs for correctly configuring hiredis parser when using redis-py version 5. From c85f609837d760f9f7d50a4390552746d15feca9 Mon Sep 17 00:00:00 2001 From: Jack Linke <73554672+jacklinke@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:40:16 -0400 Subject: [PATCH 061/148] Create 677.doc --- changelog.d/677.doc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/677.doc diff --git a/changelog.d/677.doc b/changelog.d/677.doc new file mode 100644 index 00000000..a0e0d999 --- /dev/null +++ b/changelog.d/677.doc @@ -0,0 +1 @@ +Added note in docs for correctly configuring hiredis parser when using redis-py version 5. From d79f4c9d9d613a96cbcbcae2f92972460d269f35 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 20:32:59 +0000 Subject: [PATCH 062/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.10.1 → v3.11.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.11.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a409013f..73e6547b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,12 +10,12 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.11.0 hooks: - id: pyupgrade - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black From 541dbb4a073d8e049dff8dca8594fdae5942ed77 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:52:58 +0000 Subject: [PATCH 063/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.11.0 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.11.0...v3.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 73e6547b..5a971da0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v3.11.0 + rev: v3.13.0 hooks: - id: pyupgrade From 77097d3fde4b061281b1e2d3a182adf7f6035da3 Mon Sep 17 00:00:00 2001 From: RealFatCat Date: Mon, 25 Sep 2023 15:38:35 +0300 Subject: [PATCH 064/148] Connection factory goes to cache options --- README.rst | 29 +++++++++++++ changelog.d/680.feature | 1 + django_redis/pool.py | 3 ++ setup.cfg | 1 + tests/settings/sqlite_sentinel_opts.py | 49 +++++++++++++++++++++ tests/test_connection_factory.py | 60 ++++++++++++++++++++++++++ 6 files changed, 143 insertions(+) create mode 100644 changelog.d/680.feature create mode 100644 tests/settings/sqlite_sentinel_opts.py create mode 100644 tests/test_connection_factory.py diff --git a/README.rst b/README.rst index ccf323b1..8d597df7 100644 --- a/README.rst +++ b/README.rst @@ -736,6 +736,35 @@ In order to enable this functionality you should add the following: }, } +It is also possible to set some caches as sentinels and some as not: + +.. code-block:: python + + SENTINELS = [ + ('sentinel-1', 26379), + ('sentinel-2', 26379), + ('sentinel-3', 26379), + ] + CACHES = { + "sentinel": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://service_name/db", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.SentinelClient", + "SENTINELS": SENTINELS, + "CONNECTION_POOL_CLASS": "redis.sentinel.SentinelConnectionPool", + "CONNECTION_FACTORY": "django_redis.pool.SentinelConnectionFactory", + }, + }, + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:6379/1", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + }, + } + .. _Redis Sentinels: https://redis.io/topics/sentinel Pluggable parsers diff --git a/changelog.d/680.feature b/changelog.d/680.feature new file mode 100644 index 00000000..07bd52b3 --- /dev/null +++ b/changelog.d/680.feature @@ -0,0 +1 @@ +Connection factory goes to cache options diff --git a/django_redis/pool.py b/django_redis/pool.py index 44afa943..c5704076 100644 --- a/django_redis/pool.py +++ b/django_redis/pool.py @@ -184,6 +184,9 @@ def get_connection_factory(path=None, options=None): "DJANGO_REDIS_CONNECTION_FACTORY", "django_redis.pool.ConnectionFactory", ) + opt_conn_factory = options.get("CONNECTION_FACTORY") + if opt_conn_factory: + path = opt_conn_factory cls = import_string(path) return cls(options or {}) diff --git a/setup.cfg b/setup.cfg index 9395f819..78032a20 100644 --- a/setup.cfg +++ b/setup.cfg @@ -107,6 +107,7 @@ commands = {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_lz4 {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_msgpack {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_sentinel {posargs} + {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_sentinel_opts {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_sharding {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_usock {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_zlib {posargs} diff --git a/tests/settings/sqlite_sentinel_opts.py b/tests/settings/sqlite_sentinel_opts.py new file mode 100644 index 00000000..29f079ed --- /dev/null +++ b/tests/settings/sqlite_sentinel_opts.py @@ -0,0 +1,49 @@ +SECRET_KEY = "django_tests_secret_key" + +SENTINELS = [("127.0.0.1", 26379)] + +conn_factory = "django_redis.pool.SentinelConnectionFactory" + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": ["redis://default_service?db=5"], + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "SENTINELS": SENTINELS, + "CONNECTION_FACTORY": conn_factory, + }, + }, + "doesnotexist": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://missing_service?db=1", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "SENTINELS": SENTINELS, + "CONNECTION_FACTORY": conn_factory, + }, + }, + "sample": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://default_service?db=1", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.SentinelClient", + "SENTINELS": SENTINELS, + "CONNECTION_FACTORY": conn_factory, + }, + }, + "with_prefix": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://default_service?db=1", + "KEY_PREFIX": "test-prefix", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "SENTINELS": SENTINELS, + "CONNECTION_FACTORY": conn_factory, + }, + }, +} + +INSTALLED_APPS = ["django.contrib.sessions"] + +USE_TZ = False diff --git a/tests/test_connection_factory.py b/tests/test_connection_factory.py new file mode 100644 index 00000000..58427ff6 --- /dev/null +++ b/tests/test_connection_factory.py @@ -0,0 +1,60 @@ +import pytest +from django.core.exceptions import ImproperlyConfigured + +from django_redis import pool + + +def test_connection_factory_redefine_from_opts(): + cf = pool.get_connection_factory( + path="django_redis.pool.ConnectionFactory", + options={ + "CONNECTION_FACTORY": "django_redis.pool.SentinelConnectionFactory", + "SENTINELS": [("127.0.0.1", "26739")], + }, + ) + assert cf.__class__.__name__ == "SentinelConnectionFactory" + + +@pytest.mark.parametrize( + "conn_factory,expected", + [ + ("django_redis.pool.SentinelConnectionFactory", pool.SentinelConnectionFactory), + ("django_redis.pool.ConnectionFactory", pool.ConnectionFactory), + ], +) +def test_connection_factory_opts(conn_factory: str, expected): + cf = pool.get_connection_factory( + path=None, + options={ + "CONNECTION_FACTORY": conn_factory, + "SENTINELS": [("127.0.0.1", "26739")], + }, + ) + assert isinstance(cf, expected) + + +@pytest.mark.parametrize( + "conn_factory,expected", + [ + ("django_redis.pool.SentinelConnectionFactory", pool.SentinelConnectionFactory), + ("django_redis.pool.ConnectionFactory", pool.ConnectionFactory), + ], +) +def test_connection_factory_path(conn_factory: str, expected): + cf = pool.get_connection_factory( + path=conn_factory, + options={ + "SENTINELS": [("127.0.0.1", "26739")], + }, + ) + assert isinstance(cf, expected) + + +def test_connection_factory_no_sentinels(): + with pytest.raises(ImproperlyConfigured): + pool.get_connection_factory( + path=None, + options={ + "CONNECTION_FACTORY": "django_redis.pool.SentinelConnectionFactory", + }, + ) From c09ad9bd916b65adfd9d0ee2e7caf455a25ffaee Mon Sep 17 00:00:00 2001 From: Anas Date: Sun, 1 Oct 2023 23:03:42 +0300 Subject: [PATCH 065/148] Preparing release 5.4.0 --- CHANGELOG.rst | 15 +++++++++++++++ changelog.d/677.doc | 1 - changelog.d/680.feature | 1 - django_redis/__init__.py | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/677.doc delete mode 100644 changelog.d/680.feature diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f802d185..8e865f4a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,21 @@ Changelog .. towncrier release notes start +django-redis 5.4.0 (2023-10-01) +=============================== + +Features +-------- + +- Connection factory goes to cache options (`#680 `_) + + +Documentation +------------- + +- Added note in docs for correctly configuring hiredis parser when using redis-py version 5. (`#677 `_) + + django-redis 5.3.0 (2023-06-16) =============================== diff --git a/changelog.d/677.doc b/changelog.d/677.doc deleted file mode 100644 index a0e0d999..00000000 --- a/changelog.d/677.doc +++ /dev/null @@ -1 +0,0 @@ -Added note in docs for correctly configuring hiredis parser when using redis-py version 5. diff --git a/changelog.d/680.feature b/changelog.d/680.feature deleted file mode 100644 index 07bd52b3..00000000 --- a/changelog.d/680.feature +++ /dev/null @@ -1 +0,0 @@ -Connection factory goes to cache options diff --git a/django_redis/__init__.py b/django_redis/__init__.py index f83bf322..7157dc68 100644 --- a/django_redis/__init__.py +++ b/django_redis/__init__.py @@ -1,4 +1,4 @@ -VERSION = (5, 3, 0) +VERSION = (5, 4, 0) __version__ = ".".join(map(str, VERSION)) From df7802607e193b0217eb8477c84a526b4fa80dc3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:40:00 +0000 Subject: [PATCH 066/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/asottile/pyupgrade: v3.13.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.13.0...v3.15.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a971da0..f177968b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-ast - id: check-case-conflict @@ -10,7 +10,7 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + rev: v3.15.0 hooks: - id: pyupgrade From 436382297f96a8c60705919798f0256d43cf6229 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:32:56 +0000 Subject: [PATCH 067/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.9.1 → 23.10.1](https://github.com/psf/black/compare/23.9.1...23.10.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f177968b..f0d55355 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: pyupgrade - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.1 hooks: - id: black From ad9ab64269ff6147c66fb53afe337c1bf2d37650 Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Wed, 1 Nov 2023 09:46:24 +0100 Subject: [PATCH 068/148] Pin pytest to <7 --- changelog.d/690.misc | 1 + setup.cfg | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/690.misc diff --git a/changelog.d/690.misc b/changelog.d/690.misc new file mode 100644 index 00000000..2431c0cd --- /dev/null +++ b/changelog.d/690.misc @@ -0,0 +1 @@ +Pin pytest to <7.0 until compatibility issues are resolved \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 78032a20..b781ff50 100644 --- a/setup.cfg +++ b/setup.cfg @@ -122,7 +122,7 @@ deps = dj42: Django>=4.2,<5.0 djmain: https://github.com/django/django/archive/main.tar.gz msgpack>=0.6.0 - pytest + pytest<7.0.0 pytest-cov pytest-django pytest-pythonpath From ac98af34b956f085f7e4ce37f9e7ad37cc04df03 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 15:53:33 +0200 Subject: [PATCH 069/148] Replaced flake8 and isort with ruff --- .github/workflows/ci.yml | 3 +- .ruff.toml | 129 +++++++++++++++++++++++++++++++++++++++ setup.cfg | 11 ++-- 3 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 .ruff.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b5bee6c..65e0fd58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,8 +125,7 @@ jobs: matrix: tool: - 'black' - - 'flake8' - - 'isort' + - 'ruff' - 'mypy' steps: diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 00000000..cdcc7fee --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,129 @@ +# https://beta.ruff.rs/docs/rules/ +select = [ + # rules from pyflakes +# "F", + + # rules from pycodestyle +# "E", "W", + + # rules from mccabe +# "C90", + + # rules from isort +# "I", + + # rules from pyupgrade +# "UP", + + # rules from flake8-2020 +# "YTT", + + # rules from flake8-annotations +# "ANN", + + # rules from flake8-bandit +# "S", + + # rules from flake8-blind-except +# "BLE", + + # rules from flake8-boolean-trap + # TODO: "FBT", + + # rules from flake8-bugbear + # TODO: "B", + + # rules from flake8-builtins + # TODO: "A", + + # rules from flake8-commas +# "COM", + + # rules from flake8-comprehensions +# "C4", + + # rules from flake8-datetimez + # TODO: "DTZ", + + # rules from flake8-debugger +# "T10", + + # rules from flake8-django + # TODO: "DJ", + + # rules from flake8-errmsg +# "EM", + + # rules from flake8-executable + # TODO: "EXE", + + # rules from flake8-implicit-str-concat +# "ISC", + + # rules from flake8-import-conventions +# "ICN", + + # rules from flake8-logging-format + # TODO: "G", + + # rules from flake8-no-pep420 + # TODO: "INP", + + # rules from flake8-pie + # TODO: "PIE", + + # rules from flake8-print +# "T20", + + # rules from flake8-pyi +# "PYI", + + # rules from flake8-pytest-style + # TODO: "PT", + + # rules from flake8-raise +# "RSE", + + # rules from flake8-return + # TODO: "RET", + + # rules from flake8-self + # TODO: "SLF", + + # rules from flake8-simplify +# "SIM", + + # rules from flake8-tidy-imports +# "TID", + + # rules from flake8-type-checking +# "TCH", + + # rules from flake8-gettext +# "INT", + + # rules from flake8-unused-arguments + # TODO: "ARG", + + # rules from flake8-use-pathlib + # could be enabled easily + # TODO: "PTH", + + # removes unused noqa comments +# "RUF100", +] + +ignore = [ + "COM812", # missing trailing comma, covered by black + "ANN101", # ignore missing type annotation in self parameter +] + +fix = true + +# TODO: enable this when python3.6 will stop being supported, +# from april 2024 https://docs.djangoproject.com/en/4.2/releases/3.2/ +#target-version = "py36" + +#[flake8-tidy-imports] +## Disallow all relative imports. +#ban-relative-imports = "all" diff --git a/setup.cfg b/setup.cfg index b781ff50..d765124f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,8 +69,7 @@ multi_line_output = 3 minversion = 3.15.0 envlist = black - flake8 - isort + ruff mypy # tests against released versions py{36,37,38,39,310,311}-dj{22,31,32,40,41,42}-redislatest @@ -131,19 +130,17 @@ deps = lz4>=0.15 pyzstd>=0.15 -[testenv:{black,flake8,isort,mypy}] +[testenv:{black,ruff,mypy}] basepython = python3 envdir={toxworkdir}/lint commands = black: black --target-version py36 {posargs:--check --diff} setup.py django_redis/ tests/ - flake8: flake8 {posargs} setup.py django_redis/ tests/ - isort: isort {posargs:--check-only --diff} django_redis/ tests/ + ruff: ruff {posargs:check --show-fixes} django_redis/ tests/ mypy: mypy {posargs:--cobertura-xml-report .} django_redis tests deps = black django-stubs - flake8 - isort >= 5.0.2 + ruff lxml mypy # typing dependencies From 485268d6a00c2bfe8df82eab4dbc659981048857 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 16:36:57 +0200 Subject: [PATCH 070/148] Added changelog, added ruff to pre-commit and enabled some ruff rules --- .pre-commit-config.yaml | 17 ++++------------- .ruff.toml | 25 ++++++++++++++++++------- changelog.d/692.misc | 1 + django_redis/compressors/lz4.py | 6 +----- django_redis/pool.py | 12 ++++++++---- setup.cfg | 13 +------------ 6 files changed, 33 insertions(+), 41 deletions(-) create mode 100644 changelog.d/692.misc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f177968b..c6a8803c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,22 +9,13 @@ repos: - id: check-symlinks - id: debug-statements -- repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 - hooks: - - id: pyupgrade - - repo: https://github.com/psf/black rev: 23.9.1 hooks: - id: black -- repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - -- repo: https://github.com/PyCQA/isort - rev: 5.12.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.292 hooks: - - id: isort + - id: ruff + args: [ --fix, --exit-non-zero-on-fix , --show-fixes] diff --git a/.ruff.toml b/.ruff.toml index cdcc7fee..8c958457 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,31 +1,31 @@ # https://beta.ruff.rs/docs/rules/ select = [ # rules from pyflakes -# "F", + "F", # rules from pycodestyle -# "E", "W", + "E", "W", # rules from mccabe # "C90", # rules from isort -# "I", + "I", # rules from pyupgrade -# "UP", + "UP", # rules from flake8-2020 -# "YTT", + "YTT", # rules from flake8-annotations # "ANN", # rules from flake8-bandit -# "S", + "S", # rules from flake8-blind-except -# "BLE", + "BLE", # rules from flake8-boolean-trap # TODO: "FBT", @@ -116,6 +116,7 @@ select = [ ignore = [ "COM812", # missing trailing comma, covered by black "ANN101", # ignore missing type annotation in self parameter + "S311", # ignore Standard pseudo-random generators because they are not used for cryptographic purposes ] fix = true @@ -127,3 +128,13 @@ fix = true #[flake8-tidy-imports] ## Disallow all relative imports. #ban-relative-imports = "all" + +[per-file-ignores] +# ignore assert statements in tests +"tests/*.py" = ["S101"] + +# ignore SECRET_KEY in settings files in tests +"tests/settings/*.py" = ["S105"] + +# picle is used on purpose and its use is discouraged +"django_redis/serializers/pickle.py" = ["S301"] diff --git a/changelog.d/692.misc b/changelog.d/692.misc new file mode 100644 index 00000000..2083a1a7 --- /dev/null +++ b/changelog.d/692.misc @@ -0,0 +1 @@ +Replace isort and flake8 with ruff \ No newline at end of file diff --git a/django_redis/compressors/lz4.py b/django_redis/compressors/lz4.py index ffcd4794..f2e6d987 100644 --- a/django_redis/compressors/lz4.py +++ b/django_redis/compressors/lz4.py @@ -1,7 +1,6 @@ from lz4.frame import compress as _compress from lz4.frame import decompress as _decompress -from ..exceptions import CompressorError from .base import BaseCompressor @@ -14,7 +13,4 @@ def compress(self, value: bytes) -> bytes: return value def decompress(self, value: bytes) -> bytes: - try: - return _decompress(value) - except Exception as e: - raise CompressorError(e) + return _decompress(value) diff --git a/django_redis/pool.py b/django_redis/pool.py index c5704076..f486fd8d 100644 --- a/django_redis/pool.py +++ b/django_redis/pool.py @@ -48,16 +48,20 @@ def make_connection_params(self, url): socket_timeout = self.options.get("SOCKET_TIMEOUT", None) if socket_timeout: - assert isinstance( + if not isinstance( socket_timeout, (int, float) - ), "Socket timeout should be float or integer" + ): + raise ImproperlyConfigured("Socket timeout should be float or integer") kwargs["socket_timeout"] = socket_timeout socket_connect_timeout = self.options.get("SOCKET_CONNECT_TIMEOUT", None) if socket_connect_timeout: - assert isinstance( + if not isinstance( socket_connect_timeout, (int, float) - ), "Socket connect timeout should be float or integer" + ): + raise ImproperlyConfigured( + "Socket connect timeout should be float or integer" + ) kwargs["socket_connect_timeout"] = socket_connect_timeout return kwargs diff --git a/setup.cfg b/setup.cfg index d765124f..68069da5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,17 +54,6 @@ omit = precision = 1 skip_covered = true -[flake8] -ignore = - W503 - W601 - E203 -max-line-length = 88 - -[isort] -profile = black -multi_line_output = 3 - [tox:tox] minversion = 3.15.0 envlist = @@ -81,7 +70,7 @@ envlist = python = 3.6: py36 3.7: py37 - 3.8: py38, black, flake8, isort, mypy + 3.8: py38, black, ruff, mypy 3.9: py39 3.10: py310 3.11: py311 From fe463416c474cb84aaf1da755d199ab48bcc8d60 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:38:24 +0000 Subject: [PATCH 071/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/pool.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/django_redis/pool.py b/django_redis/pool.py index f486fd8d..dd369090 100644 --- a/django_redis/pool.py +++ b/django_redis/pool.py @@ -48,17 +48,13 @@ def make_connection_params(self, url): socket_timeout = self.options.get("SOCKET_TIMEOUT", None) if socket_timeout: - if not isinstance( - socket_timeout, (int, float) - ): + if not isinstance(socket_timeout, (int, float)): raise ImproperlyConfigured("Socket timeout should be float or integer") kwargs["socket_timeout"] = socket_timeout socket_connect_timeout = self.options.get("SOCKET_CONNECT_TIMEOUT", None) if socket_connect_timeout: - if not isinstance( - socket_connect_timeout, (int, float) - ): + if not isinstance(socket_connect_timeout, (int, float)): raise ImproperlyConfigured( "Socket connect timeout should be float or integer" ) From d13ac5e09379d49f9d8ded2130f39273f9f88640 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 16:41:16 +0200 Subject: [PATCH 072/148] Fixed relative imports --- .ruff.toml | 10 +++++----- django_redis/client/default.py | 6 +++--- django_redis/client/herd.py | 4 ++-- django_redis/client/sharded.py | 8 ++++---- django_redis/compressors/lzma.py | 4 ++-- django_redis/compressors/zlib.py | 4 ++-- django_redis/compressors/zstd.py | 4 ++-- tests/test_session.py | 2 +- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.ruff.toml b/.ruff.toml index 8c958457..6be198ec 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -94,23 +94,23 @@ select = [ # "SIM", # rules from flake8-tidy-imports -# "TID", + "TID", # rules from flake8-type-checking -# "TCH", + "TCH", # rules from flake8-gettext -# "INT", + "INT", # rules from flake8-unused-arguments # TODO: "ARG", # rules from flake8-use-pathlib # could be enabled easily - # TODO: "PTH", +# TODO: "PTH", # removes unused noqa comments -# "RUF100", + "RUF100", ] ignore = [ diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 6886b46b..326aae73 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -12,9 +12,9 @@ from redis import Redis from redis.exceptions import ConnectionError, ResponseError, TimeoutError -from .. import pool -from ..exceptions import CompressorError, ConnectionInterrupted -from ..util import CacheKey +from django_redis import pool +from django_redis.exceptions import CompressorError, ConnectionInterrupted +from django_redis.util import CacheKey _main_exceptions = (TimeoutError, ResponseError, ConnectionError, socket.timeout) diff --git a/django_redis/client/herd.py b/django_redis/client/herd.py index 0c52480f..55a2b46c 100644 --- a/django_redis/client/herd.py +++ b/django_redis/client/herd.py @@ -6,8 +6,8 @@ from django.conf import settings from redis.exceptions import ConnectionError, ResponseError, TimeoutError -from ..exceptions import ConnectionInterrupted -from .default import DEFAULT_TIMEOUT, DefaultClient +from django_redis.client.default import DEFAULT_TIMEOUT, DefaultClient +from django_redis.exceptions import ConnectionInterrupted _main_exceptions = (ConnectionError, ResponseError, TimeoutError, socket.timeout) diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index a30e177e..606776f9 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -5,10 +5,10 @@ from redis.exceptions import ConnectionError -from ..exceptions import ConnectionInterrupted -from ..hash_ring import HashRing -from ..util import CacheKey -from .default import DEFAULT_TIMEOUT, DefaultClient +from django_redis.client.default import DEFAULT_TIMEOUT, DefaultClient +from django_redis.exceptions import ConnectionInterrupted +from django_redis.hash_ring import HashRing +from django_redis.util import CacheKey class ShardClient(DefaultClient): diff --git a/django_redis/compressors/lzma.py b/django_redis/compressors/lzma.py index b8c17bcf..e16670a7 100644 --- a/django_redis/compressors/lzma.py +++ b/django_redis/compressors/lzma.py @@ -1,7 +1,7 @@ import lzma -from ..exceptions import CompressorError -from .base import BaseCompressor +from django_redis.compressors.base import BaseCompressor +from django_redis.exceptions import CompressorError class LzmaCompressor(BaseCompressor): diff --git a/django_redis/compressors/zlib.py b/django_redis/compressors/zlib.py index 014fc77e..7db6cc95 100644 --- a/django_redis/compressors/zlib.py +++ b/django_redis/compressors/zlib.py @@ -1,7 +1,7 @@ import zlib -from ..exceptions import CompressorError -from .base import BaseCompressor +from django_redis.compressors.base import BaseCompressor +from django_redis.exceptions import CompressorError class ZlibCompressor(BaseCompressor): diff --git a/django_redis/compressors/zstd.py b/django_redis/compressors/zstd.py index ddf49d0d..b7d51110 100644 --- a/django_redis/compressors/zstd.py +++ b/django_redis/compressors/zstd.py @@ -1,7 +1,7 @@ import pyzstd -from ..exceptions import CompressorError -from .base import BaseCompressor +from django_redis.compressors.base import BaseCompressor +from django_redis.exceptions import CompressorError class ZStdCompressor(BaseCompressor): diff --git a/tests/test_session.py b/tests/test_session.py index 36745035..bcea9c39 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -369,7 +369,7 @@ class SessionTests(SessionTestsMixin, unittest.TestCase): @pytest.mark.skipif( django.VERSION >= (4, 2), - reason="PickleSerializer is removed as of https://code.djangoproject.com/ticket/29708", # noqa: E501 + reason="PickleSerializer is removed as of https://code.djangoproject.com/ticket/29708", ) def test_actual_expiry(self): if isinstance( From 4b5fc71edf94a08e1925dfc4bbf8482e2d2dbcff Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 16:47:41 +0200 Subject: [PATCH 073/148] Enabled more rules --- .ruff.toml | 20 ++++++++++---------- django_redis/__init__.py | 5 +++-- django_redis/client/default.py | 12 ++++++------ django_redis/client/herd.py | 4 ++-- django_redis/client/sentinel.py | 5 ++++- django_redis/client/sharded.py | 3 ++- django_redis/pool.py | 9 ++++++--- django_redis/serializers/pickle.py | 8 ++++++-- 8 files changed, 39 insertions(+), 27 deletions(-) diff --git a/.ruff.toml b/.ruff.toml index 6be198ec..2d4d5c15 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -37,31 +37,31 @@ select = [ # TODO: "A", # rules from flake8-commas -# "COM", + "COM", # rules from flake8-comprehensions -# "C4", + "C4", # rules from flake8-datetimez # TODO: "DTZ", # rules from flake8-debugger -# "T10", + "T10", # rules from flake8-django # TODO: "DJ", # rules from flake8-errmsg -# "EM", + "EM", # rules from flake8-executable # TODO: "EXE", # rules from flake8-implicit-str-concat -# "ISC", + "ISC", # rules from flake8-import-conventions -# "ICN", + "ICN", # rules from flake8-logging-format # TODO: "G", @@ -73,16 +73,16 @@ select = [ # TODO: "PIE", # rules from flake8-print -# "T20", + "T20", # rules from flake8-pyi -# "PYI", + "PYI", # rules from flake8-pytest-style # TODO: "PT", # rules from flake8-raise -# "RSE", + "RSE", # rules from flake8-return # TODO: "RET", @@ -91,7 +91,7 @@ select = [ # TODO: "SLF", # rules from flake8-simplify -# "SIM", + "SIM", # rules from flake8-tidy-imports "TID", diff --git a/django_redis/__init__.py b/django_redis/__init__.py index 7157dc68..579f1d44 100644 --- a/django_redis/__init__.py +++ b/django_redis/__init__.py @@ -11,10 +11,11 @@ def get_redis_connection(alias="default", write=True): cache = caches[alias] + error_message = "This backend does not support this feature" if not hasattr(cache, "client"): - raise NotImplementedError("This backend does not support this feature") + raise NotImplementedError(error_message) if not hasattr(cache.client, "get_client"): - raise NotImplementedError("This backend does not support this feature") + raise NotImplementedError(error_message) return cache.client.get_client(write) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 326aae73..adaf386c 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -2,6 +2,7 @@ import re import socket from collections import OrderedDict +from contextlib import suppress from datetime import datetime from typing import Any, Dict, Iterator, List, Optional, Union @@ -37,7 +38,8 @@ def __init__(self, server, params: Dict[str, Any], backend: BaseCache) -> None: ) if not self._server: - raise ImproperlyConfigured("Missing connections string") + error_message = "Missing connections string" + raise ImproperlyConfigured(error_message) if not isinstance(self._server, (list, tuple, set)): self._server = self._server.split(",") @@ -75,7 +77,7 @@ def get_next_client_index( behavior. """ if tried is None: - tried = list() + tried = [] if tried and len(tried) < len(self._server): not_tried = [i for i in range(0, len(self._server)) if i not in tried] @@ -444,11 +446,9 @@ def decode(self, value: Union[bytes, int]) -> Any: try: value = int(value) except (ValueError, TypeError): - try: + # Handle little values, chosen to be not compressed + with suppress(CompressorError): value = self._compressor.decompress(value) - except CompressorError: - # Handle little values, chosen to be not compressed - pass value = self._serializer.loads(value) return value diff --git a/django_redis/client/herd.py b/django_redis/client/herd.py index 55a2b46c..94d539cd 100644 --- a/django_redis/client/herd.py +++ b/django_redis/client/herd.py @@ -147,10 +147,10 @@ def set_many( raise ConnectionInterrupted(connection=client) from e def incr(self, *args, **kwargs): - raise NotImplementedError() + raise NotImplementedError def decr(self, *args, **kwargs): - raise NotImplementedError() + raise NotImplementedError def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None, client=None): if client is None: diff --git a/django_redis/client/sentinel.py b/django_redis/client/sentinel.py index 709f4e53..a7e7b2fe 100644 --- a/django_redis/client/sentinel.py +++ b/django_redis/client/sentinel.py @@ -33,9 +33,12 @@ def __init__(self, server, params, backend): def connect(self, *args, **kwargs): connection = super().connect(*args, **kwargs) if not isinstance(connection.connection_pool, SentinelConnectionPool): - raise ImproperlyConfigured( + error_message = ( "Settings DJANGO_REDIS_CONNECTION_FACTORY or " "CACHE[].OPTIONS.CONNECTION_POOL_CLASS is not configured correctly." ) + raise ImproperlyConfigured( + error_message + ) return connection diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index 606776f9..f96c5ffa 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -272,7 +272,8 @@ def decr(self, key, delta=1, version=None, client=None): return super().decr(key=key, delta=delta, version=version, client=client) def iter_keys(self, key, version=None): - raise NotImplementedError("iter_keys not supported on sharded client") + error_message = "iter_keys not supported on sharded client" + raise NotImplementedError(error_message) def keys(self, search, version=None): pattern = self.make_pattern(search, version=version) diff --git a/django_redis/pool.py b/django_redis/pool.py index f486fd8d..10e24933 100644 --- a/django_redis/pool.py +++ b/django_redis/pool.py @@ -51,7 +51,8 @@ def make_connection_params(self, url): if not isinstance( socket_timeout, (int, float) ): - raise ImproperlyConfigured("Socket timeout should be float or integer") + error_message = "Socket timeout should be float or integer" + raise ImproperlyConfigured(error_message) kwargs["socket_timeout"] = socket_timeout socket_connect_timeout = self.options.get("SOCKET_CONNECT_TIMEOUT", None) @@ -59,8 +60,9 @@ def make_connection_params(self, url): if not isinstance( socket_connect_timeout, (int, float) ): + error_message = "Socket connect timeout should be float or integer" raise ImproperlyConfigured( - "Socket connect timeout should be float or integer" + error_message ) kwargs["socket_connect_timeout"] = socket_connect_timeout @@ -144,8 +146,9 @@ def __init__(self, options): sentinels = options.get("SENTINELS") if not sentinels: + error_message = "SENTINELS must be provided as a list of (host, port)." raise ImproperlyConfigured( - "SENTINELS must be provided as a list of (host, port)." + error_message ) # provide the connection pool kwargs to the sentinel in case it diff --git a/django_redis/serializers/pickle.py b/django_redis/serializers/pickle.py index c304360d..1bd6915d 100644 --- a/django_redis/serializers/pickle.py +++ b/django_redis/serializers/pickle.py @@ -18,12 +18,16 @@ def setup_pickle_version(self, options) -> None: try: self._pickle_version = int(options["PICKLE_VERSION"]) if self._pickle_version > pickle.HIGHEST_PROTOCOL: - raise ImproperlyConfigured( + error_message = ( f"PICKLE_VERSION can't be higher than pickle.HIGHEST_PROTOCOL:" f" {pickle.HIGHEST_PROTOCOL}" ) + raise ImproperlyConfigured( + error_message + ) except (ValueError, TypeError): - raise ImproperlyConfigured("PICKLE_VERSION value must be an integer") + error_message = "PICKLE_VERSION value must be an integer" + raise ImproperlyConfigured(error_message) def dumps(self, value: Any) -> bytes: return pickle.dumps(value, self._pickle_version) From ba065e25bc4f789eacd13f53f111f0c03ea0d8dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:49:37 +0000 Subject: [PATCH 074/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/client/sentinel.py | 4 +--- django_redis/pool.py | 16 ++++------------ django_redis/serializers/pickle.py | 4 +--- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/django_redis/client/sentinel.py b/django_redis/client/sentinel.py index a7e7b2fe..6e6e7275 100644 --- a/django_redis/client/sentinel.py +++ b/django_redis/client/sentinel.py @@ -37,8 +37,6 @@ def connect(self, *args, **kwargs): "Settings DJANGO_REDIS_CONNECTION_FACTORY or " "CACHE[].OPTIONS.CONNECTION_POOL_CLASS is not configured correctly." ) - raise ImproperlyConfigured( - error_message - ) + raise ImproperlyConfigured(error_message) return connection diff --git a/django_redis/pool.py b/django_redis/pool.py index 10e24933..98afbe1f 100644 --- a/django_redis/pool.py +++ b/django_redis/pool.py @@ -48,22 +48,16 @@ def make_connection_params(self, url): socket_timeout = self.options.get("SOCKET_TIMEOUT", None) if socket_timeout: - if not isinstance( - socket_timeout, (int, float) - ): + if not isinstance(socket_timeout, (int, float)): error_message = "Socket timeout should be float or integer" raise ImproperlyConfigured(error_message) kwargs["socket_timeout"] = socket_timeout socket_connect_timeout = self.options.get("SOCKET_CONNECT_TIMEOUT", None) if socket_connect_timeout: - if not isinstance( - socket_connect_timeout, (int, float) - ): + if not isinstance(socket_connect_timeout, (int, float)): error_message = "Socket connect timeout should be float or integer" - raise ImproperlyConfigured( - error_message - ) + raise ImproperlyConfigured(error_message) kwargs["socket_connect_timeout"] = socket_connect_timeout return kwargs @@ -147,9 +141,7 @@ def __init__(self, options): sentinels = options.get("SENTINELS") if not sentinels: error_message = "SENTINELS must be provided as a list of (host, port)." - raise ImproperlyConfigured( - error_message - ) + raise ImproperlyConfigured(error_message) # provide the connection pool kwargs to the sentinel in case it # needs to use the socket options for the sentinels themselves diff --git a/django_redis/serializers/pickle.py b/django_redis/serializers/pickle.py index 1bd6915d..333ce517 100644 --- a/django_redis/serializers/pickle.py +++ b/django_redis/serializers/pickle.py @@ -22,9 +22,7 @@ def setup_pickle_version(self, options) -> None: f"PICKLE_VERSION can't be higher than pickle.HIGHEST_PROTOCOL:" f" {pickle.HIGHEST_PROTOCOL}" ) - raise ImproperlyConfigured( - error_message - ) + raise ImproperlyConfigured(error_message) except (ValueError, TypeError): error_message = "PICKLE_VERSION value must be an integer" raise ImproperlyConfigured(error_message) From f608da15dad4e23310777c0a7b9b6a28e7a419e9 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 17:00:57 +0200 Subject: [PATCH 075/148] Enabled more rules --- .ruff.toml | 6 +++--- django_redis/cache.py | 4 ++-- django_redis/client/default.py | 10 ++++++---- django_redis/client/herd.py | 2 +- django_redis/client/sharded.py | 8 ++++---- django_redis/compressors/lzma.py | 2 +- django_redis/compressors/zlib.py | 2 +- django_redis/compressors/zstd.py | 2 +- django_redis/serializers/pickle.py | 4 ++-- tests/test_hashring.py | 8 ++++---- 10 files changed, 25 insertions(+), 23 deletions(-) diff --git a/.ruff.toml b/.ruff.toml index 2d4d5c15..3dad1dac 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -7,7 +7,7 @@ select = [ "E", "W", # rules from mccabe -# "C90", + "C90", # rules from isort "I", @@ -31,10 +31,10 @@ select = [ # TODO: "FBT", # rules from flake8-bugbear - # TODO: "B", + "B", # rules from flake8-builtins - # TODO: "A", + "A", # rules from flake8-commas "COM", diff --git a/django_redis/cache.py b/django_redis/cache.py index 0b4d5890..8217b15b 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -33,7 +33,7 @@ def _decorator(self, *args, **kwargs): self.logger.exception("Exception ignored") return return_value - raise e.__cause__ + raise e.__cause__ # noqa: B904 return _decorator @@ -77,7 +77,7 @@ def client(self): return self._client @omit_exception - def set(self, *args, **kwargs): + def set(self, *args, **kwargs): # noqa: A003 return self.client.set(*args, **kwargs) @omit_exception diff --git a/django_redis/client/default.py b/django_redis/client/default.py index adaf386c..7e2151d0 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -125,7 +125,7 @@ def disconnect(self, index=0, client=None): client = self._clients[index] return self.connection_factory.disconnect(client) if client else None - def set( + def set( # noqa: A003 self, key: Any, value: Any, @@ -548,8 +548,9 @@ def _incr( """ value = client.eval(lua, 1, key, delta) if value is None: - raise ValueError("Key '%s' not found" % key) - except ResponseError: + error_message = f"Key '{key}' not found" + raise ValueError(error_message) + except ResponseError as e: # if cached value or total value is greater than 64 bit signed # integer. # elif int is encoded. so redis sees the data as string. @@ -561,7 +562,8 @@ def _incr( # returns -2 if the key does not exist # means, that key have expired if timeout == -2: - raise ValueError("Key '%s' not found" % key) + error_message = f"Key '{key}' not found" + raise ValueError(error_message) from e value = self.get(key, version=version, client=client) + delta self.set(key, value, version=version, timeout=timeout, client=client) except _main_exceptions as e: diff --git a/django_redis/client/herd.py b/django_redis/client/herd.py index 94d539cd..70cb0459 100644 --- a/django_redis/client/herd.py +++ b/django_redis/client/herd.py @@ -57,7 +57,7 @@ def _unpack(self, value): return unpacked, False - def set( + def set( # noqa: A003 self, key, value, diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index f96c5ffa..df58fd73 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -79,7 +79,7 @@ def get_many(self, keys, version=None): recovered_data[map_keys[key]] = value return recovered_data - def set( + def set( # noqa: A003 self, key, value, timeout=DEFAULT_TIMEOUT, version=None, client=None, nx=False ): """ @@ -279,7 +279,7 @@ def keys(self, search, version=None): pattern = self.make_pattern(search, version=version) keys = [] try: - for server, connection in self._serverdict.items(): + for connection in self._serverdict.values(): keys.extend(connection.keys(pattern)) except ConnectionError as e: # FIXME: technically all clients should be passed as `connection`. @@ -300,12 +300,12 @@ def delete_pattern( kwargs["count"] = itersize keys = [] - for server, connection in self._serverdict.items(): + for connection in self._serverdict.values(): keys.extend(key for key in connection.scan_iter(**kwargs)) res = 0 if keys: - for server, connection in self._serverdict.items(): + for connection in self._serverdict.values(): res += connection.delete(*keys) return res diff --git a/django_redis/compressors/lzma.py b/django_redis/compressors/lzma.py index e16670a7..1ab1a024 100644 --- a/django_redis/compressors/lzma.py +++ b/django_redis/compressors/lzma.py @@ -17,4 +17,4 @@ def decompress(self, value: bytes) -> bytes: try: return lzma.decompress(value) except lzma.LZMAError as e: - raise CompressorError(e) + raise CompressorError from e diff --git a/django_redis/compressors/zlib.py b/django_redis/compressors/zlib.py index 7db6cc95..98767325 100644 --- a/django_redis/compressors/zlib.py +++ b/django_redis/compressors/zlib.py @@ -17,4 +17,4 @@ def decompress(self, value: bytes) -> bytes: try: return zlib.decompress(value) except zlib.error as e: - raise CompressorError(e) + raise CompressorError from e diff --git a/django_redis/compressors/zstd.py b/django_redis/compressors/zstd.py index b7d51110..82a09187 100644 --- a/django_redis/compressors/zstd.py +++ b/django_redis/compressors/zstd.py @@ -16,4 +16,4 @@ def decompress(self, value: bytes) -> bytes: try: return pyzstd.decompress(value) except pyzstd.ZstdError as e: - raise CompressorError(e) + raise CompressorError from e diff --git a/django_redis/serializers/pickle.py b/django_redis/serializers/pickle.py index 1bd6915d..9cf45bc8 100644 --- a/django_redis/serializers/pickle.py +++ b/django_redis/serializers/pickle.py @@ -25,9 +25,9 @@ def setup_pickle_version(self, options) -> None: raise ImproperlyConfigured( error_message ) - except (ValueError, TypeError): + except (ValueError, TypeError) as e: error_message = "PICKLE_VERSION value must be an integer" - raise ImproperlyConfigured(error_message) + raise ImproperlyConfigured(error_message) from e def dumps(self, value: Any) -> bytes: return pickle.dumps(value, self._pickle_version) diff --git a/tests/test_hashring.py b/tests/test_hashring.py index b890adb7..d6182226 100644 --- a/tests/test_hashring.py +++ b/tests/test_hashring.py @@ -4,14 +4,14 @@ class Node: - def __init__(self, id): - self.id = id + def __init__(self, identifier): + self.identifier = identifier def __str__(self): - return f"node:{self.id}" + return f"node:{self.identifier}" def __repr__(self): - return f"" + return f"" @pytest.fixture From c7be6cc48f4654a2eca69c7cf8dab995b15e8d52 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 17:21:14 +0200 Subject: [PATCH 076/148] Enabled more rules and restored blind exceptino for lz4 compressor --- .pre-commit-config.yaml | 12 ++++--- .ruff.toml | 9 +++-- django_redis/client/default.py | 53 +++++++++++++++--------------- django_redis/client/sharded.py | 3 +- django_redis/compressors/lz4.py | 8 +++-- django_redis/pool.py | 3 +- django_redis/serializers/pickle.py | 2 +- 7 files changed, 46 insertions(+), 44 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c6a8803c..dd43f432 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,13 +9,15 @@ repos: - id: check-symlinks - id: debug-statements -- repo: https://github.com/psf/black - rev: 23.9.1 - hooks: - - id: black - +# from readme - ruff with autofix must run before +# other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.0.292 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] + +- repo: https://github.com/psf/black + rev: 23.9.1 + hooks: + - id: black diff --git a/.ruff.toml b/.ruff.toml index 3dad1dac..84e1d06d 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -49,13 +49,13 @@ select = [ "T10", # rules from flake8-django - # TODO: "DJ", + "DJ", # rules from flake8-errmsg "EM", # rules from flake8-executable - # TODO: "EXE", + "EXE", # rules from flake8-implicit-str-concat "ISC", @@ -85,7 +85,7 @@ select = [ "RSE", # rules from flake8-return - # TODO: "RET", + "RET", # rules from flake8-self # TODO: "SLF", @@ -106,8 +106,7 @@ select = [ # TODO: "ARG", # rules from flake8-use-pathlib - # could be enabled easily -# TODO: "PTH", + "PTH", # removes unused noqa comments "RUF100", diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 7e2151d0..1d277765 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -108,8 +108,8 @@ def get_client( if show_index: return self._clients[index], index - else: - return self._clients[index] + + return self._clients[index] def connect(self, index: int = 0) -> Redis: """ @@ -166,13 +166,13 @@ def set( # noqa: A003 # not expire (in our case delete) the value if it exists. # Obviously expire not existent value is noop. return not self.has_key(key, version=version, client=client) - else: - # redis doesn't support negative timeouts in ex flags - # so it seems that it's better to just delete the key - # than to set it and than expire in a pipeline - return bool( - self.delete(key, client=client, version=version) - ) + + # redis doesn't support negative timeouts in ex flags + # so it seems that it's better to just delete the key + # than to set it and than expire in a pipeline + return bool( + self.delete(key, client=client, version=version) + ) return bool(client.set(nkey, nvalue, nx=nx, px=timeout, xx=xx)) except _main_exceptions as e: @@ -419,7 +419,7 @@ def delete_many( keys = [self.make_key(k, version=version) for k in keys] if not keys: - return + return None try: return client.delete(*keys) @@ -459,8 +459,7 @@ def encode(self, value: Any) -> Union[bytes, Any]: if isinstance(value, bool) or not isinstance(value, int): value = self._serializer.dumps(value) - value = self._compressor.compress(value) - return value + return self._compressor.compress(value) return value @@ -623,13 +622,13 @@ def ttl( if t >= 0: return t - elif t == -1: + if t == -1: return None - elif t == -2: + if t == -2: return 0 - else: - # Should never reach here - return None + + # Should never reach here + return None def pttl(self, key, version=None, client=None): """ @@ -647,13 +646,13 @@ def pttl(self, key, version=None, client=None): if t >= 0: return t - elif t == -1: + if t == -1: return None - elif t == -2: + if t == -2: return 0 - else: - # Should never reach here - return None + + # Should never reach here + return None def has_key( self, key: Any, version: Optional[int] = None, client: Optional[Redis] = None @@ -739,7 +738,7 @@ def make_pattern( return CacheKey(self._backend.key_func(pattern, prefix, version_str)) - def close(self, **kwargs): + def close(self): close_flag = self._options.get( "CLOSE_CONNECTION", getattr(settings, "DJANGO_REDIS_CLOSE_CONNECTION", False), @@ -774,7 +773,7 @@ def touch( key = self.make_key(key, version=version) if timeout is None: return bool(client.persist(key)) - else: - # Convert to milliseconds - timeout = int(timeout * 1000) - return bool(client.pexpire(key, timeout)) + + # Convert to milliseconds + timeout = int(timeout * 1000) + return bool(client.pexpire(key, timeout)) diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index df58fd73..7c34e6e7 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -37,8 +37,7 @@ def get_server_name(self, _key): g = self._findhash.match(key) if g is not None and len(g.groups()) > 0: key = g.groups()[0] - name = self._ring.get_node(key) - return name + return self._ring.get_node(key) def get_server(self, key): name = self.get_server_name(key) diff --git a/django_redis/compressors/lz4.py b/django_redis/compressors/lz4.py index f2e6d987..32183321 100644 --- a/django_redis/compressors/lz4.py +++ b/django_redis/compressors/lz4.py @@ -1,7 +1,8 @@ from lz4.frame import compress as _compress from lz4.frame import decompress as _decompress -from .base import BaseCompressor +from django_redis.compressors.base import BaseCompressor +from django_redis.exceptions import CompressorError class Lz4Compressor(BaseCompressor): @@ -13,4 +14,7 @@ def compress(self, value: bytes) -> bytes: return value def decompress(self, value: bytes) -> bytes: - return _decompress(value) + try: + return _decompress(value) + except Exception as e: # noqa: BLE001 + raise CompressorError from e diff --git a/django_redis/pool.py b/django_redis/pool.py index 98afbe1f..979c463c 100644 --- a/django_redis/pool.py +++ b/django_redis/pool.py @@ -68,8 +68,7 @@ def connect(self, url: str) -> Redis: return a new connection. """ params = self.make_connection_params(url) - connection = self.get_connection(params) - return connection + return self.get_connection(params) def disconnect(self, connection): """ diff --git a/django_redis/serializers/pickle.py b/django_redis/serializers/pickle.py index 88372a91..92130458 100644 --- a/django_redis/serializers/pickle.py +++ b/django_redis/serializers/pickle.py @@ -23,7 +23,7 @@ def setup_pickle_version(self, options) -> None: f" {pickle.HIGHEST_PROTOCOL}" ) raise ImproperlyConfigured(error_message) - except (ValueError, TypeError): + except (ValueError, TypeError) as e: error_message = "PICKLE_VERSION value must be an integer" raise ImproperlyConfigured(error_message) from e From 61f399e027a0e09cf52f700688d9f9891500462c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:22:21 +0000 Subject: [PATCH 077/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/client/default.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 1d277765..15a5067a 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -170,9 +170,7 @@ def set( # noqa: A003 # redis doesn't support negative timeouts in ex flags # so it seems that it's better to just delete the key # than to set it and than expire in a pipeline - return bool( - self.delete(key, client=client, version=version) - ) + return bool(self.delete(key, client=client, version=version)) return bool(client.set(nkey, nvalue, nx=nx, px=timeout, xx=xx)) except _main_exceptions as e: From 7d425619a76ad963d7ec5c6da4a95515a7803d38 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 17:23:01 +0200 Subject: [PATCH 078/148] Drop django 4.0 --- .github/workflows/ci.yml | 1 - setup.cfg | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b5bee6c..9500530f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,6 @@ jobs: - '3.10' django-version: - '3.2' - - '4.0' - '4.1' - '4.2' redis-version: diff --git a/setup.cfg b/setup.cfg index b781ff50..fa2d0f50 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,7 +13,6 @@ classifiers = Environment :: Web Environment Framework :: Django Framework :: Django :: 3.2 - Framework :: Django :: 4.0 Framework :: Django :: 4.1 Framework :: Django :: 4.2 Intended Audience :: Developers @@ -73,7 +72,7 @@ envlist = isort mypy # tests against released versions - py{36,37,38,39,310,311}-dj{22,31,32,40,41,42}-redislatest + py{36,37,38,39,310,311}-dj{22,31,32,41,42}-redislatest # tests against unreleased versions py311-dj42-redismaster py311-djmain-redis{latest,master} @@ -90,7 +89,6 @@ python = [gh-actions:env] DJANGO = 3.2: dj32 - 4.0: dj40 4.1: dj41 4.2: dj42 main: djmain @@ -117,7 +115,6 @@ commands = deps = dj32: Django>=3.2,<3.3 - dj40: Django>=4.0,<4.1 dj41: Django>=4.1,<4.2 dj42: Django>=4.2,<5.0 djmain: https://github.com/django/django/archive/main.tar.gz From a5ba09e175e2d70c1fe6134209ead14960499c12 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 17:25:21 +0200 Subject: [PATCH 079/148] Added changelog --- changelog.d/693.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/693.misc diff --git a/changelog.d/693.misc b/changelog.d/693.misc new file mode 100644 index 00000000..64c724d3 --- /dev/null +++ b/changelog.d/693.misc @@ -0,0 +1 @@ +Drop django 4.0 \ No newline at end of file From 6366a9a901db6422757d68d581b14627080fe1ff Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 17:27:39 +0200 Subject: [PATCH 080/148] Fixed hashring tests --- tests/test_hashring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hashring.py b/tests/test_hashring.py index d6182226..42f5b9ff 100644 --- a/tests/test_hashring.py +++ b/tests/test_hashring.py @@ -24,7 +24,7 @@ def test_hashring(hash_ring): for key in [f"test{x}" for x in range(10)]: node = hash_ring.get_node(key) - ids.append(node.id) + ids.append(node.identifier) assert ids == [0, 2, 1, 2, 2, 2, 2, 0, 1, 1] From 61cbc50dd57108c048c54f884b1ece9e64e0e147 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 17:36:08 +0200 Subject: [PATCH 081/148] Enabled tidy imports --- .ruff.toml | 12 ++++++------ django_redis/cache.py | 2 +- django_redis/client/__init__.py | 8 ++++---- django_redis/client/sentinel.py | 2 +- django_redis/compressors/identity.py | 2 +- django_redis/serializers/json.py | 2 +- django_redis/serializers/msgpack.py | 2 +- django_redis/serializers/pickle.py | 2 +- tests/settings/__init__.py | 0 9 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 tests/settings/__init__.py diff --git a/.ruff.toml b/.ruff.toml index 84e1d06d..9c85337e 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -52,7 +52,7 @@ select = [ "DJ", # rules from flake8-errmsg - "EM", + "EM", # rules from flake8-executable "EXE", @@ -64,10 +64,10 @@ select = [ "ICN", # rules from flake8-logging-format - # TODO: "G", + "G", # rules from flake8-no-pep420 - # TODO: "INP", + "INP", # rules from flake8-pie # TODO: "PIE", @@ -124,9 +124,9 @@ fix = true # from april 2024 https://docs.djangoproject.com/en/4.2/releases/3.2/ #target-version = "py36" -#[flake8-tidy-imports] +[flake8-tidy-imports] ## Disallow all relative imports. -#ban-relative-imports = "all" +ban-relative-imports = "all" [per-file-ignores] # ignore assert statements in tests @@ -135,5 +135,5 @@ fix = true # ignore SECRET_KEY in settings files in tests "tests/settings/*.py" = ["S105"] -# picle is used on purpose and its use is discouraged +# pickle is used on purpose and its use is discouraged "django_redis/serializers/pickle.py" = ["S301"] diff --git a/django_redis/cache.py b/django_redis/cache.py index 8217b15b..732ca9df 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -7,7 +7,7 @@ from django.core.cache.backends.base import BaseCache from django.utils.module_loading import import_string -from .exceptions import ConnectionInterrupted +from django_redis.exceptions import ConnectionInterrupted CONNECTION_INTERRUPTED = object() diff --git a/django_redis/client/__init__.py b/django_redis/client/__init__.py index ba40c4a8..d8e7fe54 100644 --- a/django_redis/client/__init__.py +++ b/django_redis/client/__init__.py @@ -1,6 +1,6 @@ -from .default import DefaultClient -from .herd import HerdClient -from .sentinel import SentinelClient -from .sharded import ShardClient +from django_redis.client.default import DefaultClient +from django_redis.client.herd import HerdClient +from django_redis.client.sentinel import SentinelClient +from django_redis.client.sharded import ShardClient __all__ = ["DefaultClient", "HerdClient", "SentinelClient", "ShardClient"] diff --git a/django_redis/client/sentinel.py b/django_redis/client/sentinel.py index 6e6e7275..ba44dbeb 100644 --- a/django_redis/client/sentinel.py +++ b/django_redis/client/sentinel.py @@ -3,7 +3,7 @@ from django.core.exceptions import ImproperlyConfigured from redis.sentinel import SentinelConnectionPool -from .default import DefaultClient +from django_redis.default import DefaultClient def replace_query(url, query): diff --git a/django_redis/compressors/identity.py b/django_redis/compressors/identity.py index 4946886f..c7114203 100644 --- a/django_redis/compressors/identity.py +++ b/django_redis/compressors/identity.py @@ -1,4 +1,4 @@ -from .base import BaseCompressor +from django_redis.compressors.base import BaseCompressor class IdentityCompressor(BaseCompressor): diff --git a/django_redis/serializers/json.py b/django_redis/serializers/json.py index 06e62542..8be9bb29 100644 --- a/django_redis/serializers/json.py +++ b/django_redis/serializers/json.py @@ -3,7 +3,7 @@ from django.core.serializers.json import DjangoJSONEncoder -from .base import BaseSerializer +from django_redis.serializers.base import BaseSerializer class JSONSerializer(BaseSerializer): diff --git a/django_redis/serializers/msgpack.py b/django_redis/serializers/msgpack.py index d41591f6..6af58a67 100644 --- a/django_redis/serializers/msgpack.py +++ b/django_redis/serializers/msgpack.py @@ -2,7 +2,7 @@ import msgpack -from .base import BaseSerializer +from django_redis.serializers.base import BaseSerializer class MSGPackSerializer(BaseSerializer): diff --git a/django_redis/serializers/pickle.py b/django_redis/serializers/pickle.py index 92130458..e63d3c9a 100644 --- a/django_redis/serializers/pickle.py +++ b/django_redis/serializers/pickle.py @@ -3,7 +3,7 @@ from django.core.exceptions import ImproperlyConfigured -from .base import BaseSerializer +from django_redis.serializers.base import BaseSerializer class PickleSerializer(BaseSerializer): diff --git a/tests/settings/__init__.py b/tests/settings/__init__.py new file mode 100644 index 00000000..e69de29b From 74429f02fb791eb7cdecab619d963efefa6c87bf Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 17:48:12 +0200 Subject: [PATCH 082/148] fixed wrong import and changed how redis is started in test script --- django_redis/client/sentinel.py | 2 +- tests/start_redis.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django_redis/client/sentinel.py b/django_redis/client/sentinel.py index ba44dbeb..d4f418dc 100644 --- a/django_redis/client/sentinel.py +++ b/django_redis/client/sentinel.py @@ -3,7 +3,7 @@ from django.core.exceptions import ImproperlyConfigured from redis.sentinel import SentinelConnectionPool -from django_redis.default import DefaultClient +from django_redis.client.default import DefaultClient def replace_query(url, query): diff --git a/tests/start_redis.sh b/tests/start_redis.sh index 00bf2b03..9766fc51 100755 --- a/tests/start_redis.sh +++ b/tests/start_redis.sh @@ -47,7 +47,7 @@ docker run \ --health-interval 10s \ --health-retries 5 \ --health-timeout 5s \ - --network host \ --user $(id -u):$(id -g) \ + --publish $PORT:$PORT \ --volume /tmp:/tmp \ --detach redis:latest redis-server "${ARGS[@]}" From 1a39b2e99bbee648cfd33fb2ac058842acb54e33 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 17:55:51 +0200 Subject: [PATCH 083/148] restored old start_redis script --- tests/start_redis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/start_redis.sh b/tests/start_redis.sh index 9766fc51..00bf2b03 100755 --- a/tests/start_redis.sh +++ b/tests/start_redis.sh @@ -47,7 +47,7 @@ docker run \ --health-interval 10s \ --health-retries 5 \ --health-timeout 5s \ + --network host \ --user $(id -u):$(id -g) \ - --publish $PORT:$PORT \ --volume /tmp:/tmp \ --detach redis:latest redis-server "${ARGS[@]}" From cb4e4407376c5ba333333c70e8739d27eb33611d Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 18:10:22 +0200 Subject: [PATCH 084/148] Upgrade black to 23.10.1 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dd43f432..c14df18f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,6 @@ repos: args: [ --fix, --exit-non-zero-on-fix , --show-fixes] - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.1 hooks: - id: black From cb32e155d191bfa9c1bb92fe20fc0c1c39b1328f Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 18:11:05 +0200 Subject: [PATCH 085/148] Added changelog file --- changelog.d/695.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/695.misc diff --git a/changelog.d/695.misc b/changelog.d/695.misc new file mode 100644 index 00000000..3b1b4601 --- /dev/null +++ b/changelog.d/695.misc @@ -0,0 +1 @@ +Upgrade black to 23.10.1 \ No newline at end of file From 820618b8a86b5d5b4260589d8ebd5146fd5adbd9 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 21:09:35 +0200 Subject: [PATCH 086/148] Added types to DefaultClient --- django_redis/client/default.py | 157 ++++++++++++++++++++------------- django_redis/client/sharded.py | 21 ++++- django_redis/pool.py | 6 +- tests/start_redis.sh | 2 +- tests/test_backend.py | 12 +++ 5 files changed, 129 insertions(+), 69 deletions(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 15a5067a..05f06dd2 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -4,7 +4,7 @@ from collections import OrderedDict from contextlib import suppress from datetime import datetime -from typing import Any, Dict, Iterator, List, Optional, Union +from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Union, Any from django.conf import settings from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache, get_key_func @@ -12,6 +12,7 @@ from django.utils.module_loading import import_string from redis import Redis from redis.exceptions import ConnectionError, ResponseError, TimeoutError +from redis.typing import EncodableT, KeyT, AbsExpiryT, ExpiryT from django_redis import pool from django_redis.exceptions import CompressorError, ConnectionInterrupted @@ -63,7 +64,7 @@ def __init__(self, server, params: Dict[str, Any], backend: BaseCache) -> None: self.connection_factory = pool.get_connection_factory(options=self._options) - def __contains__(self, key: Any) -> bool: + def __contains__(self, key: KeyT) -> bool: return self.has_key(key) def get_next_client_index( @@ -92,8 +93,7 @@ def get_client( self, write: bool = True, tried: Optional[List[int]] = None, - show_index: bool = False, - ): + ) -> Redis: """ Method used for obtain a raw redis client. @@ -106,10 +106,26 @@ def get_client( if self._clients[index] is None: self._clients[index] = self.connect(index) - if show_index: - return self._clients[index], index + return self._clients[index] # type:ignore + + def get_client_with_index( + self, + write: bool = True, + tried: Optional[List[int]] = None, + ) -> Tuple[Redis, int]: + """ + Method used for obtain a raw redis client. + + This function is used by almost all cache backend + operations for obtain a native redis client/connection + instance. + """ + index = self.get_next_client_index(write=write, tried=tried) + + if self._clients[index] is None: + self._clients[index] = self.connect(index) - return self._clients[index] + return self._clients[index], index # type:ignore def connect(self, index: int = 0) -> Redis: """ @@ -119,16 +135,20 @@ def connect(self, index: int = 0) -> Redis: """ return self.connection_factory.connect(self._server[index]) - def disconnect(self, index=0, client=None): - """delegates the connection factory to disconnect the client""" - if not client: + def disconnect(self, index: int = 0, client: Optional[Redis] = None) -> None: + """ + delegates the connection factory to disconnect the client + """ + if client is None: client = self._clients[index] - return self.connection_factory.disconnect(client) if client else None + + if client is not None: + self.connection_factory.disconnect(client) def set( # noqa: A003 self, - key: Any, - value: Any, + key: KeyT, + value: EncodableT, timeout: Optional[float] = DEFAULT_TIMEOUT, version: Optional[int] = None, client: Optional[Redis] = None, @@ -152,9 +172,7 @@ def set( # noqa: A003 while True: try: if client is None: - client, index = self.get_client( - write=True, tried=tried, show_index=True - ) + client, index = self.get_client_with_index(write=True, tried=tried) if timeout is not None: # Convert to milliseconds @@ -186,7 +204,7 @@ def set( # noqa: A003 def incr_version( self, - key: Any, + key: KeyT, delta: int = 1, version: Optional[int] = None, client: Optional[Redis] = None, @@ -211,7 +229,8 @@ def incr_version( raise ConnectionInterrupted(connection=client) from e if value is None: - raise ValueError("Key '%s' not found" % key) + error_message = f"Key '{key!r}' not found" + raise ValueError(error_message) if isinstance(key, CacheKey): new_key = self.make_key(key.original_key(), version=version + delta) @@ -224,10 +243,10 @@ def incr_version( def add( self, - key: Any, - value: Any, - timeout: Any = DEFAULT_TIMEOUT, - version: Optional[Any] = None, + key: KeyT, + value: EncodableT, + timeout: Optional[float] = DEFAULT_TIMEOUT, + version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: """ @@ -239,8 +258,8 @@ def add( def get( self, - key: Any, - default=None, + key: KeyT, + default: Optional[Any] = None, version: Optional[int] = None, client: Optional[Redis] = None, ) -> Any: @@ -265,7 +284,7 @@ def get( return self.decode(value) def persist( - self, key: Any, version: Optional[int] = None, client: Optional[Redis] = None + self, key: KeyT, version: Optional[int] = None, client: Optional[Redis] = None ) -> bool: if client is None: client = self.get_client(write=True) @@ -276,8 +295,8 @@ def persist( def expire( self, - key: Any, - timeout, + key: KeyT, + timeout: ExpiryT, version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: @@ -288,7 +307,13 @@ def expire( return client.expire(key, timeout) - def pexpire(self, key, timeout, version=None, client=None) -> bool: + def pexpire( + self, + key: KeyT, + timeout: ExpiryT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> bool: if client is None: client = self.get_client(write=True) @@ -300,8 +325,8 @@ def pexpire(self, key, timeout, version=None, client=None) -> bool: def pexpire_at( self, - key: Any, - when: Union[datetime, int], + key: KeyT, + when: AbsExpiryT, version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: @@ -318,8 +343,8 @@ def pexpire_at( def expire_at( self, - key: Any, - when: Union[datetime, int], + key: KeyT, + when: AbsExpiryT, version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: @@ -336,13 +361,13 @@ def expire_at( def lock( self, - key, + key: KeyT, version: Optional[int] = None, - timeout=None, - sleep=0.1, - blocking_timeout=None, + timeout: Optional[float] = None, + sleep: float = 0.1, + blocking_timeout: Optional[float] = None, client: Optional[Redis] = None, - thread_local=True, + thread_local: bool = True, ): if client is None: client = self.get_client(write=True) @@ -358,7 +383,7 @@ def lock( def delete( self, - key: Any, + key: KeyT, version: Optional[int] = None, prefix: Optional[str] = None, client: Optional[Redis] = None, @@ -405,8 +430,11 @@ def delete_pattern( raise ConnectionInterrupted(connection=client) from e def delete_many( - self, keys, version: Optional[int] = None, client: Optional[Redis] = None - ): + self, + keys: Iterable[KeyT], + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: """ Remove multiple keys at once. """ @@ -417,7 +445,7 @@ def delete_many( keys = [self.make_key(k, version=version) for k in keys] if not keys: - return None + return 0 try: return client.delete(*keys) @@ -437,7 +465,7 @@ def clear(self, client: Optional[Redis] = None) -> None: except _main_exceptions as e: raise ConnectionInterrupted(connection=client) from e - def decode(self, value: Union[bytes, int]) -> Any: + def decode(self, value: EncodableT) -> Any: """ Decode the given value. """ @@ -450,19 +478,22 @@ def decode(self, value: Union[bytes, int]) -> Any: value = self._serializer.loads(value) return value - def encode(self, value: Any) -> Union[bytes, Any]: + def encode(self, value: EncodableT) -> Union[bytes, int]: """ Encode the given value. """ - if isinstance(value, bool) or not isinstance(value, int): + if not isinstance(value, int): value = self._serializer.dumps(value) return self._compressor.compress(value) return value def get_many( - self, keys, version: Optional[int] = None, client: Optional[Redis] = None + self, + keys: Iterable[KeyT], + version: Optional[int] = None, + client: Optional[Redis] = None, ) -> OrderedDict: """ Retrieve many keys. @@ -491,7 +522,7 @@ def get_many( def set_many( self, - data: Dict[Any, Any], + data: Dict[KeyT, EncodableT], timeout: Optional[float] = DEFAULT_TIMEOUT, version: Optional[int] = None, client: Optional[Redis] = None, @@ -516,7 +547,7 @@ def set_many( def _incr( self, - key: Any, + key: KeyT, delta: int = 1, version: Optional[int] = None, client: Optional[Redis] = None, @@ -545,7 +576,7 @@ def _incr( """ value = client.eval(lua, 1, key, delta) if value is None: - error_message = f"Key '{key}' not found" + error_message = f"Key '{key!r}' not found" raise ValueError(error_message) except ResponseError as e: # if cached value or total value is greater than 64 bit signed @@ -559,7 +590,7 @@ def _incr( # returns -2 if the key does not exist # means, that key have expired if timeout == -2: - error_message = f"Key '{key}' not found" + error_message = f"Key '{key!r}' not found" raise ValueError(error_message) from e value = self.get(key, version=version, client=client) + delta self.set(key, value, version=version, timeout=timeout, client=client) @@ -570,7 +601,7 @@ def _incr( def incr( self, - key: Any, + key: KeyT, delta: int = 1, version: Optional[int] = None, client: Optional[Redis] = None, @@ -591,7 +622,7 @@ def incr( def decr( self, - key: Any, + key: KeyT, delta: int = 1, version: Optional[int] = None, client: Optional[Redis] = None, @@ -603,7 +634,7 @@ def decr( return self._incr(key=key, delta=-delta, version=version, client=client) def ttl( - self, key: Any, version: Optional[int] = None, client: Optional[Redis] = None + self, key: KeyT, version: Optional[int] = None, client: Optional[Redis] = None ) -> Optional[int]: """ Executes TTL redis command and return the "time-to-live" of specified key. @@ -628,7 +659,9 @@ def ttl( # Should never reach here return None - def pttl(self, key, version=None, client=None): + def pttl( + self, key: KeyT, version: Optional[int] = None, client: Optional[Redis] = None + ) -> Optional[int]: """ Executes PTTL redis command and return the "time-to-live" of specified key. If key is a non volatile key, it returns None. @@ -653,7 +686,7 @@ def pttl(self, key, version=None, client=None): return None def has_key( - self, key: Any, version: Optional[int] = None, client: Optional[Redis] = None + self, key: KeyT, version: Optional[int] = None, client: Optional[Redis] = None ) -> bool: """ Test if key exists. @@ -707,8 +740,8 @@ def keys( raise ConnectionInterrupted(connection=client) from e def make_key( - self, key: Any, version: Optional[Any] = None, prefix: Optional[str] = None - ) -> CacheKey: + self, key: KeyT, version: Optional[int] = None, prefix: Optional[str] = None + ) -> KeyT: if isinstance(key, CacheKey): return key @@ -722,7 +755,7 @@ def make_key( def make_pattern( self, pattern: str, version: Optional[int] = None, prefix: Optional[str] = None - ) -> CacheKey: + ) -> str: if isinstance(pattern, CacheKey): return pattern @@ -736,7 +769,7 @@ def make_pattern( return CacheKey(self._backend.key_func(pattern, prefix, version_str)) - def close(self): + def close(self) -> None: close_flag = self._options.get( "CLOSE_CONNECTION", getattr(settings, "DJANGO_REDIS_CLOSE_CONNECTION", False), @@ -744,8 +777,10 @@ def close(self): if close_flag: self.do_close_clients() - def do_close_clients(self): - """default implementation: Override in custom client""" + def do_close_clients(self) -> None: + """ + default implementation: Override in custom client + """ num_clients = len(self._clients) for idx in range(num_clients): self.disconnect(index=idx) @@ -753,7 +788,7 @@ def do_close_clients(self): def touch( self, - key: Any, + key: KeyT, timeout: Optional[float] = DEFAULT_TIMEOUT, version: Optional[int] = None, client: Optional[Redis] = None, diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index 7c34e6e7..7a0fb0f4 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -79,7 +79,14 @@ def get_many(self, keys, version=None): return recovered_data def set( # noqa: A003 - self, key, value, timeout=DEFAULT_TIMEOUT, version=None, client=None, nx=False + self, + key, + value, + timeout=DEFAULT_TIMEOUT, + version=None, + client=None, + nx=False, + xx=False, ): """ Persist a value to the cache, and set an optional expiration time. @@ -89,10 +96,16 @@ def set( # noqa: A003 client = self.get_server(key) return super().set( - key=key, value=value, timeout=timeout, version=version, client=client, nx=nx + key=key, + value=value, + timeout=timeout, + version=version, + client=client, + nx=nx, + xx=xx, ) - def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None): + def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None, client=None): """ Set a bunch of values in the cache at once from a dict of key/value pairs. This is much more efficient than calling set() multiple times. @@ -101,7 +114,7 @@ def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None): the default cache timeout will be used. """ for key, value in data.items(): - self.set(key, value, timeout, version=version) + self.set(key, value, timeout, version=version, client=client) def has_key(self, key, version=None, client=None): """ diff --git a/django_redis/pool.py b/django_redis/pool.py index 979c463c..b0e5f2a3 100644 --- a/django_redis/pool.py +++ b/django_redis/pool.py @@ -5,7 +5,7 @@ from django.core.exceptions import ImproperlyConfigured from django.utils.module_loading import import_string from redis import Redis -from redis.connection import DefaultParser, to_bool +from redis.connection import ConnectionPool, DefaultParser, to_bool from redis.sentinel import Sentinel @@ -16,7 +16,7 @@ class ConnectionFactory: # ConnectionFactory is instantiated, as Django creates new cache client # (DefaultClient) instance for every request. - _pools: Dict[str, Redis] = {} + _pools: Dict[str, ConnectionPool] = {} def __init__(self, options): pool_cls_path = options.get( @@ -70,7 +70,7 @@ def connect(self, url: str) -> Redis: params = self.make_connection_params(url) return self.get_connection(params) - def disconnect(self, connection): + def disconnect(self, connection: Redis) -> None: """ Given a not null client connection it disconnect from the Redis server. diff --git a/tests/start_redis.sh b/tests/start_redis.sh index 00bf2b03..9766fc51 100755 --- a/tests/start_redis.sh +++ b/tests/start_redis.sh @@ -47,7 +47,7 @@ docker run \ --health-interval 10s \ --health-retries 5 \ --health-timeout 5s \ - --network host \ --user $(id -u):$(id -g) \ + --publish $PORT:$PORT \ --volume /tmp:/tmp \ --detach redis:latest redis-server "${ARGS[@]}" diff --git a/tests/test_backend.py b/tests/test_backend.py index 7f4beb70..58d6b92c 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -734,6 +734,18 @@ def test_primary_replica_switching(self, cache: RedisCache): assert client.get_client(write=True) == "Foo" assert client.get_client(write=False) == "Bar" + def test_primary_replica_switching_with_index(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache = cast(RedisCache, caches["sample"]) + client = cache.client + client._server = ["foo", "bar"] + client._clients = ["Foo", "Bar"] + + assert client.get_client_with_index(write=True) == ("Foo", 0) + assert client.get_client_with_index(write=False) == ("Bar", 1) + def test_touch_zero_timeout(self, cache: RedisCache): cache.set("test_key", 222, timeout=10) From de08bc3a2907a29bb8965b5a9d286170d8239814 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 19:11:10 +0000 Subject: [PATCH 087/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/client/default.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 05f06dd2..251668fe 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -3,8 +3,7 @@ import socket from collections import OrderedDict from contextlib import suppress -from datetime import datetime -from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Union, Any +from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union from django.conf import settings from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache, get_key_func @@ -12,7 +11,7 @@ from django.utils.module_loading import import_string from redis import Redis from redis.exceptions import ConnectionError, ResponseError, TimeoutError -from redis.typing import EncodableT, KeyT, AbsExpiryT, ExpiryT +from redis.typing import AbsExpiryT, EncodableT, ExpiryT, KeyT from django_redis import pool from django_redis.exceptions import CompressorError, ConnectionInterrupted From e29200c6a6eacd5e5d5ff150535f4759db272b4f Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 21:28:35 +0200 Subject: [PATCH 088/148] Restored start_redis script --- tests/start_redis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/start_redis.sh b/tests/start_redis.sh index 9766fc51..00bf2b03 100755 --- a/tests/start_redis.sh +++ b/tests/start_redis.sh @@ -47,7 +47,7 @@ docker run \ --health-interval 10s \ --health-retries 5 \ --health-timeout 5s \ + --network host \ --user $(id -u):$(id -g) \ - --publish $PORT:$PORT \ --volume /tmp:/tmp \ --detach redis:latest redis-server "${ARGS[@]}" From e96c4ceec57ebfb26a35a52f46204086231e1662 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Wed, 1 Nov 2023 21:39:02 +0200 Subject: [PATCH 089/148] Silenced strange mypy errors with pexpire and expire --- django_redis/client/default.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 251668fe..db9aac23 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -3,6 +3,7 @@ import socket from collections import OrderedDict from contextlib import suppress +from datetime import timedelta from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union from django.conf import settings @@ -304,7 +305,9 @@ def expire( key = self.make_key(key, version=version) - return client.expire(key, timeout) + # for some strange reason mypy complains, + # saying that timeout type is float | timedelta + return client.expire(key, timeout) # type: ignore def pexpire( self, @@ -320,7 +323,9 @@ def pexpire( # Temporary casting until https://github.com/redis/redis-py/issues/1664 # is fixed. - return bool(client.pexpire(key, timeout)) + # for some strange reason mypy complains, + # saying that timeout type is float | timedelta + return bool(client.pexpire(key, timeout)) # type: ignore def pexpire_at( self, From 13aee50506a88fb87ef8a50f22b2cb85de94a556 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 19:39:13 +0000 Subject: [PATCH 090/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/client/default.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index db9aac23..21b93a04 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -3,7 +3,6 @@ import socket from collections import OrderedDict from contextlib import suppress -from datetime import timedelta from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union from django.conf import settings From 2e2dc55696769d8d02300fc5649c6d8323c5ea96 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Thu, 2 Nov 2023 09:44:04 +0200 Subject: [PATCH 091/148] restore encoding logic --- django_redis/client/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 21b93a04..9485627f 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -486,7 +486,7 @@ def encode(self, value: EncodableT) -> Union[bytes, int]: Encode the given value. """ - if not isinstance(value, int): + if isinstance(value, bool) or not isinstance(value, int): value = self._serializer.dumps(value) return self._compressor.compress(value) From 28500908cb0f608e2fc36566e3324c3d0d6ecca5 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Thu, 2 Nov 2023 16:00:16 +0200 Subject: [PATCH 092/148] Added changelog --- changelog.d/696.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/696.misc diff --git a/changelog.d/696.misc b/changelog.d/696.misc new file mode 100644 index 00000000..066426d6 --- /dev/null +++ b/changelog.d/696.misc @@ -0,0 +1 @@ +Typed DefaultClient \ No newline at end of file From 4508d4d9b4679a345f1392dcd76c1b275e1e0f19 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Thu, 2 Nov 2023 16:09:13 +0200 Subject: [PATCH 093/148] Support pytest7 --- setup.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 77582ff1..4829bfde 100644 --- a/setup.cfg +++ b/setup.cfg @@ -107,7 +107,7 @@ deps = dj42: Django>=4.2,<5.0 djmain: https://github.com/django/django/archive/main.tar.gz msgpack>=0.6.0 - pytest<7.0.0 + pytest pytest-cov pytest-django pytest-pythonpath @@ -149,7 +149,7 @@ filterwarnings = error::FutureWarning error::PendingDeprecationWarning ignore:.*distutils package is deprecated.*:DeprecationWarning -python_paths = tests +pythonpath = tests testpaths = tests xfail_strict = true From 2e895272c3348043abc2441579504257a403d902 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Thu, 2 Nov 2023 16:14:56 +0200 Subject: [PATCH 094/148] Added changelog --- changelog.d/697.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/697.misc diff --git a/changelog.d/697.misc b/changelog.d/697.misc new file mode 100644 index 00000000..dd57591c --- /dev/null +++ b/changelog.d/697.misc @@ -0,0 +1 @@ +Support pytest>=7 \ No newline at end of file From 2417417ebb2c8a155463b4a632e1b74488ebe027 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Fri, 3 Nov 2023 09:47:43 +0200 Subject: [PATCH 095/148] Drop django 3.2 and consequentially drop python 3.6 and 3.7 --- .github/workflows/ci.yml | 12 ------------ .ruff.toml | 4 +--- setup.cfg | 6 +----- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8bc3a829..258f8105 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,25 +19,13 @@ jobs: - '3.9' - '3.10' django-version: - - '3.2' - '4.1' - '4.2' redis-version: - 'latest' # Only test pre-release dependencies for the latest Python. - # And keep testing Django 3.2 with python 3.6 and 3.7 include: - # Django 3.2 and python 3.6 with latest redis - - django-version: '3.2' - redis-version: 'latest' - python-version: '3.6' - - # Django 3.2 and python 3.7 with latest redis - - django-version: '3.2' - redis-version: 'latest' - python-version: '3.7' - # Django 4.1 and python 3.11 with latest redis - django-version: '4.1' redis-version: 'latest' diff --git a/.ruff.toml b/.ruff.toml index 9c85337e..111d1f8e 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -120,9 +120,7 @@ ignore = [ fix = true -# TODO: enable this when python3.6 will stop being supported, -# from april 2024 https://docs.djangoproject.com/en/4.2/releases/3.2/ -#target-version = "py36" +target-version = "py38" [flake8-tidy-imports] ## Disallow all relative imports. diff --git a/setup.cfg b/setup.cfg index 77582ff1..7498b062 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,15 +60,13 @@ envlist = ruff mypy # tests against released versions - py{36,37,38,39,310,311}-dj{22,31,32,41,42}-redislatest + py{38,39,310,311}-dj{41,42}-redislatest # tests against unreleased versions py311-dj42-redismaster py311-djmain-redis{latest,master} [gh-actions] python = - 3.6: py36 - 3.7: py37 3.8: py38, black, ruff, mypy 3.9: py39 3.10: py310 @@ -76,7 +74,6 @@ python = [gh-actions:env] DJANGO = - 3.2: dj32 4.1: dj41 4.2: dj42 main: djmain @@ -102,7 +99,6 @@ commands = {envpython} -m coverage xml deps = - dj32: Django>=3.2,<3.3 dj41: Django>=4.1,<4.2 dj42: Django>=4.2,<5.0 djmain: https://github.com/django/django/archive/main.tar.gz From 7c1e997569d9f5e48d0e2c42150ea12d916f2804 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Fri, 3 Nov 2023 10:28:29 +0200 Subject: [PATCH 096/148] Added changelog --- changelog.d/699.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/699.misc diff --git a/changelog.d/699.misc b/changelog.d/699.misc new file mode 100644 index 00000000..0aaebb99 --- /dev/null +++ b/changelog.d/699.misc @@ -0,0 +1 @@ +Drop support for django 3.2, python 3.6 and python 3.7 \ No newline at end of file From ed9d67091b194ce64f9b5904948b31b0af9a9898 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sat, 4 Nov 2023 21:54:39 +0200 Subject: [PATCH 097/148] Fixes to setup.cfg file --- setup.cfg | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7498b062..7413e9ef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,6 @@ classifiers = Development Status :: 5 - Production/Stable Environment :: Web Environment Framework :: Django - Framework :: Django :: 3.2 Framework :: Django :: 4.1 Framework :: Django :: 4.2 Intended Audience :: Developers @@ -21,8 +20,6 @@ classifiers = Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -31,22 +28,21 @@ classifiers = Topic :: Utilities [options] -python_requires = >=3.6 +python_requires = >=3.8 packages = django_redis django_redis.client django_redis.serializers django_redis.compressors install_requires = - Django>=3.2 - redis>=3,!=4.0.0,!=4.0.1 + Django>=4.1 + redis>=4.0.2 [options.extras_require] -hiredis = redis[hiredis]>=3,!=4.0.0,!=4.0.1 +hiredis = redis[hiredis]>=4.0.2 [coverage:run] omit = - # tests tests/*, [coverage:report] From bb7833931d880c6c86c11d9ea9680470d80db626 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:31:03 +0000 Subject: [PATCH 098/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.292 → v0.1.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.292...v0.1.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c14df18f..607b3112 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.292 + rev: v0.1.4 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] From 48e54b815b110b2e079ef3392d7ae07ee13b6ee9 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Mon, 13 Nov 2023 15:36:04 +0200 Subject: [PATCH 099/148] Unleash tox4 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 258f8105..20e9fe9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install --upgrade "tox<4" tox-gh-actions + python -m pip install --upgrade tox tox-gh-actions - name: Tox tests run: | From 13d374a4781ffbafaf86574c7c3724a26821efe5 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Mon, 13 Nov 2023 15:37:34 +0200 Subject: [PATCH 100/148] Added changelog --- changelog.d/701.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/701.misc diff --git a/changelog.d/701.misc b/changelog.d/701.misc new file mode 100644 index 00000000..89180a32 --- /dev/null +++ b/changelog.d/701.misc @@ -0,0 +1 @@ +Support tox 4 \ No newline at end of file From dc71eee2b4aa163bd74f4ed025f682bd0c557886 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Mon, 13 Nov 2023 15:43:17 +0200 Subject: [PATCH 101/148] Changed passenv syntax --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 89568f27..473a71ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -78,7 +78,7 @@ REDIS = master: redismaster [testenv] -passenv = CI GITHUB* +passenv = CI, GITHUB* commands = {envpython} -m pytest --cov-report= --ds=settings.sqlite {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_herd {posargs} From 42de1bf6c1b2f97fb4e62337e1de277f0e6ed9a9 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Mon, 13 Nov 2023 16:06:01 +0200 Subject: [PATCH 102/148] Added dependabot for github actions --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..dabead29 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "daily" \ No newline at end of file From 3f6fdd7e9c948bf163339440666b242589fab082 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Mon, 13 Nov 2023 16:07:23 +0200 Subject: [PATCH 103/148] Added changelog --- changelog.d/702.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/702.misc diff --git a/changelog.d/702.misc b/changelog.d/702.misc new file mode 100644 index 00000000..631a01c3 --- /dev/null +++ b/changelog.d/702.misc @@ -0,0 +1 @@ +Configured dependabot for github actions \ No newline at end of file From 3acf3dbbc36ec98312fbfe77e506aa61bd3d5684 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Mon, 13 Nov 2023 16:09:30 +0200 Subject: [PATCH 104/148] Use ubuntu latest in CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20e9fe9f..d9e1d5c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: Django ${{ matrix.django-version }}, Redis.py ${{ matrix.redis-version }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: fail-fast: false From b2295dfecdcac333fecf70181d3381e3400b3e62 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Mon, 13 Nov 2023 16:10:24 +0200 Subject: [PATCH 105/148] Use ubuntu latest in CI --- changelog.d/703.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/703.misc diff --git a/changelog.d/703.misc b/changelog.d/703.misc new file mode 100644 index 00000000..fc52b72f --- /dev/null +++ b/changelog.d/703.misc @@ -0,0 +1 @@ +Use ubuntu-latest for CI \ No newline at end of file From b709fc375a395636edad2bcd4cceaffa777f97f4 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Mon, 13 Nov 2023 16:12:12 +0200 Subject: [PATCH 106/148] Update codecov to v2 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20e9fe9f..5dc07dda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: REDIS: ${{ matrix.redis-version }} - name: Upload coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: env_vars: DJANGO,REDIS,PYTHON flags: tests From 5d9facc915c998f5bad6662d9dd4a3b525348951 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:39:14 +0000 Subject: [PATCH 107/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.4 → v0.1.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.4...v0.1.5) - [github.com/psf/black: 23.10.1 → 23.11.0](https://github.com/psf/black/compare/23.10.1...23.11.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 607b3112..a4d6dec4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,12 +12,12 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.5 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] - repo: https://github.com/psf/black - rev: 23.10.1 + rev: 23.11.0 hooks: - id: black From ccfd3f70cf49eae4ec2f083d45810b3cc6fdf357 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:01:37 +0000 Subject: [PATCH 108/148] Bump actions/cache from 2 to 3 Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b49dae38..70638910 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-test-python-${{ matrix.python-version }}-django-${{ matrix.django-version }}-redis-${{ matrix.redis-version }}-${{ hashFiles('**/setup.*') }} @@ -129,7 +129,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-lint-${{ hashFiles('**/setup.*') }} @@ -170,7 +170,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-check-changelog-${{ hashFiles('**/setup.*') }} From 2445cfa394ebec1b0458c77b172144eb2e114771 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:01:42 +0000 Subject: [PATCH 109/148] Bump codecov/codecov-action from 1 to 3 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 3. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1...v3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b49dae38..cca039e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: REDIS: ${{ matrix.redis-version }} - name: Upload coverage - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: env_vars: DJANGO,REDIS,PYTHON flags: tests @@ -146,7 +146,7 @@ jobs: - name: Upload coverage if: ${{ matrix.tool == 'mypy' }} - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: flags: mypy From bf3c4b5365e8bb3850cac2df0a2ce0f237a2f47f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:01:47 +0000 Subject: [PATCH 110/148] Bump actions/checkout from 2 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b49dae38..f1adc42e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: python-version: '3.11' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 @@ -116,7 +116,7 @@ jobs: - 'mypy' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v2 @@ -155,7 +155,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05640c22..9d90bdb8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 From dfb8f75866c571e9d4c066b3d9e614e5653ef239 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 05:42:24 +0000 Subject: [PATCH 111/148] Bump actions/setup-python from 2 to 4 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 4. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04c607a8..b8ffa101 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' @@ -160,7 +160,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d90bdb8..bfd06019 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 From 0aaa8488c396440354dc619cd82ea337e96b8be8 Mon Sep 17 00:00:00 2001 From: myungsegyo Date: Tue, 31 Oct 2023 01:44:13 +0900 Subject: [PATCH 112/148] Support gzip compression --- AUTHORS.rst | 1 + README.rst | 15 ++++++++++++ django_redis/compressors/gzip.py | 19 +++++++++++++++ setup.cfg | 1 + tests/settings/sqlite_gzip.py | 41 ++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 django_redis/compressors/gzip.py create mode 100644 tests/settings/sqlite_gzip.py diff --git a/AUTHORS.rst b/AUTHORS.rst index 76d317db..463d543b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -14,3 +14,4 @@ David Zderic / dzderic Kirill Zaitsev / teferi Jon Dufresne Anès Foufa +Segyo Myung \ No newline at end of file diff --git a/README.rst b/README.rst index 8d597df7..03d6cf30 100644 --- a/README.rst +++ b/README.rst @@ -291,6 +291,21 @@ Let see an example, of how make it work with *lzma* compression format: } } +*Gzip* compression support: + +.. code-block:: python + + import gzip + + CACHES = { + "default": { + # ... + "OPTIONS": { + "COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor", + } + } + } + Memcached exceptions behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/django_redis/compressors/gzip.py b/django_redis/compressors/gzip.py new file mode 100644 index 00000000..0af153bb --- /dev/null +++ b/django_redis/compressors/gzip.py @@ -0,0 +1,19 @@ +import gzip + +from ..exceptions import CompressorError +from .base import BaseCompressor + + +class GzipCompressor(BaseCompressor): + min_length = 15 + + def compress(self, value: bytes) -> bytes: + if len(value) > self.min_length: + return gzip.compress(value) + return value + + def decompress(self, value: bytes) -> bytes: + try: + return gzip.decompress(value) + except gzip.BadGzipFile as e: + raise CompressorError(e) diff --git a/setup.cfg b/setup.cfg index 473a71ab..8e474cf9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,6 +91,7 @@ commands = {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_usock {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_zlib {posargs} {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_zstd {posargs} + {envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_gzip {posargs} {envpython} -m coverage report {envpython} -m coverage xml diff --git a/tests/settings/sqlite_gzip.py b/tests/settings/sqlite_gzip.py new file mode 100644 index 00000000..7ebb1580 --- /dev/null +++ b/tests/settings/sqlite_gzip.py @@ -0,0 +1,41 @@ +SECRET_KEY = "django_tests_secret_key" + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": ["redis://127.0.0.1:6379?db=1", "redis://127.0.0.1:6379?db=1"], + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor", + }, + }, + "doesnotexist": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:56379?db=1", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor", + }, + }, + "sample": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:6379?db=1,redis://127.0.0.1:6379?db=1", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor", + }, + }, + "with_prefix": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:6379?db=1", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor", + }, + "KEY_PREFIX": "test-prefix", + }, +} + +INSTALLED_APPS = ["django.contrib.sessions"] + +USE_TZ = False From 0bf13f9b02812ae4847e8485f42f6c10ed1c8902 Mon Sep 17 00:00:00 2001 From: myungsegyo Date: Tue, 31 Oct 2023 11:40:08 +0900 Subject: [PATCH 113/148] Add changeling file --- changelog.d/688.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/688.feature diff --git a/changelog.d/688.feature b/changelog.d/688.feature new file mode 100644 index 00000000..ca7fe07d --- /dev/null +++ b/changelog.d/688.feature @@ -0,0 +1 @@ +Support gzip compression \ No newline at end of file From 1d41d87611296feee8847692ebc12fe31f9a96d8 Mon Sep 17 00:00:00 2001 From: myungsegyo Date: Fri, 17 Nov 2023 15:23:15 +0900 Subject: [PATCH 114/148] Fix ruff's error --- django_redis/compressors/gzip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_redis/compressors/gzip.py b/django_redis/compressors/gzip.py index 0af153bb..a7659d46 100644 --- a/django_redis/compressors/gzip.py +++ b/django_redis/compressors/gzip.py @@ -16,4 +16,4 @@ def decompress(self, value: bytes) -> bytes: try: return gzip.decompress(value) except gzip.BadGzipFile as e: - raise CompressorError(e) + raise CompressorError from e From 4dee637233cfff7a8b379f45a4e2486715c6f9b9 Mon Sep 17 00:00:00 2001 From: myungsegyo Date: Fri, 17 Nov 2023 15:24:49 +0900 Subject: [PATCH 115/148] Fix relative import into absolute import --- django_redis/compressors/gzip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django_redis/compressors/gzip.py b/django_redis/compressors/gzip.py index a7659d46..533499f4 100644 --- a/django_redis/compressors/gzip.py +++ b/django_redis/compressors/gzip.py @@ -1,7 +1,7 @@ import gzip -from ..exceptions import CompressorError -from .base import BaseCompressor +from django_redis.compressors.base import BaseCompressor +from django_redis.exceptions import CompressorError class GzipCompressor(BaseCompressor): From 639eca78a08e5d4da6d84bbb5e1f8ad04a044c04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:07:46 +0000 Subject: [PATCH 116/148] Bump actions/setup-python from 4 to 5 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63a82bee..8d3bda28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' @@ -160,7 +160,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bfd06019..b592707c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 From 164acfeec3928e4e2a4c4454b646e3c50c2ea8ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:36:49 +0000 Subject: [PATCH 117/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.5 → v0.1.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.5...v0.1.7) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4d6dec4..92c61a72 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.5 + rev: v0.1.7 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] From 902cc8eaf1a479db997b105f99cc5b2c12ff210b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 17:36:27 +0000 Subject: [PATCH 118/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.7 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.7...v0.1.9) - [github.com/psf/black: 23.11.0 → 23.12.1](https://github.com/psf/black/compare/23.11.0...23.12.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 92c61a72..ba89982f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,12 +12,12 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.1.9 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] - repo: https://github.com/psf/black - rev: 23.11.0 + rev: 23.12.1 hooks: - id: black From 19337c6d0459785a4f4bbb01c1eb7bcbbf2681e6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:24:25 +0000 Subject: [PATCH 119/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.9 → v0.1.13](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.9...v0.1.13) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ba89982f..7c3ac099 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.9 + rev: v0.1.13 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] From dafaad2345b4a3a91f82fe0dadb5d0761545c564 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:24:32 +0000 Subject: [PATCH 120/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/cache.py | 2 +- django_redis/client/default.py | 2 +- django_redis/client/herd.py | 2 +- django_redis/client/sharded.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django_redis/cache.py b/django_redis/cache.py index 732ca9df..57cc7e43 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -77,7 +77,7 @@ def client(self): return self._client @omit_exception - def set(self, *args, **kwargs): # noqa: A003 + def set(self, *args, **kwargs): return self.client.set(*args, **kwargs) @omit_exception diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 9485627f..69ff0df4 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -144,7 +144,7 @@ def disconnect(self, index: int = 0, client: Optional[Redis] = None) -> None: if client is not None: self.connection_factory.disconnect(client) - def set( # noqa: A003 + def set( self, key: KeyT, value: EncodableT, diff --git a/django_redis/client/herd.py b/django_redis/client/herd.py index 70cb0459..94d539cd 100644 --- a/django_redis/client/herd.py +++ b/django_redis/client/herd.py @@ -57,7 +57,7 @@ def _unpack(self, value): return unpacked, False - def set( # noqa: A003 + def set( self, key, value, diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index 7a0fb0f4..dbb1d200 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -78,7 +78,7 @@ def get_many(self, keys, version=None): recovered_data[map_keys[key]] = value return recovered_data - def set( # noqa: A003 + def set( self, key, value, From 53aef8509f66b81b9822a2013b51f2654a73b9a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:56:00 +0000 Subject: [PATCH 121/148] Bump actions/cache from 3 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d3bda28..47e8bd71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-test-python-${{ matrix.python-version }}-django-${{ matrix.django-version }}-redis-${{ matrix.redis-version }}-${{ hashFiles('**/setup.*') }} @@ -129,7 +129,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-lint-${{ hashFiles('**/setup.*') }} @@ -170,7 +170,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-check-changelog-${{ hashFiles('**/setup.*') }} From 7c44d857414735c146e894d86ccd0dc9b0efdb42 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:28:00 +0000 Subject: [PATCH 122/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.13 → v0.1.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.13...v0.1.14) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c3ac099..6a0c14ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.13 + rev: v0.1.14 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] From 09e53a15a2e5b1bebda2b57d2b9e9c7e848bbffa Mon Sep 17 00:00:00 2001 From: kasra Date: Sat, 27 Jan 2024 02:17:25 +0330 Subject: [PATCH 123/148] Add support for hashmaps --- changelog.d/598.feature | 1 + django_redis/cache.py | 20 +++++++++ django_redis/client/default.py | 76 ++++++++++++++++++++++++++++++++++ tests/test_backend.py | 38 +++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 changelog.d/598.feature diff --git a/changelog.d/598.feature b/changelog.d/598.feature new file mode 100644 index 00000000..dfae9a2a --- /dev/null +++ b/changelog.d/598.feature @@ -0,0 +1 @@ +Support HashMaps \ No newline at end of file diff --git a/django_redis/cache.py b/django_redis/cache.py index 732ca9df..9c270f1d 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -184,3 +184,23 @@ def close(self, **kwargs): @omit_exception def touch(self, *args, **kwargs): return self.client.touch(*args, **kwargs) + + @omit_exception + def hset(self, *args, **kwargs): + return self.client.hset(*args, **kwargs) + + @omit_exception + def hdel(self, *args, **kwargs): + return self.client.hdel(*args, **kwargs) + + @omit_exception + def hlen(self, *args, **kwargs): + return self.client.hlen(*args, **kwargs) + + @omit_exception + def hkeys(self, *args, **kwargs): + return self.client.hkeys(*args, **kwargs) + + @omit_exception + def hexists(self, *args, **kwargs): + return self.client.hexists(*args, **kwargs) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 9485627f..07b49f9d 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -813,3 +813,79 @@ def touch( # Convert to milliseconds timeout = int(timeout * 1000) return bool(client.pexpire(key, timeout)) + + def hset( + self, + name: str, + key: KeyT, + value: EncodableT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + """ + Set the value of hash name at key to value. + Returns the number of fields added to the hash. + """ + if client is None: + client = self.get_client(write=True) + nkey = self.make_key(key, version=version) + nvalue = self.encode(value) + return int(client.hset(name, nkey, nvalue)) + + def hdel( + self, + name: str, + key: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + """ + Remove keys from hash name. + Returns the number of fields deleted from the hash. + """ + if client is None: + client = self.get_client(write=True) + nkey = self.make_key(key, version=version) + return int(client.hdel(name, nkey)) + + def hlen( + self, + name: str, + client: Optional[Redis] = None, + ) -> int: + """ + Return the number of items in hash name. + """ + if client is None: + client = self.get_client(write=False) + return int(client.hlen(name)) + + def hkeys( + self, + name: str, + client: Optional[Redis] = None, + ) -> List: + """ + Return a list of keys in hash name. + """ + if client is None: + client = self.get_client(write=False) + try: + return [self.reverse_key(k.decode()) for k in client.hkeys(name)] + except _main_exceptions as e: + raise ConnectionInterrupted(connection=client) from e + + def hexists( + self, + name: str, + key: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> bool: + """ + Return True if key exists in hash name, else False. + """ + if client is None: + client = self.get_client(write=False) + nkey = self.make_key(key, version=version) + return bool(client.hexists(name, nkey)) diff --git a/tests/test_backend.py b/tests/test_backend.py index 58d6b92c..d654a1b7 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -797,3 +797,41 @@ def test_clear(self, cache: RedisCache): cache.clear() value_from_cache_after_clear = cache.get("foo") assert value_from_cache_after_clear is None + + def test_hset(self, cache: RedisCache): + cache.hset("foo_hash1", "foo1", "bar1") + cache.hset("foo_hash1", "foo2", "bar2") + assert cache.hlen("foo_hash1") == 2 + assert cache.hexists("foo_hash1", "foo1") + assert cache.hexists("foo_hash1", "foo2") + + def test_hdel(self, cache: RedisCache): + cache.hset("foo_hash2", "foo1", "bar1") + cache.hset("foo_hash2", "foo2", "bar2") + assert cache.hlen("foo_hash2") == 2 + deleted_count = cache.hdel("foo_hash2", "foo1") + assert deleted_count == 1 + assert cache.hlen("foo_hash2") == 1 + assert not cache.hexists("foo_hash2", "foo1") + assert cache.hexists("foo_hash2", "foo2") + + def test_hlen(self, cache: RedisCache): + assert cache.hlen("foo_hash3") == 0 + cache.hset("foo_hash3", "foo1", "bar1") + assert cache.hlen("foo_hash3") == 1 + cache.hset("foo_hash3", "foo2", "bar2") + assert cache.hlen("foo_hash3") == 2 + + def test_hkeys(self, cache: RedisCache): + cache.hset("foo_hash4", "foo1", "bar1") + cache.hset("foo_hash4", "foo2", "bar2") + cache.hset("foo_hash4", "foo3", "bar3") + keys = cache.hkeys("foo_hash4") + assert len(keys) == 3 + for i in range(len(keys)): + assert keys[i] == f"foo{i + 1}" + + def test_hexists(self, cache: RedisCache): + cache.hset("foo_hash5", "foo1", "bar1") + assert cache.hexists("foo_hash5", "foo1") + assert not cache.hexists("foo_hash5", "foo") From d08e9225c1fadd58eb09bc031c23123967814b5e Mon Sep 17 00:00:00 2001 From: kasra Date: Mon, 29 Jan 2024 19:02:37 +0330 Subject: [PATCH 124/148] Skip sharded client in hashmap tests --- tests/test_backend.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_backend.py b/tests/test_backend.py index d654a1b7..550ce79c 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -799,6 +799,8 @@ def test_clear(self, cache: RedisCache): assert value_from_cache_after_clear is None def test_hset(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") cache.hset("foo_hash1", "foo1", "bar1") cache.hset("foo_hash1", "foo2", "bar2") assert cache.hlen("foo_hash1") == 2 @@ -806,6 +808,8 @@ def test_hset(self, cache: RedisCache): assert cache.hexists("foo_hash1", "foo2") def test_hdel(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") cache.hset("foo_hash2", "foo1", "bar1") cache.hset("foo_hash2", "foo2", "bar2") assert cache.hlen("foo_hash2") == 2 @@ -816,6 +820,8 @@ def test_hdel(self, cache: RedisCache): assert cache.hexists("foo_hash2", "foo2") def test_hlen(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") assert cache.hlen("foo_hash3") == 0 cache.hset("foo_hash3", "foo1", "bar1") assert cache.hlen("foo_hash3") == 1 @@ -823,6 +829,8 @@ def test_hlen(self, cache: RedisCache): assert cache.hlen("foo_hash3") == 2 def test_hkeys(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") cache.hset("foo_hash4", "foo1", "bar1") cache.hset("foo_hash4", "foo2", "bar2") cache.hset("foo_hash4", "foo3", "bar3") @@ -832,6 +840,8 @@ def test_hkeys(self, cache: RedisCache): assert keys[i] == f"foo{i + 1}" def test_hexists(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") cache.hset("foo_hash5", "foo1", "bar1") assert cache.hexists("foo_hash5", "foo1") assert not cache.hexists("foo_hash5", "foo") From d2ce2fa3735fc1c1a233d246b8a577354defdf8c Mon Sep 17 00:00:00 2001 From: kasra Date: Mon, 29 Jan 2024 19:12:29 +0330 Subject: [PATCH 125/148] Refactor code --- django_redis/client/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 07b49f9d..4742b1b7 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -864,7 +864,7 @@ def hkeys( self, name: str, client: Optional[Redis] = None, - ) -> List: + ) -> List[Any]: """ Return a list of keys in hash name. """ From b4f948901eee638bd9742d64e4c2c6650bd58ca0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:34:02 +0000 Subject: [PATCH 126/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.12.1 → 24.1.1](https://github.com/psf/black/compare/23.12.1...24.1.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6a0c14ff..168a470b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,6 @@ repos: args: [ --fix, --exit-non-zero-on-fix , --show-fixes] - repo: https://github.com/psf/black - rev: 23.12.1 + rev: 24.1.1 hooks: - id: black From 3951d767b26fb691f492cd2e226f7067fb5f3f7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 16:44:23 +0000 Subject: [PATCH 127/148] Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47e8bd71..04a4aefd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: REDIS: ${{ matrix.redis-version }} - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: env_vars: DJANGO,REDIS,PYTHON flags: tests @@ -146,7 +146,7 @@ jobs: - name: Upload coverage if: ${{ matrix.tool == 'mypy' }} - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: mypy From 1f681f1231a55cbc872ba730872beb5be30bf6e1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:31:00 +0000 Subject: [PATCH 128/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.14 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.14...v0.2.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 168a470b..b870ffb5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.14 + rev: v0.2.0 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] From d3bf1dfd735ac1fbb36ad4ec8713f826393beab8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:32:29 +0000 Subject: [PATCH 129/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.2.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.2.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b870ffb5..3b3cc467 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.0 + rev: v0.2.1 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] From b775d0c84eb43fd712074d1b1c2a221fa66e1ab7 Mon Sep 17 00:00:00 2001 From: Saurav Sharma Date: Fri, 16 Feb 2024 15:17:33 +0530 Subject: [PATCH 130/148] Fix:Expire method fails when using DEFAULT_TIMEOUT #724 --- django_redis/client/default.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 7850d3c7..cd24062a 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -299,6 +299,9 @@ def expire( version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: + if timeout is DEFAULT_TIMEOUT: + timeout = self._backend.default_timeout + if client is None: client = self.get_client(write=True) From f3919386f4ec3eda34149b599cafed37861ed240 Mon Sep 17 00:00:00 2001 From: Saurav Sharma Date: Fri, 16 Feb 2024 20:18:43 +0530 Subject: [PATCH 131/148] Testcase and changelog added for issue #724 --- changelog.d/724.fix | 3 +++ django_redis/client/default.py | 6 +++++- tests/test_backend.py | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 changelog.d/724.fix diff --git a/changelog.d/724.fix b/changelog.d/724.fix new file mode 100644 index 00000000..303a667d --- /dev/null +++ b/changelog.d/724.fix @@ -0,0 +1,3 @@ +Fix for Issue 724(expire method fails when using DEFAULT_TIMEOUT) + +https://github.com/jazzband/django-redis/issues/724 \ No newline at end of file diff --git a/django_redis/client/default.py b/django_redis/client/default.py index cd24062a..0575283c 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -299,7 +299,11 @@ def expire( version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: - if timeout is DEFAULT_TIMEOUT: + # timeout could be of type int|timedelta + # if timeout is DEFAULT_TIMEOUT or None then + # use self._backend.default_timeout(django default cache timeout) + + if (timeout is DEFAULT_TIMEOUT) or (timeout is None): timeout = self._backend.default_timeout if client is None: diff --git a/tests/test_backend.py b/tests/test_backend.py index 550ce79c..65f0a339 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -7,6 +7,7 @@ import pytest from django.core.cache import caches +from django.core.cache.backends.base import DEFAULT_TIMEOUT from django.test import override_settings from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture @@ -606,6 +607,10 @@ def test_expire(self, cache: RedisCache): assert pytest.approx(ttl) == 20 assert cache.expire("not-existent-key", 20) is False + def test_expire_with_default_timeout(self, cache: RedisCache): + cache.set("foo", "bar", timeout=None) + assert cache.expire("foo", DEFAULT_TIMEOUT) is True + def test_pexpire(self, cache: RedisCache): cache.set("foo", "bar", timeout=None) assert cache.pexpire("foo", 20500) is True From 740520ad85c9b715deffea8088ab74174cec1690 Mon Sep 17 00:00:00 2001 From: Saurav Sharma Date: Fri, 16 Feb 2024 20:31:12 +0530 Subject: [PATCH 132/148] Changelog file renamed --- changelog.d/{724.fix => 724.bugfix} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{724.fix => 724.bugfix} (100%) diff --git a/changelog.d/724.fix b/changelog.d/724.bugfix similarity index 100% rename from changelog.d/724.fix rename to changelog.d/724.bugfix From 9a38b1c38fb2d5157f160d050805b361b39d3943 Mon Sep 17 00:00:00 2001 From: Saurav Sharma Date: Fri, 16 Feb 2024 21:34:46 +0530 Subject: [PATCH 133/148] Fix:mypy errors --- django_redis/client/default.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 0575283c..dde8669c 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -303,8 +303,10 @@ def expire( # if timeout is DEFAULT_TIMEOUT or None then # use self._backend.default_timeout(django default cache timeout) + # for some strange reason mypy complains, + # saying that timeout type is float | timedelta if (timeout is DEFAULT_TIMEOUT) or (timeout is None): - timeout = self._backend.default_timeout + timeout = self._backend.default_timeout # type: ignore if client is None: client = self.get_client(write=True) From 62852758c402c3c844036878f3919a9c989eb2d4 Mon Sep 17 00:00:00 2001 From: Saurav Sharma Date: Sat, 17 Feb 2024 00:20:00 +0530 Subject: [PATCH 134/148] Fix for DEFAULT_TIMEOUT for pexpire, unwanted comments removed and tescases & changelog updated --- changelog.d/724.bugfix | 4 +--- django_redis/client/default.py | 9 +++------ tests/test_backend.py | 6 ++++++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/changelog.d/724.bugfix b/changelog.d/724.bugfix index 303a667d..36ff0f14 100644 --- a/changelog.d/724.bugfix +++ b/changelog.d/724.bugfix @@ -1,3 +1 @@ -Fix for Issue 724(expire method fails when using DEFAULT_TIMEOUT) - -https://github.com/jazzband/django-redis/issues/724 \ No newline at end of file +Hotfix for timeout=DEFAULT_TIMEOUT in expire and pexpire \ No newline at end of file diff --git a/django_redis/client/default.py b/django_redis/client/default.py index dde8669c..5d1557d4 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -299,12 +299,6 @@ def expire( version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: - # timeout could be of type int|timedelta - # if timeout is DEFAULT_TIMEOUT or None then - # use self._backend.default_timeout(django default cache timeout) - - # for some strange reason mypy complains, - # saying that timeout type is float | timedelta if (timeout is DEFAULT_TIMEOUT) or (timeout is None): timeout = self._backend.default_timeout # type: ignore @@ -324,6 +318,9 @@ def pexpire( version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: + if (timeout is DEFAULT_TIMEOUT) or (timeout is None): + timeout = self._backend.default_timeout # type: ignore + if client is None: client = self.get_client(write=True) diff --git a/tests/test_backend.py b/tests/test_backend.py index 65f0a339..4ff60983 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -610,6 +610,7 @@ def test_expire(self, cache: RedisCache): def test_expire_with_default_timeout(self, cache: RedisCache): cache.set("foo", "bar", timeout=None) assert cache.expire("foo", DEFAULT_TIMEOUT) is True + assert cache.expire("not-existent-key", DEFAULT_TIMEOUT) is False def test_pexpire(self, cache: RedisCache): cache.set("foo", "bar", timeout=None) @@ -619,6 +620,11 @@ def test_pexpire(self, cache: RedisCache): assert pytest.approx(ttl, 10) == 20500 assert cache.pexpire("not-existent-key", 20500) is False + def test_pexpire_with_default_timeout(self, cache: RedisCache): + cache.set("foo", "bar", timeout=None) + assert cache.pexpire("foo", DEFAULT_TIMEOUT) is True + assert cache.pexpire("not-existent-key", DEFAULT_TIMEOUT) is False + def test_pexpire_at(self, cache: RedisCache): # Test settings expiration time 1 hour ahead by datetime. cache.set("foo", "bar", timeout=None) From 23b1b688ff5b2f84441e11b66628a49fbc65c542 Mon Sep 17 00:00:00 2001 From: Saurav Sharma Date: Sat, 17 Feb 2024 13:03:26 +0530 Subject: [PATCH 135/148] Improvement: None removed for timeout --- django_redis/client/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 5d1557d4..7235cdca 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -318,7 +318,7 @@ def pexpire( version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: - if (timeout is DEFAULT_TIMEOUT) or (timeout is None): + if timeout is DEFAULT_TIMEOUT: timeout = self._backend.default_timeout # type: ignore if client is None: From 8bf4390c6b3a774b7777a39133090009bb674263 Mon Sep 17 00:00:00 2001 From: Saurav Sharma Date: Sat, 17 Feb 2024 14:30:36 +0530 Subject: [PATCH 136/148] Improvement: None removed for timeout --- django_redis/client/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 7235cdca..b9a5c1b0 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -299,7 +299,7 @@ def expire( version: Optional[int] = None, client: Optional[Redis] = None, ) -> bool: - if (timeout is DEFAULT_TIMEOUT) or (timeout is None): + if timeout is DEFAULT_TIMEOUT: timeout = self._backend.default_timeout # type: ignore if client is None: From de741a7dd348d60dab4e48ff6f0230e71b9bcc12 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sat, 17 Feb 2024 17:07:24 +0200 Subject: [PATCH 137/148] Dropped django4.1 and added django 5.0 --- .github/workflows/ci.yml | 17 ++++++++--------- setup.cfg | 12 ++++++------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04a4aefd..30b52ba9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,29 +15,28 @@ jobs: fail-fast: false matrix: python-version: - - '3.8' - - '3.9' - '3.10' + - '3.11' django-version: - - '4.1' - '4.2' + - '5.0' redis-version: - 'latest' # Only test pre-release dependencies for the latest Python. include: - # Django 4.1 and python 3.11 with latest redis - - django-version: '4.1' + # Django 4.2 and python 3.8 with latest redis + - django-version: '4.2' redis-version: 'latest' - python-version: '3.11' + python-version: '3.8' - # Django 4.2 and python 3.11 with latest redis + # Django 4.2 and python 3.9 with latest redis - django-version: '4.2' redis-version: 'latest' - python-version: '3.11' + python-version: '3.9' # latest Django with pre-release redis - - django-version: '4.2' + - django-version: '5.0' redis-version: 'master' python-version: '3.11' diff --git a/setup.cfg b/setup.cfg index 8e474cf9..fbbc9113 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,8 +12,8 @@ classifiers = Development Status :: 5 - Production/Stable Environment :: Web Environment Framework :: Django - Framework :: Django :: 4.1 Framework :: Django :: 4.2 + Framework :: Django :: 5.0 Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent @@ -35,7 +35,7 @@ packages = django_redis.serializers django_redis.compressors install_requires = - Django>=4.1 + Django>=4.2 redis>=4.0.2 [options.extras_require] @@ -56,9 +56,9 @@ envlist = ruff mypy # tests against released versions - py{38,39,310,311}-dj{41,42}-redislatest + py{38,39,310,311}-dj{42,50}-redislatest # tests against unreleased versions - py311-dj42-redismaster + py311-dj50-redismaster py311-djmain-redis{latest,master} [gh-actions] @@ -70,8 +70,8 @@ python = [gh-actions:env] DJANGO = - 4.1: dj41 4.2: dj42 + 5.0: dj50 main: djmain REDIS = latest: redislatest @@ -96,8 +96,8 @@ commands = {envpython} -m coverage xml deps = - dj41: Django>=4.1,<4.2 dj42: Django>=4.2,<5.0 + dj50: Django>=5.0,<5.1 djmain: https://github.com/django/django/archive/main.tar.gz msgpack>=0.6.0 pytest From 294dcd09dded6420a50953a5efc92f889ad4c1d5 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sat, 17 Feb 2024 17:09:43 +0200 Subject: [PATCH 138/148] Added changelog file --- changelog.d/729.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/729.misc diff --git a/changelog.d/729.misc b/changelog.d/729.misc new file mode 100644 index 00000000..09df626f --- /dev/null +++ b/changelog.d/729.misc @@ -0,0 +1 @@ +Dropped support for django 4.1 and added support for django 5.0 \ No newline at end of file From 96699a9c8c3fd28ad3484e90f48b3d6d3c9036c7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:41:49 +0000 Subject: [PATCH 139/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.1 → v0.2.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.1...v0.2.2) - [github.com/psf/black: 24.1.1 → 24.2.0](https://github.com/psf/black/compare/24.1.1...24.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b3cc467..e8c4f069 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,12 +12,12 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.2.2 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] - repo: https://github.com/psf/black - rev: 24.1.1 + rev: 24.2.0 hooks: - id: black From e7d9be06dae2d585f8eb3db574ff53609aaa681e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:43:13 +0000 Subject: [PATCH 140/148] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) - [github.com/astral-sh/ruff-pre-commit: v0.2.2 → v0.4.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.2...v0.4.8) - [github.com/psf/black: 24.2.0 → 24.4.2](https://github.com/psf/black/compare/24.2.0...24.4.2) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e8c4f069..ab501954 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-ast - id: check-case-conflict @@ -12,12 +12,12 @@ repos: # from readme - ruff with autofix must run before # other formatters, such as black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.2 + rev: v0.4.8 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix , --show-fixes] - repo: https://github.com/psf/black - rev: 24.2.0 + rev: 24.4.2 hooks: - id: black From cb849def94c5b87351b6de7de50dd22cef64f498 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:43:31 +0000 Subject: [PATCH 141/148] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_redis/compressors/lz4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_redis/compressors/lz4.py b/django_redis/compressors/lz4.py index 32183321..940c96d5 100644 --- a/django_redis/compressors/lz4.py +++ b/django_redis/compressors/lz4.py @@ -16,5 +16,5 @@ def compress(self, value: bytes) -> bytes: def decompress(self, value: bytes) -> bytes: try: return _decompress(value) - except Exception as e: # noqa: BLE001 + except Exception as e: raise CompressorError from e From da4b6801d2ed86daf6b1d706100b301ed5fe4385 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Fri, 14 Jun 2024 23:10:47 +0300 Subject: [PATCH 142/148] Ruff fixes --- .ruff.toml | 8 ++++---- django_redis/client/sharded.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.ruff.toml b/.ruff.toml index 111d1f8e..80747f8e 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,5 +1,5 @@ # https://beta.ruff.rs/docs/rules/ -select = [ +lint.select = [ # rules from pyflakes "F", @@ -112,7 +112,7 @@ select = [ "RUF100", ] -ignore = [ +lint.ignore = [ "COM812", # missing trailing comma, covered by black "ANN101", # ignore missing type annotation in self parameter "S311", # ignore Standard pseudo-random generators because they are not used for cryptographic purposes @@ -122,11 +122,11 @@ fix = true target-version = "py38" -[flake8-tidy-imports] +[lint.flake8-tidy-imports] ## Disallow all relative imports. ban-relative-imports = "all" -[per-file-ignores] +[lint.per-file-ignores] # ignore assert statements in tests "tests/*.py" = ["S101"] diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index dbb1d200..ef03c3a6 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -258,7 +258,8 @@ def incr_version(self, key, delta=1, version=None, client=None): raise ConnectionInterrupted(connection=client) from e if value is None: - raise ValueError("Key '%s' not found" % key) + msg = f"Key '{key}' not found" + raise ValueError(msg) if isinstance(key, CacheKey): new_key = self.make_key(key.original_key(), version=version + delta) From 0bb4f32311c29b814ebc98dccb5183bf3c932d20 Mon Sep 17 00:00:00 2001 From: Vasyl Dizhak Date: Sun, 9 Jun 2024 20:03:38 +0100 Subject: [PATCH 143/148] Add support for the set functions from issue #597 Co-authored-by: Ali Rezaei --- changelog.d/730.feature | 1 + django_redis/cache.py | 68 ++++++++ django_redis/client/default.py | 282 ++++++++++++++++++++++++++++++++- django_redis/client/sharded.py | 149 ++++++++++++++++- tests/test_backend.py | 157 ++++++++++++++++++ 5 files changed, 654 insertions(+), 3 deletions(-) create mode 100644 changelog.d/730.feature diff --git a/changelog.d/730.feature b/changelog.d/730.feature new file mode 100644 index 00000000..d41ae639 --- /dev/null +++ b/changelog.d/730.feature @@ -0,0 +1 @@ +Support for sets and support basic operations, sadd, scard, sdiff, sdiffstore, sinter, sinterstore, smismember, sismember, smembers, smove, spop, srandmember, srem, sscan, sscan_iter, sunion, sunionstore \ No newline at end of file diff --git a/django_redis/cache.py b/django_redis/cache.py index d26c33fa..f7b943a3 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -185,6 +185,74 @@ def close(self, **kwargs): def touch(self, *args, **kwargs): return self.client.touch(*args, **kwargs) + @omit_exception + def sadd(self, *args, **kwargs): + return self.client.sadd(*args, **kwargs) + + @omit_exception + def scard(self, *args, **kwargs): + return self.client.scard(*args, **kwargs) + + @omit_exception + def sdiff(self, *args, **kwargs): + return self.client.sdiff(*args, **kwargs) + + @omit_exception + def sdiffstore(self, *args, **kwargs): + return self.client.sdiffstore(*args, **kwargs) + + @omit_exception + def sinter(self, *args, **kwargs): + return self.client.sinter(*args, **kwargs) + + @omit_exception + def sinterstore(self, *args, **kwargs): + return self.client.sinterstore(*args, **kwargs) + + @omit_exception + def sismember(self, *args, **kwargs): + return self.client.sismember(*args, **kwargs) + + @omit_exception + def smembers(self, *args, **kwargs): + return self.client.smembers(*args, **kwargs) + + @omit_exception + def smove(self, *args, **kwargs): + return self.client.smove(*args, **kwargs) + + @omit_exception + def spop(self, *args, **kwargs): + return self.client.spop(*args, **kwargs) + + @omit_exception + def srandmember(self, *args, **kwargs): + return self.client.srandmember(*args, **kwargs) + + @omit_exception + def srem(self, *args, **kwargs): + return self.client.srem(*args, **kwargs) + + @omit_exception + def sscan(self, *args, **kwargs): + return self.client.sscan(*args, **kwargs) + + @omit_exception + def sscan_iter(self, *args, **kwargs): + return self.client.sscan_iter(*args, **kwargs) + + @omit_exception + def smismember(self, *args, **kwargs): + return self.client.smismember(*args, **kwargs) + + @omit_exception + def sunion(self, *args, **kwargs): + return self.client.sunion(*args, **kwargs) + + @omit_exception + def sunionstore(self, *args, **kwargs): + return self.client.sunionstore(*args, **kwargs) + @omit_exception def hset(self, *args, **kwargs): return self.client.hset(*args, **kwargs) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index b9a5c1b0..6f51a4cd 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -3,7 +3,18 @@ import socket from collections import OrderedDict from contextlib import suppress -from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union +from typing import ( + Any, + Dict, + Iterable, + Iterator, + List, + Optional, + Set, + Tuple, + Union, + cast, +) from django.conf import settings from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache, get_key_func @@ -11,7 +22,7 @@ from django.utils.module_loading import import_string from redis import Redis from redis.exceptions import ConnectionError, ResponseError, TimeoutError -from redis.typing import AbsExpiryT, EncodableT, ExpiryT, KeyT +from redis.typing import AbsExpiryT, EncodableT, ExpiryT, KeyT, PatternT from django_redis import pool from django_redis.exceptions import CompressorError, ConnectionInterrupted @@ -66,6 +77,14 @@ def __init__(self, server, params: Dict[str, Any], backend: BaseCache) -> None: def __contains__(self, key: KeyT) -> bool: return self.has_key(key) + def _has_compression_enabled(self) -> bool: + return ( + self._options.get( + "COMPRESSOR", "django_redis.compressors.identity.IdentityCompressor" + ) + != "django_redis.compressors.identity.IdentityCompressor" + ) + def get_next_client_index( self, write: bool = True, tried: Optional[List[int]] = None ) -> int: @@ -778,6 +797,265 @@ def make_pattern( return CacheKey(self._backend.key_func(pattern, prefix, version_str)) + def sadd( + self, + key: KeyT, + *values: Any, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + client = self.get_client(write=True) + + key = self.make_key(key, version=version) + encoded_values = [self.encode(value) for value in values] + return int(client.sadd(key, *encoded_values)) + + def scard( + self, + key: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + client = self.get_client(write=False) + + key = self.make_key(key, version=version) + return int(client.scard(key)) + + def sdiff( + self, + *keys: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Set: + if client is None: + client = self.get_client(write=False) + + nkeys = [self.make_key(key, version=version) for key in keys] + return {self.decode(value) for value in client.sdiff(*nkeys)} + + def sdiffstore( + self, + dest: KeyT, + *keys: KeyT, + version_dest: Optional[int] = None, + version_keys: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + client = self.get_client(write=True) + + dest = self.make_key(dest, version=version_dest) + nkeys = [self.make_key(key, version=version_keys) for key in keys] + return int(client.sdiffstore(dest, *nkeys)) + + def sinter( + self, + *keys: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Set: + if client is None: + client = self.get_client(write=False) + + nkeys = [self.make_key(key, version=version) for key in keys] + return {self.decode(value) for value in client.sinter(*nkeys)} + + def sinterstore( + self, + dest: KeyT, + *keys: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + client = self.get_client(write=True) + + dest = self.make_key(dest, version=version) + nkeys = [self.make_key(key, version=version) for key in keys] + return int(client.sinterstore(dest, *nkeys)) + + def smismember( + self, + key: KeyT, + *members, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> List[bool]: + if client is None: + client = self.get_client(write=False) + + key = self.make_key(key, version=version) + encoded_members = [self.encode(member) for member in members] + + return [bool(value) for value in client.smismember(key, *encoded_members)] + + def sismember( + self, + key: KeyT, + member: Any, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> bool: + if client is None: + client = self.get_client(write=False) + + key = self.make_key(key, version=version) + member = self.encode(member) + return bool(client.sismember(key, member)) + + def smembers( + self, + key: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Set: + if client is None: + client = self.get_client(write=False) + + key = self.make_key(key, version=version) + return {self.decode(value) for value in client.smembers(key)} + + def smove( + self, + source: KeyT, + destination: KeyT, + member: Any, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> bool: + if client is None: + client = self.get_client(write=True) + + source = self.make_key(source, version=version) + destination = self.make_key(destination) + member = self.encode(member) + return bool(client.smove(source, destination, member)) + + def spop( + self, + key: KeyT, + count: Optional[int] = None, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Union[Set, Any]: + if client is None: + client = self.get_client(write=True) + + nkey = self.make_key(key, version=version) + result = client.spop(nkey, count) + if result is None: + return None + if isinstance(result, list): + return {self.decode(value) for value in result} + return self.decode(result) + + def srandmember( + self, + key: KeyT, + count: Optional[int] = None, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Union[List, Any]: + if client is None: + client = self.get_client(write=False) + + key = self.make_key(key, version=version) + result = client.srandmember(key, count) + if result is None: + return None + if isinstance(result, list): + return [self.decode(value) for value in result] + return self.decode(result) + + def srem( + self, + key: KeyT, + *members: EncodableT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + client = self.get_client(write=True) + + key = self.make_key(key, version=version) + nmembers = [self.encode(member) for member in members] + return int(client.srem(key, *nmembers)) + + def sscan( + self, + key: KeyT, + match: Optional[str] = None, + count: Optional[int] = 10, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Set[Any]: + if self._has_compression_enabled() and match: + err_msg = "Using match with compression is not supported." + raise ValueError(err_msg) + + if client is None: + client = self.get_client(write=False) + + key = self.make_key(key, version=version) + + cursor, result = client.sscan( + key, + match=cast(PatternT, self.encode(match)) if match else None, + count=count, + ) + return {self.decode(value) for value in result} + + def sscan_iter( + self, + key: KeyT, + match: Optional[str] = None, + count: Optional[int] = 10, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Iterator[Any]: + if self._has_compression_enabled() and match: + err_msg = "Using match with compression is not supported." + raise ValueError(err_msg) + + if client is None: + client = self.get_client(write=False) + + key = self.make_key(key, version=version) + for value in client.sscan_iter( + key, + match=cast(PatternT, self.encode(match)) if match else None, + count=count, + ): + yield self.decode(value) + + def sunion( + self, + *keys: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Set: + if client is None: + client = self.get_client(write=False) + + nkeys = [self.make_key(key, version=version) for key in keys] + return {self.decode(value) for value in client.sunion(*nkeys)} + + def sunionstore( + self, + destination: Any, + *keys: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + client = self.get_client(write=True) + + destination = self.make_key(destination, version=version) + encoded_keys = [self.make_key(key, version=version) for key in keys] + return int(client.sunionstore(destination, *encoded_keys)) + def close(self) -> None: close_flag = self._options.get( "CLOSE_CONNECTION", diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index ef03c3a6..871b1df1 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -1,9 +1,11 @@ import re from collections import OrderedDict from datetime import datetime -from typing import Union +from typing import Any, Iterator, List, Optional, Set, Union +from redis import Redis from redis.exceptions import ConnectionError +from redis.typing import KeyT from django_redis.client.default import DEFAULT_TIMEOUT, DefaultClient from django_redis.exceptions import ConnectionInterrupted @@ -336,3 +338,148 @@ def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None, client=None): def clear(self, client=None): for connection in self._serverdict.values(): connection.flushdb() + + def sadd( + self, + key: KeyT, + *values: Any, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().sadd(key, *values, version=version, client=client) + + def scard( + self, + key: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().scard(key=key, version=version, client=client) + + def smembers( + self, + key: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Set: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().smembers(key=key, version=version, client=client) + + def smove( + self, + source: KeyT, + destination: KeyT, + member: Any, + version: Optional[int] = None, + client: Optional[Redis] = None, + ): + if client is None: + source = self.make_key(source, version=version) + client = self.get_server(source) + destination = self.make_key(destination, version=version) + + return super().smove( + source=source, + destination=destination, + member=member, + version=version, + client=client, + ) + + def srem( + self, + key: KeyT, + *members, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().srem(key, *members, version=version, client=client) + + def sscan( + self, + key: KeyT, + match: Optional[str] = None, + count: Optional[int] = 10, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Set[Any]: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().sscan( + key=key, match=match, count=count, version=version, client=client + ) + + def sscan_iter( + self, + key: KeyT, + match: Optional[str] = None, + count: Optional[int] = 10, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Iterator[Any]: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().sscan_iter( + key=key, match=match, count=count, version=version, client=client + ) + + def srandmember( + self, + key: KeyT, + count: Optional[int] = None, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Union[Set, Any]: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().srandmember(key=key, count=count, version=version, client=client) + + def sismember( + self, + key: KeyT, + member: Any, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> bool: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().sismember(key, member, version=version, client=client) + + def spop( + self, + key: KeyT, + count: Optional[int] = None, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> Union[Set, Any]: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().spop(key=key, count=count, version=version, client=client) + + def smismember( + self, + key: KeyT, + *members, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> List[bool]: + if client is None: + key = self.make_key(key, version=version) + client = self.get_server(key) + return super().smismember(key, *members, version=version, client=client) diff --git a/tests/test_backend.py b/tests/test_backend.py index 4ff60983..8619931e 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -856,3 +856,160 @@ def test_hexists(self, cache: RedisCache): cache.hset("foo_hash5", "foo1", "bar1") assert cache.hexists("foo_hash5", "foo1") assert not cache.hexists("foo_hash5", "foo") + + def test_sadd(self, cache: RedisCache): + assert cache.sadd("foo", "bar") == 1 + assert cache.smembers("foo") == {"bar"} + + def test_scard(self, cache: RedisCache): + cache.sadd("foo", "bar", "bar2") + assert cache.scard("foo") == 2 + + def test_sdiff(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2") + cache.sadd("foo2", "bar2", "bar3") + assert cache.sdiff("foo1", "foo2") == {"bar1"} + + def test_sdiffstore(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2") + cache.sadd("foo2", "bar2", "bar3") + assert cache.sdiffstore("foo3", "foo1", "foo2") == 1 + assert cache.smembers("foo3") == {"bar1"} + + def test_sdiffstore_with_keys_version(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2", version=2) + cache.sadd("foo2", "bar2", "bar3", version=2) + assert cache.sdiffstore("foo3", "foo1", "foo2", version_keys=2) == 1 + assert cache.smembers("foo3") == {"bar1"} + + def test_sdiffstore_with_different_keys_versions_without_initial_set_in_version( + self, cache: RedisCache + ): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2", version=1) + cache.sadd("foo2", "bar2", "bar3", version=2) + assert cache.sdiffstore("foo3", "foo1", "foo2", version_keys=2) == 0 + + def test_sdiffstore_with_different_keys_versions_with_initial_set_in_version( + self, cache: RedisCache + ): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2", version=2) + cache.sadd("foo2", "bar2", "bar3", version=1) + assert cache.sdiffstore("foo3", "foo1", "foo2", version_keys=2) == 2 + + def test_sinter(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2") + cache.sadd("foo2", "bar2", "bar3") + assert cache.sinter("foo1", "foo2") == {"bar2"} + + def test_interstore(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2") + cache.sadd("foo2", "bar2", "bar3") + assert cache.sinterstore("foo3", "foo1", "foo2") == 1 + assert cache.smembers("foo3") == {"bar2"} + + def test_sismember(self, cache: RedisCache): + cache.sadd("foo", "bar") + assert cache.sismember("foo", "bar") is True + assert cache.sismember("foo", "bar2") is False + + def test_smove(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2") + cache.sadd("foo2", "bar2", "bar3") + assert cache.smove("foo1", "foo2", "bar1") is True + assert cache.smove("foo1", "foo2", "bar4") is False + assert cache.smembers("foo1") == {"bar2"} + assert cache.smembers("foo2") == {"bar1", "bar2", "bar3"} + + def test_spop_default_count(self, cache: RedisCache): + cache.sadd("foo", "bar1", "bar2") + assert cache.spop("foo") in {"bar1", "bar2"} + assert cache.smembers("foo") in [{"bar1"}, {"bar2"}] + + def test_spop(self, cache: RedisCache): + cache.sadd("foo", "bar1", "bar2") + assert cache.spop("foo", 1) in [{"bar1"}, {"bar2"}] + assert cache.smembers("foo") in [{"bar1"}, {"bar2"}] + + def test_srandmember_default_count(self, cache: RedisCache): + cache.sadd("foo", "bar1", "bar2") + assert cache.srandmember("foo") in {"bar1", "bar2"} + + def test_srandmember(self, cache: RedisCache): + cache.sadd("foo", "bar1", "bar2") + assert cache.srandmember("foo", 1) in [["bar1"], ["bar2"]] + + def test_srem(self, cache: RedisCache): + cache.sadd("foo", "bar1", "bar2") + assert cache.srem("foo", "bar1") == 1 + assert cache.srem("foo", "bar3") == 0 + + def test_sscan(self, cache: RedisCache): + cache.sadd("foo", "bar1", "bar2") + items = cache.sscan("foo") + assert items == {"bar1", "bar2"} + + def test_sscan_with_match(self, cache: RedisCache): + if cache.client._has_compression_enabled(): + pytest.skip("Compression is enabled, sscan with match is not supported") + cache.sadd("foo", "bar1", "bar2", "zoo") + items = cache.sscan("foo", match="zoo") + assert items == {"zoo"} + + def test_sscan_iter(self, cache: RedisCache): + cache.sadd("foo", "bar1", "bar2") + items = cache.sscan_iter("foo") + assert set(items) == {"bar1", "bar2"} + + def test_sscan_iter_with_match(self, cache: RedisCache): + if cache.client._has_compression_enabled(): + pytest.skip( + "Compression is enabled, sscan_iter with match is not supported" + ) + cache.sadd("foo", "bar1", "bar2", "zoo") + items = cache.sscan_iter("foo", match="bar*") + assert set(items) == {"bar1", "bar2"} + + def test_smismember(self, cache: RedisCache): + cache.sadd("foo", "bar1", "bar2", "bar3") + assert cache.smismember("foo", "bar1", "bar2", "xyz") == [True, True, False] + + def test_sunion(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2") + cache.sadd("foo2", "bar2", "bar3") + assert cache.sunion("foo1", "foo2") == {"bar1", "bar2", "bar3"} + + def test_sunionstore(self, cache: RedisCache): + if isinstance(cache.client, ShardClient): + pytest.skip("ShardClient doesn't support get_client") + + cache.sadd("foo1", "bar1", "bar2") + cache.sadd("foo2", "bar2", "bar3") + assert cache.sunionstore("foo3", "foo1", "foo2") == 3 + assert cache.smembers("foo3") == {"bar1", "bar2", "bar3"} From f34935cba01c7577b1200d2a6e23ea47efb9a524 Mon Sep 17 00:00:00 2001 From: Vasyl Dizhak Date: Sat, 15 Jun 2024 14:46:08 +0200 Subject: [PATCH 144/148] Fix review comments #597 --- django_redis/client/default.py | 31 +++++++++++++++++-------------- django_redis/client/sharded.py | 2 +- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 6f51a4cd..3219f7c9 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -517,6 +517,17 @@ def encode(self, value: EncodableT) -> Union[bytes, int]: return value + def _decode_iterable_result( + self, result: Any, covert_to_set: bool = True + ) -> Union[List[Any], None, Any]: + if result is None: + return None + if isinstance(result, list): + if covert_to_set: + return {self.decode(value) for value in result} + return [self.decode(value) for value in result] + return self.decode(result) + def get_many( self, keys: Iterable[KeyT], @@ -828,7 +839,7 @@ def sdiff( *keys: KeyT, version: Optional[int] = None, client: Optional[Redis] = None, - ) -> Set: + ) -> Set[Any]: if client is None: client = self.get_client(write=False) @@ -855,7 +866,7 @@ def sinter( *keys: KeyT, version: Optional[int] = None, client: Optional[Redis] = None, - ) -> Set: + ) -> Set[Any]: if client is None: client = self.get_client(write=False) @@ -910,7 +921,7 @@ def smembers( key: KeyT, version: Optional[int] = None, client: Optional[Redis] = None, - ) -> Set: + ) -> Set[Any]: if client is None: client = self.get_client(write=False) @@ -945,11 +956,7 @@ def spop( nkey = self.make_key(key, version=version) result = client.spop(nkey, count) - if result is None: - return None - if isinstance(result, list): - return {self.decode(value) for value in result} - return self.decode(result) + return self._decode_iterable_result(result) def srandmember( self, @@ -963,11 +970,7 @@ def srandmember( key = self.make_key(key, version=version) result = client.srandmember(key, count) - if result is None: - return None - if isinstance(result, list): - return [self.decode(value) for value in result] - return self.decode(result) + return self._decode_iterable_result(result, covert_to_set=False) def srem( self, @@ -1035,7 +1038,7 @@ def sunion( *keys: KeyT, version: Optional[int] = None, client: Optional[Redis] = None, - ) -> Set: + ) -> Set[Any]: if client is None: client = self.get_client(write=False) diff --git a/django_redis/client/sharded.py b/django_redis/client/sharded.py index 871b1df1..5e2eec90 100644 --- a/django_redis/client/sharded.py +++ b/django_redis/client/sharded.py @@ -367,7 +367,7 @@ def smembers( key: KeyT, version: Optional[int] = None, client: Optional[Redis] = None, - ) -> Set: + ) -> Set[Any]: if client is None: key = self.make_key(key, version=version) client = self.get_server(key) From 1b225fe4f9ddd89b3ef9005751cb06a700dcee99 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sun, 20 Oct 2024 17:47:09 +0300 Subject: [PATCH 145/148] Added blocking parameter to lock --- django_redis/client/default.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index b9a5c1b0..f6fd23bb 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -374,6 +374,7 @@ def lock( version: Optional[int] = None, timeout: Optional[float] = None, sleep: float = 0.1, + blocking: bool = True, blocking_timeout: Optional[float] = None, client: Optional[Redis] = None, thread_local: bool = True, @@ -386,6 +387,7 @@ def lock( key, timeout=timeout, sleep=sleep, + blocking=blocking, blocking_timeout=blocking_timeout, thread_local=thread_local, ) From 277f2eb07d7d49a895ac7b6de0ce4b2e5a483e55 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sun, 20 Oct 2024 18:11:22 +0300 Subject: [PATCH 146/148] Added tests for not blocking lock timeout --- tests/test_backend.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 4ff60983..1714996a 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -677,7 +677,19 @@ def test_expire_at(self, cache: RedisCache): def test_lock(self, cache: RedisCache): lock = cache.lock("foobar") - lock.acquire(blocking=True) + assert lock.acquire(blocking=True) + + assert cache.has_key("foobar") + lock.release() + assert not cache.has_key("foobar") + + def test_lock_not_blocking(self, cache: RedisCache): + lock = cache.lock("foobar") + assert lock.acquire(blocking=False) + + lock2 = cache.lock("foobar") + + assert not lock2.acquire(blocking=False) assert cache.has_key("foobar") lock.release() @@ -685,7 +697,7 @@ def test_lock(self, cache: RedisCache): def test_lock_released_by_thread(self, cache: RedisCache): lock = cache.lock("foobar", thread_local=False) - lock.acquire(blocking=True) + assert lock.acquire(blocking=True) def release_lock(lock_): lock_.release() From 9cecd04a7125158cb2b196e5f18a6b8a308a7f93 Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sun, 20 Oct 2024 17:47:09 +0300 Subject: [PATCH 147/148] Added blocking parameter to lock --- django_redis/client/default.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 3219f7c9..1c34e5ac 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -393,6 +393,7 @@ def lock( version: Optional[int] = None, timeout: Optional[float] = None, sleep: float = 0.1, + blocking: bool = True, blocking_timeout: Optional[float] = None, client: Optional[Redis] = None, thread_local: bool = True, @@ -405,6 +406,7 @@ def lock( key, timeout=timeout, sleep=sleep, + blocking=blocking, blocking_timeout=blocking_timeout, thread_local=thread_local, ) From b45d68df33505616e6059e4c350068b3a7b287fd Mon Sep 17 00:00:00 2001 From: WisdomPill Date: Sun, 20 Oct 2024 18:11:22 +0300 Subject: [PATCH 148/148] Added tests for not blocking lock timeout --- tests/test_backend.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_backend.py b/tests/test_backend.py index 8619931e..e5c54e18 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -677,7 +677,19 @@ def test_expire_at(self, cache: RedisCache): def test_lock(self, cache: RedisCache): lock = cache.lock("foobar") - lock.acquire(blocking=True) + assert lock.acquire(blocking=True) + + assert cache.has_key("foobar") + lock.release() + assert not cache.has_key("foobar") + + def test_lock_not_blocking(self, cache: RedisCache): + lock = cache.lock("foobar") + assert lock.acquire(blocking=False) + + lock2 = cache.lock("foobar") + + assert not lock2.acquire(blocking=False) assert cache.has_key("foobar") lock.release() @@ -685,7 +697,7 @@ def test_lock(self, cache: RedisCache): def test_lock_released_by_thread(self, cache: RedisCache): lock = cache.lock("foobar", thread_local=False) - lock.acquire(blocking=True) + assert lock.acquire(blocking=True) def release_lock(lock_): lock_.release()