Skip to content

Commit

Permalink
partial application of review suggestions + externalize the helper
Browse files Browse the repository at this point in the history
  • Loading branch information
RonnyPfannschmidt committed Oct 2, 2021
1 parent ae4f964 commit 985bd3e
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 33 deletions.
6 changes: 4 additions & 2 deletions changelog/4562.deprecation.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
Deprecate configureing hook specs/impls using attribute/marks.
Instead ``use pytest.hookimpl`` and ``pytest.hookspec``.
Deprecate configuring hook specs/impls using attribute/marks.

Instead use :ref:`pytest.hookimpl` and :ref:`pytest.hookspec`.
For more details, see the :ref:`docs <deprecate-hook-marks>`.
3 changes: 2 additions & 1 deletion doc/en/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ have been available since years and should be used.
pytest_runtest_call.tryfirst = True
# becomes
This now is declared as:

.. code-block:: python
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_call():
Expand Down
63 changes: 36 additions & 27 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import warnings
from functools import lru_cache
from pathlib import Path
from types import FunctionType
from types import TracebackType
from typing import Any
from typing import Callable
Expand All @@ -35,6 +36,7 @@
from pluggy import HookimplMarker
from pluggy import HookspecMarker
from pluggy import PluginManager
from typing_extensions import Literal

import _pytest._code
import _pytest.deprecated
Expand Down Expand Up @@ -330,6 +332,32 @@ def _prepareconfig(
raise


def _get_legacy_hook_marks(
method: FunctionType,
hook_type: Literal["spec", "impl"],
opt_names: Tuple[str, ...],
) -> Dict[str, bool]:
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
must_warn = False
opts = {}
for opt_name in opt_names:
if hasattr(method, opt_name) or opt_name in known_marks:
opts[opt_name] = True
must_warn = True
else:
opts[opt_name] = False
if must_warn:

hook_opts = ", ".join(f"{name}=True" for name, val in opts.items() if val)
message = _pytest.deprecated.HOOK_LEGACY_MARKING.format(
type=hook_type,
fullname=method.__qualname__,
hook_opts=hook_opts,
)
warn_explicit_for(method, message)
return opts


@final
class PytestPluginManager(PluginManager):
"""A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
Expand Down Expand Up @@ -402,39 +430,20 @@ def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
if not inspect.isroutine(method):
return
# Collect unmarked hooks as long as they have the `pytest_' prefix.

known_marks = {m.name for m in getattr(method, "pytestmark", [])}
opts = {
name: hasattr(method, name) or name in known_marks
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper")
}
if any(opts.values()):
message = _pytest.deprecated.HOOK_LEGACY_MARKING.format(
type="spec", fullname=method.__qualname__
)
warn_explicit_for(method, message)

return opts
return _get_legacy_hook_marks(
method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
)

def parse_hookspec_opts(self, module_or_class, name: str):
opts = super().parse_hookspec_opts(module_or_class, name)
if opts is None:
method = getattr(module_or_class, name)
if name.startswith("pytest_"):

known_marks = {m.name for m in getattr(method, "pytestmark", [])}
opts = {
"firstresult": hasattr(method, "firstresult")
or "firstresult" in known_marks,
"historic": hasattr(method, "historic")
or "historic" in known_marks,
}
# hook from xdist, fixing in ...
if any(opts.values()) and module_or_class.__name__ != "xdist.newhooks":
message = _pytest.deprecated.HOOK_LEGACY_MARKING.format(
type="spec", fullname=method.__qualname__
)
warn_explicit_for(method, message)
opts = _get_legacy_hook_marks(
method,
"spec",
("firstresult", "historic"),
)
return opts

def register(
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
HOOK_LEGACY_MARKING = UnformattedWarning(
PytestDeprecationWarning,
"The hook {type} {fullname} is not marked using pytest.hook{type}.\n"
" please use the pytest.hook{type}(...) decorator instead of pytest.mark \n"
" please use the pytest.hook{type}({hook_opts}) decorator instead of pytest.mark \n"
" to correctly mark hooks as pytest hooks.\n"
" see URL",
)
Expand Down
4 changes: 2 additions & 2 deletions testing/deprecated_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_fillfixtures_is_deprecated() -> None:
_pytest.fixtures.fillfixtures(mock.Mock())


def test_hooksec_marks_are_deprecated():
def test_hookspec_via_function_attributes_are_deprecated():
from _pytest.config import PytestPluginManager

pm = PytestPluginManager()
Expand All @@ -74,7 +74,7 @@ def pytest_bad_hook(self):
assert record.filename == __file__


def test_hookimpl_marks_are_deprecated():
def test_hookimpl_via_function_attributes_are_deprecated():
from _pytest.config import PytestPluginManager

pm = PytestPluginManager()
Expand Down

0 comments on commit 985bd3e

Please sign in to comment.