From b0b332d3690c79005c0eb2d3e30e4f79c40a16b7 Mon Sep 17 00:00:00 2001 From: amirreza Date: Sun, 4 May 2025 04:08:00 +0330 Subject: [PATCH 01/10] fix signal resolver issue --- django_valkey/async_cache/cache.py | 1 + django_valkey/base.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/django_valkey/async_cache/cache.py b/django_valkey/async_cache/cache.py index 0fbe416..e12a97c 100644 --- a/django_valkey/async_cache/cache.py +++ b/django_valkey/async_cache/cache.py @@ -8,3 +8,4 @@ class AsyncValkeyCache( BaseValkeyCache[AsyncDefaultClient, AValkey], AsyncBackendCommands ): DEFAULT_CLIENT_CLASS = "django_valkey.async_cache.client.default.AsyncDefaultClient" + is_async = True diff --git a/django_valkey/base.py b/django_valkey/base.py index 8e17f37..f717b08 100644 --- a/django_valkey/base.py +++ b/django_valkey/base.py @@ -558,3 +558,21 @@ async def hkeys(self, *args, **kwargs) -> list[Any]: async def hexists(self, *args, **kwargs) -> bool: return await self.client.hexists(*args, **kwargs) + + +# temp fix for django's #36047 +# TODO: remove this when it's fixed in django +from django.core import signals # noqa: E402 +from django.core.cache import caches, close_caches # noqa: E402 + + +async def close_async_caches(**kwargs): + for conn in caches.all(initialized_only=True): + if getattr(conn, "is_async", False): + await conn.aclose() + else: + conn.close() + + +signals.request_finished.connect(close_async_caches) +signals.request_finished.disconnect(close_caches) From 4c253879c9ced3ec36f914210fa0023730f4a4c8 Mon Sep 17 00:00:00 2001 From: amirreza Date: Wed, 11 Jun 2025 00:15:33 +0330 Subject: [PATCH 02/10] add note about a warning logged during async tests --- tests/tests_async/test_cache_options.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/tests_async/test_cache_options.py b/tests/tests_async/test_cache_options.py index d9d4940..72ca1a1 100644 --- a/tests/tests_async/test_cache_options.py +++ b/tests/tests_async/test_cache_options.py @@ -80,6 +80,9 @@ class TestDjangoValkeyOmitException: @pytest_asyncio.fixture async def conf_cache(self, settings: SettingsWrapper): caches_settings = copy.deepcopy(settings.CACHES) + # NOTE: this files raises RuntimeWarning because `conn.close` was not awaited, + # this is expected because django calls the signal manually during this test + # to debug, put a `raise` in django.utils.connection.BaseConnectionHandler.close_all settings.CACHES = caches_settings return caches_settings From bdcc3f70f715396af5a6cb49661f2154305ab09a Mon Sep 17 00:00:00 2001 From: amirreza Date: Wed, 11 Jun 2025 00:17:11 +0330 Subject: [PATCH 03/10] test the signal receiver is registered and works properly --- tests/tests_async/test_requests.py | 77 ++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/tests_async/test_requests.py diff --git a/tests/tests_async/test_requests.py b/tests/tests_async/test_requests.py new file mode 100644 index 0000000..981c98f --- /dev/null +++ b/tests/tests_async/test_requests.py @@ -0,0 +1,77 @@ +import pytest + +from django.core import signals +from django.core.cache import cache, close_caches + +from django_valkey.base import close_async_caches + + +class TestWithOldSignal: + @pytest.fixture(autouse=True) + def setup(self): + signals.request_finished.disconnect(close_async_caches) + signals.request_finished.connect(close_caches) + yield + signals.request_finished.disconnect(close_caches) + signals.request_finished.connect(close_async_caches) + + @pytest.mark.asyncio(loop_scope="session") + async def test_warning_output_when_request_finished(self, async_client): + with pytest.warns( + RuntimeWarning, + match="coroutine 'AsyncBackendCommands.close' was never awaited", + ) as record: + await async_client.get("/async/") + + assert ( + str(record[0].message) + == "coroutine 'AsyncBackendCommands.close' was never awaited" + ) + + @pytest.mark.asyncio(loop_scope="session") + async def test_manually_await_signal(self, recwarn): + await signals.request_finished.asend(self.__class__) + assert len(recwarn) == 1 + + assert ( + str(recwarn[0].message) + == "coroutine 'AsyncBackendCommands.close' was never awaited" + ) + + def test_manually_call_signal(self, recwarn): + signals.request_finished.send(self.__class__) + assert len(recwarn) == 1 + + assert ( + str(recwarn[0].message) + == "coroutine 'AsyncBackendCommands.close' was never awaited" + ) + + +class TestWithNewSignal: + @pytest.mark.asyncio(loop_scope="session") + async def test_warning_output_when_request_finished(self, async_client, recwarn): + await async_client.get("/async/") + + assert len(recwarn) == 0 + + @pytest.mark.asyncio(loop_scope="session") + async def test_manually_await_signal(self, recwarn): + await signals.request_finished.asend(self.__class__) + assert len(recwarn) == 0 + + def test_manually_call_signal(self, recwarn): + signals.request_finished.send(self.__class__) + assert len(recwarn) == 0 + + def test_receiver_is_registered_and_old_receiver_unregistered(self): + sync_receivers, async_receivers = signals.request_finished._live_receivers(None) + assert close_async_caches in async_receivers + assert close_caches not in sync_receivers + + @pytest.mark.asyncio(loop_scope="session") + async def test_close_is_called_by_signal(self, mocker): + close_spy = mocker.spy(cache, "close") + await signals.request_finished.asend(self.__class__) + assert close_spy.await_count == 1 + assert close_spy.call_count == 1 From 51f33c59a4b8470346d7819e928ccc91374e5b13 Mon Sep 17 00:00:00 2001 From: amirreza Date: Wed, 11 Jun 2025 04:12:36 +0330 Subject: [PATCH 04/10] switch async testing tool to anyio, since pytest-asyncio seems flaky --- pyproject.toml | 4 +-- tests/conftest.py | 6 ++-- tests/tests_async/conftest.py | 11 +++++++ tests/tests_async/test_backend.py | 7 +++-- tests/tests_async/test_cache_options.py | 17 +++++------ tests/tests_async/test_client.py | 6 ++-- tests/tests_async/test_connection_factory.py | 7 ++--- tests/tests_async/test_connection_string.py | 3 +- tests/tests_async/test_requests.py | 31 +++++++++++++------- 9 files changed, 56 insertions(+), 36 deletions(-) create mode 100644 tests/tests_async/conftest.py diff --git a/pyproject.toml b/pyproject.toml index bfb8445..b5934b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ brotli = [ [dependency-groups] dev = [ + "anyio>=4.9.0", "black>=25.1.0", "coverage>=7.8.0", "django-cmd>=2.6", @@ -66,7 +67,6 @@ dev = [ "mypy>=1.15.0", "pre-commit>=4.2.0", "pytest>=8.3.5", - "pytest-asyncio>=0.26.0", "pytest-django>=4.11.1", "pytest-mock>=3.14.0", "pytest-subtests>=0.14.1", @@ -104,8 +104,6 @@ ignore_missing_settings = true [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "tests.settings.sqlite" -asyncio_mode = "auto" -asyncio_default_fixture_loop_scope = "session" [tool.coverage.run] plugins = ["django_coverage_plugin"] diff --git a/tests/conftest.py b/tests/conftest.py index e4eedeb..f392024 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,6 @@ from typing import cast import pytest -import pytest_asyncio from pytest_django.fixtures import SettingsWrapper from asgiref.compatibility import iscoroutinefunction @@ -12,10 +11,13 @@ from django_valkey.base import BaseValkeyCache from django_valkey.cache import ValkeyCache + +pytestmark = pytest.mark.anyio + # for some reason `isawaitable` doesn't work here if iscoroutinefunction(default_cache.clear): - @pytest_asyncio.fixture(loop_scope="session") + @pytest.fixture(scope="function") async def cache(): yield default_cache await default_cache.aclear() diff --git a/tests/tests_async/conftest.py b/tests/tests_async/conftest.py new file mode 100644 index 0000000..0417e1b --- /dev/null +++ b/tests/tests_async/conftest.py @@ -0,0 +1,11 @@ +import pytest + + +@pytest.fixture(scope="session") +def anyio_backend(): + return "asyncio" + + +@pytest.fixture(scope="session", autouse=True) +async def keepalive(anyio_backend): + pass diff --git a/tests/tests_async/test_backend.py b/tests/tests_async/test_backend.py index 4c8a9cc..027e948 100644 --- a/tests/tests_async/test_backend.py +++ b/tests/tests_async/test_backend.py @@ -8,7 +8,6 @@ from unittest.mock import patch, AsyncMock import pytest -import pytest_asyncio from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture @@ -22,7 +21,10 @@ from django_valkey.serializers.msgpack import MSGPackSerializer -@pytest_asyncio.fixture(loop_scope="session") +pytestmark = pytest.mark.anyio + + +@pytest.fixture async def patch_itersize_setting() -> Iterable[None]: del caches["default"] with override_settings(DJANGO_VALKEY_SCAN_ITERSIZE=30): @@ -31,7 +33,6 @@ async def patch_itersize_setting() -> Iterable[None]: del caches["default"] -@pytest.mark.asyncio(loop_scope="session") class TestAsyncDjangoValkeyCache: async def test_set_int(self, cache: AsyncValkeyCache): if isinstance(cache.client, AsyncHerdClient): diff --git a/tests/tests_async/test_cache_options.py b/tests/tests_async/test_cache_options.py index 72ca1a1..362736a 100644 --- a/tests/tests_async/test_cache_options.py +++ b/tests/tests_async/test_cache_options.py @@ -4,7 +4,6 @@ from typing import cast import pytest -import pytest_asyncio from pytest import LogCaptureFixture from pytest_django.fixtures import SettingsWrapper @@ -15,6 +14,9 @@ from django_valkey.async_cache.cache import AsyncValkeyCache from django_valkey.async_cache.client import AsyncHerdClient, AsyncDefaultClient + +pytestmark = pytest.mark.anyio + methods_with_no_parameters = {"clear", "close"} methods_with_one_required_parameters = { @@ -75,9 +77,8 @@ } -@pytest.mark.asyncio(loop_scope="session") class TestDjangoValkeyOmitException: - @pytest_asyncio.fixture + @pytest.fixture async def conf_cache(self, settings: SettingsWrapper): caches_settings = copy.deepcopy(settings.CACHES) # NOTE: this files raises RuntimeWarning because `conn.close` was not awaited, @@ -86,7 +87,7 @@ async def conf_cache(self, settings: SettingsWrapper): settings.CACHES = caches_settings return caches_settings - @pytest_asyncio.fixture + @pytest.fixture async def conf_cache_to_ignore_exception( self, settings: SettingsWrapper, conf_cache ): @@ -95,7 +96,7 @@ async def conf_cache_to_ignore_exception( settings.DJANGO_VALKEY_IGNORE_EXCEPTIONS = True settings.DJANGO_VALKEY_LOG_IGNORE_EXCEPTIONS = True - @pytest_asyncio.fixture + @pytest.fixture async def ignore_exceptions_cache( self, conf_cache_to_ignore_exception ) -> AsyncValkeyCache: @@ -213,7 +214,7 @@ async def test_error_raised_when_ignore_is_not_set(self, conf_cache): await cache.get("key") -@pytest_asyncio.fixture +@pytest.fixture async def key_prefix_cache( cache: AsyncValkeyCache, settings: SettingsWrapper ) -> Iterable[AsyncValkeyCache]: @@ -223,14 +224,13 @@ async def key_prefix_cache( yield cache -@pytest_asyncio.fixture +@pytest.fixture async def with_prefix_cache() -> Iterable[AsyncValkeyCache]: with_prefix = cast(AsyncValkeyCache, caches["with_prefix"]) yield with_prefix await with_prefix.clear() -@pytest.mark.asyncio(loop_scope="session") class TestDjangoValkeyCacheEscapePrefix: async def test_delete_pattern( self, key_prefix_cache: AsyncValkeyCache, with_prefix_cache: AsyncValkeyCache @@ -261,7 +261,6 @@ async def test_keys( assert "b" not in keys -@pytest.mark.asyncio(loop_scope="session") async def test_custom_key_function(cache: AsyncValkeyCache, settings: SettingsWrapper): caches_setting = copy.deepcopy(settings.CACHES) caches_setting["default"]["KEY_FUNCTION"] = "tests.test_cache_options.make_key" diff --git a/tests/tests_async/test_client.py b/tests/tests_async/test_client.py index 60f09d5..341d1ce 100644 --- a/tests/tests_async/test_client.py +++ b/tests/tests_async/test_client.py @@ -2,7 +2,6 @@ from unittest.mock import AsyncMock import pytest -import pytest_asyncio from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture @@ -11,8 +10,10 @@ from django_valkey.async_cache.cache import AsyncValkeyCache from django_valkey.async_cache.client import AsyncDefaultClient +pytestmark = pytest.mark.anyio -@pytest_asyncio.fixture + +@pytest.fixture async def cache_client(cache: AsyncValkeyCache) -> Iterable[AsyncDefaultClient]: client = cache.client await client.aset("TestClientClose", 0) @@ -20,7 +21,6 @@ async def cache_client(cache: AsyncValkeyCache) -> Iterable[AsyncDefaultClient]: await client.adelete("TestClientClose") -@pytest.mark.asyncio(loop_scope="session") class TestClientClose: async def test_close_client_disconnect_default( self, cache_client: AsyncDefaultClient, mocker: MockerFixture diff --git a/tests/tests_async/test_connection_factory.py b/tests/tests_async/test_connection_factory.py index 5501bb5..3682af6 100644 --- a/tests/tests_async/test_connection_factory.py +++ b/tests/tests_async/test_connection_factory.py @@ -6,7 +6,9 @@ from django_valkey.async_cache import pool -@pytest.mark.asyncio +pytestmark = pytest.mark.anyio + + async def test_connection_factory_redefine_from_opts(): cf = sync_pool.get_connection_factory( options={ @@ -30,7 +32,6 @@ async def test_connection_factory_redefine_from_opts(): ), ], ) -@pytest.mark.asyncio async def test_connection_factory_opts(conn_factory: str, expected): cf = sync_pool.get_connection_factory( path=None, @@ -55,7 +56,6 @@ async def test_connection_factory_opts(conn_factory: str, expected): ), ], ) -@pytest.mark.asyncio async def test_connection_factory_path(conn_factory: str, expected): cf = sync_pool.get_connection_factory( path=conn_factory, @@ -66,7 +66,6 @@ async def test_connection_factory_path(conn_factory: str, expected): assert isinstance(cf, expected) -@pytest.mark.asyncio async def test_connection_factory_no_sentinels(): with pytest.raises(ImproperlyConfigured): sync_pool.get_connection_factory( diff --git a/tests/tests_async/test_connection_string.py b/tests/tests_async/test_connection_string.py index a18c574..740bf60 100644 --- a/tests/tests_async/test_connection_string.py +++ b/tests/tests_async/test_connection_string.py @@ -2,6 +2,8 @@ from django_valkey import pool +pytestmark = pytest.mark.anyio + @pytest.mark.parametrize( "connection_string", @@ -11,7 +13,6 @@ "valkeys://localhost:3333?db=2", ], ) -@pytest.mark.asyncio async def test_connection_strings(connection_string: str): cf = pool.get_connection_factory( options={ diff --git a/tests/tests_async/test_requests.py b/tests/tests_async/test_requests.py index 981c98f..b377c57 100644 --- a/tests/tests_async/test_requests.py +++ b/tests/tests_async/test_requests.py @@ -1,9 +1,13 @@ import pytest from django.core import signals -from django.core.cache import cache, close_caches +from django.core.cache import close_caches from django_valkey.base import close_async_caches +from django_valkey.async_cache.cache import AsyncValkeyCache + + +pytestmark = pytest.mark.anyio class TestWithOldSignal: @@ -15,7 +19,11 @@ def setup(self): signals.request_finished.disconnect(close_caches) signals.request_finished.connect(close_async_caches) - @pytest.mark.asyncio(loop_scope="session") + def test_old_receiver_is_registered_and_new_receiver_unregistered(self, setup): + sync_receivers, async_receivers = signals.request_finished._live_receivers(None) + assert close_caches in sync_receivers + assert close_async_caches not in async_receivers + async def test_warning_output_when_request_finished(self, async_client): with pytest.warns( RuntimeWarning, @@ -28,7 +36,6 @@ async def test_warning_output_when_request_finished(self, async_client): == "coroutine 'AsyncBackendCommands.close' was never awaited" ) - @pytest.mark.asyncio(loop_scope="session") async def test_manually_await_signal(self, recwarn): await signals.request_finished.asend(self.__class__) assert len(recwarn) == 1 @@ -38,24 +45,27 @@ async def test_manually_await_signal(self, recwarn): == "coroutine 'AsyncBackendCommands.close' was never awaited" ) - def test_manually_call_signal(self, recwarn): - signals.request_finished.send(self.__class__) - assert len(recwarn) == 1 + # for some reason if i make this function sync, it can't get the log + async def test_manually_call_signal(self): + with pytest.warns( + RuntimeWarning, + match="coroutine 'AsyncBackendCommands.close' was never awaited", + ) as record: + signals.request_finished.send(self.__class__) + assert len(record) == 1 assert ( - str(recwarn[0].message) + str(record[0].message) == "coroutine 'AsyncBackendCommands.close' was never awaited" ) class TestWithNewSignal: - @pytest.mark.asyncio(loop_scope="session") async def test_warning_output_when_request_finished(self, async_client, recwarn): await async_client.get("/async/") assert len(recwarn) == 0 - @pytest.mark.asyncio(loop_scope="session") async def test_manually_await_signal(self, recwarn): await signals.request_finished.asend(self.__class__) assert len(recwarn) == 0 @@ -69,9 +79,8 @@ def test_receiver_is_registered_and_old_receiver_unregistered(self): assert close_async_caches in async_receivers assert close_caches not in sync_receivers - @pytest.mark.asyncio(loop_scope="session") async def test_close_is_called_by_signal(self, mocker): - close_spy = mocker.spy(cache, "close") + close_spy = mocker.spy(AsyncValkeyCache, "close") await signals.request_finished.asend(self.__class__) assert close_spy.await_count == 1 assert close_spy.call_count == 1 From 0e809394397a3b74530145d48561f0c640440abb Mon Sep 17 00:00:00 2001 From: amirreza Date: Thu, 19 Jun 2025 02:31:24 +0330 Subject: [PATCH 05/10] remove old comment --- tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index f392024..4457ea0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,6 @@ pytestmark = pytest.mark.anyio -# for some reason `isawaitable` doesn't work here if iscoroutinefunction(default_cache.clear): @pytest.fixture(scope="function") From 227e3d5149b2f1bd99c01ab2403e832fad1b8127 Mon Sep 17 00:00:00 2001 From: amirreza Date: Tue, 1 Jul 2025 17:54:53 +0330 Subject: [PATCH 06/10] add comment explaining the keepalive fixture --- tests/tests_async/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tests_async/conftest.py b/tests/tests_async/conftest.py index 0417e1b..c19998b 100644 --- a/tests/tests_async/conftest.py +++ b/tests/tests_async/conftest.py @@ -6,6 +6,7 @@ def anyio_backend(): return "asyncio" +# this keeps the event loop open for the entire test suite @pytest.fixture(scope="session", autouse=True) async def keepalive(anyio_backend): pass From 76516320a409e5073700b4852e24f544a83802ca Mon Sep 17 00:00:00 2001 From: amirreza Date: Tue, 1 Jul 2025 17:56:08 +0330 Subject: [PATCH 07/10] filter coroutine warning where it's expected --- tests/tests_async/test_cache_options.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/tests_async/test_cache_options.py b/tests/tests_async/test_cache_options.py index 362736a..444f72b 100644 --- a/tests/tests_async/test_cache_options.py +++ b/tests/tests_async/test_cache_options.py @@ -14,7 +14,6 @@ from django_valkey.async_cache.cache import AsyncValkeyCache from django_valkey.async_cache.client import AsyncHerdClient, AsyncDefaultClient - pytestmark = pytest.mark.anyio methods_with_no_parameters = {"clear", "close"} @@ -77,6 +76,8 @@ } +# TODO: when django adjusts the signal, remove this decorator (and the ones below) +@pytest.mark.filterwarnings("ignore:coroutine 'AsyncBackendCommands.close'") class TestDjangoValkeyOmitException: @pytest.fixture async def conf_cache(self, settings: SettingsWrapper): @@ -231,6 +232,7 @@ async def with_prefix_cache() -> Iterable[AsyncValkeyCache]: await with_prefix.clear() +@pytest.mark.filterwarnings("ignore:coroutine 'AsyncBackendCommands.close'") class TestDjangoValkeyCacheEscapePrefix: async def test_delete_pattern( self, key_prefix_cache: AsyncValkeyCache, with_prefix_cache: AsyncValkeyCache @@ -261,6 +263,7 @@ async def test_keys( assert "b" not in keys +@pytest.mark.filterwarnings("ignore:coroutine 'AsyncBackendCommands.close'") async def test_custom_key_function(cache: AsyncValkeyCache, settings: SettingsWrapper): caches_setting = copy.deepcopy(settings.CACHES) caches_setting["default"]["KEY_FUNCTION"] = "tests.test_cache_options.make_key" From 29ec776d6a533380dcf10130caaa9c360507bf2a Mon Sep 17 00:00:00 2001 From: amirreza Date: Tue, 1 Jul 2025 17:56:27 +0330 Subject: [PATCH 08/10] use anyio tools in tests --- tests/tests_async/test_backend.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/tests_async/test_backend.py b/tests/tests_async/test_backend.py index 027e948..d17ce9e 100644 --- a/tests/tests_async/test_backend.py +++ b/tests/tests_async/test_backend.py @@ -1,4 +1,3 @@ -import asyncio import contextlib import datetime import threading @@ -11,6 +10,8 @@ from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture +import anyio + from django.core.cache import caches from django.core.cache.backends.base import DEFAULT_TIMEOUT from django.test import override_settings @@ -73,7 +74,7 @@ async def test_setnx_timeout(self, cache: AsyncValkeyCache): # test that timeout still works for nx=True res = await cache.aset("test_key_nx", 1, timeout=2, nx=True) assert res is True - await asyncio.sleep(3) + await anyio.sleep(3) res = await cache.aget("test_key_nx") assert res is None @@ -81,7 +82,7 @@ async def test_setnx_timeout(self, cache: AsyncValkeyCache): await cache.aset("test_key_nx", 1) res = await cache.aset("test_key_nx", 2, timeout=2, nx=True) assert res is None - await asyncio.sleep(3) + await anyio.sleep(3) res = await cache.aget("test_key_nx") assert res == 1 @@ -152,7 +153,7 @@ async def test_save_float(self, cache: AsyncValkeyCache): async def test_timeout(self, cache: AsyncValkeyCache): await cache.aset("test_key", 222, timeout=3) - await asyncio.sleep(4) + await anyio.sleep(4) res = await cache.aget("test_key") assert res is None @@ -171,7 +172,7 @@ async def test_timeout_parameter_as_positional_argument( await cache.aset("test_key", 222, 1) res1 = await cache.aget("test_key") - await asyncio.sleep(2) + await anyio.sleep(2) res2 = await cache.aget("test_key") assert res1 == 222 assert res2 is None @@ -732,7 +733,7 @@ async def test_lock_released_by_thread(self, cache: AsyncValkeyCache): async def release_lock(lock_): await lock_.release() - t = threading.Thread(target=asyncio.run, args=[release_lock(lock)]) + t = threading.Thread(target=anyio.run, args=[release_lock, lock]) t.start() t.join() @@ -802,7 +803,7 @@ async def test_touch_positive_timeout(self, cache: AsyncValkeyCache): assert await cache.atouch("test_key", 2) is True assert await cache.aget("test_key") == 222 - await asyncio.sleep(3) + await anyio.sleep(3) assert await cache.aget("test_key") is None async def test_touch_negative_timeout(self, cache: AsyncValkeyCache): @@ -820,7 +821,7 @@ async def test_touch_forever(self, cache: AsyncValkeyCache): result = await cache.atouch("test_key", None) assert result is True assert await cache.attl("test_key") is None - await asyncio.sleep(2) + await anyio.sleep(2) assert await cache.aget("test_key") == "foo" async def test_touch_forever_nonexistent(self, cache: AsyncValkeyCache): @@ -831,7 +832,7 @@ async def test_touch_default_timeout(self, cache: AsyncValkeyCache): await cache.aset("test_key", "foo", timeout=1) result = await cache.atouch("test_key") assert result is True - await asyncio.sleep(2) + await anyio.sleep(2) assert await cache.aget("test_key") == "foo" async def test_clear(self, cache: AsyncValkeyCache): From 09a91892b7282152b2fc6c25c07f58ee01eff201 Mon Sep 17 00:00:00 2001 From: amirreza Date: Sun, 27 Jul 2025 23:27:04 +0330 Subject: [PATCH 09/10] add a todo comment --- tests/tests_async/test_requests.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/tests_async/test_requests.py b/tests/tests_async/test_requests.py index b377c57..7e62eb8 100644 --- a/tests/tests_async/test_requests.py +++ b/tests/tests_async/test_requests.py @@ -20,7 +20,8 @@ def setup(self): signals.request_finished.connect(close_async_caches) def test_old_receiver_is_registered_and_new_receiver_unregistered(self, setup): - sync_receivers, async_receivers = signals.request_finished._live_receivers(None) + sync_receivers, async_receivers = signals.request_finished._live_receivers( + None) assert close_caches in sync_receivers assert close_async_caches not in async_receivers @@ -45,7 +46,8 @@ async def test_manually_await_signal(self, recwarn): == "coroutine 'AsyncBackendCommands.close' was never awaited" ) - # for some reason if i make this function sync, it can't get the log + # TODO: find why garbage collector doesn't collect the coroutine when the method is + # sync (even when gc is called manually, it doesn't collect) async def test_manually_call_signal(self): with pytest.warns( RuntimeWarning, @@ -75,7 +77,8 @@ def test_manually_call_signal(self, recwarn): assert len(recwarn) == 0 def test_receiver_is_registered_and_old_receiver_unregistered(self): - sync_receivers, async_receivers = signals.request_finished._live_receivers(None) + sync_receivers, async_receivers = signals.request_finished._live_receivers( + None) assert close_async_caches in async_receivers assert close_caches not in sync_receivers From 0e6b541e1edeb97fe906b53e02c2ab08cdaebaab Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Jul 2025 19:56:31 +0000 Subject: [PATCH 10/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/tests_async/test_requests.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/tests_async/test_requests.py b/tests/tests_async/test_requests.py index 7e62eb8..6e475b7 100644 --- a/tests/tests_async/test_requests.py +++ b/tests/tests_async/test_requests.py @@ -20,8 +20,7 @@ def setup(self): signals.request_finished.connect(close_async_caches) def test_old_receiver_is_registered_and_new_receiver_unregistered(self, setup): - sync_receivers, async_receivers = signals.request_finished._live_receivers( - None) + sync_receivers, async_receivers = signals.request_finished._live_receivers(None) assert close_caches in sync_receivers assert close_async_caches not in async_receivers @@ -77,8 +76,7 @@ def test_manually_call_signal(self, recwarn): assert len(recwarn) == 0 def test_receiver_is_registered_and_old_receiver_unregistered(self): - sync_receivers, async_receivers = signals.request_finished._live_receivers( - None) + sync_receivers, async_receivers = signals.request_finished._live_receivers(None) assert close_async_caches in async_receivers assert close_caches not in sync_receivers