diff --git a/importlib_resources/_py3.py b/importlib_resources/_py3.py index a2647f481fef211..4687d98f31100e7 100644 --- a/importlib_resources/_py3.py +++ b/importlib_resources/_py3.py @@ -4,7 +4,7 @@ from . import abc as resources_abc from builtins import open as builtins_open -from contextlib import contextmanager +from contextlib import contextmanager, suppress from importlib import import_module from importlib.abc import ResourceLoader from io import BytesIO, TextIOWrapper @@ -78,9 +78,11 @@ def open_binary(package: Package, resource: Resource) -> BinaryIO: # importlib.machinery loaders are and an AttributeError for # get_data() will make it clear what is needed from the loader. loader = cast(ResourceLoader, package.__spec__.loader) - try: - data = loader.get_data(full_path) - except IOError: + data = None + if hasattr(package.__spec__.loader, 'get_data'): + with suppress(IOError): + data = loader.get_data(full_path) + if data is None: package_name = package.__spec__.name message = '{!r} resource not found in {!r}'.format( resource, package_name) @@ -112,9 +114,11 @@ def open_text(package: Package, # importlib.machinery loaders are and an AttributeError for # get_data() will make it clear what is needed from the loader. loader = cast(ResourceLoader, package.__spec__.loader) - try: - data = loader.get_data(full_path) - except IOError: + data = None + if hasattr(package.__spec__.loader, 'get_data'): + with suppress(IOError): + data = loader.get_data(full_path) + if data is None: package_name = package.__spec__.name message = '{!r} resource not found in {!r}'.format( resource, package_name) @@ -255,6 +259,11 @@ def contents(package: Package) -> Iterator[str]: if reader is not None: yield from reader.contents() return + # Is the package a namespace package? By definition, namespace packages + # cannot have resources. + if (package.__spec__.origin == 'namespace' and + not package.__spec__.has_location): + return [] package_directory = Path(package.__spec__.origin).parent try: yield from os.listdir(str(package_directory)) diff --git a/importlib_resources/docs/changelog.rst b/importlib_resources/docs/changelog.rst index 05982ae7c503264..04489d531e0ee99 100644 --- a/importlib_resources/docs/changelog.rst +++ b/importlib_resources/docs/changelog.rst @@ -9,6 +9,7 @@ ``open_text()``, ``read_binary()``, and ``read_text()``. Closes #41 * Fix a bug where unrelated resources could be returned from ``contents()``. Closes #44 +* Correctly prevent namespace packages from containing resources. Closes #20 0.1 (2017-12-05) diff --git a/importlib_resources/tests/data03/__init__.py b/importlib_resources/tests/data03/__init__.py new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/importlib_resources/tests/data03/namespace/portion1/__init__.py b/importlib_resources/tests/data03/namespace/portion1/__init__.py new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/importlib_resources/tests/data03/namespace/portion2/__init__.py b/importlib_resources/tests/data03/namespace/portion2/__init__.py new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/importlib_resources/tests/data03/namespace/resource1.txt b/importlib_resources/tests/data03/namespace/resource1.txt new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py index 6d46c92ce4f0ef2..93d0800f7554404 100644 --- a/importlib_resources/tests/test_resource.py +++ b/importlib_resources/tests/test_resource.py @@ -111,5 +111,35 @@ def test_unrelated_contents(self): {'__init__.py', 'resource2.txt'}) +@unittest.skipIf(sys.version_info < (3,), 'No namespace packages in Python 2') +class NamespaceTest(unittest.TestCase): + def test_namespaces_cant_have_resources(self): + contents = set(resources.contents( + 'importlib_resources.tests.data03.namespace')) + self.assertEqual(len(contents), 0) + # Even though there is a file in the namespace directory, it is not + # considered a resource, since namespace packages can't have them. + self.assertFalse(resources.is_resource( + 'importlib_resources.tests.data03.namespace', + 'resource1.txt')) + # We should get an exception if we try to read it or open it. + self.assertRaises( + FileNotFoundError, + resources.open_text, + 'importlib_resources.tests.data03.namespace', 'resource1.txt') + self.assertRaises( + FileNotFoundError, + resources.open_binary, + 'importlib_resources.tests.data03.namespace', 'resource1.txt') + self.assertRaises( + FileNotFoundError, + resources.read_text, + 'importlib_resources.tests.data03.namespace', 'resource1.txt') + self.assertRaises( + FileNotFoundError, + resources.read_binary, + 'importlib_resources.tests.data03.namespace', 'resource1.txt') + + if __name__ == '__main__': unittest.main()