Skip to content

Commit a3d2066

Browse files
WIP faster baseline import
still breaking this change youelds a speedup form 0.69s to 0.004s for pluggy imports, by deferring imports of the stdlib
1 parent 8dadaf6 commit a3d2066

File tree

8 files changed

+238
-163
lines changed

8 files changed

+238
-163
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
repos:
2-
- repo: https://github.com/PyCQA/autoflake
3-
rev: v2.2.1
4-
hooks:
5-
- id: autoflake
6-
name: autoflake
7-
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
8-
language: python
9-
files: \.py$
102
- repo: https://github.com/asottile/reorder-python-imports
113
rev: v3.11.0
124
hooks:

src/pluggy/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,5 @@
2727
HookimplMarker,
2828
HookCaller,
2929
HookRelay,
30-
HookspecOpts,
31-
HookimplOpts,
3230
HookImpl,
3331
)

src/pluggy/_callers.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,25 @@
22
Call loop machinery
33
"""
44
from __future__ import annotations
5-
6-
from typing import cast
7-
from typing import Generator
8-
from typing import Mapping
9-
from typing import Sequence
10-
from typing import Tuple
11-
from typing import Union
12-
135
from ._hooks import HookImpl
146
from ._result import _raise_wrapfail
157
from ._result import HookCallError
168
from ._result import Result
9+
TYPE_CHECKING = False
10+
if TYPE_CHECKING:
11+
from typing import cast
12+
from typing import Generator
13+
from typing import Mapping
14+
from typing import Sequence
15+
from typing import Tuple
16+
from typing import Union
1717

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

2625

2726
def _multicall(

src/pluggy/_hooks.py

Lines changed: 75 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,71 +3,82 @@
33
"""
44
from __future__ import annotations
55

6-
import inspect
76
import sys
87
import warnings
9-
from types import ModuleType
10-
from typing import AbstractSet
11-
from typing import Any
12-
from typing import Callable
13-
from typing import Final
14-
from typing import final
15-
from typing import Generator
16-
from typing import List
17-
from typing import Mapping
18-
from typing import Optional
19-
from typing import overload
20-
from typing import Sequence
21-
from typing import Tuple
22-
from typing import TYPE_CHECKING
23-
from typing import TypedDict
24-
from typing import TypeVar
25-
from typing import Union
26-
27-
from ._result import Result
28-
29-
30-
_T = TypeVar("_T")
31-
_F = TypeVar("_F", bound=Callable[..., object])
32-
_Namespace = Union[ModuleType, type]
8+
339
_Plugin = object
34-
_HookExec = Callable[
35-
[str, Sequence["HookImpl"], Mapping[str, object], bool],
36-
Union[object, List[object]],
37-
]
38-
_HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]]
39-
40-
41-
class HookspecOpts(TypedDict):
42-
"""Options for a hook specification."""
43-
44-
#: Whether the hook is :ref:`first result only <firstresult>`.
45-
firstresult: bool
46-
#: Whether the hook is :ref:`historic <historic>`.
47-
historic: bool
48-
#: Whether the hook :ref:`warns when implemented <warn_on_impl>`.
49-
warn_on_impl: Warning | None
50-
51-
52-
class HookimplOpts(TypedDict):
53-
"""Options for a hook implementation."""
54-
55-
#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
56-
wrapper: bool
57-
#: Whether the hook implementation is an :ref:`old-style wrapper
58-
#: <old_style_hookwrappers>`.
59-
hookwrapper: bool
60-
#: Whether validation against a hook specification is :ref:`optional
61-
#: <optionalhook>`.
62-
optionalhook: bool
63-
#: Whether to try to order this hook implementation :ref:`first
64-
#: <callorder>`.
65-
tryfirst: bool
66-
#: Whether to try to order this hook implementation :ref:`last
67-
#: <callorder>`.
68-
trylast: bool
69-
#: The name of the hook specification to match, see :ref:`specname`.
70-
specname: str | None
10+
11+
TYPE_CHECKING = False
12+
if TYPE_CHECKING:
13+
from types import ModuleType
14+
from typing import AbstractSet
15+
from typing import Any
16+
from typing import Callable
17+
from typing import Final
18+
from typing import final
19+
from typing import Generator
20+
from typing import List
21+
from typing import Mapping
22+
from typing import Optional
23+
from typing import overload
24+
from typing import Sequence
25+
from typing import Tuple
26+
from typing import TYPE_CHECKING
27+
from typing import TypedDict
28+
from typing import TypeVar
29+
from typing import Union
30+
31+
from ._result import Result
32+
33+
34+
_T = TypeVar("_T")
35+
_F = TypeVar("_F", bound=Callable[..., object])
36+
_Namespace = Union[ModuleType, type]
37+
_HookExec = Callable[
38+
[str, Sequence["HookImpl"], Mapping[str, object], bool],
39+
Union[object, List[object]],
40+
]
41+
_HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]]
42+
_CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]]
43+
44+
45+
class HookspecOpts(TypedDict):
46+
"""Options for a hook specification."""
47+
48+
#: Whether the hook is :ref:`first result only <firstresult>`.
49+
firstresult: bool
50+
#: Whether the hook is :ref:`historic <historic>`.
51+
historic: bool
52+
#: Whether the hook :ref:`warns when implemented <warn_on_impl>`.
53+
warn_on_impl: Warning | None
54+
55+
56+
class HookimplOpts(TypedDict):
57+
"""Options for a hook implementation."""
58+
59+
#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
60+
wrapper: bool
61+
#: Whether the hook implementation is an :ref:`old-style wrapper
62+
#: <old_style_hookwrappers>`.
63+
hookwrapper: bool
64+
#: Whether validation against a hook specification is :ref:`optional
65+
#: <optionalhook>`.
66+
optionalhook: bool
67+
#: Whether to try to order this hook implementation :ref:`first
68+
#: <callorder>`.
69+
tryfirst: bool
70+
#: Whether to try to order this hook implementation :ref:`last
71+
#: <callorder>`.
72+
trylast: bool
73+
#: The name of the hook specification to match, see :ref:`specname`.
74+
specname: str | None
75+
76+
else:
77+
def final(func: _F) -> _F:
78+
return func
79+
overload = final
80+
81+
7182

7283

7384
@final
@@ -286,6 +297,8 @@ def varnames(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]:
286297
In case of a class, its ``__init__`` method is considered.
287298
For methods the ``self`` parameter is not included.
288299
"""
300+
import inspect
301+
289302
if inspect.isclass(func):
290303
try:
291304
func = func.__init__
@@ -364,7 +377,6 @@ def __getattr__(self, name: str) -> HookCaller:
364377
_HookRelay = HookRelay
365378

366379

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

369381

370382
class HookCaller:

src/pluggy/_importlib_metadata.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""this module contains importlib_metadata usage and importing
2+
3+
it's deferred to avoid import-time dependency on importlib_metadata
4+
5+
.. code-block:: console
6+
7+
python -X importtime -c 'import pluggy' 2> import0.log
8+
tuna import0.log
9+
10+
11+
"""
12+
from __future__ import annotations
13+
14+
import importlib.metadata
15+
from typing import Any
16+
17+
from . import _manager
18+
19+
20+
class DistFacade:
21+
"""Emulate a pkg_resources Distribution"""
22+
23+
def __init__(self, dist: importlib.metadata.Distribution) -> None:
24+
self._dist = dist
25+
26+
@property
27+
def project_name(self) -> str:
28+
name: str = self.metadata["name"]
29+
return name
30+
31+
def __getattr__(self, attr: str, default: object | None = None) -> Any:
32+
return getattr(self._dist, attr, default)
33+
34+
def __dir__(self) -> list[str]:
35+
return sorted(dir(self._dist) + ["_dist", "project_name"])
36+
37+
38+
def load_importlib_entrypoints(
39+
manager: _manager.PluginManager,
40+
group: str,
41+
name: str | None = None,
42+
) -> int:
43+
"""Load modules from querying the specified setuptools ``group``.
44+
45+
:param group:
46+
Entry point group to load plugins.
47+
:param name:
48+
If given, loads only plugins with the given ``name``.
49+
50+
:return:
51+
The number of plugins loaded by this call.
52+
"""
53+
count = 0
54+
for dist in list(importlib.metadata.distributions()):
55+
for ep in dist.entry_points:
56+
if (
57+
ep.group != group
58+
or (name is not None and ep.name != name)
59+
# already registered
60+
or manager.get_plugin(ep.name)
61+
or manager.is_blocked(ep.name)
62+
):
63+
continue
64+
plugin = ep.load()
65+
manager.register(plugin, name=ep.name)
66+
manager._plugin_dist_metadata.append((plugin, dist))
67+
count += 1
68+
return count

0 commit comments

Comments
 (0)