Skip to content

Plugins that run a fixture inside another fixture can't declare that dependency #15124

Open

Description

@tjkuson

What's the problem this feature will solve?

pytest async plugins (like anyio and pytest-asyncio) run async fixtures that users write with plain @pytest.fixture.

Via pytest_fixture_setup, they run each fixture on an event loop or backend that is itself a fixture (which is itself often parametrized). The only way to access that fixture is request.getfixturevalue(), so pytest doesn't see the dependency so parametrization and caching can break.

# conftest.py
import pytest

current_backend = None  # stands in for the running event loop

@pytest.fixture(scope="session", params=["a", "b"])
def backend(request):
    return request.param

@pytest.hookimpl(wrapper=True)
def pytest_fixture_setup(fixturedef, request):
    # mimic a plugin that runs `resource` inside `backend`
    global current_backend
    if fixturedef.argname == "resource":
        current_backend = request.getfixturevalue("backend")
    return (yield)

@pytest.fixture(scope="session")
def resource():  # user-written
    return current_backend
def test_both(backend, resource):
    assert resource == backend

def test_resource(resource):
    pass
$ pytest -v --tb=no
test_mre.py::test_both[a] PASSED
test_mre.py::test_both[b] FAILED
test_mre.py::test_resource PASSED
FAILED test_mre.py::test_both[b] - AssertionError: assert 'a' == 'b'

test_resource isn't parametrized, and test_both[b] gets the resource built on backend[a].

Adding an unused backend parameter to resource fixes it. anyio's docs tell users to do that for higher-scoped fixtures, and forgetting it silently runs tests against a fixture built on asyncio via trio.

Describe the solution you'd like

A way at collection time for a plugin that runs a fixture to name fixtures it needs without passing them as arguments, so pytest treats them like parameters.

One idea that might work is a new hook:

def pytest_fixture_dependencies(
    fixturedef: FixtureDef,
    config: Config,
) -> Iterable[str] | None: ...

Alternative Solutions

Fabricating __signature__ or using register_fixture only helps with fixtures the plugin defines itself (e.g., pytest_asyncio.fixture but not pytest.fixture).

Writing to FixtureDef.argnames is private and has to happen before collection.

Runtime tracking of getfixturevalue (#14103, #14104) can't affect parametrization, which is decided at collection.

Additional context

Relates to #14991, which is about reading rather than adding to the fixture graph.

Activity

  1. RonnyPfannschmidt commented on Oct 3, 2026

    @RonnyPfannschmidt
    Member

    If the backend is always a requirement then either a custom fixture decorator should be used or we need to introduce a entirely new concept

    This needs a planning session

  2. tjkuson commented on Oct 3, 2026

    @tjkuson
    ContributorAuthor

    Thanks, I agree that makes sense. Just asking users to use, e.g., pytest_asyncio.fixture instead of pytest.fixture would be the most straightforward solution and was my first instinct.

    The issue is that these async plugins currently allow users to use @pytest.fixture to declare async fixtures, and I'd like a solution that didn't break that long-supported behaviour (without patching or using private internals, that is).

    Happy to help with a planning session. Just one note: the pytest async plugins already take over fixture execution by replacing fixturedef.func in pytest_fixture_setup (which is where the hidden dependency originates). So if there needs to be a new concept, it might be less 'how do we add extra dependencies?' and more 'how can we allow a plugin to own a how a fixture runs including what it runs in?'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions