Skip to content

Commit 136e7e2

Browse files
committed
migrate entrypoints handling to standard library importlib.metadata
We wrap a couple different versions of the stdlib interface, and fall back to pkg_resources for compatibility with existing use cases on very old versions of python. For python 3.8 and on, we make sure to completely avoid external dependencies, even backports. Bug: PyFilesystem#577
1 parent 8ed9dc4 commit 136e7e2

File tree

2 files changed

+87
-25
lines changed

2 files changed

+87
-25
lines changed

fs/opener/registry.py

+29-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import collections
1010
import contextlib
11-
import pkg_resources
11+
import sys
1212

1313
from ..errors import ResourceReadOnly
1414
from .base import Opener
@@ -21,6 +21,30 @@
2121
from ..base import FS
2222

2323

24+
if sys.version_info >= (3, 8):
25+
import importlib.metadata
26+
27+
if sys.version_info >= (3, 10):
28+
29+
def entrypoints(group, name=None):
30+
ep = importlib.metadata.entry_points(group=group, name=name)
31+
return tuple(n for n in ep)
32+
33+
else:
34+
35+
def entrypoints(group, name=None):
36+
ep = importlib.metadata.entry_points()
37+
if name:
38+
return tuple(n for n in ep.get(group, ()) if n.name == name)
39+
return ep.get(group, ())
40+
41+
else:
42+
import pkg_resources
43+
44+
def entrypoints(group, name=None):
45+
return tuple(pkg_resources.iter_entry_points(group, name))
46+
47+
2448
class Registry(object):
2549
"""A registry for `Opener` instances."""
2650

@@ -74,10 +98,7 @@ def protocols(self):
7498
"""`list`: the list of supported protocols."""
7599
_protocols = list(self._protocols)
76100
if self.load_extern:
77-
_protocols.extend(
78-
entry_point.name
79-
for entry_point in pkg_resources.iter_entry_points("fs.opener")
80-
)
101+
_protocols.extend(n.name for n in entrypoints("fs.opener"))
81102
_protocols = list(collections.OrderedDict.fromkeys(_protocols))
82103
return _protocols
83104

@@ -101,10 +122,9 @@ def get_opener(self, protocol):
101122
"""
102123
protocol = protocol or self.default_opener
103124

104-
if self.load_extern:
105-
entry_point = next(
106-
pkg_resources.iter_entry_points("fs.opener", protocol), None
107-
)
125+
ep = entrypoints("fs.opener", protocol)
126+
if self.load_extern and ep:
127+
entry_point = ep[0]
108128
else:
109129
entry_point = None
110130

tests/test_opener.py

+58-16
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import sys
44

55
import os
6-
import pkg_resources
76
import shutil
87
import tempfile
98
import unittest
@@ -21,6 +20,11 @@
2120
except ImportError:
2221
import mock
2322

23+
if sys.version_info >= (3, 8):
24+
import importlib.metadata
25+
else:
26+
import pkg_resources
27+
2428

2529
class TestParse(unittest.TestCase):
2630
def test_registry_repr(self):
@@ -111,14 +115,25 @@ def test_protocols(self):
111115

112116
def test_registry_protocols(self):
113117
# Check registry.protocols list the names of all available extension
114-
extensions = [
115-
pkg_resources.EntryPoint("proto1", "mod1"),
116-
pkg_resources.EntryPoint("proto2", "mod2"),
117-
]
118-
m = mock.MagicMock(return_value=extensions)
119-
with mock.patch.object(
120-
sys.modules["pkg_resources"], "iter_entry_points", new=m
121-
):
118+
if sys.version_info >= (3, 8):
119+
extensions = (
120+
importlib.metadata.EntryPoint("proto1", "mod1", "fs.opener"),
121+
importlib.metadata.EntryPoint("proto2", "mod2", "fs.opener"),
122+
)
123+
if sys.version_info >= (3, 10):
124+
m = mock.MagicMock(return_value=extensions)
125+
else:
126+
m = mock.MagicMock(return_value={"fs.opener": extensions})
127+
patch = mock.patch("importlib.metadata.entry_points", m)
128+
else:
129+
extensions = [
130+
pkg_resources.EntryPoint("proto1", "mod1"),
131+
pkg_resources.EntryPoint("proto2", "mod2"),
132+
]
133+
m = mock.MagicMock(return_value=extensions)
134+
patch = mock.patch("pkg_resources.iter_entry_points", m)
135+
136+
with patch:
122137
self.assertIn("proto1", opener.registry.protocols)
123138
self.assertIn("proto2", opener.registry.protocols)
124139

@@ -129,11 +144,19 @@ def test_unknown_protocol(self):
129144
def test_entry_point_load_error(self):
130145

131146
entry_point = mock.MagicMock()
147+
entry_point.name = "test"
132148
entry_point.load.side_effect = ValueError("some error")
133149

134-
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
135-
136-
with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
150+
if sys.version_info >= (3, 8):
151+
if sys.version_info >= (3, 10):
152+
entry_points = mock.MagicMock(return_value=tuple([entry_point]))
153+
else:
154+
entry_points = mock.MagicMock(return_value={"fs.opener": [entry_point]})
155+
patch = mock.patch("importlib.metadata.entry_points", entry_points)
156+
else:
157+
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
158+
patch = mock.patch("pkg_resources.iter_entry_points", iter_entry_points)
159+
with patch:
137160
with self.assertRaises(errors.EntryPointError) as ctx:
138161
opener.open_fs("test://")
139162
self.assertEqual(
@@ -145,10 +168,19 @@ class NotAnOpener(object):
145168
pass
146169

147170
entry_point = mock.MagicMock()
171+
entry_point.name = "test"
148172
entry_point.load = mock.MagicMock(return_value=NotAnOpener)
149-
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
150173

151-
with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
174+
if sys.version_info >= (3, 8):
175+
if sys.version_info >= (3, 10):
176+
entry_points = mock.MagicMock(return_value=tuple([entry_point]))
177+
else:
178+
entry_points = mock.MagicMock(return_value={"fs.opener": [entry_point]})
179+
patch = mock.patch("importlib.metadata.entry_points", entry_points)
180+
else:
181+
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
182+
patch = mock.patch("pkg_resources.iter_entry_points", iter_entry_points)
183+
with patch:
152184
with self.assertRaises(errors.EntryPointError) as ctx:
153185
opener.open_fs("test://")
154186
self.assertEqual("entry point did not return an opener", str(ctx.exception))
@@ -162,10 +194,20 @@ def open_fs(self, *args, **kwargs):
162194
pass
163195

164196
entry_point = mock.MagicMock()
197+
entry_point.name = "test"
165198
entry_point.load = mock.MagicMock(return_value=BadOpener)
166-
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
167199

168-
with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
200+
if sys.version_info >= (3, 8):
201+
if sys.version_info >= (3, 10):
202+
entry_points = mock.MagicMock(return_value=tuple([entry_point]))
203+
else:
204+
entry_points = mock.MagicMock(return_value={"fs.opener": [entry_point]})
205+
patch = mock.patch("importlib.metadata.entry_points", entry_points)
206+
else:
207+
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
208+
patch = mock.patch("pkg_resources.iter_entry_points", iter_entry_points)
209+
210+
with patch:
169211
with self.assertRaises(errors.EntryPointError) as ctx:
170212
opener.open_fs("test://")
171213
self.assertEqual(

0 commit comments

Comments
 (0)