Skip to content

Commit 1dfc4ca

Browse files
Refactor legacy setuptools compatibility into _compat module
- Create src/pluggy/_compat.py for legacy pkg_resources compatibility - Move DistFacade from _manager.py to _compat.py - Update PluginManager to use stdlib Distribution internally - Add new list_plugin_distributions() method returning unwrapped Distribution objects - Update list_plugin_distinfo() to wrap with DistFacade for backward compatibility - Update test import to use _compat module This separates legacy setuptools API emulation from modern importlib.metadata usage, while maintaining full backward compatibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 78fe2e9 commit 1dfc4ca

File tree

3 files changed

+71
-23
lines changed

3 files changed

+71
-23
lines changed

src/pluggy/_compat.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Compatibility layer for legacy setuptools/pkg_resources API.
3+
4+
This module provides backward compatibility wrappers around modern
5+
importlib.metadata, allowing gradual migration away from setuptools.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import importlib.metadata
11+
from typing import Any
12+
13+
14+
class DistFacade:
15+
"""Facade providing pkg_resources.Distribution-like interface.
16+
17+
This class wraps importlib.metadata.Distribution to provide a
18+
compatibility layer for code expecting the legacy pkg_resources API.
19+
The primary difference is the ``project_name`` attribute which
20+
pkg_resources provided but importlib.metadata.Distribution does not.
21+
"""
22+
23+
__slots__ = ("_dist",)
24+
25+
def __init__(self, dist: importlib.metadata.Distribution) -> None:
26+
self._dist = dist
27+
28+
@property
29+
def project_name(self) -> str:
30+
"""Get the project name (for pkg_resources compatibility).
31+
32+
This is equivalent to dist.metadata["name"] but matches the
33+
pkg_resources.Distribution.project_name attribute.
34+
"""
35+
name: str = self.metadata["name"]
36+
return name
37+
38+
def __getattr__(self, attr: str) -> Any:
39+
"""Delegate all other attributes to the wrapped Distribution."""
40+
return getattr(self._dist, attr)
41+
42+
def __dir__(self) -> list[str]:
43+
"""List available attributes including facade additions."""
44+
return sorted(dir(self._dist) + ["_dist", "project_name"])

src/pluggy/_manager.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from . import _tracing
1717
from ._callers import _multicall
18+
from ._compat import DistFacade
1819
from ._hooks import _HookImplFunction
1920
from ._hooks import _Namespace
2021
from ._hooks import _Plugin
@@ -29,7 +30,6 @@
2930

3031

3132
if TYPE_CHECKING:
32-
# importtlib.metadata import is slow, defer it.
3333
import importlib.metadata
3434

3535

@@ -62,24 +62,6 @@ def __init__(self, plugin: _Plugin, message: str) -> None:
6262
self.plugin = plugin
6363

6464

65-
class DistFacade:
66-
"""Emulate a pkg_resources Distribution"""
67-
68-
def __init__(self, dist: importlib.metadata.Distribution) -> None:
69-
self._dist = dist
70-
71-
@property
72-
def project_name(self) -> str:
73-
name: str = self.metadata["name"]
74-
return name
75-
76-
def __getattr__(self, attr: str, default: Any | None = None) -> Any:
77-
return getattr(self._dist, attr, default)
78-
79-
def __dir__(self) -> list[str]:
80-
return sorted(dir(self._dist) + ["_dist", "project_name"])
81-
82-
8365
class PluginManager:
8466
"""Core class which manages registration of plugin objects and 1:N hook
8567
calling.
@@ -101,7 +83,9 @@ def __init__(self, project_name: str) -> None:
10183
#: The project name.
10284
self.project_name: Final = project_name
10385
self._name2plugin: Final[dict[str, _Plugin]] = {}
104-
self._plugin_distinfo: Final[list[tuple[_Plugin, DistFacade]]] = []
86+
self._plugin_distinfo: Final[
87+
list[tuple[_Plugin, importlib.metadata.Distribution]]
88+
] = []
10589
#: The "hook relay", used to call a hook on all registered plugins.
10690
#: See :ref:`calling`.
10791
self.hook: Final = HookRelay()
@@ -418,13 +402,33 @@ def load_setuptools_entrypoints(self, group: str, name: str | None = None) -> in
418402
continue
419403
plugin = ep.load()
420404
self.register(plugin, name=ep.name)
421-
self._plugin_distinfo.append((plugin, DistFacade(dist)))
405+
self._plugin_distinfo.append((plugin, dist))
422406
count += 1
423407
return count
424408

425409
def list_plugin_distinfo(self) -> list[tuple[_Plugin, DistFacade]]:
426410
"""Return a list of (plugin, distinfo) pairs for all
427-
setuptools-registered plugins."""
411+
setuptools-registered plugins.
412+
413+
.. note::
414+
The distinfo objects are wrapped with :class:`~pluggy._compat.DistFacade`
415+
for backward compatibility with the legacy pkg_resources API.
416+
Use the modern :meth:`list_plugin_distributions` method to get
417+
unwrapped :class:`importlib.metadata.Distribution` objects.
418+
"""
419+
return [(plugin, DistFacade(dist)) for plugin, dist in self._plugin_distinfo]
420+
421+
def list_plugin_distributions(
422+
self,
423+
) -> list[tuple[_Plugin, importlib.metadata.Distribution]]:
424+
"""Return a list of (plugin, distribution) pairs for all plugins
425+
loaded via entry points.
426+
427+
Returns modern :class:`importlib.metadata.Distribution` objects without
428+
the legacy pkg_resources compatibility layer.
429+
430+
.. versionadded:: 1.6
431+
"""
428432
return list(self._plugin_distinfo)
429433

430434
def list_name_plugin(self) -> list[tuple[str, _Plugin]]:

testing/test_details.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def myhook(self):
197197

198198

199199
def test_dist_facade_list_attributes() -> None:
200-
from pluggy._manager import DistFacade
200+
from pluggy._compat import DistFacade
201201

202202
fc = DistFacade(distribution("pluggy"))
203203
res = dir(fc)

0 commit comments

Comments
 (0)