From 8ed2897c787e2059bdb1272c1c5f1c79eccc9e94 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 19 Jul 2026 10:02:53 +0100 Subject: [PATCH 1/2] Type gethookproxy/ihook as HookRelay | FSHookProxy. gethookproxy already returns FSHookProxy when some conftests are stripped; annotate that honestly and drop the assignment type ignore. Co-authored-by: Cursor --- src/_pytest/main.py | 6 +++--- src/_pytest/nodes.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index c4df4e46983..df54d91fb35 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -728,7 +728,7 @@ def isinitpath( else: return path_ in self._initialpaths - def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: + def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay | FSHookProxy: # Optimization: Path(Path(...)) is much slower than isinstance. path = fspath if isinstance(fspath, Path) else Path(fspath) pm = self.config.pluginmanager @@ -736,10 +736,10 @@ def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: # hooks with all conftest.py files. my_conftestmodules = pm._getconftestmodules(path) remove_mods = pm._conftest_plugins.difference(my_conftestmodules) - proxy: pluggy.HookRelay + proxy: pluggy.HookRelay | FSHookProxy if remove_mods: # One or more conftests are not in use at this path. - proxy = FSHookProxy(pm, remove_mods) # type: ignore[assignment] + proxy = FSHookProxy(pm, remove_mods) else: # All plugins are active for this fspath. proxy = self.config.hook diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index f0629c2daf7..51a3fea3f22 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -44,6 +44,7 @@ from typing_extensions import Self # Imported here due to circular import. + from _pytest.main import FSHookProxy from _pytest.main import Session @@ -225,7 +226,7 @@ def from_parent(cls, parent: Node, **kw) -> Self: return cls._create(parent=parent, **kw) @property - def ihook(self) -> pluggy.HookRelay: + def ihook(self) -> pluggy.HookRelay | FSHookProxy: """Path-sensitive hook proxy used to call pytest hooks.""" return self.session.gethookproxy(self.path) From f3b530597f5f90e80bb449f86500e339216e5966 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 19 Jul 2026 12:21:40 +0100 Subject: [PATCH 2/2] Satisfy RTD and Chronographer for the gethookproxy typing PR. Ignore undocumented FSHookProxy in Sphinx nitpicks, and add a misc changelog entry. Co-authored-by: Cursor --- changelog/14728.misc.rst | 1 + doc/en/conf.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog/14728.misc.rst diff --git a/changelog/14728.misc.rst b/changelog/14728.misc.rst new file mode 100644 index 00000000000..803859bb52a --- /dev/null +++ b/changelog/14728.misc.rst @@ -0,0 +1 @@ +Improved the type annotations of ``Node.ihook`` and ``Session.gethookproxy``. diff --git a/doc/en/conf.py b/doc/en/conf.py index 04ba1cfc616..d275c75fc60 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -80,6 +80,7 @@ ("py:class", "_pytest.logging.LogCaptureHandler"), ("py:class", "_pytest.mark.structures.ParameterSet"), # Intentionally undocumented/private + ("py:class", "FSHookProxy"), ("py:class", "_pytest._code.code.Traceback"), ("py:class", "_pytest._py.path.LocalPath"), ("py:class", "_pytest.capture.CaptureResult"),