Skip to content

Commit b5f1b15

Browse files
committed
Merge branch 'issue20' into 'master'
Properly prevent namespaces packages from containing resources Closes python#20 See merge request python-devs/importlib_resources!50
2 parents b33cb47 + a6ff818 commit b5f1b15

File tree

7 files changed

+47
-7
lines changed

7 files changed

+47
-7
lines changed

importlib_resources/_py3.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from . import abc as resources_abc
66
from builtins import open as builtins_open
7-
from contextlib import contextmanager
7+
from contextlib import contextmanager, suppress
88
from importlib import import_module
99
from importlib.abc import ResourceLoader
1010
from io import BytesIO, TextIOWrapper
@@ -78,9 +78,11 @@ def open_binary(package: Package, resource: Resource) -> BinaryIO:
7878
# importlib.machinery loaders are and an AttributeError for
7979
# get_data() will make it clear what is needed from the loader.
8080
loader = cast(ResourceLoader, package.__spec__.loader)
81-
try:
82-
data = loader.get_data(full_path)
83-
except IOError:
81+
data = None
82+
if hasattr(package.__spec__.loader, 'get_data'):
83+
with suppress(IOError):
84+
data = loader.get_data(full_path)
85+
if data is None:
8486
package_name = package.__spec__.name
8587
message = '{!r} resource not found in {!r}'.format(
8688
resource, package_name)
@@ -112,9 +114,11 @@ def open_text(package: Package,
112114
# importlib.machinery loaders are and an AttributeError for
113115
# get_data() will make it clear what is needed from the loader.
114116
loader = cast(ResourceLoader, package.__spec__.loader)
115-
try:
116-
data = loader.get_data(full_path)
117-
except IOError:
117+
data = None
118+
if hasattr(package.__spec__.loader, 'get_data'):
119+
with suppress(IOError):
120+
data = loader.get_data(full_path)
121+
if data is None:
118122
package_name = package.__spec__.name
119123
message = '{!r} resource not found in {!r}'.format(
120124
resource, package_name)
@@ -255,6 +259,11 @@ def contents(package: Package) -> Iterator[str]:
255259
if reader is not None:
256260
yield from reader.contents()
257261
return
262+
# Is the package a namespace package? By definition, namespace packages
263+
# cannot have resources.
264+
if (package.__spec__.origin == 'namespace' and
265+
not package.__spec__.has_location):
266+
return []
258267
package_directory = Path(package.__spec__.origin).parent
259268
try:
260269
yield from os.listdir(str(package_directory))

importlib_resources/docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
``open_text()``, ``read_binary()``, and ``read_text()``. Closes #41
1010
* Fix a bug where unrelated resources could be returned from ``contents()``.
1111
Closes #44
12+
* Correctly prevent namespace packages from containing resources. Closes #20
1213

1314

1415
0.1 (2017-12-05)

importlib_resources/tests/data03/__init__.py

Whitespace-only changes.

importlib_resources/tests/data03/namespace/portion1/__init__.py

Whitespace-only changes.

importlib_resources/tests/data03/namespace/portion2/__init__.py

Whitespace-only changes.

importlib_resources/tests/data03/namespace/resource1.txt

Whitespace-only changes.

importlib_resources/tests/test_resource.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,35 @@ def test_unrelated_contents(self):
111111
{'__init__.py', 'resource2.txt'})
112112

113113

114+
@unittest.skipIf(sys.version_info < (3,), 'No namespace packages in Python 2')
115+
class NamespaceTest(unittest.TestCase):
116+
def test_namespaces_cant_have_resources(self):
117+
contents = set(resources.contents(
118+
'importlib_resources.tests.data03.namespace'))
119+
self.assertEqual(len(contents), 0)
120+
# Even though there is a file in the namespace directory, it is not
121+
# considered a resource, since namespace packages can't have them.
122+
self.assertFalse(resources.is_resource(
123+
'importlib_resources.tests.data03.namespace',
124+
'resource1.txt'))
125+
# We should get an exception if we try to read it or open it.
126+
self.assertRaises(
127+
FileNotFoundError,
128+
resources.open_text,
129+
'importlib_resources.tests.data03.namespace', 'resource1.txt')
130+
self.assertRaises(
131+
FileNotFoundError,
132+
resources.open_binary,
133+
'importlib_resources.tests.data03.namespace', 'resource1.txt')
134+
self.assertRaises(
135+
FileNotFoundError,
136+
resources.read_text,
137+
'importlib_resources.tests.data03.namespace', 'resource1.txt')
138+
self.assertRaises(
139+
FileNotFoundError,
140+
resources.read_binary,
141+
'importlib_resources.tests.data03.namespace', 'resource1.txt')
142+
143+
114144
if __name__ == '__main__':
115145
unittest.main()

0 commit comments

Comments
 (0)