Skip to content

Commit

Permalink
Merge pull request #11353 from bluetech/pluggy-typing
Browse files Browse the repository at this point in the history
Fixes for typed pluggy
  • Loading branch information
bluetech authored Aug 26, 2023
2 parents 7500fe4 + 77f7f59 commit 00fedcc
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 15 deletions.
1 change: 1 addition & 0 deletions changelog/11353.trivial.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pluggy>=1.3.0 is now required. This adds typing to :class:`~pytest.PytestPluginManager`.
4 changes: 2 additions & 2 deletions doc/en/reference/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -980,10 +980,10 @@ TestShortLogReport
.. autoclass:: pytest.TestShortLogReport()
: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
~~~~~
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ py_modules = py
install_requires =
iniconfig
packaging
pluggy>=1.2.0,<2.0
pluggy>=1.3.0,<2.0
colorama;sys_platform=="win32"
exceptiongroup>=1.0.0rc8;python_version<"3.11"
tomli>=1.0.0;python_version<"3.11"
Expand Down
21 changes: 13 additions & 8 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
from typing import Union

from pluggy import HookimplMarker
from pluggy import HookimplOpts
from pluggy import HookspecMarker
from pluggy import HookspecOpts
from pluggy import PluginManager

import _pytest._code
Expand Down Expand Up @@ -440,15 +442,17 @@ def __init__(self) -> None:
# Used to know when we are importing conftests after the pytest_configure stage.
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_",
# so we avoid accessing possibly non-readable attributes
# (see issue #1073).
if not name.startswith("pytest_"):
return
return None
# Ignore names which can not be hooks.
if name == "pytest_plugins":
return
return None

opts = super().parse_hookimpl_opts(plugin, name)
if opts is not None:
Expand All @@ -457,18 +461,18 @@ def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
method = getattr(plugin, name)
# Consider only actual functions for hooks (#3775).
if not inspect.isroutine(method):
return
return None
# 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")
)

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)
if opts is None:
method = getattr(module_or_class, name)
if name.startswith("pytest_"):
opts = _get_legacy_hook_marks(
opts = _get_legacy_hook_marks( # type: ignore[assignment]
method,
"spec",
("firstresult", "historic"),
Expand Down Expand Up @@ -1067,9 +1071,10 @@ def _ensure_unconfigure(self) -> None:
fin()

def get_terminal_writer(self) -> TerminalWriter:
terminalreporter: TerminalReporter = self.pluginmanager.get_plugin(
terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin(
"terminalreporter"
)
assert terminalreporter is not None
return terminalreporter._tw

def pytest_cmdline_parse(
Expand Down
6 changes: 5 additions & 1 deletion src/_pytest/helpconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from _pytest.config import ExitCode
from _pytest.config import PrintHelp
from _pytest.config.argparsing import Parser
from _pytest.terminal import TerminalReporter


class HelpAction(Action):
Expand Down Expand Up @@ -161,7 +162,10 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
def showhelp(config: Config) -> None:
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.write(config._parser.optparser.format_help())
tw.line()
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@ def __init__(self, config: Config) -> None:
)
if self._log_cli_enabled():
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")
# if capturemanager plugin is disabled, live logging still works.
self.log_cli_handler: Union[
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ def preserve_module(name):

def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
"""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)
return reprec

Expand Down
10 changes: 8 additions & 2 deletions testing/test_pluginmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,12 @@ def test_consider_module(
mod = types.ModuleType("temp")
mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"]
pytestpm.consider_module(mod)
assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1"
assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2"
p1 = pytestpm.get_plugin("pytest_p1")
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(
self, pytester: Pytester, _config_for_test: Config
Expand Down Expand Up @@ -336,6 +340,7 @@ def test_import_plugin_importname(
len2 = len(pytestpm.get_plugins())
assert len1 == len2
plugin1 = pytestpm.get_plugin("pytest_hello")
assert plugin1 is not None
assert plugin1.__name__.endswith("pytest_hello")
plugin2 = pytestpm.get_plugin("pytest_hello")
assert plugin2 is plugin1
Expand All @@ -351,6 +356,7 @@ def test_import_plugin_dotted_name(
pluginname = "pkg.plug"
pytestpm.import_plugin(pluginname)
mod = pytestpm.get_plugin("pkg.plug")
assert mod is not None
assert mod.x == 3

def test_consider_conftest_deps(
Expand Down

0 comments on commit 00fedcc

Please sign in to comment.