tests/workflow_contracts annotates several functions with names imported only under typing.TYPE_CHECKING. Introspecting those annotations at runtime raises NameError. This records the position reached on #728 and #729, where reviewers asked for a change on both, so the next reviewer does not reopen it from scratch.
The position
Four facts, each measured rather than assumed.
The annotations do not fail at import. PYTHON_BASELINE is 3.14, where PEP 649 defers annotation evaluation. Both affected modules import, and the workflow-contract suite that imports them passes:
$ python3 -c "import whole_run_ordering, nextest_budgets"
both modules import cleanly under 3.14.3
The future import is forbidden here. The house lint refuses it by name:
tests/workflow_contracts/whole_run_ordering.py:13:0: C9112: Remove
'from __future__ import annotations' on a 3.14+ baseline
(redundant-future-annotations)
No module in this repository uses it, which is consistent with the rule rather than an oversight. Applying the reviewers' suggested fix reds make lint-python.
The placement is ruff's own requirement. typing-only-standard-library-import fires on a runtime import used only in annotations, so moving the import back out trades one gate failure for another.
The introspection failure is real, and older than either pull request. It is true on main today for cabc.Iterable[CoverageLane] in whole_run_ordering, from well before this work:
>>> whole_run_ordering.watchdog_required_for.__annotations__
NameError: name 'fractions' is not defined
>>> typing.get_type_hints(whole_run_ordering.watchdog_required_for)
NameError: name 'fractions' is not defined
Nothing in the repository introspects them. ty reads them statically and passes.
What would not fix it
The obvious candidate is extending lint-workflow-scripts, which loads every module under .github/scripts and whose recipe comment says that "only loading them catches a definition-time failure such as an annotation naming a TYPE_CHECKING-only import", to walk tests/ as well.
Measured on a minimal module with exactly this shape, it would not catch it:
--- runpy.run_path, the loader gate's own mechanism ---
loaded with no error
--- accessing __annotations__ ---
NameError: name 'fractions' is not defined
--- typing.get_type_hints ---
NameError: name 'fractions' is not defined
The loader's mechanism is what PEP 649 made insufficient. Worth noting separately: that makes the recipe comment stale for the 3.14 baseline. Loading a module no longer catches this class, so the gate is still useful for other definition-time failures but not for the one it names.
What would
A check that resolves the annotations rather than merely loading the module: walk each module's public functions and call typing.get_type_hints on each, which is what the two runs above show to be the discriminating operation. That is a different gate from the loader, not an extension of it, and it would fail on main today until the existing annotations are addressed, so adopting it means deciding what to do about those at the same time.
Three ways to address them, none obviously right, which is why this is an issue rather than a change:
- Import the names at runtime and suppress
typing-only-standard-library-import where they are annotation-only. Costs a suppression per module and a small import cost.
- Quote the annotations. Keeps the lint clean and makes
get_type_hints work, at the cost of the quoting being invisible to a reader as a deliberate choice.
- Decide that runtime introspection is not a use these modules support, and write that down instead of adding a gate. They are workflow contracts read by
ty and by pytest, neither of which introspects.
Not urgent
Nothing reads these annotations at runtime, every gate passes, and both #728 and #729 are unaffected in behaviour. This is a latent sharp edge in a helper plus a stale comment, recorded so the position is citable.
Raised out of the review rounds on #728 and #729, which each declined the change on the grounds above.
tests/workflow_contractsannotates several functions with names imported only undertyping.TYPE_CHECKING. Introspecting those annotations at runtime raisesNameError. This records the position reached on #728 and #729, where reviewers asked for a change on both, so the next reviewer does not reopen it from scratch.The position
Four facts, each measured rather than assumed.
The annotations do not fail at import.
PYTHON_BASELINEis 3.14, where PEP 649 defers annotation evaluation. Both affected modules import, and the workflow-contract suite that imports them passes:The future import is forbidden here. The house lint refuses it by name:
No module in this repository uses it, which is consistent with the rule rather than an oversight. Applying the reviewers' suggested fix reds
make lint-python.The placement is ruff's own requirement.
typing-only-standard-library-importfires on a runtime import used only in annotations, so moving the import back out trades one gate failure for another.The introspection failure is real, and older than either pull request. It is true on
maintoday forcabc.Iterable[CoverageLane]inwhole_run_ordering, from well before this work:Nothing in the repository introspects them.
tyreads them statically and passes.What would not fix it
The obvious candidate is extending
lint-workflow-scripts, which loads every module under.github/scriptsand whose recipe comment says that "only loading them catches a definition-time failure such as an annotation naming a TYPE_CHECKING-only import", to walktests/as well.Measured on a minimal module with exactly this shape, it would not catch it:
The loader's mechanism is what PEP 649 made insufficient. Worth noting separately: that makes the recipe comment stale for the 3.14 baseline. Loading a module no longer catches this class, so the gate is still useful for other definition-time failures but not for the one it names.
What would
A check that resolves the annotations rather than merely loading the module: walk each module's public functions and call
typing.get_type_hintson each, which is what the two runs above show to be the discriminating operation. That is a different gate from the loader, not an extension of it, and it would fail onmaintoday until the existing annotations are addressed, so adopting it means deciding what to do about those at the same time.Three ways to address them, none obviously right, which is why this is an issue rather than a change:
typing-only-standard-library-importwhere they are annotation-only. Costs a suppression per module and a small import cost.get_type_hintswork, at the cost of the quoting being invisible to a reader as a deliberate choice.tyand by pytest, neither of which introspects.Not urgent
Nothing reads these annotations at runtime, every gate passes, and both #728 and #729 are unaffected in behaviour. This is a latent sharp edge in a helper plus a stale comment, recorded so the position is citable.
Raised out of the review rounds on #728 and #729, which each declined the change on the grounds above.