From 078c80187cc8dd412038ef32d9ec67b167a047e8 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Fri, 5 Sep 2025 15:36:11 +0800 Subject: [PATCH 1/8] Raise an exception with a better message from importlib.resources.files() when module spec is not available --- Lib/importlib/resources/_adapters.py | 10 +++++++--- Lib/importlib/resources/_common.py | 5 ++++- Lib/test/test_importlib/resources/test_resource.py | 13 +++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Lib/importlib/resources/_adapters.py b/Lib/importlib/resources/_adapters.py index 50688fbb666658..02d8183ccda2d4 100644 --- a/Lib/importlib/resources/_adapters.py +++ b/Lib/importlib/resources/_adapters.py @@ -3,13 +3,17 @@ from . import abc +TYPE_CHECKING = False +if TYPE_CHECKING: + from ..machinery import ModuleSpec + class SpecLoaderAdapter: """ Adapt a package spec to adapt the underlying loader. """ - def __init__(self, spec, adapter=lambda spec: spec.loader): + def __init__(self, spec: ModuleSpec, adapter): self.spec = spec self.loader = adapter(spec) @@ -160,9 +164,9 @@ def files(self): return CompatibilityFiles.SpecPath(self.spec, self._reader) -def wrap_spec(package): +def wrap_spec(spec: ModuleSpec): """ Construct a package spec with traversable compatibility on the spec/loader/reader. """ - return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) + return SpecLoaderAdapter(spec, TraversableResourcesLoader) diff --git a/Lib/importlib/resources/_common.py b/Lib/importlib/resources/_common.py index 4e9014c45a056e..3c4a4c15addd28 100644 --- a/Lib/importlib/resources/_common.py +++ b/Lib/importlib/resources/_common.py @@ -113,7 +113,10 @@ def from_package(package: types.ModuleType): # deferred for performance (python/cpython#109829) from ._adapters import wrap_spec - spec = wrap_spec(package) + if package.__spec__ is None: + raise TypeError(f"Can't access resources on a module with no spec: {package}") + + spec = wrap_spec(package.__spec__) reader = spec.loader.get_resource_reader(spec.name) return reader.files() diff --git a/Lib/test/test_importlib/resources/test_resource.py b/Lib/test/test_importlib/resources/test_resource.py index fcede14b891a84..8f8fa9825b9b3e 100644 --- a/Lib/test/test_importlib/resources/test_resource.py +++ b/Lib/test/test_importlib/resources/test_resource.py @@ -1,4 +1,5 @@ import unittest +import types from . import util from importlib import resources, import_module @@ -232,5 +233,17 @@ class ResourceFromNamespaceZipTests( MODULE = 'namespacedata01' +class ResourceFromMainModuleWithNoneSpecTests(unittest.TestCase): + # `__main__.__spec__` can be `None` depending on how it is populated. + # https://docs.python.org/3/reference/import.html#main-spec + def test_main_module_with_none_spec(self): + mainmodule = types.ModuleType("__main__") + + self.assertIsNone(mainmodule.__spec__) + + with self.assertRaises(TypeError): + resources.files(mainmodule) + + if __name__ == '__main__': unittest.main() From c79a7cc8ff44e716dbede4fda9f039d0c1d72338 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 08:26:12 +0000 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst diff --git a/Misc/NEWS.d/next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst b/Misc/NEWS.d/next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst new file mode 100644 index 00000000000000..c7a25da98eacf6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst @@ -0,0 +1 @@ +`importlib.resources.files(package)` raises an error with a clearer message when `package.__spec__` is `None` From 5ed2652f1ecddb55b3f63f8e0212e842a038bdb9 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Fri, 5 Sep 2025 16:32:59 +0800 Subject: [PATCH 3/8] Fix Blurb --- .../next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst b/Misc/NEWS.d/next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst index c7a25da98eacf6..c252d16b1d02ca 100644 --- a/Misc/NEWS.d/next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst +++ b/Misc/NEWS.d/next/Library/2025-09-05-08-26-11.gh-issue-121190.nBBcWu.rst @@ -1 +1 @@ -`importlib.resources.files(package)` raises an error with a clearer message when `package.__spec__` is `None` +``importlib.resources.files(package)`` raises an error with a clearer message when ``package.__spec__`` is ``None`` From 847d8dfb318d9407e8abae1c889ebdb79ec55643 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Sat, 6 Sep 2025 17:04:14 +0800 Subject: [PATCH 4/8] Remove type annotations --- Lib/importlib/resources/_adapters.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Lib/importlib/resources/_adapters.py b/Lib/importlib/resources/_adapters.py index 02d8183ccda2d4..76b0bf8109baf4 100644 --- a/Lib/importlib/resources/_adapters.py +++ b/Lib/importlib/resources/_adapters.py @@ -3,17 +3,13 @@ from . import abc -TYPE_CHECKING = False -if TYPE_CHECKING: - from ..machinery import ModuleSpec - class SpecLoaderAdapter: """ Adapt a package spec to adapt the underlying loader. """ - def __init__(self, spec: ModuleSpec, adapter): + def __init__(self, spec, adapter): self.spec = spec self.loader = adapter(spec) @@ -164,7 +160,7 @@ def files(self): return CompatibilityFiles.SpecPath(self.spec, self._reader) -def wrap_spec(spec: ModuleSpec): +def wrap_spec(spec): """ Construct a package spec with traversable compatibility on the spec/loader/reader. From e25491eb5e6286d0cf0ba143c4b08ecb317e0720 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Sun, 7 Sep 2025 13:05:16 +0800 Subject: [PATCH 5/8] Modify the error message --- Lib/importlib/resources/_common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/importlib/resources/_common.py b/Lib/importlib/resources/_common.py index 3c4a4c15addd28..51739f00642b6b 100644 --- a/Lib/importlib/resources/_common.py +++ b/Lib/importlib/resources/_common.py @@ -114,7 +114,10 @@ def from_package(package: types.ModuleType): from ._adapters import wrap_spec if package.__spec__ is None: - raise TypeError(f"Can't access resources on a module with no spec: {package}") + raise TypeError( + f"Cannot access resources because the code used to populate '{package.__name__}' " + "does not correspond directly with an importable module." + ) spec = wrap_spec(package.__spec__) reader = spec.loader.get_resource_reader(spec.name) From 7dc7e46185dc2386b5050f277a4944a94edcd6fd Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Fri, 19 Sep 2025 23:59:27 +0900 Subject: [PATCH 6/8] Revert a change removing the default adapter argument on SpecLoaderAdapter.__init__() --- Lib/importlib/resources/_adapters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/importlib/resources/_adapters.py b/Lib/importlib/resources/_adapters.py index 76b0bf8109baf4..222888b4b3b02a 100644 --- a/Lib/importlib/resources/_adapters.py +++ b/Lib/importlib/resources/_adapters.py @@ -9,7 +9,7 @@ class SpecLoaderAdapter: Adapt a package spec to adapt the underlying loader. """ - def __init__(self, spec, adapter): + def __init__(self, spec, adapter=lambda spec: spec.loader): self.spec = spec self.loader = adapter(spec) From 9e98cfccf3437acad88987df16ce898c052f1a57 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Wed, 22 Oct 2025 22:03:27 +0900 Subject: [PATCH 7/8] Apply suggestion from @danielhollas Co-authored-by: Daniel Hollas --- Lib/importlib/resources/_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/importlib/resources/_common.py b/Lib/importlib/resources/_common.py index 51739f00642b6b..59cf8573a3cfd0 100644 --- a/Lib/importlib/resources/_common.py +++ b/Lib/importlib/resources/_common.py @@ -115,8 +115,8 @@ def from_package(package: types.ModuleType): if package.__spec__ is None: raise TypeError( - f"Cannot access resources because the code used to populate '{package.__name__}' " - "does not correspond directly with an importable module." + f"Cannot access resources for '{package.__name__}' " + "as it does not correspond to an importable module." ) spec = wrap_spec(package.__spec__) From d3ec82a71c119e3eda2f2ea81a2af4a734c4785c Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Wed, 22 Oct 2025 22:41:51 +0900 Subject: [PATCH 8/8] fix the error message --- Lib/importlib/resources/_common.py | 4 ++-- Lib/test/test_importlib/resources/test_resource.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/importlib/resources/_common.py b/Lib/importlib/resources/_common.py index 59cf8573a3cfd0..22fd32568be3e9 100644 --- a/Lib/importlib/resources/_common.py +++ b/Lib/importlib/resources/_common.py @@ -115,8 +115,8 @@ def from_package(package: types.ModuleType): if package.__spec__ is None: raise TypeError( - f"Cannot access resources for '{package.__name__}' " - "as it does not correspond to an importable module." + f"Cannot access resources for '{package.__name__ or package!r}' " + "as it does not appear to correspond to an importable module (its __spec__ is None)." ) spec = wrap_spec(package.__spec__) diff --git a/Lib/test/test_importlib/resources/test_resource.py b/Lib/test/test_importlib/resources/test_resource.py index 8f8fa9825b9b3e..cdc1e38118a381 100644 --- a/Lib/test/test_importlib/resources/test_resource.py +++ b/Lib/test/test_importlib/resources/test_resource.py @@ -241,7 +241,7 @@ def test_main_module_with_none_spec(self): self.assertIsNone(mainmodule.__spec__) - with self.assertRaises(TypeError): + with self.assertRaises(TypeError, msg="Cannot access resources for '__main__' as it does not appear to correspond to an importable module (its __spec__ is None)."): resources.files(mainmodule)