From 47b3f09366f5abc8ef3d8c51a9573ac48d866d00 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Wed, 22 Oct 2025 23:06:18 +0900 Subject: [PATCH 1/5] A better error message from importlib.resources.files() when module spec is None from https://github.com/python/cpython/pull/138531 --- importlib_resources/_adapters.py | 4 ++-- importlib_resources/_common.py | 8 +++++++- importlib_resources/tests/test_resource.py | 13 +++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/importlib_resources/_adapters.py b/importlib_resources/_adapters.py index 50688fbb..222888b4 100644 --- a/importlib_resources/_adapters.py +++ b/importlib_resources/_adapters.py @@ -160,9 +160,9 @@ def files(self): return CompatibilityFiles.SpecPath(self.spec, self._reader) -def wrap_spec(package): +def wrap_spec(spec): """ Construct a package spec with traversable compatibility on the spec/loader/reader. """ - return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) + return SpecLoaderAdapter(spec, TraversableResourcesLoader) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index 5f41c265..de6d84f5 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -113,7 +113,13 @@ def from_package(package: types.ModuleType): # deferred for performance (python/cpython#109829) from .future.adapters import wrap_spec - spec = wrap_spec(package) + if package.__spec__ is None: + raise TypeError( + 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__) reader = spec.loader.get_resource_reader(spec.name) return reader.files() diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py index c80afdc7..c02f0b0b 100644 --- a/importlib_resources/tests/test_resource.py +++ b/importlib_resources/tests/test_resource.py @@ -1,3 +1,4 @@ +import types import unittest from importlib import import_module @@ -234,5 +235,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, msg="Cannot access resources for '__main__' as it does not appear to correspond to an importable module (its __spec__ is None)."): + resources.files(mainmodule) + + if __name__ == '__main__': unittest.main() From 74f308ada1a555e97e994b27a9bbbd988924a879 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 11 Apr 2026 20:42:56 -0400 Subject: [PATCH 2/5] Revert unrelated change to `wrap_spec`. --- importlib_resources/_adapters.py | 4 ++-- importlib_resources/_common.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/importlib_resources/_adapters.py b/importlib_resources/_adapters.py index 222888b4..50688fbb 100644 --- a/importlib_resources/_adapters.py +++ b/importlib_resources/_adapters.py @@ -160,9 +160,9 @@ def files(self): return CompatibilityFiles.SpecPath(self.spec, self._reader) -def wrap_spec(spec): +def wrap_spec(package): """ Construct a package spec with traversable compatibility on the spec/loader/reader. """ - return SpecLoaderAdapter(spec, TraversableResourcesLoader) + return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index de6d84f5..7c45779c 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -119,7 +119,7 @@ def from_package(package: types.ModuleType): "as it does not appear to correspond to an importable module (its __spec__ is None)." ) - spec = wrap_spec(package.__spec__) + spec = wrap_spec(package) reader = spec.loader.get_resource_reader(spec.name) return reader.files() From a542188be96e9992149e03ec82c6e21c8acd3740 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 11 Apr 2026 20:49:19 -0400 Subject: [PATCH 3/5] Extract method to encapsulate the validation. --- importlib_resources/_common.py | 20 ++++++++++++++------ importlib_resources/tests/test_resource.py | 5 ++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index 7c45779c..e3c55118 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -105,6 +105,19 @@ def is_wrapper(frame_info): return next(callers).frame +def _assert_spec(package: types.ModuleType) -> None: + """ + Provide a nicer error message when package is ``__main__`` + and its ``__spec__`` is ``None`` + (https://docs.python.org/3/reference/import.html#main-spec). + """ + if package.__spec__ is None: + raise TypeError( + f"Cannot access resources for '{package.__name__}' " + "as it does not appear to correspond to an importable module (its __spec__ is None)." + ) + + def from_package(package: types.ModuleType): """ Return a Traversable object for the given package. @@ -113,12 +126,7 @@ def from_package(package: types.ModuleType): # deferred for performance (python/cpython#109829) from .future.adapters import wrap_spec - if package.__spec__ is None: - raise TypeError( - 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)." - ) - + _assert_spec(package) spec = wrap_spec(package) reader = spec.loader.get_resource_reader(spec.name) return reader.files() diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py index c02f0b0b..89a34f03 100644 --- a/importlib_resources/tests/test_resource.py +++ b/importlib_resources/tests/test_resource.py @@ -243,7 +243,10 @@ def test_main_module_with_none_spec(self): self.assertIsNone(mainmodule.__spec__) - 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)."): + 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) From 6e2183f425ce89a743db51288dc6097ac8a8e115 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 11 Apr 2026 20:56:21 -0400 Subject: [PATCH 4/5] Revise the test for clarity and traceability. --- importlib_resources/tests/test_resource.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py index 31bf05fa..63a8bfbe 100644 --- a/importlib_resources/tests/test_resource.py +++ b/importlib_resources/tests/test_resource.py @@ -220,13 +220,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 +class MainModuleTests(unittest.TestCase): def test_main_module_with_none_spec(self): + """ + __main__ module with no spec should raise TypeError (for clarity). + + See python/cpython#138531 for details. + """ + # construct a __main__ module with no __spec__. mainmodule = types.ModuleType("__main__") - self.assertIsNone(mainmodule.__spec__) + assert mainmodule.__spec__ is None with self.assertRaises( TypeError, From d80822a9018c1a2438fe0cfe5b526c81a3705267 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 11 Apr 2026 20:58:25 -0400 Subject: [PATCH 5/5] Add news fragment. --- newsfragments/331.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/331.feature.rst diff --git a/newsfragments/331.feature.rst b/newsfragments/331.feature.rst new file mode 100644 index 00000000..04e93448 --- /dev/null +++ b/newsfragments/331.feature.rst @@ -0,0 +1 @@ +``files()`` now provides a nicer error when __main__.__spec__ is None.