Skip to content

Commit aee2b85

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 f68db34 commit aee2b85

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
@@ -14,6 +14,7 @@
1414

1515
from . import _tracing
1616
from ._callers import _multicall
17+
from ._compat import DistFacade
1718
from ._hooks import _HookImplFunction
1819
from ._hooks import _Namespace
1920
from ._hooks import _Plugin
@@ -28,7 +29,6 @@
2829

2930

3031
if TYPE_CHECKING:
31-
# importtlib.metadata import is slow, defer it.
3232
import importlib.metadata
3333

3434

@@ -59,24 +59,6 @@ def __init__(self, plugin: _Plugin, message: str) -> None:
5959
self.plugin = plugin
6060

6161

62-
class DistFacade:
63-
"""Emulate a pkg_resources Distribution"""
64-
65-
def __init__(self, dist: importlib.metadata.Distribution) -> None:
66-
self._dist = dist
67-
68-
@property
69-
def project_name(self) -> str:
70-
name: str = self.metadata["name"]
71-
return name
72-
73-
def __getattr__(self, attr: str, default: Any | None = None) -> Any:
74-
return getattr(self._dist, attr, default)
75-
76-
def __dir__(self) -> list[str]:
77-
return sorted(dir(self._dist) + ["_dist", "project_name"])
78-
79-
8062
class PluginManager:
8163
"""Core class which manages registration of plugin objects and 1:N hook
8264
calling.
@@ -98,7 +80,9 @@ def __init__(self, project_name: str) -> None:
9880
#: The project name.
9981
self.project_name: Final = project_name
10082
self._name2plugin: Final[dict[str, _Plugin]] = {}
101-
self._plugin_distinfo: Final[list[tuple[_Plugin, DistFacade]]] = []
83+
self._plugin_distinfo: Final[
84+
list[tuple[_Plugin, importlib.metadata.Distribution]]
85+
] = []
10286
#: The "hook relay", used to call a hook on all registered plugins.
10387
#: See :ref:`calling`.
10488
self.hook: Final = HookRelay()
@@ -415,13 +399,33 @@ def load_setuptools_entrypoints(self, group: str, name: str | None = None) -> in
415399
continue
416400
plugin = ep.load()
417401
self.register(plugin, name=ep.name)
418-
self._plugin_distinfo.append((plugin, DistFacade(dist)))
402+
self._plugin_distinfo.append((plugin, dist))
419403
count += 1
420404
return count
421405

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

427431
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)