Skip to content

Commit 20a5e9f

Browse files
authored
Restore use of importlib.metadata instead of pkg_resources (#589)
This reverts commit 731092b. This restores commit a9de909. This restores commit 5f9f5ff. This restores commit 7b70e61.
1 parent f2c3296 commit 20a5e9f

File tree

9 files changed

+107
-93
lines changed

9 files changed

+107
-93
lines changed

colcon_core/extension_point.py

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@
66
import os
77
import traceback
88

9+
try:
10+
from importlib.metadata import distributions
11+
from importlib.metadata import EntryPoint
12+
from importlib.metadata import entry_points
13+
except ImportError:
14+
# TODO: Drop this with Python 3.7 support
15+
from importlib_metadata import distributions
16+
from importlib_metadata import EntryPoint
17+
from importlib_metadata import entry_points
18+
919
from colcon_core.environment_variable import EnvironmentVariable
1020
from colcon_core.logging import colcon_logger
11-
from pkg_resources import EntryPoint
12-
from pkg_resources import iter_entry_points
13-
from pkg_resources import WorkingSet
1421

1522
"""Environment variable to block extensions"""
1623
EXTENSION_BLOCKLIST_ENVIRONMENT_VARIABLE = EnvironmentVariable(
@@ -44,27 +51,26 @@ def get_all_extension_points():
4451
colcon_extension_points.setdefault(EXTENSION_POINT_GROUP_NAME, None)
4552

4653
entry_points = defaultdict(dict)
47-
working_set = WorkingSet()
48-
for dist in sorted(working_set):
49-
entry_map = dist.get_entry_map()
50-
for group_name in entry_map.keys():
54+
seen = set()
55+
for dist in distributions():
56+
dist_name = dist.metadata['Name']
57+
if dist_name in seen:
58+
continue
59+
seen.add(dist_name)
60+
for entry_point in dist.entry_points:
5161
# skip groups which are not registered as extension points
52-
if group_name not in colcon_extension_points:
62+
if entry_point.group not in colcon_extension_points:
5363
continue
5464

55-
group = entry_map[group_name]
56-
for entry_point_name, entry_point in group.items():
57-
if entry_point_name in entry_points[group_name]:
58-
previous = entry_points[group_name][entry_point_name]
59-
logger.error(
60-
f"Entry point '{group_name}.{entry_point_name}' is "
61-
f"declared multiple times, '{entry_point}' "
62-
f"overwriting '{previous}'")
63-
value = entry_point.module_name
64-
if entry_point.attrs:
65-
value += f":{'.'.join(entry_point.attrs)}"
66-
entry_points[group_name][entry_point_name] = (
67-
value, dist.project_name, getattr(dist, 'version', None))
65+
if entry_point.name in entry_points[entry_point.group]:
66+
previous = entry_points[entry_point.group][entry_point.name]
67+
logger.error(
68+
f"Entry point '{entry_point.group}.{entry_point.name}' is "
69+
f"declared multiple times, '{entry_point.value}' "
70+
f"from '{dist._path}' "
71+
f"overwriting '{previous}'")
72+
entry_points[entry_point.group][entry_point.name] = \
73+
(entry_point.value, dist_name, dist.version)
6874
return entry_points
6975

7076

@@ -76,19 +82,21 @@ def get_extension_points(group):
7682
:returns: mapping of extension point names to extension point values
7783
:rtype: dict
7884
"""
79-
entry_points = {}
80-
for entry_point in iter_entry_points(group=group):
81-
if entry_point.name in entry_points:
82-
previous_entry_point = entry_points[entry_point.name]
85+
extension_points = {}
86+
try:
87+
# Python 3.10 and newer
88+
query = entry_points(group=group)
89+
except TypeError:
90+
query = entry_points().get(group, ())
91+
for entry_point in query:
92+
if entry_point.name in extension_points:
93+
previous_entry_point = extension_points[entry_point.name]
8394
logger.error(
8495
f"Entry point '{group}.{entry_point.name}' is declared "
85-
f"multiple times, '{entry_point}' overwriting "
96+
f"multiple times, '{entry_point.value}' overwriting "
8697
f"'{previous_entry_point}'")
87-
value = entry_point.module_name
88-
if entry_point.attrs:
89-
value += f":{'.'.join(entry_point.attrs)}"
90-
entry_points[entry_point.name] = value
91-
return entry_points
98+
extension_points[entry_point.name] = entry_point.value
99+
return extension_points
92100

93101

94102
def load_extension_points(group, *, excludes=None):
@@ -146,10 +154,4 @@ def load_extension_point(name, value, group):
146154
raise RuntimeError(
147155
'The entry point name is listed in the environment variable '
148156
f"'{EXTENSION_BLOCKLIST_ENVIRONMENT_VARIABLE.name}'")
149-
if ':' in value:
150-
module_name, attr = value.split(':', 1)
151-
attrs = attr.split('.')
152-
else:
153-
module_name = value
154-
attrs = ()
155-
return EntryPoint(name, module_name, attrs).resolve()
157+
return EntryPoint(name, value, group).load()

colcon_core/package_identification/python.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,17 @@ def get_configuration(setup_cfg):
9090
except ImportError:
9191
from setuptools.config import read_configuration
9292
except ImportError as e:
93-
from pkg_resources import get_distribution
94-
from pkg_resources import parse_version
95-
setuptools_version = get_distribution('setuptools').version
93+
try:
94+
from importlib.metadata import distribution
95+
except ImportError:
96+
from importlib_metadata import distribution
97+
from packaging.version import Version
98+
try:
99+
setuptools_version = distribution('setuptools').version
100+
except ModuleNotFoundError:
101+
setuptools_version = '0'
96102
minimum_version = '30.3.0'
97-
if parse_version(setuptools_version) < parse_version(minimum_version):
103+
if Version(setuptools_version) < Version(minimum_version):
98104
e.msg += ', ' \
99105
"'setuptools' needs to be at least version " \
100106
f'{minimum_version}, if a newer version is not available ' \

colcon_core/plugin_system.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from colcon_core.extension_point import load_extension_points
88
from colcon_core.logging import colcon_logger
9-
from pkg_resources import parse_version
9+
from packaging.version import Version
1010

1111
logger = colcon_logger.getChild(__name__)
1212

@@ -166,8 +166,8 @@ def satisfies_version(version, caret_range):
166166
:raises RuntimeError: if the version doesn't match the caret range
167167
"""
168168
assert caret_range.startswith('^'), 'Only supports caret ranges'
169-
extension_point_version = parse_version(version)
170-
extension_version = parse_version(caret_range[1:])
169+
extension_point_version = Version(version)
170+
extension_version = Version(caret_range[1:])
171171
next_extension_version = _get_upper_bound_caret_version(
172172
extension_version)
173173

@@ -192,4 +192,4 @@ def _get_upper_bound_caret_version(version):
192192
minor = 0
193193
else:
194194
minor += 1
195-
return parse_version('%d.%d.0' % (major, minor))
195+
return Version('%d.%d.0' % (major, minor))

colcon_core/task/python/test/pytest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from colcon_core.task.python.test import has_test_dependency
1414
from colcon_core.task.python.test import PythonTestingStepExtensionPoint
1515
from colcon_core.verb.test import logger
16-
from pkg_resources import parse_version
16+
from packaging.version import Version
1717

1818

1919
class PytestPythonTestingStep(PythonTestingStepExtensionPoint):
@@ -64,7 +64,7 @@ async def step(self, context, env, setup_py_data): # noqa: D102
6464
# use -o option only when available
6565
# https://github.com/pytest-dev/pytest/blob/3.3.0/CHANGELOG.rst
6666
from pytest import __version__ as pytest_version
67-
if parse_version(pytest_version) >= parse_version('3.3.0'):
67+
if Version(pytest_version) >= Version('3.3.0'):
6868
args += [
6969
'-o', 'cache_dir=' + str(PurePosixPath(
7070
*(Path(context.args.build_base).parts)) / '.pytest_cache'),
@@ -95,7 +95,7 @@ async def step(self, context, env, setup_py_data): # noqa: D102
9595
]
9696
# use --cov-branch option only when available
9797
# https://github.com/pytest-dev/pytest-cov/blob/v2.5.0/CHANGELOG.rst
98-
if parse_version(pytest_cov_version) >= parse_version('2.5.0'):
98+
if Version(pytest_cov_version) >= Version('2.5.0'):
9999
args += [
100100
'--cov-branch',
101101
]

debian/patches/setup.cfg.patch

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ Author: Dirk Thomas <[email protected]>
55

66
--- setup.cfg 2018-05-27 11:22:33.000000000 -0700
77
+++ setup.cfg.patched 2018-05-27 11:22:33.000000000 -0700
8-
@@ -31,9 +31,12 @@
9-
distlib
10-
EmPy
8+
@@ -33,9 +33,12 @@
9+
importlib-metadata; python_version < "3.8"
10+
packaging
1111
pytest
1212
- pytest-cov
1313
- pytest-repeat

setup.cfg

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ install_requires =
3030
coloredlogs; sys_platform == 'win32'
3131
distlib
3232
EmPy
33+
importlib-metadata; python_version < "3.8"
34+
packaging
3335
# the pytest dependency and its extensions are provided for convenience
3436
# even though they are only conditional
3537
pytest
@@ -67,7 +69,7 @@ filterwarnings =
6769
error
6870
# Suppress deprecation warnings in other packages
6971
ignore:lib2to3 package is deprecated::scspell
70-
ignore:pkg_resources is deprecated as an API
72+
ignore:pkg_resources is deprecated as an API::colcon_core.entry_point
7173
ignore:SelectableGroups dict interface is deprecated::flake8
7274
ignore:The loop argument is deprecated::asyncio
7375
ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated::pydocstyle

stdeb.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[colcon-core]
22
No-Python2:
3-
Depends3: python3-distlib, python3-empy, python3-packaging, python3-pytest, python3-setuptools
3+
Depends3: python3-distlib, python3-empy, python3-packaging, python3-pytest, python3-setuptools, python3 (>= 3.8) | python3-importlib-metadata
44
Recommends3: python3-pytest-cov
55
Suggests3: python3-pytest-repeat, python3-pytest-rerunfailures
66
Suite: focal jammy bullseye bookworm

test/spell_check.words

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ addopts
22
apache
33
argparse
44
asyncio
5-
attrs
65
autouse
76
basepath
87
bazqux
@@ -47,6 +46,7 @@ hardcodes
4746
hookimpl
4847
hookwrapper
4948
https
49+
importlib
5050
isatty
5151
iterdir
5252
junit

test/test_extension_point.py

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,96 +17,100 @@
1717
from .environment_context import EnvironmentContext
1818

1919

20-
Group1 = EntryPoint('group1', 'g1')
21-
Group2 = EntryPoint('group2', 'g2')
20+
Group1 = EntryPoint('group1', 'g1', EXTENSION_POINT_GROUP_NAME)
21+
Group2 = EntryPoint('group2', 'g2', EXTENSION_POINT_GROUP_NAME)
22+
ExtA = EntryPoint('extA', 'eA', Group1.name)
23+
ExtB = EntryPoint('extB', 'eB', Group1.name)
2224

2325

2426
class Dist():
2527

26-
project_name = 'dist'
28+
version = '0.0.0'
2729

28-
def __init__(self, group_name, group):
29-
self._group_name = group_name
30-
self._group = group
30+
def __init__(self, entry_points):
31+
self.metadata = {'Name': f'dist-{id(self)}'}
32+
self._entry_points = entry_points
3133

32-
def __lt__(self, other):
33-
return self._group_name < other._group_name
34+
@property
35+
def entry_points(self):
36+
return list(self._entry_points)
3437

35-
def get_entry_map(self):
36-
return self._group
38+
@property
39+
def name(self):
40+
return self.metadata['Name']
3741

3842

39-
def iter_entry_points(*, group):
43+
def iter_entry_points(*, group=None):
4044
if group == EXTENSION_POINT_GROUP_NAME:
4145
return [Group1, Group2]
42-
assert group == Group1.name
43-
ep1 = EntryPoint('extA', 'eA')
44-
ep2 = EntryPoint('extB', 'eB')
45-
return [ep1, ep2]
46+
elif group == Group1.name:
47+
return [ExtA, ExtB]
48+
assert not group
49+
return {
50+
EXTENSION_POINT_GROUP_NAME: [Group1, Group2],
51+
Group1.name: [ExtA, ExtB],
52+
}
4653

4754

48-
def working_set():
55+
def distributions():
4956
return [
50-
Dist('group1', {
51-
'group1': {ep.name: ep for ep in iter_entry_points(group='group1')}
52-
}),
53-
Dist('group2', {'group2': {'extC': EntryPoint('extC', 'eC')}}),
54-
Dist('groupX', {'groupX': {'extD': EntryPoint('extD', 'eD')}}),
57+
Dist(iter_entry_points(group='group1')),
58+
Dist([EntryPoint('extC', 'eC', Group2.name)]),
59+
Dist([EntryPoint('extD', 'eD', 'groupX')]),
5560
]
5661

5762

5863
def test_all_extension_points():
5964
with patch(
60-
'colcon_core.extension_point.iter_entry_points',
65+
'colcon_core.extension_point.entry_points',
6166
side_effect=iter_entry_points
6267
):
6368
with patch(
64-
'colcon_core.extension_point.WorkingSet',
65-
side_effect=working_set
69+
'colcon_core.extension_point.distributions',
70+
side_effect=distributions
6671
):
6772
# successfully load a known entry point
6873
extension_points = get_all_extension_points()
6974
assert set(extension_points.keys()) == {'group1', 'group2'}
7075
assert set(extension_points['group1'].keys()) == {'extA', 'extB'}
71-
assert extension_points['group1']['extA'] == (
72-
'eA', Dist.project_name, None)
76+
assert extension_points['group1']['extA'][0] == 'eA'
7377

7478

7579
def test_extension_point_blocklist():
7680
# successful loading of extension point without a blocklist
7781
with patch(
78-
'colcon_core.extension_point.iter_entry_points',
82+
'colcon_core.extension_point.entry_points',
7983
side_effect=iter_entry_points
8084
):
8185
with patch(
82-
'colcon_core.extension_point.WorkingSet',
83-
side_effect=working_set
86+
'colcon_core.extension_point.distributions',
87+
side_effect=distributions
8488
):
8589
extension_points = get_extension_points('group1')
8690
assert 'extA' in extension_points.keys()
8791
extension_point = extension_points['extA']
8892
assert extension_point == 'eA'
8993

90-
with patch.object(EntryPoint, 'resolve', return_value=None) as resolve:
94+
with patch.object(EntryPoint, 'load', return_value=None) as load:
9195
load_extension_point('extA', 'eA', 'group1')
92-
assert resolve.call_count == 1
96+
assert load.call_count == 1
9397

9498
# successful loading of entry point not in blocklist
95-
resolve.reset_mock()
99+
load.reset_mock()
96100
with EnvironmentContext(COLCON_EXTENSION_BLOCKLIST=os.pathsep.join([
97101
'group1.extB', 'group2.extC'])
98102
):
99103
load_extension_point('extA', 'eA', 'group1')
100-
assert resolve.call_count == 1
104+
assert load.call_count == 1
101105

102106
# entry point in a blocked group can't be loaded
103-
resolve.reset_mock()
107+
load.reset_mock()
104108
with EnvironmentContext(COLCON_EXTENSION_BLOCKLIST='group1'):
105109
with pytest.raises(RuntimeError) as e:
106110
load_extension_point('extA', 'eA', 'group1')
107111
assert 'The entry point group name is listed in the environment ' \
108112
'variable' in str(e.value)
109-
assert resolve.call_count == 0
113+
assert load.call_count == 0
110114

111115
# entry point listed in the blocklist can't be loaded
112116
with EnvironmentContext(COLCON_EXTENSION_BLOCKLIST=os.pathsep.join([
@@ -116,10 +120,10 @@ def test_extension_point_blocklist():
116120
load_extension_point('extA', 'eA', 'group1')
117121
assert 'The entry point name is listed in the environment ' \
118122
'variable' in str(e.value)
119-
assert resolve.call_count == 0
123+
assert load.call_count == 0
120124

121125

122-
def entry_point_resolve(self, *args, **kwargs):
126+
def entry_point_load(self, *args, **kwargs):
123127
if self.name == 'exception':
124128
raise Exception('entry point raising exception')
125129
if self.name == 'runtime_error':
@@ -129,7 +133,7 @@ def entry_point_resolve(self, *args, **kwargs):
129133
return DEFAULT
130134

131135

132-
@patch.object(EntryPoint, 'resolve', entry_point_resolve)
136+
@patch.object(EntryPoint, 'load', entry_point_load)
133137
@patch(
134138
'colcon_core.extension_point.get_extension_points',
135139
return_value={'exception': 'a', 'runtime_error': 'b', 'success': 'c'}

0 commit comments

Comments
 (0)