Skip to content

Commit 6411fbc

Browse files
committed
Add support deprecating hook parameters
Fix pytest-dev#178.
1 parent 91f88d2 commit 6411fbc

File tree

5 files changed

+85
-2
lines changed

5 files changed

+85
-2
lines changed

Diff for: changelog/178.feature.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add support for deprecating specific hook parameters, or more generally, for issuing a warning whenever a hook implementation requests certain parameters.
2+
3+
See :ref:`warn_on_impl` for details.

Diff for: docs/index.rst

+25-2
Original file line numberDiff line numberDiff line change
@@ -659,11 +659,34 @@ If a hookspec specifies a ``warn_on_impl``, pluggy will trigger it for any plugi
659659
.. code-block:: python
660660
661661
@hookspec(
662-
warn_on_impl=DeprecationWarning("oldhook is deprecated and will be removed soon")
662+
warn_on_impl=DeprecationWarning("old_hook is deprecated and will be removed soon")
663663
)
664-
def oldhook():
664+
def old_hook():
665665
pass
666666
667+
668+
If you don't want to deprecate implementing the entire hook, but just specific
669+
parameters of it, you can specify ``warn_on_impl_args``, a dict mapping
670+
parameter names to warnings. The warnings will trigger whenever any plugin
671+
implements the hook requesting one of the specified parameters.
672+
673+
.. code-block:: python
674+
675+
@hookspec(
676+
warn_on_impl_args={
677+
"lousy_arg": DeprecationWarning(
678+
"The lousy_arg parameter of refreshed_hook is deprecated and will be removed soon; "
679+
"use awesome_arg instead"
680+
),
681+
},
682+
)
683+
def refreshed_hook(lousy_arg, awesome_arg):
684+
pass
685+
686+
.. versionadded:: 1.5
687+
The ``warn_on_impl_args`` parameter.
688+
689+
667690
.. _manage:
668691

669692
The Plugin registry

Diff for: src/pluggy/_hooks.py

+18
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ class HookspecOpts(TypedDict):
4848
historic: bool
4949
#: Whether the hook :ref:`warns when implemented <warn_on_impl>`.
5050
warn_on_impl: Warning | None
51+
#: Whether the hook warns when :ref:`certain arguments are requested
52+
#: <warn_on_impl>`.
53+
#:
54+
#: .. versionadded:: 1.5
55+
warn_on_impl_args: Mapping[str, Warning] | None
5156

5257

5358
class HookimplOpts(TypedDict):
@@ -92,6 +97,7 @@ def __call__(
9297
firstresult: bool = False,
9398
historic: bool = False,
9499
warn_on_impl: Warning | None = None,
100+
warn_on_impl_args: Mapping[str, Warning] | None = None,
95101
) -> _F: ...
96102

97103
@overload # noqa: F811
@@ -101,6 +107,7 @@ def __call__( # noqa: F811
101107
firstresult: bool = ...,
102108
historic: bool = ...,
103109
warn_on_impl: Warning | None = ...,
110+
warn_on_impl_args: Mapping[str, Warning] | None = ...,
104111
) -> Callable[[_F], _F]: ...
105112

106113
def __call__( # noqa: F811
@@ -109,6 +116,7 @@ def __call__( # noqa: F811
109116
firstresult: bool = False,
110117
historic: bool = False,
111118
warn_on_impl: Warning | None = None,
119+
warn_on_impl_args: Mapping[str, Warning] | None = None,
112120
) -> _F | Callable[[_F], _F]:
113121
"""If passed a function, directly sets attributes on the function
114122
which will make it discoverable to :meth:`PluginManager.add_hookspecs`.
@@ -128,6 +136,13 @@ def __call__( # noqa: F811
128136
:param warn_on_impl:
129137
If given, every implementation of this hook will trigger the given
130138
warning. See :ref:`warn_on_impl`.
139+
140+
:param warn_on_impl_args:
141+
If given, every implementation of this hook which requests one of
142+
the arguments in the dict will trigger the corresponding warning.
143+
See :ref:`warn_on_impl`.
144+
145+
.. versionadded:: 1.5
131146
"""
132147

133148
def setattr_hookspec_opts(func: _F) -> _F:
@@ -137,6 +152,7 @@ def setattr_hookspec_opts(func: _F) -> _F:
137152
"firstresult": firstresult,
138153
"historic": historic,
139154
"warn_on_impl": warn_on_impl,
155+
"warn_on_impl_args": warn_on_impl_args,
140156
}
141157
setattr(func, self.project_name + "_spec", opts)
142158
return func
@@ -686,6 +702,7 @@ class HookSpec:
686702
"kwargnames",
687703
"opts",
688704
"warn_on_impl",
705+
"warn_on_impl_args",
689706
)
690707

691708
def __init__(self, namespace: _Namespace, name: str, opts: HookspecOpts) -> None:
@@ -695,3 +712,4 @@ def __init__(self, namespace: _Namespace, name: str, opts: HookspecOpts) -> None
695712
self.argnames, self.kwargnames = varnames(self.function)
696713
self.opts = opts
697714
self.warn_on_impl = opts.get("warn_on_impl")
715+
self.warn_on_impl_args = opts.get("warn_on_impl_args")

Diff for: src/pluggy/_manager.py

+6
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,12 @@ def _verify_hook(self, hook: HookCaller, hookimpl: HookImpl) -> None:
353353
),
354354
)
355355

356+
if hook.spec.warn_on_impl_args:
357+
for hookimpl_argname in hookimpl.argnames:
358+
argname_warning = hook.spec.warn_on_impl_args.get(hookimpl_argname)
359+
if argname_warning is not None:
360+
_warn_for_function(argname_warning, hookimpl.function)
361+
356362
if (
357363
hookimpl.wrapper or hookimpl.hookwrapper
358364
) and not inspect.isgeneratorfunction(hookimpl.function):

Diff for: testing/test_details.py

+33
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,39 @@ def foo(self):
9393
assert record.lineno == Plugin.foo.__code__.co_firstlineno
9494

9595

96+
def test_warn_when_deprecated_args_specified(recwarn) -> None:
97+
warning1 = DeprecationWarning("old1 is deprecated")
98+
warning2 = DeprecationWarning("old2 is deprecated")
99+
100+
class Spec:
101+
@hookspec(
102+
warn_on_impl_args={
103+
"old1": warning1,
104+
"old2": warning2,
105+
},
106+
)
107+
def foo(self, old1, new, old2):
108+
pass
109+
110+
class Plugin:
111+
@hookimpl
112+
def foo(self, old2, old1, new):
113+
pass
114+
115+
pm = PluginManager(hookspec.project_name)
116+
pm.add_hookspecs(Spec)
117+
118+
with pytest.warns(DeprecationWarning) as records:
119+
pm.register(Plugin())
120+
(record1, record2) = records
121+
assert record1.message is warning2
122+
assert record1.filename == Plugin.foo.__code__.co_filename
123+
assert record1.lineno == Plugin.foo.__code__.co_firstlineno
124+
assert record2.message is warning1
125+
assert record2.filename == Plugin.foo.__code__.co_filename
126+
assert record2.lineno == Plugin.foo.__code__.co_firstlineno
127+
128+
96129
def test_plugin_getattr_raises_errors() -> None:
97130
"""Pluggy must be able to handle plugins which raise weird exceptions
98131
when getattr() gets called (#11).

0 commit comments

Comments
 (0)