Skip to content

Commit

Permalink
docs: improve docstrings and API Reference
Browse files Browse the repository at this point in the history
  • Loading branch information
bluetech committed Aug 5, 2023
1 parent 9abeeba commit 0d2850a
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 66 deletions.
37 changes: 27 additions & 10 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,34 @@
API Reference
=============

.. automodule:: pluggy
.. autoclass:: pluggy.PluginManager
:members:
:undoc-members:

.. autoclass:: pluggy._result._Result
.. automethod:: pluggy._result._Result.get_result
.. automethod:: pluggy._result._Result.force_result
.. automethod:: pluggy._result._Result.force_exception
.. autoclass:: pluggy.PluginValidationError
:show-inheritance:
:members:

.. autodecorator:: pluggy.HookspecMarker

.. autodecorator:: pluggy.HookimplMarker

.. autoclass:: pluggy._result._Result()
:show-inheritance:
:members:

.. autoclass:: pluggy._hooks._HookCaller()
:members:
:special-members: __call__

.. autoclass:: pluggy.HookCallError()
:show-inheritance:
:members:

.. autoclass:: pluggy._hooks._HookRelay()
:members:

.. data:: <hook name>

.. autoclass:: pluggy._hooks._HookCaller
.. automethod:: pluggy._hooks._HookCaller.call_extra
.. automethod:: pluggy._hooks._HookCaller.call_historic
:type: _HookCaller

.. autoclass:: pluggy._hooks._HookRelay
The caller for the hook with the given name.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "pluggy", "pluggy Documentation", [author], 1)]

autodoc_typehints = "none"
autodoc_member_order = "bysource"

# -- Options for Texinfo output -------------------------------------------

Expand Down
13 changes: 12 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ be matched and checked against the ``setup_project`` hookspec:
return config
.. _callorder:

Call time order
^^^^^^^^^^^^^^^
By default hooks are :ref:`called <calling>` in LIFO registered order, however,
Expand Down Expand Up @@ -422,6 +424,8 @@ to the hook caller.

Also see the :ref:`pytest:hookwrapper` section in the ``pytest`` docs.

.. _old_style_hookwrappers:

Old-style wrappers
^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -629,12 +633,14 @@ dynamically loaded plugins.
For more info see :ref:`call_historic`.


.. _warn_on_impl:

Warnings on hook implementation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

As projects evolve new hooks may be introduced and/or deprecated.

if a hookspec specifies a ``warn_on_impl``, pluggy will trigger it for any plugin implementing the hook.
If a hookspec specifies a ``warn_on_impl``, pluggy will trigger it for any plugin implementing the hook.


.. code-block:: python
Expand Down Expand Up @@ -907,6 +913,8 @@ is registered.
hooks since only the first registered plugin's hook(s) would
ever be called.

.. _call_extra:

Calling with extras
-------------------
You can call a hook with temporarily participating *implementation* functions
Expand All @@ -925,6 +933,9 @@ You then can use that :py:class:`_HookCaller <pluggy._hooks._HookCaller>`
to make normal, :py:meth:`~pluggy._hooks._HookCaller.call_historic`, or
:py:meth:`~pluggy._hooks._HookCaller.call_extra` calls as necessary.


.. _tracing:

Built-in tracing
****************
``pluggy`` comes with some batteries included hook tracing for your
Expand Down
136 changes: 95 additions & 41 deletions src/pluggy/_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,34 @@


class _HookSpecOpts(TypedDict):
"""Options for a hook specification."""

#: Whether the hook is :ref:`first result only <firstresult>`.
firstresult: bool
#: Whether the hook is :ref:`historic <historic>`.
historic: bool
#: Whether the hook :ref:`warns when implemented <warn_on_impl>`.
warn_on_impl: Warning | None


class _HookImplOpts(TypedDict):
"""Options for a hook implementation."""

#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
wrapper: bool
#: Whether the hook implementation is an :ref:`old-style wrapper
#: <old_style_hookwrappers>`.
hookwrapper: bool
#: Whether validation against a hook specification is :ref:`optional
#: <optionalhook>`.
optionalhook: bool
#: Whether to try to order this hook implementation :ref:`first
#: <callorder>`.
tryfirst: bool
#: Whether to try to order this hook implementation :ref:`last
#: <callorder>`.
trylast: bool
#: The name of the hook specification to match, see :ref:`specname`.
specname: str | None


Expand All @@ -57,7 +74,7 @@ class HookspecMarker:
Instantiate it with a project_name to get a decorator.
Calling :meth:`PluginManager.add_hookspecs` later will discover all marked
functions if the :class:`PluginManager` uses the same project_name.
functions if the :class:`PluginManager` uses the same project name.
"""

__slots__ = ("project_name",)
Expand Down Expand Up @@ -98,12 +115,18 @@ def __call__( # noqa: F811
If passed no function, returns a decorator which can be applied to a
function later using the attributes supplied.
If ``firstresult`` is ``True``, the 1:N hook call (N being the number of
registered hook implementation functions) will stop at I<=N when the
I'th function returns a non-``None`` result.
:param firstresult:
If ``True``, the 1:N hook call (N being the number of registered
hook implementation functions) will stop at I<=N when the I'th
function returns a non-``None`` result. See :ref:`firstresult`.
If ``historic`` is ``True``, every call to the hook will be memorized
and replayed on plugins registered after the call was made.
:param historic:
If ``True``, every call to the hook will be memorized and replayed
on plugins registered after the call was made. See :ref:`historic`.
:param warn_on_impl:
If given, every implementation of this hook will trigger the given
warning. See :ref:`warn_on_impl`.
"""

def setattr_hookspec_opts(func: _F) -> _F:
Expand All @@ -128,7 +151,7 @@ class HookimplMarker:
Instantiate it with a ``project_name`` to get a decorator.
Calling :meth:`PluginManager.register` later will discover all marked
functions if the :class:`PluginManager` uses the same project_name.
functions if the :class:`PluginManager` uses the same project name.
"""

__slots__ = ("project_name",)
Expand Down Expand Up @@ -178,36 +201,46 @@ def __call__( # noqa: F811
If passed no function, returns a decorator which can be applied to a
function later using the attributes supplied.
If ``optionalhook`` is ``True``, a missing matching hook specification
will not result in an error (by default it is an error if no matching
spec is found).
If ``tryfirst`` is ``True``, this hook implementation will run as early
as possible in the chain of N hook implementations for a specification.
If ``trylast`` is ``True``, this hook implementation will run as late as
possible in the chain of N hook implementations.
If ``wrapper`` is ``True``("new-style hook wrapper"), the hook
implementation needs to execute exactly one ``yield``. The code before
the ``yield`` is run early before any non-hook-wrapper function is run.
The code after the ``yield`` is run after all non-hook-wrapper functions
have run. The ``yield`` receives the result value of the inner calls, or
raises the exception of inner calls (including earlier hook wrapper
calls). The return value of the function becomes the return value of the
hook, and a raised exception becomes the exception of the hook.
If ``hookwrapper`` is ``True`` ("old-style hook wrapper"), the hook
implementation needs to execute exactly one ``yield``. The code before
the ``yield`` is run early before any non-hook-wrapper function is run.
The code after the ``yield`` is run after all non-hook-wrapper function
have run The ``yield`` receives a :class:`_Result` object representing
the exception or result outcome of the inner calls (including earlier
hook wrapper calls). This option is mutually exclusive with ``wrapper``.
If ``specname`` is provided, it will be used instead of the function
name when matching this hook implementation to a hook specification
during registration.
:param optionalhook:
If ``True``, a missing matching hook specification will not result
in an error (by default it is an error if no matching spec is
found). See :ref:`optionalhook`.
:param tryfirst:
If ``True``, this hook implementation will run as early as possible
in the chain of N hook implementations for a specification. See
:ref:`callorder`.
:param trylast:
If ``True``, this hook implementation will run as late as possible
in the chain of N hook implementations for a specification. See
:ref:`callorder`.
:param wrapper:
If ``True`` ("new-style hook wrapper"), the hook implementation
needs to execute exactly one ``yield``. The code before the
``yield`` is run early before any non-hook-wrapper function is run.
The code after the ``yield`` is run after all non-hook-wrapper
functions have run. The ``yield`` receives the result value of the
inner calls, or raises the exception of inner calls (including
earlier hook wrapper calls). The return value of the function
becomes the return value of the hook, and a raised exception becomes
the exception of the hook. See :ref:`hookwrapper`.
:param hookwrapper:
If ``True`` ("old-style hook wrapper"), the hook implementation
needs to execute exactly one ``yield``. The code before the
``yield`` is run early before any non-hook-wrapper function is run.
The code after the ``yield`` is run after all non-hook-wrapper
function have run The ``yield`` receives a :class:`_Result` object
representing the exception or result outcome of the inner calls
(including earlier hook wrapper calls). This option is mutually
exclusive with ``wrapper``. See :ref:`old_style_hookwrapper`.
:param specname:
If provided, the given name will be used instead of the function
name when matching this hook implementation to a hook specification
during registration. See :ref:`specname`.
.. versionadded:: 1.2.0
The ``wrapper`` parameter.
Expand Down Expand Up @@ -314,6 +347,9 @@ class _HookRelay:

__slots__ = ("__dict__",)

def __init__(self) -> None:
""":meta private:"""

if TYPE_CHECKING:

def __getattr__(self, name: str) -> _HookCaller:
Expand All @@ -324,6 +360,8 @@ def __getattr__(self, name: str) -> _HookCaller:


class _HookCaller:
"""A caller of all registered implementations of a hook specification."""

__slots__ = (
"name",
"spec",
Expand All @@ -339,6 +377,7 @@ def __init__(
specmodule_or_class: _Namespace | None = None,
spec_opts: _HookSpecOpts | None = None,
) -> None:
""":meta private:"""
self.name: Final = name
self._hookexec: Final = hook_execute
self._hookimpls: Final[list[HookImpl]] = []
Expand All @@ -348,9 +387,11 @@ def __init__(
assert spec_opts is not None
self.set_specification(specmodule_or_class, spec_opts)

# TODO: Document, or make private.
def has_spec(self) -> bool:
return self.spec is not None

# TODO: Document, or make private.
def set_specification(
self,
specmodule_or_class: _Namespace,
Expand All @@ -366,6 +407,7 @@ def set_specification(
self._call_history = []

def is_historic(self) -> bool:
"""Whether this caller is :ref:`historic <historic>`."""
return self._call_history is not None

def _remove_plugin(self, plugin: _Plugin) -> None:
Expand All @@ -376,6 +418,7 @@ def _remove_plugin(self, plugin: _Plugin) -> None:
raise ValueError(f"plugin {plugin!r} not found")

def get_hookimpls(self) -> list[HookImpl]:
"""Get all registered hook implementations for this hook."""
return self._hookimpls.copy()

def _add_hookimpl(self, hookimpl: HookImpl) -> None:
Expand Down Expand Up @@ -424,6 +467,14 @@ def _verify_all_args_are_provided(self, kwargs: Mapping[str, object]) -> None:
break

def __call__(self, **kwargs: object) -> Any:
"""Call the hook.
Only accepts keyword arguments, which should match the hook
specification.
Returns the result(s) of calling all registered plugins, see
:ref:`calling`.
"""
assert (
not self.is_historic()
), "Cannot directly call a historic hook - use call_historic instead."
Expand All @@ -437,10 +488,12 @@ def call_historic(
kwargs: Mapping[str, object] | None = None,
) -> None:
"""Call the hook with given ``kwargs`` for all registered plugins and
for all plugins which will be registered afterwards.
for all plugins which will be registered afterwards, see
:ref:`historic`.
If ``result_callback`` is provided, it will be called for each
non-``None`` result obtained from a hook implementation.
:param result_callback:
If provided, will be called for each non-``None`` result obtained
from a hook implementation.
"""
assert self._call_history is not None
kwargs = kwargs or {}
Expand All @@ -459,7 +512,8 @@ def call_extra(
self, methods: Sequence[Callable[..., object]], kwargs: Mapping[str, object]
) -> Any:
"""Call the hook with some additional temporarily participating
methods using the specified ``kwargs`` as call parameters."""
methods using the specified ``kwargs`` as call parameters, see
:ref:`call_extra`."""
assert (
not self.is_historic()
), "Cannot directly call a historic hook - use call_historic instead."
Expand Down
Loading

0 comments on commit 0d2850a

Please sign in to comment.