Skip to content

Commit 5c819dc

Browse files
committed
switch async testing tool to anyio, since pytest-asyncio seems flaky
1 parent 7ec29db commit 5c819dc

File tree

9 files changed

+56
-36
lines changed

9 files changed

+56
-36
lines changed

pyproject.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ brotli = [
5757

5858
[dependency-groups]
5959
dev = [
60+
"anyio>=4.9.0",
6061
"black>=25.1.0",
6162
"coverage>=7.8.0",
6263
"django-cmd>=2.6",
@@ -66,7 +67,6 @@ dev = [
6667
"mypy>=1.15.0",
6768
"pre-commit>=4.2.0",
6869
"pytest>=8.3.5",
69-
"pytest-asyncio>=0.26.0",
7070
"pytest-django>=4.11.1",
7171
"pytest-mock>=3.14.0",
7272
"pytest-subtests>=0.14.1",
@@ -104,8 +104,6 @@ ignore_missing_settings = true
104104

105105
[tool.pytest.ini_options]
106106
DJANGO_SETTINGS_MODULE = "tests.settings.sqlite"
107-
asyncio_mode = "auto"
108-
asyncio_default_fixture_loop_scope = "session"
109107

110108
[tool.coverage.run]
111109
plugins = ["django_coverage_plugin"]

tests/conftest.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from typing import cast
44

55
import pytest
6-
import pytest_asyncio
76
from pytest_django.fixtures import SettingsWrapper
87

98
from asgiref.compatibility import iscoroutinefunction
@@ -12,10 +11,13 @@
1211
from django_valkey.base import BaseValkeyCache
1312
from django_valkey.cache import ValkeyCache
1413

14+
15+
pytestmark = pytest.mark.anyio
16+
1517
# for some reason `isawaitable` doesn't work here
1618
if iscoroutinefunction(default_cache.clear):
1719

18-
@pytest_asyncio.fixture(loop_scope="session")
20+
@pytest.fixture(scope="function")
1921
async def cache():
2022
yield default_cache
2123
await default_cache.aclear()

tests/tests_async/conftest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import pytest
2+
3+
4+
@pytest.fixture(scope="session")
5+
def anyio_backend():
6+
return "asyncio"
7+
8+
9+
@pytest.fixture(scope="session", autouse=True)
10+
async def keepalive(anyio_backend):
11+
pass

tests/tests_async/test_backend.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from unittest.mock import patch, AsyncMock
99

1010
import pytest
11-
import pytest_asyncio
1211
from pytest_django.fixtures import SettingsWrapper
1312
from pytest_mock import MockerFixture
1413

@@ -22,7 +21,10 @@
2221
from django_valkey.serializers.msgpack import MSGPackSerializer
2322

2423

25-
@pytest_asyncio.fixture(loop_scope="session")
24+
pytestmark = pytest.mark.anyio
25+
26+
27+
@pytest.fixture
2628
async def patch_itersize_setting() -> Iterable[None]:
2729
del caches["default"]
2830
with override_settings(DJANGO_VALKEY_SCAN_ITERSIZE=30):
@@ -31,7 +33,6 @@ async def patch_itersize_setting() -> Iterable[None]:
3133
del caches["default"]
3234

3335

34-
@pytest.mark.asyncio(loop_scope="session")
3536
class TestAsyncDjangoValkeyCache:
3637
async def test_set_int(self, cache: AsyncValkeyCache):
3738
if isinstance(cache.client, AsyncHerdClient):

tests/tests_async/test_cache_options.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import cast
55

66
import pytest
7-
import pytest_asyncio
87
from pytest import LogCaptureFixture
98
from pytest_django.fixtures import SettingsWrapper
109

@@ -15,6 +14,9 @@
1514
from django_valkey.async_cache.cache import AsyncValkeyCache
1615
from django_valkey.async_cache.client import AsyncHerdClient, AsyncDefaultClient
1716

17+
18+
pytestmark = pytest.mark.anyio
19+
1820
methods_with_no_parameters = {"clear", "close"}
1921

2022
methods_with_one_required_parameters = {
@@ -75,9 +77,8 @@
7577
}
7678

7779

78-
@pytest.mark.asyncio(loop_scope="session")
7980
class TestDjangoValkeyOmitException:
80-
@pytest_asyncio.fixture
81+
@pytest.fixture
8182
async def conf_cache(self, settings: SettingsWrapper):
8283
caches_settings = copy.deepcopy(settings.CACHES)
8384
# NOTE: this files raises RuntimeWarning because `conn.close` was not awaited,
@@ -86,7 +87,7 @@ async def conf_cache(self, settings: SettingsWrapper):
8687
settings.CACHES = caches_settings
8788
return caches_settings
8889

89-
@pytest_asyncio.fixture
90+
@pytest.fixture
9091
async def conf_cache_to_ignore_exception(
9192
self, settings: SettingsWrapper, conf_cache
9293
):
@@ -95,7 +96,7 @@ async def conf_cache_to_ignore_exception(
9596
settings.DJANGO_VALKEY_IGNORE_EXCEPTIONS = True
9697
settings.DJANGO_VALKEY_LOG_IGNORE_EXCEPTIONS = True
9798

98-
@pytest_asyncio.fixture
99+
@pytest.fixture
99100
async def ignore_exceptions_cache(
100101
self, conf_cache_to_ignore_exception
101102
) -> AsyncValkeyCache:
@@ -213,7 +214,7 @@ async def test_error_raised_when_ignore_is_not_set(self, conf_cache):
213214
await cache.get("key")
214215

215216

216-
@pytest_asyncio.fixture
217+
@pytest.fixture
217218
async def key_prefix_cache(
218219
cache: AsyncValkeyCache, settings: SettingsWrapper
219220
) -> Iterable[AsyncValkeyCache]:
@@ -223,14 +224,13 @@ async def key_prefix_cache(
223224
yield cache
224225

225226

226-
@pytest_asyncio.fixture
227+
@pytest.fixture
227228
async def with_prefix_cache() -> Iterable[AsyncValkeyCache]:
228229
with_prefix = cast(AsyncValkeyCache, caches["with_prefix"])
229230
yield with_prefix
230231
await with_prefix.clear()
231232

232233

233-
@pytest.mark.asyncio(loop_scope="session")
234234
class TestDjangoValkeyCacheEscapePrefix:
235235
async def test_delete_pattern(
236236
self, key_prefix_cache: AsyncValkeyCache, with_prefix_cache: AsyncValkeyCache
@@ -261,7 +261,6 @@ async def test_keys(
261261
assert "b" not in keys
262262

263263

264-
@pytest.mark.asyncio(loop_scope="session")
265264
async def test_custom_key_function(cache: AsyncValkeyCache, settings: SettingsWrapper):
266265
caches_setting = copy.deepcopy(settings.CACHES)
267266
caches_setting["default"]["KEY_FUNCTION"] = "tests.test_cache_options.make_key"

tests/tests_async/test_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from unittest.mock import AsyncMock
33

44
import pytest
5-
import pytest_asyncio
65
from pytest_django.fixtures import SettingsWrapper
76
from pytest_mock import MockerFixture
87

@@ -11,16 +10,17 @@
1110
from django_valkey.async_cache.cache import AsyncValkeyCache
1211
from django_valkey.async_cache.client import AsyncDefaultClient
1312

13+
pytestmark = pytest.mark.anyio
1414

15-
@pytest_asyncio.fixture
15+
16+
@pytest.fixture
1617
async def cache_client(cache: AsyncValkeyCache) -> Iterable[AsyncDefaultClient]:
1718
client = cache.client
1819
await client.aset("TestClientClose", 0)
1920
yield client
2021
await client.adelete("TestClientClose")
2122

2223

23-
@pytest.mark.asyncio(loop_scope="session")
2424
class TestClientClose:
2525
async def test_close_client_disconnect_default(
2626
self, cache_client: AsyncDefaultClient, mocker: MockerFixture

tests/tests_async/test_connection_factory.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from django_valkey.async_cache import pool
77

88

9-
@pytest.mark.asyncio
9+
pytestmark = pytest.mark.anyio
10+
11+
1012
async def test_connection_factory_redefine_from_opts():
1113
cf = sync_pool.get_connection_factory(
1214
options={
@@ -30,7 +32,6 @@ async def test_connection_factory_redefine_from_opts():
3032
),
3133
],
3234
)
33-
@pytest.mark.asyncio
3435
async def test_connection_factory_opts(conn_factory: str, expected):
3536
cf = sync_pool.get_connection_factory(
3637
path=None,
@@ -55,7 +56,6 @@ async def test_connection_factory_opts(conn_factory: str, expected):
5556
),
5657
],
5758
)
58-
@pytest.mark.asyncio
5959
async def test_connection_factory_path(conn_factory: str, expected):
6060
cf = sync_pool.get_connection_factory(
6161
path=conn_factory,
@@ -66,7 +66,6 @@ async def test_connection_factory_path(conn_factory: str, expected):
6666
assert isinstance(cf, expected)
6767

6868

69-
@pytest.mark.asyncio
7069
async def test_connection_factory_no_sentinels():
7170
with pytest.raises(ImproperlyConfigured):
7271
sync_pool.get_connection_factory(

tests/tests_async/test_connection_string.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from django_valkey import pool
44

5+
pytestmark = pytest.mark.anyio
6+
57

68
@pytest.mark.parametrize(
79
"connection_string",
@@ -11,7 +13,6 @@
1113
"valkeys://localhost:3333?db=2",
1214
],
1315
)
14-
@pytest.mark.asyncio
1516
async def test_connection_strings(connection_string: str):
1617
cf = pool.get_connection_factory(
1718
options={

tests/tests_async/test_requests.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import pytest
22

33
from django.core import signals
4-
from django.core.cache import cache, close_caches
4+
from django.core.cache import close_caches
55

66
from django_valkey.base import close_async_caches
7+
from django_valkey.async_cache.cache import AsyncValkeyCache
8+
9+
10+
pytestmark = pytest.mark.anyio
711

812

913
class TestWithOldSignal:
@@ -15,7 +19,11 @@ def setup(self):
1519
signals.request_finished.disconnect(close_caches)
1620
signals.request_finished.connect(close_async_caches)
1721

18-
@pytest.mark.asyncio(loop_scope="session")
22+
def test_old_receiver_is_registered_and_new_receiver_unregistered(self, setup):
23+
sync_receivers, async_receivers = signals.request_finished._live_receivers(None)
24+
assert close_caches in sync_receivers
25+
assert close_async_caches not in async_receivers
26+
1927
async def test_warning_output_when_request_finished(self, async_client):
2028
with pytest.warns(
2129
RuntimeWarning,
@@ -28,7 +36,6 @@ async def test_warning_output_when_request_finished(self, async_client):
2836
== "coroutine 'AsyncBackendCommands.close' was never awaited"
2937
)
3038

31-
@pytest.mark.asyncio(loop_scope="session")
3239
async def test_manually_await_signal(self, recwarn):
3340
await signals.request_finished.asend(self.__class__)
3441
assert len(recwarn) == 1
@@ -38,24 +45,27 @@ async def test_manually_await_signal(self, recwarn):
3845
== "coroutine 'AsyncBackendCommands.close' was never awaited"
3946
)
4047

41-
def test_manually_call_signal(self, recwarn):
42-
signals.request_finished.send(self.__class__)
43-
assert len(recwarn) == 1
48+
# for some reason if i make this function sync, it can't get the log
49+
async def test_manually_call_signal(self):
50+
with pytest.warns(
51+
RuntimeWarning,
52+
match="coroutine 'AsyncBackendCommands.close' was never awaited",
53+
) as record:
54+
signals.request_finished.send(self.__class__)
55+
assert len(record) == 1
4456

4557
assert (
46-
str(recwarn[0].message)
58+
str(record[0].message)
4759
== "coroutine 'AsyncBackendCommands.close' was never awaited"
4860
)
4961

5062

5163
class TestWithNewSignal:
52-
@pytest.mark.asyncio(loop_scope="session")
5364
async def test_warning_output_when_request_finished(self, async_client, recwarn):
5465
await async_client.get("/async/")
5566

5667
assert len(recwarn) == 0
5768

58-
@pytest.mark.asyncio(loop_scope="session")
5969
async def test_manually_await_signal(self, recwarn):
6070
await signals.request_finished.asend(self.__class__)
6171
assert len(recwarn) == 0
@@ -69,9 +79,8 @@ def test_receiver_is_registered_and_old_receiver_unregistered(self):
6979
assert close_async_caches in async_receivers
7080
assert close_caches not in sync_receivers
7181

72-
@pytest.mark.asyncio(loop_scope="session")
7382
async def test_close_is_called_by_signal(self, mocker):
74-
close_spy = mocker.spy(cache, "close")
83+
close_spy = mocker.spy(AsyncValkeyCache, "close")
7584
await signals.request_finished.asend(self.__class__)
7685
assert close_spy.await_count == 1
7786
assert close_spy.call_count == 1

0 commit comments

Comments
 (0)