Repository navigation
Typed FixtureRequest: node type by scope and param type (deferred to an official mypy plugin)聽#15232
Description
馃 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]typesnodeasFunctionandparamasint.FixtureRequest[pytest.Session]flagsrequest.node.callspec.mypy srcgives no new errors.
Gaps:
- Subscripting with one argument at runtime (
FixtureRequest[pytest.Function]withoutfrom __future__ import annotations) raisesTypeError: Too few argumentsunless the runtimeTypeVars have defaults. That needstyping.TypeVar(default=...)(3.13+) plus a__class_getitem__shim ortyping_extensionson older versions. - The annotation is only the user's assertion. A mismatch like
@pytest.fixture(scope="session")withrequest: FixtureRequest[pytest.Function]is not caught, because type checkers cannot link the decorator'sscope=/params=to the annotation of a namedrequestparameter. Overloads can't express it, sinceConcatenatecovers only leading positional parameters.
Needs a plugin
- Inferring
nodefromscope=andparamfromparams=, 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
requestannotation 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