Skip to content

Commit 4ae102c

Browse files
authored
Merge pull request #11446 from bluetech/pluggy-typing
Improve pluggy-related typing
2 parents d2b2142 + f43a8db commit 4ae102c

File tree

4 files changed

+47
-32
lines changed

4 files changed

+47
-32
lines changed

src/_pytest/config/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from typing import TYPE_CHECKING
3838
from typing import Union
3939

40+
import pluggy
4041
from pluggy import HookimplMarker
4142
from pluggy import HookimplOpts
4243
from pluggy import HookspecMarker
@@ -46,6 +47,7 @@
4647
import _pytest._code
4748
import _pytest.deprecated
4849
import _pytest.hookspec
50+
from .compat import PathAwareHookProxy
4951
from .exceptions import PrintHelp as PrintHelp
5052
from .exceptions import UsageError as UsageError
5153
from .findpaths import determine_setup
@@ -1005,10 +1007,8 @@ def __init__(
10051007
# Deprecated alias. Was never public. Can be removed in a few releases.
10061008
self._store = self.stash
10071009

1008-
from .compat import PathAwareHookProxy
1009-
10101010
self.trace = self.pluginmanager.trace.root.get("config")
1011-
self.hook = PathAwareHookProxy(self.pluginmanager.hook)
1011+
self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment]
10121012
self._inicache: Dict[str, Any] = {}
10131013
self._override_ini: Sequence[str] = ()
10141014
self._opt2dest: Dict[str, str] = {}

src/_pytest/config/compat.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
from __future__ import annotations
2+
13
import functools
24
import warnings
35
from pathlib import Path
4-
from typing import Optional
6+
from typing import Mapping
7+
8+
import pluggy
59

610
from ..compat import LEGACY_PATH
711
from ..compat import legacy_path
812
from ..deprecated import HOOK_LEGACY_PATH_ARG
9-
from _pytest.nodes import _check_path
1013

1114
# hookname: (Path, LEGACY_PATH)
12-
imply_paths_hooks = {
15+
imply_paths_hooks: Mapping[str, tuple[str, str]] = {
1316
"pytest_ignore_collect": ("collection_path", "path"),
1417
"pytest_collect_file": ("file_path", "path"),
1518
"pytest_pycollect_makemodule": ("module_path", "path"),
@@ -18,6 +21,14 @@
1821
}
1922

2023

24+
def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
25+
if Path(fspath) != path:
26+
raise ValueError(
27+
f"Path({fspath!r}) != {path!r}\n"
28+
"if both path and fspath are given they need to be equal"
29+
)
30+
31+
2132
class PathAwareHookProxy:
2233
"""
2334
this helper wraps around hook callers
@@ -27,24 +38,24 @@ class PathAwareHookProxy:
2738
this may have to be changed later depending on bugs
2839
"""
2940

30-
def __init__(self, hook_caller):
31-
self.__hook_caller = hook_caller
41+
def __init__(self, hook_relay: pluggy.HookRelay) -> None:
42+
self._hook_relay = hook_relay
3243

33-
def __dir__(self):
34-
return dir(self.__hook_caller)
44+
def __dir__(self) -> list[str]:
45+
return dir(self._hook_relay)
3546

36-
def __getattr__(self, key, _wraps=functools.wraps):
37-
hook = getattr(self.__hook_caller, key)
47+
def __getattr__(self, key: str) -> pluggy.HookCaller:
48+
hook: pluggy.HookCaller = getattr(self._hook_relay, key)
3849
if key not in imply_paths_hooks:
3950
self.__dict__[key] = hook
4051
return hook
4152
else:
4253
path_var, fspath_var = imply_paths_hooks[key]
4354

44-
@_wraps(hook)
55+
@functools.wraps(hook)
4556
def fixed_hook(**kw):
46-
path_value: Optional[Path] = kw.pop(path_var, None)
47-
fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None)
57+
path_value: Path | None = kw.pop(path_var, None)
58+
fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None)
4859
if fspath_value is not None:
4960
warnings.warn(
5061
HOOK_LEGACY_PATH_ARG.format(
@@ -65,6 +76,8 @@ def fixed_hook(**kw):
6576
kw[fspath_var] = fspath_value
6677
return hook(**kw)
6778

79+
fixed_hook.name = hook.name # type: ignore[attr-defined]
80+
fixed_hook.spec = hook.spec # type: ignore[attr-defined]
6881
fixed_hook.__name__ = key
6982
self.__dict__[key] = fixed_hook
70-
return fixed_hook
83+
return fixed_hook # type: ignore[return-value]

src/_pytest/main.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import sys
99
from pathlib import Path
10+
from typing import AbstractSet
1011
from typing import Callable
1112
from typing import Dict
1213
from typing import final
@@ -22,6 +23,8 @@
2223
from typing import TYPE_CHECKING
2324
from typing import Union
2425

26+
import pluggy
27+
2528
import _pytest._code
2629
from _pytest import nodes
2730
from _pytest.config import Config
@@ -31,6 +34,7 @@
3134
from _pytest.config import PytestPluginManager
3235
from _pytest.config import UsageError
3336
from _pytest.config.argparsing import Parser
37+
from _pytest.config.compat import PathAwareHookProxy
3438
from _pytest.fixtures import FixtureManager
3539
from _pytest.outcomes import exit
3640
from _pytest.pathlib import absolutepath
@@ -429,11 +433,15 @@ def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> No
429433

430434

431435
class FSHookProxy:
432-
def __init__(self, pm: PytestPluginManager, remove_mods) -> None:
436+
def __init__(
437+
self,
438+
pm: PytestPluginManager,
439+
remove_mods: AbstractSet[object],
440+
) -> None:
433441
self.pm = pm
434442
self.remove_mods = remove_mods
435443

436-
def __getattr__(self, name: str):
444+
def __getattr__(self, name: str) -> pluggy.HookCaller:
437445
x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
438446
self.__dict__[name] = x
439447
return x
@@ -546,7 +554,7 @@ def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:
546554
path_ = path if isinstance(path, Path) else Path(path)
547555
return path_ in self._initialpaths
548556

549-
def gethookproxy(self, fspath: "os.PathLike[str]"):
557+
def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay:
550558
# Optimization: Path(Path(...)) is much slower than isinstance.
551559
path = fspath if isinstance(fspath, Path) else Path(fspath)
552560
pm = self.config.pluginmanager
@@ -563,11 +571,10 @@ def gethookproxy(self, fspath: "os.PathLike[str]"):
563571
)
564572
my_conftestmodules = pm._getconftestmodules(path)
565573
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
574+
proxy: pluggy.HookRelay
566575
if remove_mods:
567-
# One or more conftests are not in use at this fspath.
568-
from .config.compat import PathAwareHookProxy
569-
570-
proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods))
576+
# One or more conftests are not in use at this path.
577+
proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment]
571578
else:
572579
# All plugins are active for this fspath.
573580
proxy = self.config.hook

src/_pytest/nodes.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from typing import TypeVar
2020
from typing import Union
2121

22+
import pluggy
23+
2224
import _pytest._code
2325
from _pytest._code import getfslineno
2426
from _pytest._code.code import ExceptionInfo
@@ -27,6 +29,7 @@
2729
from _pytest.compat import LEGACY_PATH
2830
from _pytest.config import Config
2931
from _pytest.config import ConftestImportFailure
32+
from _pytest.config.compat import _check_path
3033
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
3134
from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
3235
from _pytest.mark.structures import Mark
@@ -94,14 +97,6 @@ def iterparentnodeids(nodeid: str) -> Iterator[str]:
9497
yield nodeid
9598

9699

97-
def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
98-
if Path(fspath) != path:
99-
raise ValueError(
100-
f"Path({fspath!r}) != {path!r}\n"
101-
"if both path and fspath are given they need to be equal"
102-
)
103-
104-
105100
def _imply_path(
106101
node_type: Type["Node"],
107102
path: Optional[Path],
@@ -278,7 +273,7 @@ def from_parent(cls, parent: "Node", **kw):
278273
return cls._create(parent=parent, **kw)
279274

280275
@property
281-
def ihook(self):
276+
def ihook(self) -> pluggy.HookRelay:
282277
"""fspath-sensitive hook proxy used to call pytest hooks."""
283278
return self.session.gethookproxy(self.path)
284279

0 commit comments

Comments
 (0)