Skip to content

feat: expose FixtureFunctionDefinition for typing - #14862

Open
teddygood wants to merge 6 commits into
pytest-dev:mainfrom
teddygood:expose-fixture-function-definition
Open

teddygood wants to merge 6 commits into
pytest-dev:mainfrom
teddygood:expose-fixture-function-definition

Conversation

@teddygood

@teddygood teddygood commented Aug 12, 2026 •

Copy link
Copy Markdown

Closes #14853

Summary

pytest.fixture returns a FixtureFunctionDefinition, but users previously had to import this type from the private _pytest.fixtures module when annotating fixture factories.

This change exports the existing class as pytest.FixtureFunctionDefinition and adds it to the API reference. It does not introduce a new class or change fixture behavior at runtime. The supported public surface is limited to its use in type annotations. Direct instantiation, subclassing, and its attributes remain outside the public API.

A typing check covers fixture factories returning the public type. The obsolete Sphinx cross-reference exception is also removed now that the type has a public documentation target.

Co-authored-by: OpenAI Codex <noreply@openai.com>
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Aug 12, 2026
Comment thread src/_pytest/fixtures.py
@@ -1454,6 +1454,12 @@ def __call__(self, function: FixtureFunction) -> FixtureFunctionDefinition:

# TODO: paramspec/return type annotation tracking and storing
class FixtureFunctionDefinition:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pytest-dev/core if we expose this - do we want to make the return type a generic?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems directly related to the TODO introduced in #12473. I found #13036, which explored preserving both the ParamSpec and return type, and it was closed due to lack of time rather than because the design was rejected.
Since the generic arity and semantics would become part of the public API, I agree that this is worth settling before exposing the type. Do we want only a declared return-type parameter, or the ParamSpec plus return-type shape from #13036? For yield fixtures, should the type parameter represent the declared Generator[...] return type or the yielded fixture value?

I’ll wait for input from @pytest-dev/core before making further changes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to make the return type generic if possible.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems directly related to the TODO introduced in #12473. I found #13036, which explored preserving both the ParamSpec and return type, and it was closed due to lack of time rather than because the design was rejected. Since the generic arity and semantics would become part of the public API, I agree that this is worth settling before exposing the type. Do we want only a declared return-type parameter, or the ParamSpec plus return-type shape from #13036? For yield fixtures, should the type parameter represent the declared Generator[...] return type or the yielded fixture value?

I’ll wait for input from @pytest-dev/core before making further changes.

this seems quite a lot like a gpt driven rely - i think we may need to alter ai contribution policy a bit more as it increasingly difficult to distinguish good unattended model and attended model eagerly doing things on behalf of the user

@teddygood teddygood Aug 17, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

umm... I did use ai to help write the reply, but I reviewed it before posting. However, reading it again now, I can see that it sounds too ai assisted. sorry about that.

I found 13036 while looking into whether the return type should be generic, because it explored carrying the fixture function's parameter and return types through FixtureFunctionDefinition. That's where the ParamSpec question came from.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Written by Claude Opus 5.5 via Claude Code for the pytest maintainers; I prompted it, it did the work, I read it.

@teddygood sorry for the long wait. Here are answers to your questions, so you can move forward:

  1. Only a return-type parameter, no ParamSpec. Make it FixtureFunctionDefinition[R] with R = TypeVar("R", default=Any) from typing_extensions. Because of the default, a bare pytest.FixtureFunctionDefinition annotation still passes --strict.
  2. For yield fixtures, R is the yielded value, not the declared Generator[...]. Add overloads to fixture() that unwrap Generator[T, Any, Any] and Iterator[T] to T (plus AsyncGenerator/AsyncIterator for async plugins), with Callable[..., R] as the fallback. The overloads also need the fixture(None, ...) / FixtureFunctionMarker.__call__ path.
  3. Hide __call__ from type checkers by defining it under if not TYPE_CHECKING:. The runtime behavior stays as it is, but mypy and pyright then report direct calls as "not callable" by default. I tried -> NoReturn and @deprecated too: NoReturn gives no error at the call site, and @deprecated only reports when users opt in.

Please extend testing/typing_checks.py with assert_type checks for a plain fixture, a yield fixture, the factory case from #14853, and a direct call that type checkers reject (for example with # type: ignore[operator] under warn_unused_ignores).

@bluetech this keeps the runtime class and the isinstance check in collection unchanged, avoids the ParamSpec work from #13036, and covers the vws-python-mock use case with -> pytest.FixtureFunctionDefinition[str]. I'm dropping the protocol idea.


Generated by Claude Code

Comment thread src/_pytest/fixtures.py Outdated
"""The type of a fixture function after decoration by :func:`pytest.fixture`.

This type is public for type annotations. It should not be instantiated
or subclassed by users, and its attributes are not part of the public API.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should probably be marked @final?

@teddygood teddygood Aug 12, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I had considered whether @final was necessary, but you’re right. I’ll add it.

@bluetech bluetech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR itself looks good, but I'd like to see if we need this first, please see my comment on the issue #14853 (comment)

Comment thread src/_pytest/fixtures.py Outdated
class FixtureFunctionDefinition:
"""The type of a fixture function after decoration by :func:`pytest.fixture`.

This type is public for type annotations. It should not be instantiated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph isn't needed, we have runtime protection against this, so better to keep things short.

@teddygood
teddygood requested a review from bluetech October 7, 2026 15:29

This branch has not been deployed

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

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose FixtureFunctionDefinition as part of the public API

6 participants