Skip to content

Typed FixtureRequest: node type by scope and param type (deferred to an official mypy plugin)聽#15232

Open

Description

@RonnyPfannschmidt

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

Follow-up to #13888 and #15227. request.node and request.param are typed Any. This issue records what can be typed more precisely. The technical design is deferred until pytest has an official mypy plugin (related: #9334). Until then, nothing here is planned.

What the scope guarantees at runtime

From SubRequest.__init__ / get_scope_node / get_scope_package in src/_pytest/fixtures.py:

scope request.node exact?
function the Item (Function, DoctestItem, plugin items) no: Item, not Function
class Class, or the item itself when the test is not in a class no: Class | Item
module Module yes
package Package, or Session when there is no package no: Package | Session
session Session yes
callable (dynamic) scope unknown statically no
test's own request the Item no

So the scope alone never implies Function. The common accesses (.function, .callspec, .cls) would still need narrowing on function-scoped fixtures.

Without a plugin: generic FixtureRequest[NodeT, ParamT]

A prototype made FixtureRequest generic over NodeT (bound to Node) and ParamT, both defaulting to Any. It uses PEP 696 defaults via typing_extensions under TYPE_CHECKING, which pytest already does elsewhere. Checked with mypy --strict and pyright strict:

  • A bare pytest.FixtureRequest (all existing code) gives no new errors and no "missing type parameters" error.
  • FixtureRequest[pytest.Function, int] types node as Function and param as int.
  • FixtureRequest[pytest.Session] flags request.node.callspec.
  • mypy src gives no new errors.

Gaps:

  • Subscripting with one argument at runtime (FixtureRequest[pytest.Function] without from __future__ import annotations) raises TypeError: Too few arguments unless the runtime TypeVars have defaults. That needs typing.TypeVar(default=...) (3.13+) plus a __class_getitem__ shim or typing_extensions on older versions.
  • The annotation is only the user's assertion. A mismatch like @pytest.fixture(scope="session") with request: FixtureRequest[pytest.Function] is not caught, because type checkers cannot link the decorator's scope=/params= to the annotation of a named request parameter. Overloads can't express it, since Concatenate covers only leading positional parameters.

Needs a plugin

  • Inferring node from scope= and param from params=, or checking an explicit annotation against them. This needs a mypy plugin hook on the fixture decorator. pyright has no plugin API, so pyright users would only get the explicit generic.
  • An option without a plugin that covers both checkers: pytest validates the request annotation against scope/params when it registers the fixture, and warns on a mismatch.

The design (generic vs. plugin-only, runtime validation, how to handle dynamic scope) is left for the mypy plugin work.

Activity

  1. added
    type: proposalproposal for a new feature, often to gather opinions or design the API around the new feature
    topic: fixturesanything involving fixtures directly or indirectly
    on Oct 21, 2026
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

    topic: fixturesanything involving fixtures directly or indirectlytopic: typingtype-annotation issuetype: proposalproposal for a new feature, often to gather opinions or design the API around the new feature

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions