Skip to content

Commit ba687d9

Browse files
authored
pythongh-121735: Fix module-adjacent references in zip files (python#123037)
* pythongh-116608: Apply style and compatibility changes from importlib_metadata. * pythongh-121735: Ensure module-adjacent resources are loadable from a zipfile. * pythongh-121735: Allow all modules to be processed by the ZipReader. * Add blurb * Remove update-zips script, unneeded. * Remove unnecessary references to removed static fixtures. * Remove zipdata fixtures, unused.
1 parent 3bd942f commit ba687d9

40 files changed

+223
-261
lines changed

.gitattributes

-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ Lib/test/cjkencodings/* noeol
2727
Lib/test/tokenizedata/coding20731.py noeol
2828
Lib/test/decimaltestdata/*.decTest noeol
2929
Lib/test/test_email/data/*.txt noeol
30-
Lib/test/test_importlib/resources/data01/* noeol
31-
Lib/test/test_importlib/resources/namespacedata01/* noeol
3230
Lib/test/xmltestdata/* noeol
3331

3432
# Shell scripts should have LF even on Windows because of Cygwin

Lib/importlib/resources/readers.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ def files(self):
3434

3535
class ZipReader(abc.TraversableResources):
3636
def __init__(self, loader, module):
37-
_, _, name = module.rpartition('.')
38-
self.prefix = loader.prefix.replace('\\', '/') + name + '/'
37+
self.prefix = loader.prefix.replace('\\', '/')
38+
if loader.is_package(module):
39+
_, _, name = module.rpartition('.')
40+
self.prefix += name + '/'
3941
self.archive = loader.archive
4042

4143
def open_resource(self, resource):

Lib/test/test_importlib/resources/data01/__init__.py

Whitespace-only changes.
Binary file not shown.

Lib/test/test_importlib/resources/data01/subdirectory/__init__.py

Whitespace-only changes.

Lib/test/test_importlib/resources/data01/subdirectory/binary.file

-1
This file was deleted.
Binary file not shown.

Lib/test/test_importlib/resources/data01/utf-8.file

-1
This file was deleted.

Lib/test/test_importlib/resources/data02/__init__.py

Whitespace-only changes.

Lib/test/test_importlib/resources/data02/one/__init__.py

Whitespace-only changes.

Lib/test/test_importlib/resources/data02/one/resource1.txt

-1
This file was deleted.

Lib/test/test_importlib/resources/data02/subdirectory/subsubdir/resource.txt

-1
This file was deleted.

Lib/test/test_importlib/resources/data02/two/__init__.py

Whitespace-only changes.

Lib/test/test_importlib/resources/data02/two/resource2.txt

-1
This file was deleted.

Lib/test/test_importlib/resources/data03/__init__.py

Whitespace-only changes.

Lib/test/test_importlib/resources/data03/namespace/portion1/__init__.py

Whitespace-only changes.

Lib/test/test_importlib/resources/data03/namespace/portion2/__init__.py

Whitespace-only changes.

Lib/test/test_importlib/resources/data03/namespace/resource1.txt

Whitespace-only changes.
Binary file not shown.

Lib/test/test_importlib/resources/namespacedata01/subdirectory/binary.file

-1
This file was deleted.
Binary file not shown.

Lib/test/test_importlib/resources/namespacedata01/utf-8.file

-1
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import unittest
22
from importlib import resources
33

4-
from . import data01
54
from . import util
65

76

@@ -19,25 +18,21 @@ def test_contents(self):
1918
assert self.expected <= contents
2019

2120

22-
class ContentsDiskTests(ContentsTests, unittest.TestCase):
23-
def setUp(self):
24-
self.data = data01
21+
class ContentsDiskTests(ContentsTests, util.DiskSetup, unittest.TestCase):
22+
pass
2523

2624

2725
class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
2826
pass
2927

3028

31-
class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
29+
class ContentsNamespaceTests(ContentsTests, util.DiskSetup, unittest.TestCase):
30+
MODULE = 'namespacedata01'
31+
3232
expected = {
3333
# no __init__ because of namespace design
3434
'binary.file',
3535
'subdirectory',
3636
'utf-16.file',
3737
'utf-8.file',
3838
}
39-
40-
def setUp(self):
41-
from . import namespacedata01
42-
43-
self.data = namespacedata01

Lib/test/test_importlib/resources/test_files.py

+62-40
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@
66

77
from importlib import resources
88
from importlib.resources.abc import Traversable
9-
from . import data01
109
from . import util
11-
from . import _path
12-
from test.support import os_helper
13-
from test.support import import_helper
1410

1511

1612
@contextlib.contextmanager
@@ -48,70 +44,96 @@ def test_old_parameter(self):
4844
resources.files(package=self.data)
4945

5046

51-
class OpenDiskTests(FilesTests, unittest.TestCase):
52-
def setUp(self):
53-
self.data = data01
47+
class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase):
48+
pass
5449

5550

5651
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
5752
pass
5853

5954

60-
class OpenNamespaceTests(FilesTests, unittest.TestCase):
61-
def setUp(self):
62-
from . import namespacedata01
63-
64-
self.data = namespacedata01
55+
class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase):
56+
MODULE = 'namespacedata01'
6557

6658

6759
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
6860
ZIP_MODULE = 'namespacedata01'
6961

7062

71-
class SiteDir:
72-
def setUp(self):
73-
self.fixtures = contextlib.ExitStack()
74-
self.addCleanup(self.fixtures.close)
75-
self.site_dir = self.fixtures.enter_context(os_helper.temp_dir())
76-
self.fixtures.enter_context(import_helper.DirsOnSysPath(self.site_dir))
77-
self.fixtures.enter_context(import_helper.isolated_modules())
63+
class DirectSpec:
64+
"""
65+
Override behavior of ModuleSetup to write a full spec directly.
66+
"""
67+
68+
MODULE = 'unused'
69+
70+
def load_fixture(self, name):
71+
self.tree_on_path(self.spec)
7872

7973

80-
class ModulesFilesTests(SiteDir, unittest.TestCase):
74+
class ModulesFiles:
75+
spec = {
76+
'mod.py': '',
77+
'res.txt': 'resources are the best',
78+
}
79+
8180
def test_module_resources(self):
8281
"""
8382
A module can have resources found adjacent to the module.
8483
"""
85-
spec = {
86-
'mod.py': '',
87-
'res.txt': 'resources are the best',
88-
}
89-
_path.build(spec, self.site_dir)
9084
import mod
9185

9286
actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
93-
assert actual == spec['res.txt']
87+
assert actual == self.spec['res.txt']
88+
89+
90+
class ModuleFilesDiskTests(DirectSpec, util.DiskSetup, ModulesFiles, unittest.TestCase):
91+
pass
92+
93+
94+
class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.TestCase):
95+
pass
96+
9497

98+
class ImplicitContextFiles:
99+
set_val = textwrap.dedent(
100+
"""
101+
import importlib.resources as res
102+
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
103+
"""
104+
)
105+
spec = {
106+
'somepkg': {
107+
'__init__.py': set_val,
108+
'submod.py': set_val,
109+
'res.txt': 'resources are the best',
110+
},
111+
}
95112

96-
class ImplicitContextFilesTests(SiteDir, unittest.TestCase):
97-
def test_implicit_files(self):
113+
def test_implicit_files_package(self):
98114
"""
99115
Without any parameter, files() will infer the location as the caller.
100116
"""
101-
spec = {
102-
'somepkg': {
103-
'__init__.py': textwrap.dedent(
104-
"""
105-
import importlib.resources as res
106-
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
107-
"""
108-
),
109-
'res.txt': 'resources are the best',
110-
},
111-
}
112-
_path.build(spec, self.site_dir)
113117
assert importlib.import_module('somepkg').val == 'resources are the best'
114118

119+
def test_implicit_files_submodule(self):
120+
"""
121+
Without any parameter, files() will infer the location as the caller.
122+
"""
123+
assert importlib.import_module('somepkg.submod').val == 'resources are the best'
124+
125+
126+
class ImplicitContextFilesDiskTests(
127+
DirectSpec, util.DiskSetup, ImplicitContextFiles, unittest.TestCase
128+
):
129+
pass
130+
131+
132+
class ImplicitContextFilesZipTests(
133+
DirectSpec, util.ZipSetup, ImplicitContextFiles, unittest.TestCase
134+
):
135+
pass
136+
115137

116138
if __name__ == '__main__':
117139
unittest.main()

Lib/test/test_importlib/resources/test_functional.py

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,38 @@
11
import unittest
22
import os
3+
import importlib
34

45
from test.support import warnings_helper
56

67
from importlib import resources
78

9+
from . import util
10+
811
# Since the functional API forwards to Traversable, we only test
912
# filesystem resources here -- not zip files, namespace packages etc.
1013
# We do test for two kinds of Anchor, though.
1114

1215

1316
class StringAnchorMixin:
14-
anchor01 = 'test.test_importlib.resources.data01'
15-
anchor02 = 'test.test_importlib.resources.data02'
17+
anchor01 = 'data01'
18+
anchor02 = 'data02'
1619

1720

1821
class ModuleAnchorMixin:
19-
from . import data01 as anchor01
20-
from . import data02 as anchor02
22+
@property
23+
def anchor01(self):
24+
return importlib.import_module('data01')
25+
26+
@property
27+
def anchor02(self):
28+
return importlib.import_module('data02')
29+
2130

31+
class FunctionalAPIBase(util.DiskSetup):
32+
def setUp(self):
33+
super().setUp()
34+
self.load_fixture('data02')
2235

23-
class FunctionalAPIBase:
2436
def _gen_resourcetxt_path_parts(self):
2537
"""Yield various names of a text file in anchor02, each in a subTest"""
2638
for path_parts in (
@@ -228,16 +240,16 @@ def test_text_errors(self):
228240

229241

230242
class FunctionalAPITest_StringAnchor(
231-
unittest.TestCase,
232-
FunctionalAPIBase,
233243
StringAnchorMixin,
244+
FunctionalAPIBase,
245+
unittest.TestCase,
234246
):
235247
pass
236248

237249

238250
class FunctionalAPITest_ModuleAnchor(
239-
unittest.TestCase,
240-
FunctionalAPIBase,
241251
ModuleAnchorMixin,
252+
FunctionalAPIBase,
253+
unittest.TestCase,
242254
):
243255
pass

Lib/test/test_importlib/resources/test_open.py

+5-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import unittest
22

33
from importlib import resources
4-
from . import data01
54
from . import util
65

76

@@ -65,24 +64,20 @@ def test_open_text_FileNotFoundError(self):
6564
target.open(encoding='utf-8')
6665

6766

68-
class OpenDiskTests(OpenTests, unittest.TestCase):
69-
def setUp(self):
70-
self.data = data01
71-
67+
class OpenDiskTests(OpenTests, util.DiskSetup, unittest.TestCase):
68+
pass
7269

73-
class OpenDiskNamespaceTests(OpenTests, unittest.TestCase):
74-
def setUp(self):
75-
from . import namespacedata01
7670

77-
self.data = namespacedata01
71+
class OpenDiskNamespaceTests(OpenTests, util.DiskSetup, unittest.TestCase):
72+
MODULE = 'namespacedata01'
7873

7974

8075
class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
8176
pass
8277

8378

8479
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
85-
ZIP_MODULE = 'namespacedata01'
80+
MODULE = 'namespacedata01'
8681

8782

8883
if __name__ == '__main__':

Lib/test/test_importlib/resources/test_path.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import unittest
44

55
from importlib import resources
6-
from . import data01
76
from . import util
87

98

@@ -25,9 +24,7 @@ def test_reading(self):
2524
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
2625

2726

28-
class PathDiskTests(PathTests, unittest.TestCase):
29-
data = data01
30-
27+
class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase):
3128
def test_natural_path(self):
3229
# Guarantee the internal implementation detail that
3330
# file-system-backed resources do not get the tempdir

Lib/test/test_importlib/resources/test_read.py

+6-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import unittest
22

33
from importlib import import_module, resources
4-
from . import data01
4+
55
from . import util
66

77

@@ -51,8 +51,8 @@ def test_read_text_with_errors(self):
5151
)
5252

5353

54-
class ReadDiskTests(ReadTests, unittest.TestCase):
55-
data = data01
54+
class ReadDiskTests(ReadTests, util.DiskSetup, unittest.TestCase):
55+
pass
5656

5757

5858
class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
@@ -68,15 +68,12 @@ def test_read_submodule_resource_by_name(self):
6868
self.assertEqual(result, bytes(range(4, 8)))
6969

7070

71-
class ReadNamespaceTests(ReadTests, unittest.TestCase):
72-
def setUp(self):
73-
from . import namespacedata01
74-
75-
self.data = namespacedata01
71+
class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase):
72+
MODULE = 'namespacedata01'
7673

7774

7875
class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
79-
ZIP_MODULE = 'namespacedata01'
76+
MODULE = 'namespacedata01'
8077

8178
def test_read_submodule_resource(self):
8279
submodule = import_module('namespacedata01.subdirectory')

0 commit comments

Comments
 (0)