From 170a2e49d7b9930c783303596ddb5d96f20a13c5 Mon Sep 17 00:00:00 2001 From: Rudra <320163285+rudra337-lgtm@users.noreply.github.com> Date: Sun, 18 Oct 2026 15:48:22 +0530 Subject: [PATCH 1/2] Defer EntryPoint value validation until load Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- importlib_metadata/__init__.py | 7 +++---- newsfragments/523.bugfix.rst | 2 ++ tests/test_api.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 newsfragments/523.bugfix.rst diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 3796fbce..0eebf847 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -179,9 +179,10 @@ class EntryPoint: ... ValueError: ('Invalid object reference...invalid-name... - The same thing happens on construction. + The invalid value is stored during construction and raises when accessed + or loaded. - >>> EntryPoint(name=None, group=None, value='invalid-name') + >>> EntryPoint(name=None, group=None, value='invalid-name').load() Traceback (most recent call last): ... ValueError: ('Invalid object reference...invalid-name... @@ -217,8 +218,6 @@ class EntryPoint: def __init__(self, name: str, value: str, group: str) -> None: vars(self).update(name=name, value=value, group=group) - # resolve the value now, raising ValueError if it's invalid - _ = self.module def load(self) -> Any: """Load the entry point from its definition. If only a module diff --git a/newsfragments/523.bugfix.rst b/newsfragments/523.bugfix.rst new file mode 100644 index 00000000..3519d5cb --- /dev/null +++ b/newsfragments/523.bugfix.rst @@ -0,0 +1,2 @@ +Defer validation of invalid ``EntryPoint.value`` values until their components +are accessed or they are loaded. diff --git a/tests/test_api.py b/tests/test_api.py index 1468e0a9..59d0a52d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -132,6 +132,34 @@ def test_entry_points_missing_name(self): def test_entry_points_missing_group(self): assert entry_points(group='missing') == () + def test_entry_points_with_invalid_value_in_unselected_group(self): + alt_site_dir = self.fixtures.enter_context(fixtures.tmp_path()) + self.fixtures.enter_context(self.add_sys_path(alt_site_dir)) + pkg = { + "invalid-entry-points-1.0.dist-info": { + "METADATA": """ + Name: invalid-entry-points + Version: 1.0.0 + """, + "entry_points.txt": textwrap.dedent( + """ + [selected] + valid = importlib_metadata:EntryPoint + + [unselected] + invalid = invalid-name + """ + ), + }, + } + fixtures.build_files(pkg, alt_site_dir) + + entries = entry_points(group='selected') + + self.assertEqual([ep.name for ep in entries], ['valid']) + with self.assertRaises(ValueError): + entry_points(group='unselected')['invalid'].load() + def test_entry_points_allows_no_attributes(self): ep = entry_points().select(group='entries', name='main') with self.assertRaises(AttributeError): From 7d96b5a6dea4b4162a2c613a9aff18c0e67c607e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2026 14:20:14 -0700 Subject: [PATCH 2/2] Capture the deferred expectation in the docstring and simplify the test. The distinctive behavior is that construction no longer fails, so demonstrate that by constructing an invalid EntryPoint and only then raising on load. In the test, rely on the fixtures' dedenting of file contents and match the style of the neighboring tests. Co-Authored-By: Claude Opus 5 --- importlib_metadata/__init__.py | 7 ++++--- newsfragments/523.bugfix.rst | 6 ++++-- tests/test_api.py | 32 +++++++++++++++++--------------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 0eebf847..b3ff2c31 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -179,10 +179,11 @@ class EntryPoint: ... ValueError: ('Invalid object reference...invalid-name... - The invalid value is stored during construction and raises when accessed - or loaded. + Construction never raises; the value is validated only when + accessed or loaded. - >>> EntryPoint(name=None, group=None, value='invalid-name').load() + >>> ep = EntryPoint(name=None, group=None, value='invalid-name') + >>> ep.load() Traceback (most recent call last): ... ValueError: ('Invalid object reference...invalid-name... diff --git a/newsfragments/523.bugfix.rst b/newsfragments/523.bugfix.rst index 3519d5cb..36a5d48e 100644 --- a/newsfragments/523.bugfix.rst +++ b/newsfragments/523.bugfix.rst @@ -1,2 +1,4 @@ -Defer validation of invalid ``EntryPoint.value`` values until their components -are accessed or they are loaded. +Restored lazy validation of ``EntryPoint.value``, deferring the ``ValueError`` +for an invalid value until a component is accessed or the entry point is +loaded, so one malformed entry point no longer prevents entry points in other +groups from being selected. diff --git a/tests/test_api.py b/tests/test_api.py index 59d0a52d..2d660b3d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -132,31 +132,33 @@ def test_entry_points_missing_name(self): def test_entry_points_missing_group(self): assert entry_points(group='missing') == () - def test_entry_points_with_invalid_value_in_unselected_group(self): + def test_entry_points_invalid_value_in_other_group(self): + """ + An invalid value in one group should not prevent entry points + in other groups from being selected. + """ alt_site_dir = self.fixtures.enter_context(fixtures.tmp_path()) self.fixtures.enter_context(self.add_sys_path(alt_site_dir)) - pkg = { - "invalid-entry-points-1.0.dist-info": { + alt_pkg = { + "invalid_entry_points-1.0.0.dist-info": { "METADATA": """ Name: invalid-entry-points Version: 1.0.0 """, - "entry_points.txt": textwrap.dedent( - """ - [selected] - valid = importlib_metadata:EntryPoint - - [unselected] - invalid = invalid-name - """ - ), + "entry_points.txt": """ + [selected] + valid = mod:main + + [unselected] + invalid = invalid-name + """, }, } - fixtures.build_files(pkg, alt_site_dir) + fixtures.build_files(alt_pkg, alt_site_dir) - entries = entry_points(group='selected') + assert [ep.name for ep in entry_points(group='selected')] == ['valid'] - self.assertEqual([ep.name for ep in entries], ['valid']) + # the invalid value is still rejected when the entry point is used with self.assertRaises(ValueError): entry_points(group='unselected')['invalid'].load()