Skip to content

Commit

Permalink
Merge pull request #430 from bluetech/typing3
Browse files Browse the repository at this point in the history
More steps towards exporting our typings
  • Loading branch information
bluetech authored Aug 26, 2023
2 parents ba6fb4a + 20b419d commit 1b1042e
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 42 deletions.
23 changes: 17 additions & 6 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ API Reference

.. autodecorator:: pluggy.HookimplMarker

.. autoclass:: pluggy.Result()
:show-inheritance:
.. autoclass:: pluggy.HookRelay()
:members:

.. data:: <hook name>

:type: HookCaller

The caller for the hook with the given name.

.. autoclass:: pluggy.HookCaller()
:members:
:special-members: __call__
Expand All @@ -26,11 +31,17 @@ API Reference
:show-inheritance:
:members:

.. autoclass:: pluggy.HookRelay()
.. autoclass:: pluggy.Result()
:show-inheritance:
:members:

.. data:: <hook name>
.. autoclass:: pluggy.HookImpl()
:members:

:type: HookCaller
.. autoclass:: pluggy.HookspecOpts()
:show-inheritance:
:members:

The caller for the hook with the given name.
.. autoclass:: pluggy.HookimplOpts()
:show-inheritance:
:members:
13 changes: 13 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@

autodoc_member_order = "bysource"

nitpicky = True
nitpick_ignore = {
# Don't want to expose this yet (see #428).
("py:class", "pluggy._tracing.TagTracerSub"),
# Compat hack, don't want to expose it.
("py:class", "pluggy._manager.DistFacade"),
# `types.ModuleType` turns into `module` but then fails to resolve...
("py:class", "module"),
# Just a TypeVar.
("py:obj", "pluggy._result.ResultType"),
("py:class", "pluggy._result.ResultType"),
}

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

# Grouping the document tree into Texinfo files. List of tuples
Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -966,11 +966,11 @@ The expected signature and default implementations for these functions is:

.. code-block:: python
def before(hook_name, methods, kwargs):
def before(hook_name, hook_impls, kwargs):
pass
def after(outcome, hook_name, methods, kwargs):
def after(outcome, hook_name, hook_impls, kwargs):
pass
Public API
Expand Down
10 changes: 6 additions & 4 deletions src/pluggy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"PluginValidationError",
"HookCaller",
"HookCallError",
"HookSpecOpts",
"HookImplOpts",
"HookspecOpts",
"HookimplOpts",
"HookImpl",
"HookRelay",
"HookspecMarker",
"HookimplMarker",
Expand All @@ -25,6 +26,7 @@
HookimplMarker,
HookCaller,
HookRelay,
HookSpecOpts,
HookImplOpts,
HookspecOpts,
HookimplOpts,
HookImpl,
)
66 changes: 47 additions & 19 deletions src/pluggy/_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Any
from typing import Callable
from typing import Final
from typing import final
from typing import Generator
from typing import List
from typing import Mapping
Expand All @@ -37,7 +38,7 @@
_HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]]


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

#: Whether the hook is :ref:`first result only <firstresult>`.
Expand All @@ -48,7 +49,7 @@ class HookSpecOpts(TypedDict):
warn_on_impl: Warning | None


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

#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
Expand All @@ -69,6 +70,7 @@ class HookImplOpts(TypedDict):
specname: str | None


@final
class HookspecMarker:
"""Decorator for marking functions as hook specifications.
Expand Down Expand Up @@ -132,7 +134,7 @@ def __call__( # noqa: F811
def setattr_hookspec_opts(func: _F) -> _F:
if historic and firstresult:
raise ValueError("cannot have a historic firstresult hook")
opts: HookSpecOpts = {
opts: HookspecOpts = {
"firstresult": firstresult,
"historic": historic,
"warn_on_impl": warn_on_impl,
Expand All @@ -146,6 +148,7 @@ def setattr_hookspec_opts(func: _F) -> _F:
return setattr_hookspec_opts


@final
class HookimplMarker:
"""Decorator for marking functions as hook implementations.
Expand Down Expand Up @@ -247,7 +250,7 @@ def __call__( # noqa: F811
"""

def setattr_hookimpl_opts(func: _F) -> _F:
opts: HookImplOpts = {
opts: HookimplOpts = {
"wrapper": wrapper,
"hookwrapper": hookwrapper,
"optionalhook": optionalhook,
Expand All @@ -264,7 +267,7 @@ def setattr_hookimpl_opts(func: _F) -> _F:
return setattr_hookimpl_opts(function)


def normalize_hookimpl_opts(opts: HookImplOpts) -> None:
def normalize_hookimpl_opts(opts: HookimplOpts) -> None:
opts.setdefault("tryfirst", False)
opts.setdefault("trylast", False)
opts.setdefault("wrapper", False)
Expand Down Expand Up @@ -341,6 +344,7 @@ def varnames(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]:
return args, kwargs


@final
class HookRelay:
"""Hook holder object for performing 1:N hook calls where N is the number
of registered plugins."""
Expand Down Expand Up @@ -379,13 +383,15 @@ def __init__(
name: str,
hook_execute: _HookExec,
specmodule_or_class: _Namespace | None = None,
spec_opts: HookSpecOpts | None = None,
spec_opts: HookspecOpts | None = None,
) -> None:
""":meta private:"""
#: Name of the hook getting called.
self.name: Final = name
self._hookexec: Final = hook_execute
self._hookimpls: Final[list[HookImpl]] = []
self._call_history: _CallHistory | None = None
# TODO: Document, or make private.
self.spec: HookSpec | None = None
if specmodule_or_class is not None:
assert spec_opts is not None
Expand All @@ -399,7 +405,7 @@ def has_spec(self) -> bool:
def set_specification(
self,
specmodule_or_class: _Namespace,
spec_opts: HookSpecOpts,
spec_opts: HookspecOpts,
) -> None:
if self.spec is not None:
raise ValueError(
Expand Down Expand Up @@ -522,7 +528,7 @@ def call_extra(
not self.is_historic()
), "Cannot directly call a historic hook - use call_historic instead."
self._verify_all_args_are_provided(kwargs)
opts: HookImplOpts = {
opts: HookimplOpts = {
"wrapper": False,
"hookwrapper": False,
"optionalhook": False,
Expand Down Expand Up @@ -606,7 +612,10 @@ def __repr__(self) -> str:
return f"<_SubsetHookCaller {self.name!r}>"


@final
class HookImpl:
"""A hook implementation in a :class:`HookCaller`."""

__slots__ = (
"function",
"argnames",
Expand All @@ -626,23 +635,42 @@ def __init__(
plugin: _Plugin,
plugin_name: str,
function: _HookImplFunction[object],
hook_impl_opts: HookImplOpts,
hook_impl_opts: HookimplOpts,
) -> None:
""":meta private:"""
#: The hook implementation function.
self.function: Final = function
self.argnames, self.kwargnames = varnames(self.function)
self.plugin = plugin
self.opts = hook_impl_opts
self.plugin_name = plugin_name
self.wrapper = hook_impl_opts["wrapper"]
self.hookwrapper = hook_impl_opts["hookwrapper"]
self.optionalhook = hook_impl_opts["optionalhook"]
self.tryfirst = hook_impl_opts["tryfirst"]
self.trylast = hook_impl_opts["trylast"]
argnames, kwargnames = varnames(self.function)
#: The positional parameter names of ``function```.
self.argnames: Final = argnames
#: The keyword parameter names of ``function```.
self.kwargnames: Final = kwargnames
#: The plugin which defined this hook implementation.
self.plugin: Final = plugin
#: The :class:`HookimplOpts` used to configure this hook implementation.
self.opts: Final = hook_impl_opts
#: The name of the plugin which defined this hook implementation.
self.plugin_name: Final = plugin_name
#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
self.wrapper: Final = hook_impl_opts["wrapper"]
#: Whether the hook implementation is an :ref:`old-style wrapper
#: <old_style_hookwrappers>`.
self.hookwrapper: Final = hook_impl_opts["hookwrapper"]
#: Whether validation against a hook specification is :ref:`optional
#: <optionalhook>`.
self.optionalhook: Final = hook_impl_opts["optionalhook"]
#: Whether to try to order this hook implementation :ref:`first
#: <callorder>`.
self.tryfirst: Final = hook_impl_opts["tryfirst"]
#: Whether to try to order this hook implementation :ref:`last
#: <callorder>`.
self.trylast: Final = hook_impl_opts["trylast"]

def __repr__(self) -> str:
return f"<HookImpl plugin_name={self.plugin_name!r}, plugin={self.plugin!r}>"


@final
class HookSpec:
__slots__ = (
"namespace",
Expand All @@ -654,7 +682,7 @@ class HookSpec:
"warn_on_impl",
)

def __init__(self, namespace: _Namespace, name: str, opts: HookSpecOpts) -> None:
def __init__(self, namespace: _Namespace, name: str, opts: HookspecOpts) -> None:
self.namespace = namespace
self.function: Callable[..., object] = getattr(namespace, name)
self.name = name
Expand Down
21 changes: 10 additions & 11 deletions src/pluggy/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
from ._hooks import _SubsetHookCaller
from ._hooks import HookCaller
from ._hooks import HookImpl
from ._hooks import HookImplOpts
from ._hooks import HookimplOpts
from ._hooks import HookRelay
from ._hooks import HookSpec
from ._hooks import HookSpecOpts
from ._hooks import HookspecOpts
from ._hooks import normalize_hookimpl_opts
from ._result import Result

Expand Down Expand Up @@ -92,12 +91,12 @@ class PluginManager:

def __init__(self, project_name: str) -> None:
#: The project name.
self.project_name: Final[str] = project_name
self.project_name: Final = project_name
self._name2plugin: Final[dict[str, _Plugin]] = {}
self._plugin_distinfo: Final[list[tuple[_Plugin, DistFacade]]] = []
#: The "hook relay", used to call a hook on all registered plugins.
#: See :ref:`calling`.
self.hook: Final[HookRelay] = HookRelay()
self.hook: Final = HookRelay()
#: The tracing entry point. See :ref:`tracing`.
self.trace: Final[_tracing.TagTracerSub] = _tracing.TagTracer().get(
"pluginmanage"
Expand Down Expand Up @@ -166,7 +165,7 @@ def register(self, plugin: _Plugin, name: str | None = None) -> str | None:
hook._add_hookimpl(hookimpl)
return plugin_name

def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookImplOpts | None:
def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None:
"""Try to obtain a hook implementation from an item with the given name
in the given plugin which is being searched for hook impls.
Expand All @@ -175,13 +174,13 @@ def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookImplOpts | None
This method can be overridden by ``PluginManager`` subclasses to
customize how hook implementation are picked up. By default, returns the
options for items decorated with :class:`HookImplMarker`.
options for items decorated with :class:`HookimplMarker`.
"""
method: object = getattr(plugin, name)
if not inspect.isroutine(method):
return None
try:
res: HookImplOpts | None = getattr(
res: HookimplOpts | None = getattr(
method, self.project_name + "_impl", None
)
except Exception:
Expand Down Expand Up @@ -260,7 +259,7 @@ def add_hookspecs(self, module_or_class: _Namespace) -> None:

def parse_hookspec_opts(
self, module_or_class: _Namespace, name: str
) -> HookSpecOpts | None:
) -> HookspecOpts | None:
"""Try to obtain a hook specification from an item with the given name
in the given module or class which is being searched for hook specs.
Expand All @@ -272,8 +271,8 @@ def parse_hookspec_opts(
customize how hook specifications are picked up. By default, returns the
options for items decorated with :class:`HookspecMarker`.
"""
method: HookSpec = getattr(module_or_class, name)
opts: HookSpecOpts | None = getattr(method, self.project_name + "_spec", None)
method = getattr(module_or_class, name)
opts: HookspecOpts | None = getattr(method, self.project_name + "_spec", None)
return opts

def get_plugins(self) -> set[Any]:
Expand Down
2 changes: 2 additions & 0 deletions src/pluggy/_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from types import TracebackType
from typing import Callable
from typing import cast
from typing import final
from typing import Generator
from typing import Generic
from typing import NoReturn
Expand Down Expand Up @@ -36,6 +37,7 @@ class HookCallError(Exception):
"""Hook was called incorrectly."""


@final
class Result(Generic[ResultType]):
"""An object used to inspect and set the result in a :ref:`hook wrapper
<hookwrappers>`."""
Expand Down

0 comments on commit 1b1042e

Please sign in to comment.