Fixes for typed pluggy

Since version 1.3 pluggy added typing, which requires some fixes to
please mypy.
This commit is contained in:
Ran Benita 2023-08-26 09:53:45 +03:00
parent 7500fe44b2
commit 77f7f59b2a
8 changed files with 33 additions and 15 deletions

View File

@ -0,0 +1 @@
pluggy>=1.3.0 is now required. This adds typing to :class:`~pytest.PytestPluginManager`.

View File

@ -980,10 +980,10 @@ TestShortLogReport
.. autoclass:: pytest.TestShortLogReport() .. autoclass:: pytest.TestShortLogReport()
:members: :members:
_Result Result
~~~~~~~ ~~~~~~~
Result object used within :ref:`hook wrappers <hookwrapper>`, see :py:class:`_Result in the pluggy documentation <pluggy._callers._Result>` for more information. Result object used within :ref:`hook wrappers <hookwrapper>`, see :py:class:`Result in the pluggy documentation <pluggy.Result>` for more information.
Stash Stash
~~~~~ ~~~~~

View File

@ -46,7 +46,7 @@ py_modules = py
install_requires = install_requires =
iniconfig iniconfig
packaging packaging
pluggy>=1.2.0,<2.0 pluggy>=1.3.0,<2.0
colorama;sys_platform=="win32" colorama;sys_platform=="win32"
exceptiongroup>=1.0.0rc8;python_version<"3.11" exceptiongroup>=1.0.0rc8;python_version<"3.11"
tomli>=1.0.0;python_version<"3.11" tomli>=1.0.0;python_version<"3.11"

View File

@ -38,7 +38,9 @@ from typing import TYPE_CHECKING
from typing import Union from typing import Union
from pluggy import HookimplMarker from pluggy import HookimplMarker
from pluggy import HookimplOpts
from pluggy import HookspecMarker from pluggy import HookspecMarker
from pluggy import HookspecOpts
from pluggy import PluginManager from pluggy import PluginManager
import _pytest._code import _pytest._code
@ -440,15 +442,17 @@ class PytestPluginManager(PluginManager):
# Used to know when we are importing conftests after the pytest_configure stage. # Used to know when we are importing conftests after the pytest_configure stage.
self._configured = False self._configured = False
def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): def parse_hookimpl_opts(
self, plugin: _PluggyPlugin, name: str
) -> Optional[HookimplOpts]:
# pytest hooks are always prefixed with "pytest_", # pytest hooks are always prefixed with "pytest_",
# so we avoid accessing possibly non-readable attributes # so we avoid accessing possibly non-readable attributes
# (see issue #1073). # (see issue #1073).
if not name.startswith("pytest_"): if not name.startswith("pytest_"):
return return None
# Ignore names which can not be hooks. # Ignore names which can not be hooks.
if name == "pytest_plugins": if name == "pytest_plugins":
return return None
opts = super().parse_hookimpl_opts(plugin, name) opts = super().parse_hookimpl_opts(plugin, name)
if opts is not None: if opts is not None:
@ -457,18 +461,18 @@ class PytestPluginManager(PluginManager):
method = getattr(plugin, name) method = getattr(plugin, name)
# Consider only actual functions for hooks (#3775). # Consider only actual functions for hooks (#3775).
if not inspect.isroutine(method): if not inspect.isroutine(method):
return return None
# Collect unmarked hooks as long as they have the `pytest_' prefix. # Collect unmarked hooks as long as they have the `pytest_' prefix.
return _get_legacy_hook_marks( return _get_legacy_hook_marks( # type: ignore[return-value]
method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
) )
def parse_hookspec_opts(self, module_or_class, name: str): def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOpts]:
opts = super().parse_hookspec_opts(module_or_class, name) opts = super().parse_hookspec_opts(module_or_class, name)
if opts is None: if opts is None:
method = getattr(module_or_class, name) method = getattr(module_or_class, name)
if name.startswith("pytest_"): if name.startswith("pytest_"):
opts = _get_legacy_hook_marks( opts = _get_legacy_hook_marks( # type: ignore[assignment]
method, method,
"spec", "spec",
("firstresult", "historic"), ("firstresult", "historic"),
@ -1067,9 +1071,10 @@ class Config:
fin() fin()
def get_terminal_writer(self) -> TerminalWriter: def get_terminal_writer(self) -> TerminalWriter:
terminalreporter: TerminalReporter = self.pluginmanager.get_plugin( terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin(
"terminalreporter" "terminalreporter"
) )
assert terminalreporter is not None
return terminalreporter._tw return terminalreporter._tw
def pytest_cmdline_parse( def pytest_cmdline_parse(

View File

@ -12,6 +12,7 @@ from _pytest.config import Config
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.config import PrintHelp from _pytest.config import PrintHelp
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.terminal import TerminalReporter
class HelpAction(Action): class HelpAction(Action):
@ -161,7 +162,10 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
def showhelp(config: Config) -> None: def showhelp(config: Config) -> None:
import textwrap import textwrap
reporter = config.pluginmanager.get_plugin("terminalreporter") reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin(
"terminalreporter"
)
assert reporter is not None
tw = reporter._tw tw = reporter._tw
tw.write(config._parser.optparser.format_help()) tw.write(config._parser.optparser.format_help())
tw.line() tw.line()

View File

@ -659,6 +659,8 @@ class LoggingPlugin:
) )
if self._log_cli_enabled(): if self._log_cli_enabled():
terminal_reporter = config.pluginmanager.get_plugin("terminalreporter") terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
# Guaranteed by `_log_cli_enabled()`.
assert terminal_reporter is not None
capture_manager = config.pluginmanager.get_plugin("capturemanager") capture_manager = config.pluginmanager.get_plugin("capturemanager")
# if capturemanager plugin is disabled, live logging still works. # if capturemanager plugin is disabled, live logging still works.
self.log_cli_handler: Union[ self.log_cli_handler: Union[

View File

@ -751,7 +751,7 @@ class Pytester:
def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
"""Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`.""" """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`."""
pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) # type: ignore[attr-defined]
self._request.addfinalizer(reprec.finish_recording) self._request.addfinalizer(reprec.finish_recording)
return reprec return reprec

View File

@ -242,8 +242,12 @@ class TestPytestPluginManager:
mod = types.ModuleType("temp") mod = types.ModuleType("temp")
mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"] mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"]
pytestpm.consider_module(mod) pytestpm.consider_module(mod)
assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1" p1 = pytestpm.get_plugin("pytest_p1")
assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2" assert p1 is not None
assert p1.__name__ == "pytest_p1"
p2 = pytestpm.get_plugin("pytest_p2")
assert p2 is not None
assert p2.__name__ == "pytest_p2"
def test_consider_module_import_module( def test_consider_module_import_module(
self, pytester: Pytester, _config_for_test: Config self, pytester: Pytester, _config_for_test: Config
@ -336,6 +340,7 @@ class TestPytestPluginManager:
len2 = len(pytestpm.get_plugins()) len2 = len(pytestpm.get_plugins())
assert len1 == len2 assert len1 == len2
plugin1 = pytestpm.get_plugin("pytest_hello") plugin1 = pytestpm.get_plugin("pytest_hello")
assert plugin1 is not None
assert plugin1.__name__.endswith("pytest_hello") assert plugin1.__name__.endswith("pytest_hello")
plugin2 = pytestpm.get_plugin("pytest_hello") plugin2 = pytestpm.get_plugin("pytest_hello")
assert plugin2 is plugin1 assert plugin2 is plugin1
@ -351,6 +356,7 @@ class TestPytestPluginManager:
pluginname = "pkg.plug" pluginname = "pkg.plug"
pytestpm.import_plugin(pluginname) pytestpm.import_plugin(pluginname)
mod = pytestpm.get_plugin("pkg.plug") mod = pytestpm.get_plugin("pkg.plug")
assert mod is not None
assert mod.x == 3 assert mod.x == 3
def test_consider_conftest_deps( def test_consider_conftest_deps(