Merge pull request #11446 from bluetech/pluggy-typing
Improve pluggy-related typing
This commit is contained in:
		
						commit
						4ae102c003
					
				| 
						 | 
					@ -37,6 +37,7 @@ from typing import Type
 | 
				
			||||||
from typing import TYPE_CHECKING
 | 
					from typing import TYPE_CHECKING
 | 
				
			||||||
from typing import Union
 | 
					from typing import Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pluggy
 | 
				
			||||||
from pluggy import HookimplMarker
 | 
					from pluggy import HookimplMarker
 | 
				
			||||||
from pluggy import HookimplOpts
 | 
					from pluggy import HookimplOpts
 | 
				
			||||||
from pluggy import HookspecMarker
 | 
					from pluggy import HookspecMarker
 | 
				
			||||||
| 
						 | 
					@ -46,6 +47,7 @@ from pluggy import PluginManager
 | 
				
			||||||
import _pytest._code
 | 
					import _pytest._code
 | 
				
			||||||
import _pytest.deprecated
 | 
					import _pytest.deprecated
 | 
				
			||||||
import _pytest.hookspec
 | 
					import _pytest.hookspec
 | 
				
			||||||
 | 
					from .compat import PathAwareHookProxy
 | 
				
			||||||
from .exceptions import PrintHelp as PrintHelp
 | 
					from .exceptions import PrintHelp as PrintHelp
 | 
				
			||||||
from .exceptions import UsageError as UsageError
 | 
					from .exceptions import UsageError as UsageError
 | 
				
			||||||
from .findpaths import determine_setup
 | 
					from .findpaths import determine_setup
 | 
				
			||||||
| 
						 | 
					@ -1005,10 +1007,8 @@ class Config:
 | 
				
			||||||
        # Deprecated alias. Was never public. Can be removed in a few releases.
 | 
					        # Deprecated alias. Was never public. Can be removed in a few releases.
 | 
				
			||||||
        self._store = self.stash
 | 
					        self._store = self.stash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        from .compat import PathAwareHookProxy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.trace = self.pluginmanager.trace.root.get("config")
 | 
					        self.trace = self.pluginmanager.trace.root.get("config")
 | 
				
			||||||
        self.hook = PathAwareHookProxy(self.pluginmanager.hook)
 | 
					        self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook)  # type: ignore[assignment]
 | 
				
			||||||
        self._inicache: Dict[str, Any] = {}
 | 
					        self._inicache: Dict[str, Any] = {}
 | 
				
			||||||
        self._override_ini: Sequence[str] = ()
 | 
					        self._override_ini: Sequence[str] = ()
 | 
				
			||||||
        self._opt2dest: Dict[str, str] = {}
 | 
					        self._opt2dest: Dict[str, str] = {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,18 @@
 | 
				
			||||||
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import functools
 | 
					import functools
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from typing import Optional
 | 
					from typing import Mapping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pluggy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..compat import LEGACY_PATH
 | 
					from ..compat import LEGACY_PATH
 | 
				
			||||||
from ..compat import legacy_path
 | 
					from ..compat import legacy_path
 | 
				
			||||||
from ..deprecated import HOOK_LEGACY_PATH_ARG
 | 
					from ..deprecated import HOOK_LEGACY_PATH_ARG
 | 
				
			||||||
from _pytest.nodes import _check_path
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# hookname: (Path, LEGACY_PATH)
 | 
					# hookname: (Path, LEGACY_PATH)
 | 
				
			||||||
imply_paths_hooks = {
 | 
					imply_paths_hooks: Mapping[str, tuple[str, str]] = {
 | 
				
			||||||
    "pytest_ignore_collect": ("collection_path", "path"),
 | 
					    "pytest_ignore_collect": ("collection_path", "path"),
 | 
				
			||||||
    "pytest_collect_file": ("file_path", "path"),
 | 
					    "pytest_collect_file": ("file_path", "path"),
 | 
				
			||||||
    "pytest_pycollect_makemodule": ("module_path", "path"),
 | 
					    "pytest_pycollect_makemodule": ("module_path", "path"),
 | 
				
			||||||
| 
						 | 
					@ -18,6 +21,14 @@ imply_paths_hooks = {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
 | 
				
			||||||
 | 
					    if Path(fspath) != path:
 | 
				
			||||||
 | 
					        raise ValueError(
 | 
				
			||||||
 | 
					            f"Path({fspath!r}) != {path!r}\n"
 | 
				
			||||||
 | 
					            "if both path and fspath are given they need to be equal"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PathAwareHookProxy:
 | 
					class PathAwareHookProxy:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    this helper wraps around hook callers
 | 
					    this helper wraps around hook callers
 | 
				
			||||||
| 
						 | 
					@ -27,24 +38,24 @@ class PathAwareHookProxy:
 | 
				
			||||||
    this may have to be changed later depending on bugs
 | 
					    this may have to be changed later depending on bugs
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, hook_caller):
 | 
					    def __init__(self, hook_relay: pluggy.HookRelay) -> None:
 | 
				
			||||||
        self.__hook_caller = hook_caller
 | 
					        self._hook_relay = hook_relay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __dir__(self):
 | 
					    def __dir__(self) -> list[str]:
 | 
				
			||||||
        return dir(self.__hook_caller)
 | 
					        return dir(self._hook_relay)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __getattr__(self, key, _wraps=functools.wraps):
 | 
					    def __getattr__(self, key: str) -> pluggy.HookCaller:
 | 
				
			||||||
        hook = getattr(self.__hook_caller, key)
 | 
					        hook: pluggy.HookCaller = getattr(self._hook_relay, key)
 | 
				
			||||||
        if key not in imply_paths_hooks:
 | 
					        if key not in imply_paths_hooks:
 | 
				
			||||||
            self.__dict__[key] = hook
 | 
					            self.__dict__[key] = hook
 | 
				
			||||||
            return hook
 | 
					            return hook
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            path_var, fspath_var = imply_paths_hooks[key]
 | 
					            path_var, fspath_var = imply_paths_hooks[key]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            @_wraps(hook)
 | 
					            @functools.wraps(hook)
 | 
				
			||||||
            def fixed_hook(**kw):
 | 
					            def fixed_hook(**kw):
 | 
				
			||||||
                path_value: Optional[Path] = kw.pop(path_var, None)
 | 
					                path_value: Path | None = kw.pop(path_var, None)
 | 
				
			||||||
                fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None)
 | 
					                fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None)
 | 
				
			||||||
                if fspath_value is not None:
 | 
					                if fspath_value is not None:
 | 
				
			||||||
                    warnings.warn(
 | 
					                    warnings.warn(
 | 
				
			||||||
                        HOOK_LEGACY_PATH_ARG.format(
 | 
					                        HOOK_LEGACY_PATH_ARG.format(
 | 
				
			||||||
| 
						 | 
					@ -65,6 +76,8 @@ class PathAwareHookProxy:
 | 
				
			||||||
                kw[fspath_var] = fspath_value
 | 
					                kw[fspath_var] = fspath_value
 | 
				
			||||||
                return hook(**kw)
 | 
					                return hook(**kw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fixed_hook.name = hook.name  # type: ignore[attr-defined]
 | 
				
			||||||
 | 
					            fixed_hook.spec = hook.spec  # type: ignore[attr-defined]
 | 
				
			||||||
            fixed_hook.__name__ = key
 | 
					            fixed_hook.__name__ = key
 | 
				
			||||||
            self.__dict__[key] = fixed_hook
 | 
					            self.__dict__[key] = fixed_hook
 | 
				
			||||||
            return fixed_hook
 | 
					            return fixed_hook  # type: ignore[return-value]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ import importlib
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					from typing import AbstractSet
 | 
				
			||||||
from typing import Callable
 | 
					from typing import Callable
 | 
				
			||||||
from typing import Dict
 | 
					from typing import Dict
 | 
				
			||||||
from typing import final
 | 
					from typing import final
 | 
				
			||||||
| 
						 | 
					@ -22,6 +23,8 @@ from typing import Type
 | 
				
			||||||
from typing import TYPE_CHECKING
 | 
					from typing import TYPE_CHECKING
 | 
				
			||||||
from typing import Union
 | 
					from typing import Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pluggy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import _pytest._code
 | 
					import _pytest._code
 | 
				
			||||||
from _pytest import nodes
 | 
					from _pytest import nodes
 | 
				
			||||||
from _pytest.config import Config
 | 
					from _pytest.config import Config
 | 
				
			||||||
| 
						 | 
					@ -31,6 +34,7 @@ from _pytest.config import hookimpl
 | 
				
			||||||
from _pytest.config import PytestPluginManager
 | 
					from _pytest.config import PytestPluginManager
 | 
				
			||||||
from _pytest.config import UsageError
 | 
					from _pytest.config import UsageError
 | 
				
			||||||
from _pytest.config.argparsing import Parser
 | 
					from _pytest.config.argparsing import Parser
 | 
				
			||||||
 | 
					from _pytest.config.compat import PathAwareHookProxy
 | 
				
			||||||
from _pytest.fixtures import FixtureManager
 | 
					from _pytest.fixtures import FixtureManager
 | 
				
			||||||
from _pytest.outcomes import exit
 | 
					from _pytest.outcomes import exit
 | 
				
			||||||
from _pytest.pathlib import absolutepath
 | 
					from _pytest.pathlib import absolutepath
 | 
				
			||||||
| 
						 | 
					@ -429,11 +433,15 @@ def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> No
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FSHookProxy:
 | 
					class FSHookProxy:
 | 
				
			||||||
    def __init__(self, pm: PytestPluginManager, remove_mods) -> None:
 | 
					    def __init__(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        pm: PytestPluginManager,
 | 
				
			||||||
 | 
					        remove_mods: AbstractSet[object],
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
        self.pm = pm
 | 
					        self.pm = pm
 | 
				
			||||||
        self.remove_mods = remove_mods
 | 
					        self.remove_mods = remove_mods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __getattr__(self, name: str):
 | 
					    def __getattr__(self, name: str) -> pluggy.HookCaller:
 | 
				
			||||||
        x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
 | 
					        x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
 | 
				
			||||||
        self.__dict__[name] = x
 | 
					        self.__dict__[name] = x
 | 
				
			||||||
        return x
 | 
					        return x
 | 
				
			||||||
| 
						 | 
					@ -546,7 +554,7 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
        path_ = path if isinstance(path, Path) else Path(path)
 | 
					        path_ = path if isinstance(path, Path) else Path(path)
 | 
				
			||||||
        return path_ in self._initialpaths
 | 
					        return path_ in self._initialpaths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def gethookproxy(self, fspath: "os.PathLike[str]"):
 | 
					    def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay:
 | 
				
			||||||
        # Optimization: Path(Path(...)) is much slower than isinstance.
 | 
					        # Optimization: Path(Path(...)) is much slower than isinstance.
 | 
				
			||||||
        path = fspath if isinstance(fspath, Path) else Path(fspath)
 | 
					        path = fspath if isinstance(fspath, Path) else Path(fspath)
 | 
				
			||||||
        pm = self.config.pluginmanager
 | 
					        pm = self.config.pluginmanager
 | 
				
			||||||
| 
						 | 
					@ -563,11 +571,10 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        my_conftestmodules = pm._getconftestmodules(path)
 | 
					        my_conftestmodules = pm._getconftestmodules(path)
 | 
				
			||||||
        remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
 | 
					        remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
 | 
				
			||||||
 | 
					        proxy: pluggy.HookRelay
 | 
				
			||||||
        if remove_mods:
 | 
					        if remove_mods:
 | 
				
			||||||
            # One or more conftests are not in use at this fspath.
 | 
					            # One or more conftests are not in use at this path.
 | 
				
			||||||
            from .config.compat import PathAwareHookProxy
 | 
					            proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods))  # type: ignore[arg-type,assignment]
 | 
				
			||||||
 | 
					 | 
				
			||||||
            proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods))
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # All plugins are active for this fspath.
 | 
					            # All plugins are active for this fspath.
 | 
				
			||||||
            proxy = self.config.hook
 | 
					            proxy = self.config.hook
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,8 @@ from typing import TYPE_CHECKING
 | 
				
			||||||
from typing import TypeVar
 | 
					from typing import TypeVar
 | 
				
			||||||
from typing import Union
 | 
					from typing import Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pluggy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import _pytest._code
 | 
					import _pytest._code
 | 
				
			||||||
from _pytest._code import getfslineno
 | 
					from _pytest._code import getfslineno
 | 
				
			||||||
from _pytest._code.code import ExceptionInfo
 | 
					from _pytest._code.code import ExceptionInfo
 | 
				
			||||||
| 
						 | 
					@ -27,6 +29,7 @@ from _pytest._code.code import Traceback
 | 
				
			||||||
from _pytest.compat import LEGACY_PATH
 | 
					from _pytest.compat import LEGACY_PATH
 | 
				
			||||||
from _pytest.config import Config
 | 
					from _pytest.config import Config
 | 
				
			||||||
from _pytest.config import ConftestImportFailure
 | 
					from _pytest.config import ConftestImportFailure
 | 
				
			||||||
 | 
					from _pytest.config.compat import _check_path
 | 
				
			||||||
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
 | 
					from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
 | 
				
			||||||
from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
 | 
					from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
 | 
				
			||||||
from _pytest.mark.structures import Mark
 | 
					from _pytest.mark.structures import Mark
 | 
				
			||||||
| 
						 | 
					@ -94,14 +97,6 @@ def iterparentnodeids(nodeid: str) -> Iterator[str]:
 | 
				
			||||||
        yield nodeid
 | 
					        yield nodeid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
 | 
					 | 
				
			||||||
    if Path(fspath) != path:
 | 
					 | 
				
			||||||
        raise ValueError(
 | 
					 | 
				
			||||||
            f"Path({fspath!r}) != {path!r}\n"
 | 
					 | 
				
			||||||
            "if both path and fspath are given they need to be equal"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _imply_path(
 | 
					def _imply_path(
 | 
				
			||||||
    node_type: Type["Node"],
 | 
					    node_type: Type["Node"],
 | 
				
			||||||
    path: Optional[Path],
 | 
					    path: Optional[Path],
 | 
				
			||||||
| 
						 | 
					@ -278,7 +273,7 @@ class Node(metaclass=NodeMeta):
 | 
				
			||||||
        return cls._create(parent=parent, **kw)
 | 
					        return cls._create(parent=parent, **kw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def ihook(self):
 | 
					    def ihook(self) -> pluggy.HookRelay:
 | 
				
			||||||
        """fspath-sensitive hook proxy used to call pytest hooks."""
 | 
					        """fspath-sensitive hook proxy used to call pytest hooks."""
 | 
				
			||||||
        return self.session.gethookproxy(self.path)
 | 
					        return self.session.gethookproxy(self.path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue