Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1090.deprecated.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Using :func:`pytest_asyncio.fixture` to decorate synchronous fixtures is now deprecated. Use :func:`pytest.fixture` instead.
20 changes: 16 additions & 4 deletions pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
import warnings
from asyncio import AbstractEventLoop
from collections.abc import (
AsyncIterator,
Awaitable,
Callable,
Collection,
Generator,
Expand Down Expand Up @@ -69,7 +67,7 @@
from asyncio import AbstractEventLoopPolicy

_ScopeName = Literal["session", "package", "module", "class", "function"]
_R = TypeVar("_R", bound=Awaitable[Any] | AsyncIterator[Any])
_R = TypeVar("_R")
_P = ParamSpec("_P")
FixtureFunction = Callable[_P, _R]
LoopFactory: TypeAlias = Callable[[], AbstractEventLoop]
Expand Down Expand Up @@ -211,6 +209,19 @@ def _make_asyncio_fixture_function(obj: Any, loop_scope: _ScopeName | None) -> N
if hasattr(obj, "__func__"):
# instance method, check the function object
obj = obj.__func__
if not _is_coroutine_or_asyncgen(obj):
warnings.warn(
PytestDeprecationWarning(
"@pytest_asyncio.fixture was applied to the synchronous function "
f"{obj.__name__!r}. Use @pytest.fixture for synchronous fixtures. "
"This will become an error in future versions of pytest-asyncio."
),
stacklevel=3,
# stacklevel=3 points at the user's @pytest_asyncio.fixture line for
# the bare-decorator path (user → fixture() → here). The factory path
# (@pytest_asyncio.fixture(...)) adds one extra frame via inner(), so
# the warning lands on plugin.py:inner instead — still the plugin.
)
obj._force_asyncio_fixture = True
obj._loop_scope = loop_scope

Expand Down Expand Up @@ -938,7 +949,8 @@ def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None:
functools.partial(fixturedef.finish, request=request)
)
synchronizer = _fixture_synchronizer(fixturedef, runner, request)
_make_asyncio_fixture_function(synchronizer, loop_scope)
synchronizer._force_asyncio_fixture = True # type: ignore[attr-defined]
synchronizer._loop_scope = loop_scope # type: ignore[attr-defined]
with MonkeyPatch.context() as c:
c.setattr(fixturedef, "func", synchronizer)
hook_result = yield
Expand Down
52 changes: 52 additions & 0 deletions tests/test_asyncio_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,55 @@ def test_sync_function_uses_async_fixture(always_true):
"""))
result = pytester.runpytest(f"--asyncio-mode={mode}")
result.assert_outcomes(passed=1)


def test_sync_fixture_emits_deprecation_warning(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import pytest_asyncio

@pytest_asyncio.fixture
def sync_fixture():
return 42

@pytest.mark.asyncio
async def test_uses_sync(sync_fixture):
assert sync_fixture == 42
"""))
result = pytester.runpytest("-W", "default")
result.assert_outcomes(passed=1)
result.stdout.fnmatch_lines(
[
(
"*PytestDeprecationWarning*@pytest_asyncio.fixture*"
"*sync_fixture*@pytest.fixture*"
)
]
)


def test_sync_fixture_factory_path_emits_deprecation_warning(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import pytest_asyncio

@pytest_asyncio.fixture(loop_scope="function")
def sync_fixture():
return 42

@pytest.mark.asyncio
async def test_uses_sync(sync_fixture):
assert sync_fixture == 42
"""))
result = pytester.runpytest("-W", "default")
result.assert_outcomes(passed=1)
result.stdout.fnmatch_lines(
[
(
"*PytestDeprecationWarning*@pytest_asyncio.fixture*"
"*sync_fixture*@pytest.fixture*"
)
]
)
Loading