Skip to content

Commit

Permalink
WIP faster baseline import
Browse files Browse the repository at this point in the history
still breaking

this change youelds a speedup form 0.69s to 0.004s for pluggy imports,
by deferring imports of the stdlib
  • Loading branch information
RonnyPfannschmidt committed Sep 19, 2023
1 parent 8dadaf6 commit a3d2066
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 163 deletions.
8 changes: 0 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
repos:
- repo: https://github.com/PyCQA/autoflake
rev: v2.2.1
hooks:
- id: autoflake
name: autoflake
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
language: python
files: \.py$
- repo: https://github.com/asottile/reorder-python-imports
rev: v3.11.0
hooks:
Expand Down
2 changes: 0 additions & 2 deletions src/pluggy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,5 @@
HookimplMarker,
HookCaller,
HookRelay,
HookspecOpts,
HookimplOpts,
HookImpl,
)
29 changes: 14 additions & 15 deletions src/pluggy/_callers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@
Call loop machinery
"""
from __future__ import annotations

from typing import cast
from typing import Generator
from typing import Mapping
from typing import Sequence
from typing import Tuple
from typing import Union

from ._hooks import HookImpl
from ._result import _raise_wrapfail
from ._result import HookCallError
from ._result import Result
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import cast
from typing import Generator
from typing import Mapping
from typing import Sequence
from typing import Tuple
from typing import Union


# Need to distinguish between old- and new-style hook wrappers.
# Wrapping one a singleton tuple is the fastest type-safe way I found to do it.
Teardown = Union[
Tuple[Generator[None, Result[object], None]],
Generator[None, object, object],
]
# Need to distinguish between old- and new-style hook wrappers.
# Wrapping one a singleton tuple is the fastest type-safe way I found to do it.
Teardown = Union[
Tuple[Generator[None, Result[object], None]],
Generator[None, object, object],
]


def _multicall(
Expand Down
138 changes: 75 additions & 63 deletions src/pluggy/_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,82 @@
"""
from __future__ import annotations

import inspect
import sys
import warnings
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]

_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>`.
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

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>`.
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

else:
def final(func: _F) -> _F:
return func
overload = final




@final
Expand Down Expand Up @@ -286,6 +297,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__
Expand Down Expand Up @@ -364,7 +377,6 @@ def __getattr__(self, name: str) -> HookCaller:
_HookRelay = HookRelay


_CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]]


class HookCaller:
Expand Down
68 changes: 68 additions & 0 deletions src/pluggy/_importlib_metadata.py
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit a3d2066

Please sign in to comment.