fix: fallback to importlib_metadata
when loading entry points
since `pkg_resources` is deprecated for this purpose, per https://setuptools.pypa.io/en/latest/pkg_resources.html
This commit is contained in:
parent
5802391382
commit
f654906029
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
# WuttJamaican -- Base package for Wutta Framework
|
||||||
# Copyright © 2023 Lance Edgar
|
# Copyright © 2023-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
@ -58,44 +58,27 @@ def load_entry_points(group, ignore_errors=False):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# nb. this package was added in python 3.8
|
# nb. this package was added in python 3.8
|
||||||
import importlib.metadata
|
import importlib.metadata as importlib_metadata
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# older setup, must use pkg_resources
|
import importlib_metadata
|
||||||
# TODO: remove this section once we require python 3.8
|
|
||||||
from pkg_resources import iter_entry_points
|
|
||||||
|
|
||||||
for entry_point in iter_entry_points(group):
|
|
||||||
try:
|
|
||||||
ep = entry_point.load()
|
|
||||||
except:
|
|
||||||
if not ignore_errors:
|
|
||||||
raise
|
|
||||||
log.warning("failed to load entry point: %s", entry_point,
|
|
||||||
exc_info=True)
|
|
||||||
else:
|
|
||||||
entry_points[entry_point.name] = ep
|
|
||||||
|
|
||||||
|
eps = importlib_metadata.entry_points()
|
||||||
|
if not hasattr(eps, 'select'):
|
||||||
|
# python < 3.10
|
||||||
|
eps = eps.get(group, [])
|
||||||
else:
|
else:
|
||||||
# newer setup (python >= 3.8); can use importlib, but the
|
# python >= 3.10
|
||||||
# details may vary
|
eps = eps.select(group=group)
|
||||||
eps = importlib.metadata.entry_points()
|
for entry_point in eps:
|
||||||
if not hasattr(eps, 'select'):
|
try:
|
||||||
# python < 3.10
|
ep = entry_point.load()
|
||||||
eps = eps.get(group, [])
|
except:
|
||||||
|
if not ignore_errors:
|
||||||
|
raise
|
||||||
|
log.warning("failed to load entry point: %s", entry_point,
|
||||||
|
exc_info=True)
|
||||||
else:
|
else:
|
||||||
# python >= 3.10
|
entry_points[entry_point.name] = ep
|
||||||
eps = eps.select(group=group)
|
|
||||||
for entry_point in eps:
|
|
||||||
try:
|
|
||||||
ep = entry_point.load()
|
|
||||||
except:
|
|
||||||
if not ignore_errors:
|
|
||||||
raise
|
|
||||||
log.warning("failed to load entry point: %s", entry_point,
|
|
||||||
exc_info=True)
|
|
||||||
else:
|
|
||||||
entry_points[entry_point.name] = ep
|
|
||||||
|
|
||||||
return entry_points
|
return entry_points
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,44 @@ class TestLoadEntryPoints(TestCase):
|
||||||
# even in a testing environment. basic sanity check
|
# even in a testing environment. basic sanity check
|
||||||
result = util.load_entry_points('console_scripts', ignore_errors=True)
|
result = util.load_entry_points('console_scripts', ignore_errors=True)
|
||||||
self.assertTrue(len(result) >= 1)
|
self.assertTrue(len(result) >= 1)
|
||||||
self.assertIn('pip', result)
|
self.assertIn('pytest', result)
|
||||||
|
|
||||||
|
def test_basic_pre_python_3_8(self):
|
||||||
|
|
||||||
|
# the goal here is to get coverage for code which would only
|
||||||
|
# run on python 3.7 and older, but we only need that coverage
|
||||||
|
# if we are currently testing python 3.8+
|
||||||
|
if sys.version_info.major == 3 and sys.version_info.minor < 8:
|
||||||
|
pytest.skip("this test is not relevant before python 3.8")
|
||||||
|
|
||||||
|
from importlib.metadata import entry_points
|
||||||
|
real_entry_points = entry_points()
|
||||||
|
|
||||||
|
class FakeEntryPoints(dict):
|
||||||
|
def get(self, group, default):
|
||||||
|
if hasattr(real_entry_points, 'select'):
|
||||||
|
return real_entry_points.select(group=group)
|
||||||
|
return real_entry_points.get(group, [])
|
||||||
|
|
||||||
|
importlib_metadata = MagicMock()
|
||||||
|
importlib_metadata.entry_points.return_value = FakeEntryPoints()
|
||||||
|
|
||||||
|
orig_import = __import__
|
||||||
|
|
||||||
|
def mock_import(name, *args, **kwargs):
|
||||||
|
if name == 'importlib.metadata':
|
||||||
|
raise ImportError
|
||||||
|
if name == 'importlib_metadata':
|
||||||
|
return importlib_metadata
|
||||||
|
return orig_import(name, *args, **kwargs)
|
||||||
|
|
||||||
|
with patch('builtins.__import__', side_effect=mock_import):
|
||||||
|
|
||||||
|
# load some entry points which should "always" be present,
|
||||||
|
# even in a testing environment. basic sanity check
|
||||||
|
result = util.load_entry_points('console_scripts', ignore_errors=True)
|
||||||
|
self.assertTrue(len(result) >= 1)
|
||||||
|
self.assertIn('pytest', result)
|
||||||
|
|
||||||
def test_error(self):
|
def test_error(self):
|
||||||
|
|
||||||
|
@ -86,66 +123,6 @@ class TestLoadEntryPoints(TestCase):
|
||||||
entry_points.select.assert_called_once_with(group='wuttatest.thingers')
|
entry_points.select.assert_called_once_with(group='wuttatest.thingers')
|
||||||
entry_point.load.assert_called_once_with()
|
entry_point.load.assert_called_once_with()
|
||||||
|
|
||||||
def test_pkg_resources_empty(self):
|
|
||||||
orig_import = __import__
|
|
||||||
|
|
||||||
def mock_import(name, *args, **kwargs):
|
|
||||||
if name == 'importlib.metadata':
|
|
||||||
raise ImportError
|
|
||||||
return orig_import(name, *args, **kwargs)
|
|
||||||
|
|
||||||
with patch('builtins.__import__', side_effect=mock_import):
|
|
||||||
|
|
||||||
# empty set returned for unknown group
|
|
||||||
result = util.load_entry_points('this_should_never_exist!!!!!!')
|
|
||||||
self.assertEqual(result, {})
|
|
||||||
|
|
||||||
def test_pkg_resources_basic(self):
|
|
||||||
orig_import = __import__
|
|
||||||
|
|
||||||
def mock_import(name, *args, **kwargs):
|
|
||||||
if name == 'importlib.metadata':
|
|
||||||
raise ImportError
|
|
||||||
return orig_import(name, *args, **kwargs)
|
|
||||||
|
|
||||||
with patch('builtins.__import__', side_effect=mock_import):
|
|
||||||
|
|
||||||
# load some entry points which should "always" be present,
|
|
||||||
# even in a testing environment. basic sanity check
|
|
||||||
result = util.load_entry_points('console_scripts', ignore_errors=True)
|
|
||||||
self.assertTrue(len(result) >= 1)
|
|
||||||
self.assertIn('pip', result)
|
|
||||||
|
|
||||||
def test_pkg_resources_error(self):
|
|
||||||
orig_import = __import__
|
|
||||||
|
|
||||||
entry_point = MagicMock()
|
|
||||||
entry_point.load.side_effect = NotImplementedError
|
|
||||||
|
|
||||||
iter_entry_points = MagicMock(return_value=[entry_point])
|
|
||||||
pkg_resources = MagicMock(iter_entry_points=iter_entry_points)
|
|
||||||
|
|
||||||
def mock_import(name, *args, **kwargs):
|
|
||||||
if name == 'importlib.metadata':
|
|
||||||
raise ImportError
|
|
||||||
return orig_import(name, *args, **kwargs)
|
|
||||||
|
|
||||||
with patch('builtins.__import__', side_effect=mock_import):
|
|
||||||
with patch.dict('sys.modules', **{'pkg_resources': pkg_resources}):
|
|
||||||
|
|
||||||
# empty set returned if errors suppressed
|
|
||||||
result = util.load_entry_points('wuttatest.thingers', ignore_errors=True)
|
|
||||||
self.assertEqual(result, {})
|
|
||||||
iter_entry_points.assert_called_once_with('wuttatest.thingers')
|
|
||||||
entry_point.load.assert_called_once_with()
|
|
||||||
|
|
||||||
# error is raised, if not suppressed
|
|
||||||
iter_entry_points.reset_mock()
|
|
||||||
entry_point.load.reset_mock()
|
|
||||||
self.assertRaises(NotImplementedError, util.load_entry_points, 'wuttatest.thingers')
|
|
||||||
iter_entry_points.assert_called_once_with('wuttatest.thingers')
|
|
||||||
entry_point.load.assert_called_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
class TestLoadObject(TestCase):
|
class TestLoadObject(TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue