diff --git a/changelog.d/3684.misc.rst b/changelog.d/3684.misc.rst new file mode 100644 index 0000000000..d6b56e1aa1 --- /dev/null +++ b/changelog.d/3684.misc.rst @@ -0,0 +1 @@ +Improved exception/traceback when invalid entry-points are specified. diff --git a/setuptools/_entry_points.py b/setuptools/_entry_points.py index f087681b59..a2346342d6 100644 --- a/setuptools/_entry_points.py +++ b/setuptools/_entry_points.py @@ -2,6 +2,7 @@ import operator import itertools +from .errors import OptionError from .extern.jaraco.text import yield_lines from .extern.jaraco.functools import pass_none from ._importlib import metadata @@ -14,7 +15,14 @@ def ensure_valid(ep): Exercise one of the dynamic properties to trigger the pattern match. """ - ep.extras + try: + ep.extras + except AttributeError as ex: + msg = ( + f"Problems to parse {ep}.\nPlease ensure entry-point follows the spec: " + "https://packaging.python.org/en/latest/specifications/entry-points/" + ) + raise OptionError(msg) from ex def load_group(value, group): diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index ee07b5a1be..387773c135 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -6,12 +6,18 @@ import stat import time from typing import List, Tuple +from pathlib import Path import pytest from jaraco import path +from setuptools import errors from setuptools.command.egg_info import ( - egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision, + EggInfoDeprecationWarning, + egg_info, + get_pkg_info_revision, + manifest_maker, + write_entries, ) from setuptools.dist import Distribution @@ -24,6 +30,28 @@ class Environment(str): pass +@pytest.fixture +def env(): + with contexts.tempdir(prefix='setuptools-test.') as env_dir: + env = Environment(env_dir) + os.chmod(env_dir, stat.S_IRWXU) + subs = 'home', 'lib', 'scripts', 'data', 'egg-base' + env.paths = dict( + (dirname, os.path.join(env_dir, dirname)) + for dirname in subs + ) + list(map(os.mkdir, env.paths.values())) + path.build({ + env.paths['home']: { + '.pydistutils.cfg': DALS(""" + [egg_info] + egg-base = %(egg-base)s + """ % env.paths) + } + }) + yield env + + class TestEggInfo: setup_script = DALS(""" @@ -51,27 +79,6 @@ def _extract_mv_version(pkg_info_lines: List[str]) -> Tuple[int, int]: version_str = pkg_info_lines[0].split(' ')[1] return tuple(map(int, version_str.split('.')[:2])) - @pytest.fixture - def env(self): - with contexts.tempdir(prefix='setuptools-test.') as env_dir: - env = Environment(env_dir) - os.chmod(env_dir, stat.S_IRWXU) - subs = 'home', 'lib', 'scripts', 'data', 'egg-base' - env.paths = dict( - (dirname, os.path.join(env_dir, dirname)) - for dirname in subs - ) - list(map(os.mkdir, env.paths.values())) - path.build({ - env.paths['home']: { - '.pydistutils.cfg': DALS(""" - [egg_info] - egg-base = %(egg-base)s - """ % env.paths) - } - }) - yield env - def test_egg_info_save_version_info_setup_empty(self, tmpdir_cwd, env): """ When the egg_info section is empty or not present, running @@ -1084,3 +1091,27 @@ def test_egg_info_tag_only_once(self, tmpdir_cwd, env): def test_get_pkg_info_revision_deprecated(self): pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision) + + +class TestWriteEntries: + + def test_invalid_entry_point(self, tmpdir_cwd, env): + dist = Distribution({"name": "foo", "version": "0.0.1"}) + dist.entry_points = {"foo": "foo = invalid-identifier:foo"} + cmd = dist.get_command_obj("egg_info") + expected_msg = r"Problems to parse .*invalid-identifier.*" + with pytest.raises(errors.OptionError, match=expected_msg) as ex: + write_entries(cmd, "entry_points", "entry_points.txt") + assert "ensure entry-point follows the spec" in ex.value.args[0] + + def test_valid_entry_point(self, tmpdir_cwd, env): + dist = Distribution({"name": "foo", "version": "0.0.1"}) + dist.entry_points = { + "abc": "foo = bar:baz", + "def": ["faa = bor:boz"], + } + cmd = dist.get_command_obj("egg_info") + write_entries(cmd, "entry_points", "entry_points.txt") + content = Path("entry_points.txt").read_text(encoding="utf-8") + assert "[abc]\nfoo = bar:baz\n" in content + assert "[def]\nfaa = bor:boz\n" in content