Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/704.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Hook options are now :class:`pluggy.HookspecConfiguration` /
:class:`pluggy.HookimplConfiguration` objects (markers attach these instead of
dicts). ``PluginManager.parse_hookimpl_opts`` /
``parse_hookspec_opts`` remain as a deprecated pytest/support concession that
returns legacy dicts and are only invoked during registration when a subclass
overrides them and no modern configuration attribute was found.
``HookspecOpts`` / ``HookimplOpts`` TypedDicts remain importable for
pytest/typing compatibility.
3 changes: 3 additions & 0 deletions changelog/706.trivial.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
``HookSpec`` now stores its :class:`pluggy.HookspecConfiguration` as
``config``; the old ``opts`` attribute remains as a deprecated alias
property.
9 changes: 9 additions & 0 deletions changelog/707.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hook implementations are now represented by dedicated types:
:class:`pluggy.NormalImpl` for normal implementations and
:class:`pluggy.WrapperImpl` for (old- and new-style) wrappers, both
subclasses of :class:`pluggy.HookImpl`.
``HookimplConfiguration.create_hookimpl()`` selects the appropriate
subclass, and ``WrapperImpl.setup_and_get_completion_hook()`` exposes
wrapper setup/teardown as a ``CompletionHook`` callback.
``HookImpl`` now stores its configuration as ``hookimpl_config``; the old
``opts`` attribute remains as a deprecated alias property.
10 changes: 8 additions & 2 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ API Reference
.. autoclass:: pluggy.HookImpl()
:members:

.. autoclass:: pluggy.HookspecOpts()
.. autoclass:: pluggy.NormalImpl()
:show-inheritance:
:members:

.. autoclass:: pluggy.HookimplOpts()
.. autoclass:: pluggy.WrapperImpl()
:show-inheritance:
:members:

.. autoclass:: pluggy.HookspecConfiguration()
:members:

.. autoclass:: pluggy.HookimplConfiguration()
:members:


Warnings
--------
Expand Down
9 changes: 6 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -800,10 +800,13 @@ and particular plugins in it:

Parsing mark options
^^^^^^^^^^^^^^^^^^^^
You can retrieve the *options* applied to a particular
*hookspec* or *hookimpl* as per :ref:`marking_hooks` using the
Markers attach :class:`~pluggy.HookspecConfiguration` /
:class:`~pluggy.HookimplConfiguration` objects to functions. The
:py:meth:`~pluggy.PluginManager.parse_hookspec_opts()` and
:py:meth:`~pluggy.PluginManager.parse_hookimpl_opts()` respectively.
:py:meth:`~pluggy.PluginManager.parse_hookimpl_opts()` methods remain as a
**deprecated** pytest/support concession that returns legacy dict-shaped
options; registration only calls them when a subclass overrides them and no
modern configuration attribute was found.


.. _calling:
Expand Down
12 changes: 10 additions & 2 deletions src/pluggy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,34 @@
"HookCaller",
"HookImpl",
"HookRelay",
"HookimplConfiguration",
"HookimplMarker",
"HookimplOpts",
"HookspecConfiguration",
"HookspecMarker",
"HookspecOpts",
"NormalImpl",
"PluggyTeardownRaisedWarning",
"PluggyWarning",
"PluginManager",
"PluginValidationError",
"Result",
"WrapperImpl",
"__version__",
]
from ._config import HookimplConfiguration
from ._config import HookspecConfiguration
from ._hooks import HookCaller
from ._hooks import HookImpl
from ._hooks import HookimplMarker
from ._hooks import HookimplOpts
from ._hooks import HookRelay
from ._hooks import HookspecMarker
from ._hooks import HookspecOpts
from ._hooks import NormalImpl
from ._hooks import WrapperImpl
from ._manager import PluginManager
from ._manager import PluginValidationError
from ._pytest_compat import HookimplOpts
from ._pytest_compat import HookspecOpts
from ._result import HookCallError
from ._result import Result
from ._warnings import PluggyTeardownRaisedWarning
Expand Down
25 changes: 9 additions & 16 deletions src/pluggy/_caller.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from typing import TypeAlias
import warnings

from ._config import HookimplOpts
from ._config import HookspecOpts
from ._config import HookimplConfiguration
from ._config import HookspecConfiguration
from ._decorators import _Namespace
from ._decorators import HookSpec
from ._impl import _Plugin
Expand Down Expand Up @@ -69,7 +69,7 @@ def __init__(
name: str,
hook_execute: _HookExec,
specmodule_or_class: _Namespace | None = None,
spec_opts: HookspecOpts | None = None,
spec_opts: HookspecConfiguration | None = None,
) -> None:
""":meta private:"""
#: Name of the hook getting called.
Expand Down Expand Up @@ -98,15 +98,15 @@ def has_spec(self) -> bool:
def set_specification(
self,
specmodule_or_class: _Namespace,
spec_opts: HookspecOpts,
spec_opts: HookspecConfiguration,
) -> None:
if self.spec is not None:
raise ValueError(
f"Hook {self.spec.name!r} is already registered "
f"within namespace {self.spec.namespace}"
)
self.spec = HookSpec(specmodule_or_class, self.name, spec_opts)
if spec_opts.get("historic"):
if spec_opts.historic:
self._call_history = []

def is_historic(self) -> bool:
Expand Down Expand Up @@ -192,7 +192,7 @@ def __call__(self, **kwargs: object) -> Any:
)
call_kwargs = self._apply_defaults(kwargs)
self._verify_all_args_are_provided(call_kwargs)
firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
firstresult = self.spec.config.firstresult if self.spec else False
# Copy because plugins may register other plugins during iteration (#438).
return self._hookexec(
self.name, self._hookimpls.copy(), call_kwargs, firstresult
Expand Down Expand Up @@ -237,17 +237,10 @@ def call_extra(
)
kwargs = self._apply_defaults(kwargs)
self._verify_all_args_are_provided(kwargs)
opts: HookimplOpts = {
"wrapper": False,
"hookwrapper": False,
"optionalhook": False,
"trylast": False,
"tryfirst": False,
"specname": None,
}
config = HookimplConfiguration()
hookimpls = self._hookimpls.copy()
for method in methods:
hookimpl = HookImpl(None, "<temp>", method, opts)
hookimpl = config.create_hookimpl(None, "<temp>", method)
# Find last non-tryfirst nonwrapper method.
i = len(hookimpls) - 1
while i >= 0 and (
Expand All @@ -258,7 +251,7 @@ def call_extra(
):
i -= 1
hookimpls.insert(i + 1, hookimpl)
firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
firstresult = self.spec.config.firstresult if self.spec else False
return self._hookexec(self.name, hookimpls, kwargs, firstresult)

def _maybe_apply_history(self, method: HookImpl) -> None:
Expand Down
Loading
Loading