Skip to content

Commit

Permalink
Merge pull request pytest-dev#349 from bluetech/docstrings
Browse files Browse the repository at this point in the history
Some style improvements to docstrings/API Reference
  • Loading branch information
bluetech committed Jan 16, 2022
2 parents 2304a0f + e735336 commit 172ca9f
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 88 deletions.
3 changes: 1 addition & 2 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
:orphan:

Api Reference
API Reference
=============

.. automodule:: pluggy
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: pluggy._callers._Result
.. automethod:: pluggy._callers._Result.get_result
Expand Down
75 changes: 39 additions & 36 deletions src/pluggy/_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ class _HookImplOpts(TypedDict):


class HookspecMarker:
"""Decorator helper class for marking functions as hook specifications.
"""Decorator for marking functions as hook specifications.
You can instantiate it with a project_name to get a decorator.
Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions
if the :py:class:`.PluginManager` uses the same project_name.
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.
"""

__slots__ = ("project_name",)
Expand Down Expand Up @@ -92,18 +92,18 @@ def __call__( # noqa: F811
historic: bool = False,
warn_on_impl: Optional[Warning] = None,
) -> Union[_F, Callable[[_F], _F]]:
"""if passed a function, directly sets attributes on the function
which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`.
If passed no function, returns a decorator which can be applied to a function
later using the attributes supplied.
"""If passed a function, directly sets attributes on the function
which will make it discoverable to :meth:`PluginManager.add_hookspecs`.
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.
If passed no function, returns a decorator which can be applied to a
function later using the attributes supplied.
If ``historic`` is ``True`` calls to a hook will be memorized and replayed
on later registered plugins.
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.
If ``historic`` is ``True``, every call to the hook will be memorized
and replayed on plugins registered after the call was made.
"""

def setattr_hookspec_opts(func: _F) -> _F:
Expand All @@ -124,11 +124,11 @@ def setattr_hookspec_opts(func: _F) -> _F:


class HookimplMarker:
"""Decorator helper class for marking functions as hook implementations.
"""Decorator for marking functions as hook implementations.
You can instantiate with a ``project_name`` to get a decorator.
Calling :py:meth:`.PluginManager.register` later will discover all marked functions
if the :py:class:`.PluginManager` uses the same project_name.
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.
"""

__slots__ = ("project_name",)
Expand Down Expand Up @@ -169,30 +169,33 @@ def __call__( # noqa: F811
trylast: bool = False,
specname: Optional[str] = None,
) -> Union[_F, Callable[[_F], _F]]:
"""if passed a function, directly sets attributes on the function
which will make it discoverable to :py:meth:`.PluginManager.register`.
"""If passed a function, directly sets attributes on the function
which will make it discoverable to :meth:`PluginManager.register`.
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 ``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 ``trylast`` is ``True`` this hook implementation will run as late as possible
in the chain of N hook implementations.
If ``tryfirst`` is ``True``, this hook implementation will run as early
as possible in the chain of N hook implementations for a specification.
If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly
one ``yield``. The code before the ``yield`` is run early before any non-hookwrapper
function is run. The code after the ``yield`` is run after all non-hookwrapper
function have run. The ``yield`` receives a :py:class:`.callers._Result` object
representing the exception or result outcome of the inner calls (including other
hookwrapper calls).
If ``trylast`` is ``True``, this hook implementation will run as late as
possible in the chain of N hook implementations.
If ``specname`` is provided, it will be used instead of the function name when
matching this hook implementation to a hook specification during registration.
If ``hookwrapper`` is ``True``, the hook implementations needs to
execute exactly one ``yield``. The code before the ``yield`` is run
early before any non-hookwrapper function is run. The code after the
``yield`` is run after all non-hookwrapper function have run The
``yield`` receives a :class:`_Result` object representing the exception
or result outcome of the inner calls (including other hookwrapper
calls).
If ``specname`` is provided, it will be used instead of the function
name when matching this hook implementation to a hook specification
during registration.
"""

def setattr_hookimpl_opts(func: _F) -> _F:
Expand Down Expand Up @@ -268,7 +271,7 @@ def varnames(func: object) -> Tuple[Tuple[str, ...], Tuple[str, ...]]:


class _HookRelay:
"""hook holder object for performing 1:N hook calls where N is the number
"""Hook holder object for performing 1:N hook calls where N is the number
of registered plugins."""

__slots__ = ("__dict__",)
Expand Down Expand Up @@ -396,7 +399,7 @@ def call_historic(
"""Call the hook with given ``kwargs`` for all registered plugins and
for all plugins which will be registered afterwards.
If ``result_callback`` is not ``None`` it will be called for for each
If ``result_callback`` is provided, it will be called for each
non-``None`` result obtained from a hook implementation.
"""
assert self._call_history is not None
Expand Down
111 changes: 64 additions & 47 deletions src/pluggy/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,14 @@ def _warn_for_function(warning: Warning, function: Callable[..., object]) -> Non


class PluginValidationError(Exception):
"""plugin failed validation.
"""Plugin failed validation.
:param object plugin: the plugin which failed validation,
may be a module or an arbitrary object.
:param plugin: The plugin which failed validation.
"""

def __init__(self, plugin: _Plugin, message: str) -> None:
super().__init__(message)
self.plugin = plugin
super(Exception, self).__init__(message)


class DistFacade:
Expand All @@ -88,17 +87,16 @@ def __dir__(self) -> List[str]:


class PluginManager:
"""Core :py:class:`.PluginManager` class which manages registration
of plugin objects and 1:N hook calling.
"""Core class which manages registration of plugin objects and 1:N hook
calling.
You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class)
<.PluginManager.add_hookspecs>`.
You can register plugin objects (which contain hooks) by calling
:py:meth:`register(plugin) <.PluginManager.register>`. The :py:class:`.PluginManager`
is initialized with a prefix that is searched for in the names of the dict
of registered plugin objects.
You can register new hooks by calling :meth:`add_hookspecs(module_or_class)
<PluginManager.add_hookspecs>`.
For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing`
You can register plugin objects (which contain hook implementations) by
calling :meth:`register(plugin) <PluginManager.register>`.
For debugging purposes you can call :meth:`PluginManager.enable_tracing`
which will subsequently send debug information to the trace helper.
"""

Expand Down Expand Up @@ -132,9 +130,15 @@ def _hookexec(
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)

def register(self, plugin: _Plugin, name: Optional[str] = None) -> Optional[str]:
"""Register a plugin and return its canonical name or ``None`` if the name
is blocked from registering. Raise a :py:class:`ValueError` if the plugin
is already registered."""
"""Register a plugin and return its name.
If a name is not specified, a name is generated using
:func:`get_canonical_name`.
If the name is blocked from registering, returns ``None``.
If the plugin is already registered, raises a :class:`ValueError`.
"""
plugin_name = name or self.get_canonical_name(plugin)

if plugin_name in self._name2plugin:
Expand Down Expand Up @@ -193,8 +197,11 @@ def parse_hookimpl_opts(
def unregister(
self, plugin: Optional[_Plugin] = None, name: Optional[str] = None
) -> _Plugin:
"""unregister a plugin object and all its contained hook implementations
from internal data structures."""
"""Unregister a plugin and all of its hook implementations.
The plugin can be specified either by the plugin object or the plugin
name. If both are specified, they must agree.
"""
if name is None:
assert plugin is not None, "one of name or plugin needs to be specified"
name = self.get_name(plugin)
Expand All @@ -216,17 +223,20 @@ def unregister(
return plugin

def set_blocked(self, name: str) -> None:
"""block registrations of the given name, unregister if already registered."""
"""Block registrations of the given name, unregister if already registered."""
self.unregister(name=name)
self._name2plugin[name] = None

def is_blocked(self, name: str) -> bool:
"""return ``True`` if the given plugin name is blocked."""
"""Return whether the given plugin name is blocked."""
return name in self._name2plugin and self._name2plugin[name] is None

def add_hookspecs(self, module_or_class: _Namespace) -> None:
"""add new hook specifications defined in the given ``module_or_class``.
Functions are recognized if they have been decorated accordingly."""
"""Add new hook specifications defined in the given ``module_or_class``.
Functions are recognized as hook specifications if they have been
decorated with a matching :class:`HookspecMarker`.
"""
names = []
for name in dir(module_or_class):
spec_opts = self.parse_hookspec_opts(module_or_class, name)
Expand All @@ -236,7 +246,7 @@ def add_hookspecs(self, module_or_class: _Namespace) -> None:
hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts)
setattr(self.hook, name, hc)
else:
# plugins registered this hook without knowing the spec
# Plugins registered this hook without knowing the spec.
hc.set_specification(module_or_class, spec_opts)
for hookfunction in hc.get_hookimpls():
self._verify_hook(hc, hookfunction)
Expand All @@ -257,32 +267,35 @@ def parse_hookspec_opts(
return opts

def get_plugins(self) -> Set[Any]:
"""return the set of registered plugins."""
"""Return a set of all registered plugin objects."""
return set(self._name2plugin.values())

def is_registered(self, plugin: _Plugin) -> bool:
"""Return ``True`` if the plugin is already registered."""
"""Return whether the plugin is already registered."""
return any(plugin == val for val in self._name2plugin.values())

def get_canonical_name(self, plugin: _Plugin) -> str:
"""Return canonical name for a plugin object. Note that a plugin
may be registered under a different name which was specified
by the caller of :py:meth:`register(plugin, name) <.PluginManager.register>`.
To obtain the name of an registered plugin use :py:meth:`get_name(plugin)
<.PluginManager.get_name>` instead."""
"""Return a canonical name for a plugin object.
Note that a plugin may be registered under a different name
specified by the caller of :meth:`register(plugin, name) <register>`.
To obtain the name of n registered plugin use :meth:`get_name(plugin)
<get_name>` instead.
"""
name: Optional[str] = getattr(plugin, "__name__", None)
return name or str(id(plugin))

def get_plugin(self, name: str) -> Optional[Any]:
"""Return a plugin or ``None`` for the given name."""
"""Return the plugin registered under the given name, if any."""
return self._name2plugin.get(name)

def has_plugin(self, name: str) -> bool:
"""Return ``True`` if a plugin with the given name is registered."""
"""Return whether a plugin with the given name is registered."""
return self.get_plugin(name) is not None

def get_name(self, plugin: _Plugin) -> Optional[str]:
"""Return name for registered plugin or ``None`` if not registered."""
"""Return the name the plugin is registered under, or ``None`` if
is isn't."""
for name, val in self._name2plugin.items():
if plugin == val:
return name
Expand Down Expand Up @@ -325,8 +338,9 @@ def _verify_hook(self, hook: _HookCaller, hookimpl: HookImpl) -> None:
)

def check_pending(self) -> None:
"""Verify that all hooks which have not been verified against
a hook specification are optional, otherwise raise :py:class:`.PluginValidationError`."""
"""Verify that all hooks which have not been verified against a
hook specification are optional, otherwise raise
:class:`PluginValidationError`."""
for name in self.hook.__dict__:
if name[0] != "_":
hook: _HookCaller = getattr(self.hook, name)
Expand All @@ -344,10 +358,10 @@ def load_setuptools_entrypoints(
) -> int:
"""Load modules from querying the specified setuptools ``group``.
:param str group: entry point group to load plugins
:param str name: if given, loads only plugins with the given ``name``.
:param str group: Entry point group to load plugins.
:param str name: If given, loads only plugins with the given ``name``.
:rtype: int
:return: return the number of loaded plugins by this call.
:return: The number of plugins loaded by this call.
"""
count = 0
for dist in list(importlib_metadata.distributions()):
Expand All @@ -367,16 +381,16 @@ def load_setuptools_entrypoints(
return count

def list_plugin_distinfo(self) -> List[Tuple[_Plugin, DistFacade]]:
"""return list of distinfo/plugin tuples for all setuptools registered
plugins."""
"""Return a list of (plugin, distinfo) pairs for all
setuptools-registered plugins."""
return list(self._plugin_distinfo)

def list_name_plugin(self) -> List[Tuple[str, _Plugin]]:
"""return list of name/plugin pairs."""
"""Return a list of (name, plugin) pairs for all registered plugins."""
return list(self._name2plugin.items())

def get_hookcallers(self, plugin: _Plugin) -> Optional[List[_HookCaller]]:
"""get all hook callers for the specified plugin."""
"""Get all hook callers for the specified plugin."""
if self.get_name(plugin) is None:
return None
hookcallers = []
Expand All @@ -389,16 +403,16 @@ def get_hookcallers(self, plugin: _Plugin) -> Optional[List[_HookCaller]]:
def add_hookcall_monitoring(
self, before: _BeforeTrace, after: _AfterTrace
) -> Callable[[], None]:
"""add before/after tracing functions for all hooks
and return an undo function which, when called,
will remove the added tracers.
"""Add before/after tracing functions for all hooks.
Returns an undo function which, when called, removes the added tracers.
``before(hook_name, hook_impls, kwargs)`` will be called ahead
of all hook calls and receive a hookcaller instance, a list
of HookImpl instances and the keyword arguments for the hook call.
``after(outcome, hook_name, hook_impls, kwargs)`` receives the
same arguments as ``before`` but also a :py:class:`pluggy._callers._Result` object
same arguments as ``before`` but also a :class:`_Result` object
which represents the result of the overall hook call.
"""
oldcall = self._inner_hookexec
Expand All @@ -424,7 +438,10 @@ def undo() -> None:
return undo

def enable_tracing(self) -> Callable[[], None]:
"""enable tracing of hook calls and return an undo function."""
"""Enable tracing of hook calls.
Returns an undo function which, when called, removes the added tracing.
"""
hooktrace = self.trace.root.get("hook")

def before(
Expand Down
Loading

0 comments on commit 172ca9f

Please sign in to comment.