From 24f5da0d583c73eab6a507d37f05a2c492966b5c Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 3 Sep 2024 20:48:32 +0200 Subject: [PATCH 1/5] cleanups * switch ruff config to extend-select and enable UP rules * make mypy --strict pass for src --- src/pluggy/_callers.py | 36 +++++--- src/pluggy/_hooks.py | 143 ++++++++++++++++-------------- src/pluggy/_importlib_metadata.py | 68 ++++++++++++++ src/pluggy/_manager.py | 105 ++++++++++------------ src/pluggy/_result.py | 38 +++++--- src/pluggy/_tracing.py | 11 ++- 6 files changed, 246 insertions(+), 155 deletions(-) create mode 100644 src/pluggy/_importlib_metadata.py diff --git a/src/pluggy/_callers.py b/src/pluggy/_callers.py index f4a2aced..ab8db678 100644 --- a/src/pluggy/_callers.py +++ b/src/pluggy/_callers.py @@ -4,28 +4,38 @@ from __future__ import annotations -from typing import cast -from typing import Generator -from typing import Mapping -from typing import NoReturn -from typing import Sequence -from typing import Tuple -from typing import Union import warnings from ._hooks import HookImpl +from ._result import _raise_wrapfail from ._result import HookCallError from ._result import Result -from ._warnings import PluggyTeardownRaisedWarning - -# Need to distinguish between old- and new-style hook wrappers. -# Wrapping with a tuple is the fastest type-safe way I found to do it. -Teardown = Union[ - Tuple[Generator[None, Result[object], None], HookImpl], +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import cast + from typing import Generator + from typing import Mapping + from typing import NoReturn + from typing import Sequence + from typing import Tuple + from typing import Union + + + # Need to distinguish between old- and new-style hook wrappers. + # Wrapping with a tuple is the fastest type-safe way I found to do it. + Teardown = Union[ + Tuple[Generator[None, Result[object], None], HookImpl], Generator[None, object, object], ] +from ._hooks import HookImpl +from ._result import HookCallError +from ._result import Result +from ._warnings import PluggyTeardownRaisedWarning + + + def _raise_wrapfail( wrap_controller: ( diff --git a/src/pluggy/_hooks.py b/src/pluggy/_hooks.py index a81d2b28..72c7cbbd 100644 --- a/src/pluggy/_hooks.py +++ b/src/pluggy/_hooks.py @@ -4,76 +4,87 @@ from __future__ import annotations -import inspect import sys -from types import ModuleType -from typing import AbstractSet -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 -from typing import Optional -from typing import overload -from typing import Sequence -from typing import Tuple -from typing import TYPE_CHECKING -from typing import TypedDict -from typing import TypeVar -from typing import Union import warnings -from ._result import Result +_Plugin = object + +TYPE_CHECKING = False +if TYPE_CHECKING: + from types import ModuleType + from typing import AbstractSet + 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 + from typing import Optional + from typing import overload + from typing import Sequence + from typing import Tuple + from typing import TYPE_CHECKING + from typing import TypedDict + from typing import TypeVar + from typing import Union + + from ._result import Result + + + _T = TypeVar("_T") + _F = TypeVar("_F", bound=Callable[..., object]) + _Namespace = Union[ModuleType, type] + _HookExec = Callable[ + [str, Sequence["HookImpl"], Mapping[str, object], bool], + Union[object, List[object]], + ] + _HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]] + _CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]] + + + class HookspecOpts(TypedDict): + """Options for a hook specification.""" + + #: Whether the hook is :ref:`first result only `. + firstresult: bool + #: Whether the hook is :ref:`historic `. + historic: bool + #: Whether the hook :ref:`warns when implemented `. + warn_on_impl: Warning | None + #: Whether the hook warns when :ref:`certain arguments are requested + #: `. + #: + #: .. versionadded:: 1.5 + warn_on_impl_args: Mapping[str, Warning] | None + + + class HookimplOpts(TypedDict): + """Options for a hook implementation.""" + + #: Whether the hook implementation is a :ref:`wrapper `. + wrapper: bool + #: Whether the hook implementation is an :ref:`old-style wrapper + #: `. + hookwrapper: bool + #: Whether validation against a hook specification is :ref:`optional + #: `. + optionalhook: bool + #: Whether to try to order this hook implementation :ref:`first + #: `. + tryfirst: bool + #: Whether to try to order this hook implementation :ref:`last + #: `. + trylast: bool + #: The name of the hook specification to match, see :ref:`specname`. + specname: str | None + +else: + def final(func: _F) -> _F: + return func + overload = final -_T = TypeVar("_T") -_F = TypeVar("_F", bound=Callable[..., object]) -_Namespace = Union[ModuleType, type] -_Plugin = object -_HookExec = Callable[ - [str, Sequence["HookImpl"], Mapping[str, object], bool], - Union[object, List[object]], -] -_HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]] - - -class HookspecOpts(TypedDict): - """Options for a hook specification.""" - - #: Whether the hook is :ref:`first result only `. - firstresult: bool - #: Whether the hook is :ref:`historic `. - historic: bool - #: Whether the hook :ref:`warns when implemented `. - warn_on_impl: Warning | None - #: Whether the hook warns when :ref:`certain arguments are requested - #: `. - #: - #: .. versionadded:: 1.5 - warn_on_impl_args: Mapping[str, Warning] | None - - -class HookimplOpts(TypedDict): - """Options for a hook implementation.""" - - #: Whether the hook implementation is a :ref:`wrapper `. - wrapper: bool - #: Whether the hook implementation is an :ref:`old-style wrapper - #: `. - hookwrapper: bool - #: Whether validation against a hook specification is :ref:`optional - #: `. - optionalhook: bool - #: Whether to try to order this hook implementation :ref:`first - #: `. - tryfirst: bool - #: Whether to try to order this hook implementation :ref:`last - #: `. - trylast: bool - #: The name of the hook specification to match, see :ref:`specname`. - specname: str | None @final @@ -299,6 +310,8 @@ def varnames(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]: In case of a class, its ``__init__`` method is considered. For methods the ``self`` parameter is not included. """ + import inspect + if inspect.isclass(func): try: func = func.__init__ diff --git a/src/pluggy/_importlib_metadata.py b/src/pluggy/_importlib_metadata.py new file mode 100644 index 00000000..3b831f7a --- /dev/null +++ b/src/pluggy/_importlib_metadata.py @@ -0,0 +1,68 @@ +"""this module contains importlib_metadata usage and importing + +it's deferred to avoid import-time dependency on importlib_metadata + +.. code-block:: console + + python -X importtime -c 'import pluggy' 2> import0.log + tuna import0.log + + +""" +from __future__ import annotations + +import importlib.metadata +from typing import Any + +from . import _manager + + +class DistFacade: + """Emulate a pkg_resources Distribution""" + + def __init__(self, dist: importlib.metadata.Distribution) -> None: + self._dist = dist + + @property + def project_name(self) -> str: + name: str = self.metadata["name"] + return name + + def __getattr__(self, attr: str, default: object | None = None) -> Any: + return getattr(self._dist, attr, default) + + def __dir__(self) -> list[str]: + return sorted(dir(self._dist) + ["_dist", "project_name"]) + + +def load_importlib_entrypoints( + manager: _manager.PluginManager, + group: str, + name: str | None = None, +) -> int: + """Load modules from querying the specified setuptools ``group``. + + :param group: + Entry point group to load plugins. + :param name: + If given, loads only plugins with the given ``name``. + + :return: + The number of plugins loaded by this call. + """ + count = 0 + for dist in list(importlib.metadata.distributions()): + for ep in dist.entry_points: + if ( + ep.group != group + or (name is not None and ep.name != name) + # already registered + or manager.get_plugin(ep.name) + or manager.is_blocked(ep.name) + ): + continue + plugin = ep.load() + manager.register(plugin, name=ep.name) + manager._plugin_dist_metadata.append((plugin, dist)) + count += 1 + return count diff --git a/src/pluggy/_manager.py b/src/pluggy/_manager.py index d778334b..01f3f619 100644 --- a/src/pluggy/_manager.py +++ b/src/pluggy/_manager.py @@ -1,42 +1,46 @@ from __future__ import annotations -import inspect import types -from typing import Any -from typing import Callable -from typing import cast -from typing import Final -from typing import Iterable -from typing import Mapping -from typing import Sequence -from typing import TYPE_CHECKING import warnings from . import _tracing from ._callers import _multicall -from ._hooks import _HookImplFunction -from ._hooks import _Namespace from ._hooks import _Plugin from ._hooks import _SubsetHookCaller from ._hooks import HookCaller from ._hooks import HookImpl -from ._hooks import HookimplOpts from ._hooks import HookRelay -from ._hooks import HookspecOpts from ._hooks import normalize_hookimpl_opts from ._result import Result +TYPE_CHECKING = False if TYPE_CHECKING: - # importtlib.metadata import is slow, defer it. - import importlib.metadata + from ._hooks import HookimplOpts + from ._hooks import HookspecOpts + from ._hooks import _HookImplFunction + from ._hooks import _Namespace -_BeforeTrace = Callable[[str, Sequence[HookImpl], Mapping[str, Any]], None] -_AfterTrace = Callable[[Result[Any], str, Sequence[HookImpl], Mapping[str, Any]], None] + from typing import Any + from typing import Callable + from typing import Final + from typing import Iterable + from typing import Mapping + from typing import Sequence + from typing import TypeAlias + from importlib.metadata import Distribution + from ._importlib_metadata import DistFacade + +_BeforeTrace: TypeAlias = "Callable[[str, Sequence[HookImpl], Mapping[str, Any]], None]" +_AfterTrace: TypeAlias = ( + "Callable[[Result[Any], str, Sequence[HookImpl], Mapping[str, Any]], None]" +) def _warn_for_function(warning: Warning, function: Callable[..., object]) -> None: + from typing import cast + func = cast(types.FunctionType, function) warnings.warn_explicit( warning, @@ -59,24 +63,6 @@ def __init__(self, plugin: _Plugin, message: str) -> None: self.plugin = plugin -class DistFacade: - """Emulate a pkg_resources Distribution""" - - def __init__(self, dist: importlib.metadata.Distribution) -> None: - self._dist = dist - - @property - def project_name(self) -> str: - name: str = self.metadata["name"] - return name - - def __getattr__(self, attr: str, default: Any | None = None) -> Any: - return getattr(self._dist, attr, default) - - def __dir__(self) -> list[str]: - return sorted(dir(self._dist) + ["_dist", "project_name"]) - - class PluginManager: """Core class which manages registration of plugin objects and 1:N hook calling. @@ -98,7 +84,7 @@ def __init__(self, project_name: str) -> None: #: The project name. self.project_name: Final = project_name self._name2plugin: Final[dict[str, _Plugin]] = {} - self._plugin_distinfo: Final[list[tuple[_Plugin, DistFacade]]] = [] + self._plugin_dist_metadata: Final[list[tuple[_Plugin, Distribution]]] = [] #: The "hook relay", used to call a hook on all registered plugins. #: See :ref:`calling`. self.hook: Final = HookRelay() @@ -182,6 +168,8 @@ def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None options for items decorated with :class:`HookimplMarker`. """ method: object = getattr(plugin, name) + import inspect + if not inspect.isroutine(method): return None try: @@ -347,6 +335,7 @@ def _verify_hook(self, hook: HookCaller, hookimpl: HookImpl) -> None: f"Argument(s) {notinspec} are declared in the hookimpl but " "can not be found in the hookspec", ) + import inspect if hook.spec.warn_on_impl_args: for hookimpl_argname in hookimpl.argnames: @@ -389,7 +378,11 @@ def check_pending(self) -> None: ) def load_setuptools_entrypoints(self, group: str, name: str | None = None) -> int: - """Load modules from querying the specified setuptools ``group``. + """legacy alias for load_importlib_entrypoints""" + return self.load_importlib_entrypoints(group, name) + + def load_importlib_entrypoints(self, group: str, name: str | None = None) -> int: + """Load modules for the given importlib_metadata entrypoint ``group``. :param group: Entry point group to load plugins. @@ -399,29 +392,25 @@ def load_setuptools_entrypoints(self, group: str, name: str | None = None) -> in :return: The number of plugins loaded by this call. """ - import importlib.metadata - - count = 0 - for dist in list(importlib.metadata.distributions()): - for ep in dist.entry_points: - if ( - ep.group != group - or (name is not None and ep.name != name) - # already registered - or self.get_plugin(ep.name) - or self.is_blocked(ep.name) - ): - continue - plugin = ep.load() - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((plugin, DistFacade(dist))) - count += 1 - return count + from ._importlib_metadata import load_importlib_entrypoints + + return load_importlib_entrypoints(self, group, name) def list_plugin_distinfo(self) -> list[tuple[_Plugin, DistFacade]]: """Return a list of (plugin, distinfo) pairs for all - setuptools-registered plugins.""" - return list(self._plugin_distinfo) + setuptools-registered plugins. + + (soft deprecated) + """ + from ._importlib_metadata import DistFacade + + return [ + (plugin, DistFacade(metadata)) + for plugin, metadata in self._plugin_dist_metadata + ] + + def list_plugin_distributions(self) -> list[tuple[_Plugin, Distribution]]: + return self._plugin_dist_metadata[:] def list_name_plugin(self) -> list[tuple[str, _Plugin]]: """Return a list of (name, plugin) pairs for all registered plugins.""" @@ -519,4 +508,6 @@ def subset_hook_caller( def _formatdef(func: Callable[..., object]) -> str: + import inspect + return f"{func.__name__}{inspect.signature(func)}" diff --git a/src/pluggy/_result.py b/src/pluggy/_result.py index aa3912c0..ccfa796f 100644 --- a/src/pluggy/_result.py +++ b/src/pluggy/_result.py @@ -3,20 +3,30 @@ """ from __future__ import annotations - -from types import TracebackType -from typing import Callable -from typing import cast -from typing import final -from typing import Generic -from typing import Optional -from typing import Tuple -from typing import Type -from typing import TypeVar - - -_ExcInfo = Tuple[Type[BaseException], BaseException, Optional[TracebackType]] -ResultType = TypeVar("ResultType") +TYPE_CHECKING = False +if TYPE_CHECKING: + from types import TracebackType + from typing import Callable + from typing import cast + from typing import final + from typing import Generic + + from typing import Optional + from typing import Tuple + from typing import Type + from typing import TypeVar + + + _ExcInfo = Tuple[Type[BaseException], BaseException, Optional[TracebackType]] + ResultType = TypeVar("ResultType") +else: + from ._hooks import final + + class Generic: + """fake generic""" + def __class_getitem__(cls, key)-> type[object]: + return object + ResultType = "ResultType" class HookCallError(Exception): diff --git a/src/pluggy/_tracing.py b/src/pluggy/_tracing.py index cd238ad7..de8ee176 100644 --- a/src/pluggy/_tracing.py +++ b/src/pluggy/_tracing.py @@ -4,14 +4,13 @@ from __future__ import annotations -from typing import Any -from typing import Callable -from typing import Sequence -from typing import Tuple +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Sequence, TypeAlias, Callable, Any, Tuple -_Writer = Callable[[str], object] -_Processor = Callable[[Tuple[str, ...], Tuple[Any, ...]], object] +_Writer: TypeAlias = "Callable[[str], object]" +_Processor: TypeAlias = "Callable[[Tuple[str, ...], Tuple[Any, ...]], object]" class TagTracer: From 987ecfd17e1a9a5da514177d60bf97e1ff0222c0 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 20 Sep 2023 09:31:18 +0200 Subject: [PATCH 2/5] fix casts --- src/pluggy/_callers.py | 8 ++++++-- src/pluggy/_hooks.py | 9 +++------ src/pluggy/_result.py | 11 ++++++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/pluggy/_callers.py b/src/pluggy/_callers.py index ab8db678..cfeb8f62 100644 --- a/src/pluggy/_callers.py +++ b/src/pluggy/_callers.py @@ -28,6 +28,10 @@ Tuple[Generator[None, Result[object], None], HookImpl], Generator[None, object, object], ] +else: + + def cast(t, v): + return v from ._hooks import HookImpl from ._result import HookCallError @@ -94,7 +98,7 @@ def _multicall( # If this cast is not valid, a type error is raised below, # which is the desired response. res = hook_impl.function(*args) - wrapper_gen = cast(Generator[None, Result[object], None], res) + wrapper_gen = cast("Generator[None, Result[object], None]", res) next(wrapper_gen) # first yield teardowns.append((wrapper_gen, hook_impl)) except StopIteration: @@ -104,7 +108,7 @@ def _multicall( # If this cast is not valid, a type error is raised below, # which is the desired response. res = hook_impl.function(*args) - function_gen = cast(Generator[None, object, object], res) + function_gen = cast("Generator[None, object, object]", res) next(function_gen) # first yield teardowns.append(function_gen) except StopIteration: diff --git a/src/pluggy/_hooks.py b/src/pluggy/_hooks.py index 72c7cbbd..a33be864 100644 --- a/src/pluggy/_hooks.py +++ b/src/pluggy/_hooks.py @@ -31,7 +31,6 @@ from ._result import Result - _T = TypeVar("_T") _F = TypeVar("_F", bound=Callable[..., object]) _Namespace = Union[ModuleType, type] @@ -42,7 +41,6 @@ _HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]] _CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]] - class HookspecOpts(TypedDict): """Options for a hook specification.""" @@ -58,7 +56,6 @@ class HookspecOpts(TypedDict): #: .. versionadded:: 1.5 warn_on_impl_args: Mapping[str, Warning] | None - class HookimplOpts(TypedDict): """Options for a hook implementation.""" @@ -80,11 +77,11 @@ class HookimplOpts(TypedDict): specname: str | None else: + def final(func: _F) -> _F: return func - overload = final - + overload = final @final @@ -384,12 +381,12 @@ def __init__(self) -> None: def __getattr__(self, name: str) -> HookCaller: ... + _CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]] # Historical name (pluggy<=1.2), kept for backward compatibility. _HookRelay = HookRelay -_CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]] class HookCaller: diff --git a/src/pluggy/_result.py b/src/pluggy/_result.py index ccfa796f..f2f45f59 100644 --- a/src/pluggy/_result.py +++ b/src/pluggy/_result.py @@ -3,6 +3,7 @@ """ from __future__ import annotations + TYPE_CHECKING = False if TYPE_CHECKING: from types import TracebackType @@ -16,16 +17,20 @@ from typing import Type from typing import TypeVar - _ExcInfo = Tuple[Type[BaseException], BaseException, Optional[TracebackType]] ResultType = TypeVar("ResultType") else: from ._hooks import final + def cast(v, t): + return t + class Generic: """fake generic""" - def __class_getitem__(cls, key)-> type[object]: + + def __class_getitem__(cls, key) -> type[object]: return object + ResultType = "ResultType" @@ -110,7 +115,7 @@ def get_result(self) -> ResultType: exc = self._exception tb = self._traceback if exc is None: - return cast(ResultType, self._result) + return cast("ResultType", self._result) else: raise exc.with_traceback(tb) From caeefbd8c2142e38c6d646b481d98f1d0cf19647 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 1 Sep 2024 17:36:01 +0200 Subject: [PATCH 3/5] WIP: fix rebase artifacts --- src/pluggy/_callers.py | 15 ++++----------- src/pluggy/_hooks.py | 4 ++-- src/pluggy/_importlib_metadata.py | 1 + src/pluggy/_manager.py | 13 ++++++------- src/pluggy/_result.py | 4 ++-- src/pluggy/_tracing.py | 7 ++++++- 6 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/pluggy/_callers.py b/src/pluggy/_callers.py index cfeb8f62..7759f1fa 100644 --- a/src/pluggy/_callers.py +++ b/src/pluggy/_callers.py @@ -7,9 +7,10 @@ import warnings from ._hooks import HookImpl -from ._result import _raise_wrapfail from ._result import HookCallError from ._result import Result +from ._warnings import PluggyTeardownRaisedWarning + TYPE_CHECKING = False if TYPE_CHECKING: @@ -21,25 +22,17 @@ from typing import Tuple from typing import Union - # Need to distinguish between old- and new-style hook wrappers. # Wrapping with a tuple is the fastest type-safe way I found to do it. Teardown = Union[ Tuple[Generator[None, Result[object], None], HookImpl], - Generator[None, object, object], -] + Generator[None, object, object], + ] else: def cast(t, v): return v -from ._hooks import HookImpl -from ._result import HookCallError -from ._result import Result -from ._warnings import PluggyTeardownRaisedWarning - - - def _raise_wrapfail( wrap_controller: ( diff --git a/src/pluggy/_hooks.py b/src/pluggy/_hooks.py index a33be864..c1a6f9c7 100644 --- a/src/pluggy/_hooks.py +++ b/src/pluggy/_hooks.py @@ -7,6 +7,7 @@ import sys import warnings + _Plugin = object TYPE_CHECKING = False @@ -383,12 +384,11 @@ def __getattr__(self, name: str) -> HookCaller: ... _CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]] + # Historical name (pluggy<=1.2), kept for backward compatibility. _HookRelay = HookRelay - - class HookCaller: """A caller of all registered implementations of a hook specification.""" diff --git a/src/pluggy/_importlib_metadata.py b/src/pluggy/_importlib_metadata.py index 3b831f7a..c3b27a33 100644 --- a/src/pluggy/_importlib_metadata.py +++ b/src/pluggy/_importlib_metadata.py @@ -9,6 +9,7 @@ """ + from __future__ import annotations import importlib.metadata diff --git a/src/pluggy/_manager.py b/src/pluggy/_manager.py index 01f3f619..87259451 100644 --- a/src/pluggy/_manager.py +++ b/src/pluggy/_manager.py @@ -16,12 +16,7 @@ TYPE_CHECKING = False if TYPE_CHECKING: - from ._hooks import HookimplOpts - from ._hooks import HookspecOpts - - from ._hooks import _HookImplFunction - from ._hooks import _Namespace - + from importlib.metadata import Distribution from typing import Any from typing import Callable from typing import Final @@ -29,7 +24,11 @@ from typing import Mapping from typing import Sequence from typing import TypeAlias - from importlib.metadata import Distribution + + from ._hooks import _HookImplFunction + from ._hooks import _Namespace + from ._hooks import HookimplOpts + from ._hooks import HookspecOpts from ._importlib_metadata import DistFacade _BeforeTrace: TypeAlias = "Callable[[str, Sequence[HookImpl], Mapping[str, Any]], None]" diff --git a/src/pluggy/_result.py b/src/pluggy/_result.py index f2f45f59..d9e050f4 100644 --- a/src/pluggy/_result.py +++ b/src/pluggy/_result.py @@ -4,14 +4,14 @@ from __future__ import annotations + TYPE_CHECKING = False if TYPE_CHECKING: from types import TracebackType from typing import Callable from typing import cast from typing import final - from typing import Generic - + from typing import Generic from typing import Optional from typing import Tuple from typing import Type diff --git a/src/pluggy/_tracing.py b/src/pluggy/_tracing.py index de8ee176..7cddc35c 100644 --- a/src/pluggy/_tracing.py +++ b/src/pluggy/_tracing.py @@ -4,9 +4,14 @@ from __future__ import annotations + TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Sequence, TypeAlias, Callable, Any, Tuple + from typing import Any + from typing import Callable + from typing import Sequence + from typing import Tuple + from typing import TypeAlias _Writer: TypeAlias = "Callable[[str], object]" From a3618410a1408b363f415628b8dcf605f942352c Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 1 Sep 2024 17:53:55 +0200 Subject: [PATCH 4/5] WIP: get pytest importable again --- src/pluggy/__init__.py | 16 ++++++++++++++-- src/pluggy/_hooks.py | 40 ++------------------------------------- src/pluggy/_manager.py | 4 ++-- src/pluggy/_types.py | 43 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 42 deletions(-) create mode 100644 src/pluggy/_types.py diff --git a/src/pluggy/__init__.py b/src/pluggy/__init__.py index 36ce1680..8aacb0bc 100644 --- a/src/pluggy/__init__.py +++ b/src/pluggy/__init__.py @@ -25,13 +25,25 @@ 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 ._manager import PluginManager from ._manager import PluginValidationError from ._result import HookCallError from ._result import Result from ._warnings import PluggyTeardownRaisedWarning from ._warnings import PluggyWarning + + +TYPE_CHECKING = False +if TYPE_CHECKING: + from ._types import HookimplOpts + from ._types import HookspecOpts +else: + + def __getattr__(name: str) -> object: + if name.endswith("Opts"): + from . import _types + + return getattr(_types, name) + raise AttributeError(name) diff --git a/src/pluggy/_hooks.py b/src/pluggy/_hooks.py index c1a6f9c7..bb442ec5 100644 --- a/src/pluggy/_hooks.py +++ b/src/pluggy/_hooks.py @@ -26,7 +26,6 @@ from typing import Sequence from typing import Tuple from typing import TYPE_CHECKING - from typing import TypedDict from typing import TypeVar from typing import Union @@ -42,41 +41,8 @@ _HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]] _CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]] - class HookspecOpts(TypedDict): - """Options for a hook specification.""" - - #: Whether the hook is :ref:`first result only `. - firstresult: bool - #: Whether the hook is :ref:`historic `. - historic: bool - #: Whether the hook :ref:`warns when implemented `. - warn_on_impl: Warning | None - #: Whether the hook warns when :ref:`certain arguments are requested - #: `. - #: - #: .. versionadded:: 1.5 - warn_on_impl_args: Mapping[str, Warning] | None - - class HookimplOpts(TypedDict): - """Options for a hook implementation.""" - - #: Whether the hook implementation is a :ref:`wrapper `. - wrapper: bool - #: Whether the hook implementation is an :ref:`old-style wrapper - #: `. - hookwrapper: bool - #: Whether validation against a hook specification is :ref:`optional - #: `. - optionalhook: bool - #: Whether to try to order this hook implementation :ref:`first - #: `. - tryfirst: bool - #: Whether to try to order this hook implementation :ref:`last - #: `. - trylast: bool - #: The name of the hook specification to match, see :ref:`specname`. - specname: str | None - + from ._types import HookimplOpts + from ._types import HookspecOpts else: def final(func: _F) -> _F: @@ -382,8 +348,6 @@ def __init__(self) -> None: def __getattr__(self, name: str) -> HookCaller: ... - _CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]] - # Historical name (pluggy<=1.2), kept for backward compatibility. _HookRelay = HookRelay diff --git a/src/pluggy/_manager.py b/src/pluggy/_manager.py index 87259451..10c16ef6 100644 --- a/src/pluggy/_manager.py +++ b/src/pluggy/_manager.py @@ -27,9 +27,9 @@ from ._hooks import _HookImplFunction from ._hooks import _Namespace - from ._hooks import HookimplOpts - from ._hooks import HookspecOpts from ._importlib_metadata import DistFacade + from ._types import HookimplOpts + from ._types import HookspecOpts _BeforeTrace: TypeAlias = "Callable[[str, Sequence[HookImpl], Mapping[str, Any]], None]" _AfterTrace: TypeAlias = ( diff --git a/src/pluggy/_types.py b/src/pluggy/_types.py new file mode 100644 index 00000000..1e978f8e --- /dev/null +++ b/src/pluggy/_types.py @@ -0,0 +1,43 @@ +from typing import Mapping +from typing import TypedDict +import warnings + + +warnings.warn(ImportWarning(f"{__name__} imported outside of type checking")) + + +class HookspecOpts(TypedDict): + """Options for a hook specification.""" + + #: Whether the hook is :ref:`first result only `. + firstresult: bool + #: Whether the hook is :ref:`historic `. + historic: bool + #: Whether the hook :ref:`warns when implemented `. + warn_on_impl: Warning | None + #: Whether the hook warns when :ref:`certain arguments are requested + #: `. + #: + #: .. versionadded:: 1.5 + warn_on_impl_args: Mapping[str, Warning] | None + + +class HookimplOpts(TypedDict): + """Options for a hook implementation.""" + + #: Whether the hook implementation is a :ref:`wrapper `. + wrapper: bool + #: Whether the hook implementation is an :ref:`old-style wrapper + #: `. + hookwrapper: bool + #: Whether validation against a hook specification is :ref:`optional + #: `. + optionalhook: bool + #: Whether to try to order this hook implementation :ref:`first + #: `. + tryfirst: bool + #: Whether to try to order this hook implementation :ref:`last + #: `. + trylast: bool + #: The name of the hook specification to match, see :ref:`specname`. + specname: str | None From 98ccb709848a19345adcf8d087ac8a6c69fc6fe1 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 1 Sep 2024 17:57:49 +0200 Subject: [PATCH 5/5] fixup: future annotations --- src/pluggy/_types.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pluggy/_types.py b/src/pluggy/_types.py index 1e978f8e..41fd914f 100644 --- a/src/pluggy/_types.py +++ b/src/pluggy/_types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Mapping from typing import TypedDict import warnings