Skip to content

Commit 41fcac4

Browse files
Drop usage of pkg_resources
1 parent 59f6e4d commit 41fcac4

File tree

5 files changed

+88
-37
lines changed

5 files changed

+88
-37
lines changed

fs/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Python filesystem abstraction layer.
22
"""
33

4-
__import__("pkg_resources").declare_namespace(__name__) # type: ignore
4+
__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore
55

66
from . import path
77
from ._fscompat import fsdecode, fsencode

fs/opener/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55
# Declare fs.opener as a namespace package
6-
__import__("pkg_resources").declare_namespace(__name__) # type: ignore
6+
__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore
77

88
# Import opener modules so that `registry.install` if called on each opener
99
from . import appfs, ftpfs, memoryfs, osfs, tarfs, tempfs, zipfs

fs/opener/registry.py

+31-7
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,44 @@
44

55
from __future__ import absolute_import, print_function, unicode_literals
66

7+
import sys
78
import typing
89

910
import collections
1011
import contextlib
11-
import pkg_resources
1212

1313
from ..errors import ResourceReadOnly
1414
from .base import Opener
1515
from .errors import EntryPointError, UnsupportedProtocol
1616
from .parse import parse_fs_url
1717

1818
if typing.TYPE_CHECKING:
19-
from typing import Callable, Dict, Iterator, List, Text, Tuple, Type, Union
19+
from typing import (
20+
Callable,
21+
Dict,
22+
Iterator,
23+
List,
24+
Sequence,
25+
Text,
26+
Tuple,
27+
Type,
28+
Union,
29+
)
2030

2131
from ..base import FS
2232

33+
if sys.version_info >= (3, 8):
34+
# The entry_points function takes keyword arguments starting with Python 3.10
35+
# https://docs.python.org/3.10/library/importlib.metadata.html#entry-points
36+
from importlib import metadata as importlib_metadata
37+
else:
38+
import importlib_metadata
39+
40+
41+
def _get_entry_points():
42+
# type: () -> Sequence[importlib_metadata.EntryPoint]
43+
return importlib_metadata.entry_points().get("fs.opener", [])
44+
2345

2446
class Registry(object):
2547
"""A registry for `Opener` instances."""
@@ -74,10 +96,7 @@ def protocols(self):
7496
"""`list`: the list of supported protocols."""
7597
_protocols = list(self._protocols)
7698
if self.load_extern:
77-
_protocols.extend(
78-
entry_point.name
79-
for entry_point in pkg_resources.iter_entry_points("fs.opener")
80-
)
99+
_protocols.extend(entry_point.name for entry_point in _get_entry_points())
81100
_protocols = list(collections.OrderedDict.fromkeys(_protocols))
82101
return _protocols
83102

@@ -103,7 +122,12 @@ def get_opener(self, protocol):
103122

104123
if self.load_extern:
105124
entry_point = next(
106-
pkg_resources.iter_entry_points("fs.opener", protocol), None
125+
(
126+
entry_point
127+
for entry_point in _get_entry_points()
128+
if entry_point.name == protocol
129+
),
130+
None,
107131
)
108132
else:
109133
entry_point = None

setup.cfg

+4-3
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ install_requires =
4545
appdirs~=1.4.3
4646
setuptools
4747
six ~=1.10
48-
enum34 ~=1.1.6 ; python_version < '3.4'
49-
typing ~=3.6 ; python_version < '3.6'
50-
backports.os ~=0.1 ; python_version < '3.0'
48+
enum34 ~=1.1.6 ; python_version < '3.4'
49+
typing ~=3.6 ; python_version < '3.6'
50+
backports.os ~=0.1 ; python_version < '3.0'
51+
importlib-metadata ~=2.0 ; python_version < '3.8'
5152
5253
[options.extras_require]
5354
scandir =

tests/test_opener.py

+51-25
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
from __future__ import unicode_literals
22

3-
import sys
4-
53
import os
6-
import pkg_resources
74
import shutil
85
import tempfile
96
import unittest
@@ -13,14 +10,19 @@
1310
from fs.memoryfs import MemoryFS
1411
from fs.opener import errors, registry
1512
from fs.opener.parse import ParseResult
16-
from fs.opener.registry import Registry
13+
from fs.opener.registry import Registry, importlib_metadata
1714
from fs.osfs import OSFS
1815

1916
try:
2017
from unittest import mock
2118
except ImportError:
2219
import mock
2320

21+
try:
22+
from pytest import MonkeyPatch
23+
except ImportError:
24+
from _pytest.monkeypatch import MonkeyPatch
25+
2426

2527
class TestParse(unittest.TestCase):
2628
def test_registry_repr(self):
@@ -106,34 +108,44 @@ def test_parse_params_decode(self):
106108

107109

108110
class TestRegistry(unittest.TestCase):
111+
def setUp(self):
112+
self.monkeypatch = MonkeyPatch()
113+
109114
def test_protocols(self):
110115
self.assertIsInstance(opener.registry.protocols, list)
111116

112117
def test_registry_protocols(self):
113118
# 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-
):
119+
class EntryPoint(object):
120+
def __init__(self, name):
121+
self.name = name
122+
123+
def _my_entry_points():
124+
return {"fs.opener": [EntryPoint("proto1"), EntryPoint("proto2")]}
125+
126+
with self.monkeypatch.context() as m:
127+
m.setattr(importlib_metadata, "entry_points", _my_entry_points)
122128
self.assertIn("proto1", opener.registry.protocols)
123129
self.assertIn("proto2", opener.registry.protocols)
130+
self.assertNotIn("wheel", opener.registry.protocols)
124131

125132
def test_unknown_protocol(self):
126133
with self.assertRaises(errors.UnsupportedProtocol):
127134
opener.open_fs("unknown://")
128135

129136
def test_entry_point_load_error(self):
137+
class EntryPoint(object):
138+
def __init__(self, name):
139+
self.name = name
130140

131-
entry_point = mock.MagicMock()
132-
entry_point.load.side_effect = ValueError("some error")
141+
def load(self):
142+
raise ValueError("some error")
133143

134-
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
144+
def _my_entry_points():
145+
return {"fs.opener": [EntryPoint("test")]}
135146

136-
with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
147+
with self.monkeypatch.context() as m:
148+
m.setattr(importlib_metadata, "entry_points", _my_entry_points)
137149
with self.assertRaises(errors.EntryPointError) as ctx:
138150
opener.open_fs("test://")
139151
self.assertEqual(
@@ -144,11 +156,18 @@ def test_entry_point_type_error(self):
144156
class NotAnOpener(object):
145157
pass
146158

147-
entry_point = mock.MagicMock()
148-
entry_point.load = mock.MagicMock(return_value=NotAnOpener)
149-
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
159+
class EntryPoint(object):
160+
def __init__(self, name):
161+
self.name = name
162+
163+
def load(self):
164+
return NotAnOpener
150165

151-
with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
166+
def _my_entry_points():
167+
return {"fs.opener": [EntryPoint("test")]}
168+
169+
with self.monkeypatch.context() as m:
170+
m.setattr(importlib_metadata, "entry_points", _my_entry_points)
152171
with self.assertRaises(errors.EntryPointError) as ctx:
153172
opener.open_fs("test://")
154173
self.assertEqual("entry point did not return an opener", str(ctx.exception))
@@ -161,11 +180,18 @@ def __init__(self, *args, **kwargs):
161180
def open_fs(self, *args, **kwargs):
162181
pass
163182

164-
entry_point = mock.MagicMock()
165-
entry_point.load = mock.MagicMock(return_value=BadOpener)
166-
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
183+
class EntryPoint(object):
184+
def __init__(self, name):
185+
self.name = name
186+
187+
def load(self):
188+
return BadOpener
189+
190+
def _my_entry_points():
191+
return {"fs.opener": [EntryPoint("test")]}
167192

168-
with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
193+
with self.monkeypatch.context() as m:
194+
m.setattr(importlib_metadata, "entry_points", _my_entry_points)
169195
with self.assertRaises(errors.EntryPointError) as ctx:
170196
opener.open_fs("test://")
171197
self.assertEqual(
@@ -217,7 +243,7 @@ def tearDown(self):
217243

218244
def test_repr(self):
219245
# Check __repr__ works
220-
for entry_point in pkg_resources.iter_entry_points("fs.opener"):
246+
for entry_point in importlib_metadata.entry_points().get("fs.opener", []):
221247
_opener = entry_point.load()
222248
repr(_opener())
223249

0 commit comments

Comments
 (0)