Skip to content

Commit

Permalink
[3.12] pythongh-121735: Fix module-adjacent references in zip files (p…
Browse files Browse the repository at this point in the history
…ythonGH-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.
(cherry picked from commit ba687d9)

Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
  • Loading branch information
jaraco committed Sep 12, 2024
1 parent 53af5b2 commit 330cfcd
Show file tree
Hide file tree
Showing 38 changed files with 282 additions and 294 deletions.
2 changes: 0 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ Lib/test/cjkencodings/* noeol
Lib/test/tokenizedata/coding20731.py noeol
Lib/test/decimaltestdata/*.decTest noeol
Lib/test/test_email/data/*.txt noeol
Lib/test/test_importlib/resources/data01/* noeol
Lib/test/test_importlib/resources/namespacedata01/* noeol
Lib/test/xmltestdata/* noeol

# Shell scripts should have LF even on Windows because of Cygwin
Expand Down
6 changes: 4 additions & 2 deletions Lib/importlib/resources/readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ def files(self):

class ZipReader(abc.TraversableResources):
def __init__(self, loader, module):
_, _, name = module.rpartition('.')
self.prefix = loader.prefix.replace('\\', '/') + name + '/'
self.prefix = loader.prefix.replace('\\', '/')
if loader.is_package(module):
_, _, name = module.rpartition('.')
self.prefix += name + '/'
self.archive = loader.archive

def open_resource(self, resource):
Expand Down
Empty file.
Binary file removed Lib/test/test_importlib/resources/data01/binary.file
Binary file not shown.
Empty file.
Binary file not shown.
Binary file removed Lib/test/test_importlib/resources/data01/utf-16.file
Binary file not shown.
1 change: 0 additions & 1 deletion Lib/test/test_importlib/resources/data01/utf-8.file

This file was deleted.

Empty file.
Empty file.
1 change: 0 additions & 1 deletion Lib/test/test_importlib/resources/data02/one/resource1.txt

This file was deleted.

This file was deleted.

Empty file.
1 change: 0 additions & 1 deletion Lib/test/test_importlib/resources/data02/two/resource2.txt

This file was deleted.

Empty file.
Empty file.
Empty file.
Empty file.
Binary file not shown.
Binary file not shown.

This file was deleted.

15 changes: 5 additions & 10 deletions Lib/test/test_importlib/resources/test_contents.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import unittest
from importlib import resources

from . import data01
from . import util


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


class ContentsDiskTests(ContentsTests, unittest.TestCase):
def setUp(self):
self.data = data01
class ContentsDiskTests(ContentsTests, util.DiskSetup, unittest.TestCase):
pass


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


class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
class ContentsNamespaceTests(ContentsTests, util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'

expected = {
# no __init__ because of namespace design
# no subdirectory as incidental difference in fixture
'binary.file',
'utf-16.file',
'utf-8.file',
}

def setUp(self):
from . import namespacedata01

self.data = namespacedata01
104 changes: 65 additions & 39 deletions Lib/test/test_importlib/resources/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@

from importlib import resources
from importlib.resources.abc import Traversable
from . import data01
from . import util
from . import _path
from test.support import os_helper
from test.support import import_helper


@contextlib.contextmanager
Expand Down Expand Up @@ -48,66 +44,96 @@ def test_old_parameter(self):
resources.files(package=self.data)


class OpenDiskTests(FilesTests, unittest.TestCase):
def setUp(self):
self.data = data01
class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase):
pass


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


class OpenNamespaceTests(FilesTests, unittest.TestCase):
def setUp(self):
from . import namespacedata01
class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'


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


self.data = namespacedata01
class DirectSpec:
"""
Override behavior of ModuleSetup to write a full spec directly.
"""

MODULE = 'unused'

class SiteDir:
def setUp(self):
self.fixtures = contextlib.ExitStack()
self.addCleanup(self.fixtures.close)
self.site_dir = self.fixtures.enter_context(os_helper.temp_dir())
self.fixtures.enter_context(import_helper.DirsOnSysPath(self.site_dir))
self.fixtures.enter_context(import_helper.isolated_modules())
def load_fixture(self, name):
self.tree_on_path(self.spec)


class ModulesFilesTests(SiteDir, unittest.TestCase):
class ModulesFiles:
spec = {
'mod.py': '',
'res.txt': 'resources are the best',
}

def test_module_resources(self):
"""
A module can have resources found adjacent to the module.
"""
spec = {
'mod.py': '',
'res.txt': 'resources are the best',
}
_path.build(spec, self.site_dir)
import mod

actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
assert actual == spec['res.txt']
assert actual == self.spec['res.txt']


class ModuleFilesDiskTests(DirectSpec, util.DiskSetup, ModulesFiles, unittest.TestCase):
pass


class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.TestCase):
pass

class ImplicitContextFilesTests(SiteDir, unittest.TestCase):
def test_implicit_files(self):

class ImplicitContextFiles:
set_val = textwrap.dedent(
"""
import importlib.resources as res
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
"""
)
spec = {
'somepkg': {
'__init__.py': set_val,
'submod.py': set_val,
'res.txt': 'resources are the best',
},
}

def test_implicit_files_package(self):
"""
Without any parameter, files() will infer the location as the caller.
"""
spec = {
'somepkg': {
'__init__.py': textwrap.dedent(
"""
import importlib.resources as res
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
"""
),
'res.txt': 'resources are the best',
},
}
_path.build(spec, self.site_dir)
assert importlib.import_module('somepkg').val == 'resources are the best'

def test_implicit_files_submodule(self):
"""
Without any parameter, files() will infer the location as the caller.
"""
assert importlib.import_module('somepkg.submod').val == 'resources are the best'


class ImplicitContextFilesDiskTests(
DirectSpec, util.DiskSetup, ImplicitContextFiles, unittest.TestCase
):
pass


class ImplicitContextFilesZipTests(
DirectSpec, util.ZipSetup, ImplicitContextFiles, unittest.TestCase
):
pass


if __name__ == '__main__':
unittest.main()
17 changes: 8 additions & 9 deletions Lib/test/test_importlib/resources/test_open.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import unittest

from importlib import resources
from . import data01
from . import util


Expand Down Expand Up @@ -65,21 +64,21 @@ def test_open_text_FileNotFoundError(self):
target.open(encoding='utf-8')


class OpenDiskTests(OpenTests, unittest.TestCase):
def setUp(self):
self.data = data01

class OpenDiskTests(OpenTests, util.DiskSetup, unittest.TestCase):
pass

class OpenDiskNamespaceTests(OpenTests, unittest.TestCase):
def setUp(self):
from . import namespacedata01

self.data = namespacedata01
class OpenDiskNamespaceTests(OpenTests, util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'


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


class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
MODULE = 'namespacedata01'


if __name__ == '__main__':
unittest.main()
5 changes: 1 addition & 4 deletions Lib/test/test_importlib/resources/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import unittest

from importlib import resources
from . import data01
from . import util


Expand All @@ -29,9 +28,7 @@ def test_reading(self):
self.assertEqual('Hello, UTF-8 world!\n', text)


class PathDiskTests(PathTests, unittest.TestCase):
data = data01

class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase):
def test_natural_path(self):
# Guarantee the internal implementation detail that
# file-system-backed resources do not get the tempdir
Expand Down
28 changes: 21 additions & 7 deletions Lib/test/test_importlib/resources/test_read.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import unittest

from importlib import import_module, resources
from . import data01

from . import util


Expand Down Expand Up @@ -51,8 +51,8 @@ def test_read_text_with_errors(self):
)


class ReadDiskTests(ReadTests, unittest.TestCase):
data = data01
class ReadDiskTests(ReadTests, util.DiskSetup, unittest.TestCase):
pass


class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
Expand All @@ -70,11 +70,25 @@ def test_read_submodule_resource_by_name(self):
self.assertEqual(result, b'\0\1\2\3')


class ReadNamespaceTests(ReadTests, unittest.TestCase):
def setUp(self):
from . import namespacedata01
class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'


class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
MODULE = 'namespacedata01'

def test_read_submodule_resource(self):
submodule = import_module('namespacedata01.subdirectory')
result = resources.files(submodule).joinpath('binary.file').read_bytes()
self.assertEqual(result, bytes(range(12, 16)))

self.data = namespacedata01
def test_read_submodule_resource_by_name(self):
result = (
resources.files('namespacedata01.subdirectory')
.joinpath('binary.file')
.read_bytes()
)
self.assertEqual(result, bytes(range(12, 16)))


if __name__ == '__main__':
Expand Down
Loading

0 comments on commit 330cfcd

Please sign in to comment.