mirror of https://github.com/python/cpython
gh-121735: Fix module-adjacent references in zip files (#123037)
* gh-116608: Apply style and compatibility changes from importlib_metadata. * gh-121735: Ensure module-adjacent resources are loadable from a zipfile. * gh-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.
This commit is contained in:
parent
3bd942f106
commit
ba687d9481
|
@ -27,8 +27,6 @@ Lib/test/cjkencodings/* noeol
|
||||||
Lib/test/tokenizedata/coding20731.py noeol
|
Lib/test/tokenizedata/coding20731.py noeol
|
||||||
Lib/test/decimaltestdata/*.decTest noeol
|
Lib/test/decimaltestdata/*.decTest noeol
|
||||||
Lib/test/test_email/data/*.txt 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
|
Lib/test/xmltestdata/* noeol
|
||||||
|
|
||||||
# Shell scripts should have LF even on Windows because of Cygwin
|
# Shell scripts should have LF even on Windows because of Cygwin
|
||||||
|
|
|
@ -34,8 +34,10 @@ class FileReader(abc.TraversableResources):
|
||||||
|
|
||||||
class ZipReader(abc.TraversableResources):
|
class ZipReader(abc.TraversableResources):
|
||||||
def __init__(self, loader, module):
|
def __init__(self, loader, module):
|
||||||
|
self.prefix = loader.prefix.replace('\\', '/')
|
||||||
|
if loader.is_package(module):
|
||||||
_, _, name = module.rpartition('.')
|
_, _, name = module.rpartition('.')
|
||||||
self.prefix = loader.prefix.replace('\\', '/') + name + '/'
|
self.prefix += name + '/'
|
||||||
self.archive = loader.archive
|
self.archive = loader.archive
|
||||||
|
|
||||||
def open_resource(self, resource):
|
def open_resource(self, resource):
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
Hello, UTF-8 world!
|
|
|
@ -1 +0,0 @@
|
||||||
one resource
|
|
|
@ -1 +0,0 @@
|
||||||
a resource
|
|
|
@ -1 +0,0 @@
|
||||||
two resource
|
|
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
Hello, UTF-8 world!
|
|
|
@ -1,7 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
|
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,16 +18,17 @@ class ContentsTests:
|
||||||
assert self.expected <= contents
|
assert self.expected <= contents
|
||||||
|
|
||||||
|
|
||||||
class ContentsDiskTests(ContentsTests, unittest.TestCase):
|
class ContentsDiskTests(ContentsTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
self.data = data01
|
|
||||||
|
|
||||||
|
|
||||||
class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
|
class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
|
class ContentsNamespaceTests(ContentsTests, util.DiskSetup, unittest.TestCase):
|
||||||
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
# no __init__ because of namespace design
|
# no __init__ because of namespace design
|
||||||
'binary.file',
|
'binary.file',
|
||||||
|
@ -36,8 +36,3 @@ class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
|
||||||
'utf-16.file',
|
'utf-16.file',
|
||||||
'utf-8.file',
|
'utf-8.file',
|
||||||
}
|
}
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
from . import namespacedata01
|
|
||||||
|
|
||||||
self.data = namespacedata01
|
|
||||||
|
|
|
@ -6,11 +6,7 @@ import contextlib
|
||||||
|
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
from importlib.resources.abc import Traversable
|
from importlib.resources.abc import Traversable
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
from . import _path
|
|
||||||
from test.support import os_helper
|
|
||||||
from test.support import import_helper
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
@ -48,70 +44,96 @@ class FilesTests:
|
||||||
resources.files(package=self.data)
|
resources.files(package=self.data)
|
||||||
|
|
||||||
|
|
||||||
class OpenDiskTests(FilesTests, unittest.TestCase):
|
class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
self.data = data01
|
|
||||||
|
|
||||||
|
|
||||||
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OpenNamespaceTests(FilesTests, unittest.TestCase):
|
class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
MODULE = 'namespacedata01'
|
||||||
from . import namespacedata01
|
|
||||||
|
|
||||||
self.data = namespacedata01
|
|
||||||
|
|
||||||
|
|
||||||
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'namespacedata01'
|
ZIP_MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
class SiteDir:
|
class DirectSpec:
|
||||||
def setUp(self):
|
"""
|
||||||
self.fixtures = contextlib.ExitStack()
|
Override behavior of ModuleSetup to write a full spec directly.
|
||||||
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))
|
MODULE = 'unused'
|
||||||
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:
|
||||||
def test_module_resources(self):
|
|
||||||
"""
|
|
||||||
A module can have resources found adjacent to the module.
|
|
||||||
"""
|
|
||||||
spec = {
|
spec = {
|
||||||
'mod.py': '',
|
'mod.py': '',
|
||||||
'res.txt': 'resources are the best',
|
'res.txt': 'resources are the best',
|
||||||
}
|
}
|
||||||
_path.build(spec, self.site_dir)
|
|
||||||
|
def test_module_resources(self):
|
||||||
|
"""
|
||||||
|
A module can have resources found adjacent to the module.
|
||||||
|
"""
|
||||||
import mod
|
import mod
|
||||||
|
|
||||||
actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
|
actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
|
||||||
assert actual == spec['res.txt']
|
assert actual == self.spec['res.txt']
|
||||||
|
|
||||||
|
|
||||||
class ImplicitContextFilesTests(SiteDir, unittest.TestCase):
|
class ModuleFilesDiskTests(DirectSpec, util.DiskSetup, ModulesFiles, unittest.TestCase):
|
||||||
def test_implicit_files(self):
|
pass
|
||||||
"""
|
|
||||||
Without any parameter, files() will infer the location as the caller.
|
|
||||||
"""
|
class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.TestCase):
|
||||||
spec = {
|
pass
|
||||||
'somepkg': {
|
|
||||||
'__init__.py': textwrap.dedent(
|
|
||||||
|
class ImplicitContextFiles:
|
||||||
|
set_val = textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
import importlib.resources as res
|
import importlib.resources as res
|
||||||
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
|
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',
|
'res.txt': 'resources are the best',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_path.build(spec, self.site_dir)
|
|
||||||
|
def test_implicit_files_package(self):
|
||||||
|
"""
|
||||||
|
Without any parameter, files() will infer the location as the caller.
|
||||||
|
"""
|
||||||
assert importlib.import_module('somepkg').val == 'resources are the best'
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,26 +1,38 @@
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
|
import importlib
|
||||||
|
|
||||||
from test.support import warnings_helper
|
from test.support import warnings_helper
|
||||||
|
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
|
|
||||||
|
from . import util
|
||||||
|
|
||||||
# Since the functional API forwards to Traversable, we only test
|
# Since the functional API forwards to Traversable, we only test
|
||||||
# filesystem resources here -- not zip files, namespace packages etc.
|
# filesystem resources here -- not zip files, namespace packages etc.
|
||||||
# We do test for two kinds of Anchor, though.
|
# We do test for two kinds of Anchor, though.
|
||||||
|
|
||||||
|
|
||||||
class StringAnchorMixin:
|
class StringAnchorMixin:
|
||||||
anchor01 = 'test.test_importlib.resources.data01'
|
anchor01 = 'data01'
|
||||||
anchor02 = 'test.test_importlib.resources.data02'
|
anchor02 = 'data02'
|
||||||
|
|
||||||
|
|
||||||
class ModuleAnchorMixin:
|
class ModuleAnchorMixin:
|
||||||
from . import data01 as anchor01
|
@property
|
||||||
from . import data02 as anchor02
|
def anchor01(self):
|
||||||
|
return importlib.import_module('data01')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def anchor02(self):
|
||||||
|
return importlib.import_module('data02')
|
||||||
|
|
||||||
|
|
||||||
class FunctionalAPIBase:
|
class FunctionalAPIBase(util.DiskSetup):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.load_fixture('data02')
|
||||||
|
|
||||||
def _gen_resourcetxt_path_parts(self):
|
def _gen_resourcetxt_path_parts(self):
|
||||||
"""Yield various names of a text file in anchor02, each in a subTest"""
|
"""Yield various names of a text file in anchor02, each in a subTest"""
|
||||||
for path_parts in (
|
for path_parts in (
|
||||||
|
@ -228,16 +240,16 @@ class FunctionalAPIBase:
|
||||||
|
|
||||||
|
|
||||||
class FunctionalAPITest_StringAnchor(
|
class FunctionalAPITest_StringAnchor(
|
||||||
unittest.TestCase,
|
|
||||||
FunctionalAPIBase,
|
|
||||||
StringAnchorMixin,
|
StringAnchorMixin,
|
||||||
|
FunctionalAPIBase,
|
||||||
|
unittest.TestCase,
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FunctionalAPITest_ModuleAnchor(
|
class FunctionalAPITest_ModuleAnchor(
|
||||||
unittest.TestCase,
|
|
||||||
FunctionalAPIBase,
|
|
||||||
ModuleAnchorMixin,
|
ModuleAnchorMixin,
|
||||||
|
FunctionalAPIBase,
|
||||||
|
unittest.TestCase,
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,16 +64,12 @@ class OpenTests:
|
||||||
target.open(encoding='utf-8')
|
target.open(encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
class OpenDiskTests(OpenTests, unittest.TestCase):
|
class OpenDiskTests(OpenTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
self.data = data01
|
|
||||||
|
|
||||||
|
|
||||||
class OpenDiskNamespaceTests(OpenTests, unittest.TestCase):
|
class OpenDiskNamespaceTests(OpenTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
MODULE = 'namespacedata01'
|
||||||
from . import namespacedata01
|
|
||||||
|
|
||||||
self.data = namespacedata01
|
|
||||||
|
|
||||||
|
|
||||||
class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
@ -82,7 +77,7 @@ class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'namespacedata01'
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -3,7 +3,6 @@ import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,9 +24,7 @@ class PathTests:
|
||||||
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
|
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
|
||||||
|
|
||||||
|
|
||||||
class PathDiskTests(PathTests, unittest.TestCase):
|
class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase):
|
||||||
data = data01
|
|
||||||
|
|
||||||
def test_natural_path(self):
|
def test_natural_path(self):
|
||||||
# Guarantee the internal implementation detail that
|
# Guarantee the internal implementation detail that
|
||||||
# file-system-backed resources do not get the tempdir
|
# file-system-backed resources do not get the tempdir
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from importlib import import_module, resources
|
from importlib import import_module, resources
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@ class ReadTests:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReadDiskTests(ReadTests, unittest.TestCase):
|
class ReadDiskTests(ReadTests, util.DiskSetup, unittest.TestCase):
|
||||||
data = data01
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
@ -68,15 +68,12 @@ class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||||
self.assertEqual(result, bytes(range(4, 8)))
|
self.assertEqual(result, bytes(range(4, 8)))
|
||||||
|
|
||||||
|
|
||||||
class ReadNamespaceTests(ReadTests, unittest.TestCase):
|
class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
MODULE = 'namespacedata01'
|
||||||
from . import namespacedata01
|
|
||||||
|
|
||||||
self.data = namespacedata01
|
|
||||||
|
|
||||||
|
|
||||||
class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'namespacedata01'
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
def test_read_submodule_resource(self):
|
def test_read_submodule_resource(self):
|
||||||
submodule = import_module('namespacedata01.subdirectory')
|
submodule = import_module('namespacedata01.subdirectory')
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from importlib.readers import MultiplexedPath, NamespaceReader
|
from importlib.readers import MultiplexedPath, NamespaceReader
|
||||||
|
|
||||||
|
from . import util
|
||||||
|
|
||||||
class MultiplexedPathTest(unittest.TestCase):
|
|
||||||
@classmethod
|
class MultiplexedPathTest(util.DiskSetup, unittest.TestCase):
|
||||||
def setUpClass(cls):
|
MODULE = 'namespacedata01'
|
||||||
cls.folder = pathlib.Path(__file__).parent / 'namespacedata01'
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.folder = pathlib.Path(self.data.__path__[0])
|
||||||
|
self.data01 = pathlib.Path(self.load_fixture('data01').__file__).parent
|
||||||
|
self.data02 = pathlib.Path(self.load_fixture('data02').__file__).parent
|
||||||
|
|
||||||
def test_init_no_paths(self):
|
def test_init_no_paths(self):
|
||||||
with self.assertRaises(FileNotFoundError):
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
@ -31,9 +36,8 @@ class MultiplexedPathTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_iterdir_duplicate(self):
|
def test_iterdir_duplicate(self):
|
||||||
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
|
||||||
contents = {
|
contents = {
|
||||||
path.name for path in MultiplexedPath(self.folder, data01).iterdir()
|
path.name for path in MultiplexedPath(self.folder, self.data01).iterdir()
|
||||||
}
|
}
|
||||||
for remove in ('__pycache__', '__init__.pyc'):
|
for remove in ('__pycache__', '__init__.pyc'):
|
||||||
try:
|
try:
|
||||||
|
@ -61,9 +65,8 @@ class MultiplexedPathTest(unittest.TestCase):
|
||||||
path.open()
|
path.open()
|
||||||
|
|
||||||
def test_join_path(self):
|
def test_join_path(self):
|
||||||
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
prefix = str(self.folder.parent)
|
||||||
prefix = str(data01.parent)
|
path = MultiplexedPath(self.folder, self.data01)
|
||||||
path = MultiplexedPath(self.folder, data01)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
|
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
|
||||||
os.path.join('namespacedata01', 'binary.file'),
|
os.path.join('namespacedata01', 'binary.file'),
|
||||||
|
@ -83,10 +86,8 @@ class MultiplexedPathTest(unittest.TestCase):
|
||||||
assert not path.joinpath('imaginary/foo.py').exists()
|
assert not path.joinpath('imaginary/foo.py').exists()
|
||||||
|
|
||||||
def test_join_path_common_subdir(self):
|
def test_join_path_common_subdir(self):
|
||||||
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
prefix = str(self.data02.parent)
|
||||||
data02 = pathlib.Path(__file__).parent.joinpath('data02')
|
path = MultiplexedPath(self.data01, self.data02)
|
||||||
prefix = str(data01.parent)
|
|
||||||
path = MultiplexedPath(data01, data02)
|
|
||||||
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
|
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :],
|
str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :],
|
||||||
|
@ -106,16 +107,8 @@ class MultiplexedPathTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NamespaceReaderTest(unittest.TestCase):
|
class NamespaceReaderTest(util.DiskSetup, unittest.TestCase):
|
||||||
site_dir = str(pathlib.Path(__file__).parent)
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
sys.path.append(cls.site_dir)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tearDownClass(cls):
|
|
||||||
sys.path.remove(cls.site_dir)
|
|
||||||
|
|
||||||
def test_init_error(self):
|
def test_init_error(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
|
@ -125,7 +118,7 @@ class NamespaceReaderTest(unittest.TestCase):
|
||||||
namespacedata01 = import_module('namespacedata01')
|
namespacedata01 = import_module('namespacedata01')
|
||||||
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
||||||
|
|
||||||
root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01'))
|
root = self.data.__path__[0]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
reader.resource_path('binary.file'), os.path.join(root, 'binary.file')
|
reader.resource_path('binary.file'), os.path.join(root, 'binary.file')
|
||||||
)
|
)
|
||||||
|
@ -134,9 +127,8 @@ class NamespaceReaderTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_files(self):
|
def test_files(self):
|
||||||
namespacedata01 = import_module('namespacedata01')
|
reader = NamespaceReader(self.data.__spec__.submodule_search_locations)
|
||||||
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
root = self.data.__path__[0]
|
||||||
root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01'))
|
|
||||||
self.assertIsInstance(reader.files(), MultiplexedPath)
|
self.assertIsInstance(reader.files(), MultiplexedPath)
|
||||||
self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')")
|
self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')")
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import sys
|
|
||||||
import unittest
|
import unittest
|
||||||
import pathlib
|
|
||||||
|
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
from importlib import resources, import_module
|
from importlib import resources, import_module
|
||||||
|
|
||||||
|
@ -24,9 +21,8 @@ class ResourceTests:
|
||||||
self.assertTrue(target.is_dir())
|
self.assertTrue(target.is_dir())
|
||||||
|
|
||||||
|
|
||||||
class ResourceDiskTests(ResourceTests, unittest.TestCase):
|
class ResourceDiskTests(ResourceTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
self.data = data01
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase):
|
class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
@ -37,33 +33,39 @@ def names(traversable):
|
||||||
return {item.name for item in traversable.iterdir()}
|
return {item.name for item in traversable.iterdir()}
|
||||||
|
|
||||||
|
|
||||||
class ResourceLoaderTests(unittest.TestCase):
|
class ResourceLoaderTests(util.DiskSetup, unittest.TestCase):
|
||||||
def test_resource_contents(self):
|
def test_resource_contents(self):
|
||||||
package = util.create_package(
|
package = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C']
|
file=self.data, path=self.data.__file__, contents=['A', 'B', 'C']
|
||||||
)
|
)
|
||||||
self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'})
|
self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'})
|
||||||
|
|
||||||
def test_is_file(self):
|
def test_is_file(self):
|
||||||
package = util.create_package(
|
package = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
file=self.data,
|
||||||
|
path=self.data.__file__,
|
||||||
|
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||||
)
|
)
|
||||||
self.assertTrue(resources.files(package).joinpath('B').is_file())
|
self.assertTrue(resources.files(package).joinpath('B').is_file())
|
||||||
|
|
||||||
def test_is_dir(self):
|
def test_is_dir(self):
|
||||||
package = util.create_package(
|
package = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
file=self.data,
|
||||||
|
path=self.data.__file__,
|
||||||
|
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||||
)
|
)
|
||||||
self.assertTrue(resources.files(package).joinpath('D').is_dir())
|
self.assertTrue(resources.files(package).joinpath('D').is_dir())
|
||||||
|
|
||||||
def test_resource_missing(self):
|
def test_resource_missing(self):
|
||||||
package = util.create_package(
|
package = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
file=self.data,
|
||||||
|
path=self.data.__file__,
|
||||||
|
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||||
)
|
)
|
||||||
self.assertFalse(resources.files(package).joinpath('Z').is_file())
|
self.assertFalse(resources.files(package).joinpath('Z').is_file())
|
||||||
|
|
||||||
|
|
||||||
class ResourceCornerCaseTests(unittest.TestCase):
|
class ResourceCornerCaseTests(util.DiskSetup, unittest.TestCase):
|
||||||
def test_package_has_no_reader_fallback(self):
|
def test_package_has_no_reader_fallback(self):
|
||||||
"""
|
"""
|
||||||
Test odd ball packages which:
|
Test odd ball packages which:
|
||||||
|
@ -72,7 +74,7 @@ class ResourceCornerCaseTests(unittest.TestCase):
|
||||||
# 3. Are not in a zip file
|
# 3. Are not in a zip file
|
||||||
"""
|
"""
|
||||||
module = util.create_package(
|
module = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C']
|
file=self.data, path=self.data.__file__, contents=['A', 'B', 'C']
|
||||||
)
|
)
|
||||||
# Give the module a dummy loader.
|
# Give the module a dummy loader.
|
||||||
module.__loader__ = object()
|
module.__loader__ = object()
|
||||||
|
@ -83,9 +85,7 @@ class ResourceCornerCaseTests(unittest.TestCase):
|
||||||
self.assertFalse(resources.files(module).joinpath('A').is_file())
|
self.assertFalse(resources.files(module).joinpath('A').is_file())
|
||||||
|
|
||||||
|
|
||||||
class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
|
class ResourceFromZipsTest01(util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'data01'
|
|
||||||
|
|
||||||
def test_is_submodule_resource(self):
|
def test_is_submodule_resource(self):
|
||||||
submodule = import_module('data01.subdirectory')
|
submodule = import_module('data01.subdirectory')
|
||||||
self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
|
self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
|
||||||
|
@ -116,8 +116,8 @@ class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
|
||||||
assert not data.parent.exists()
|
assert not data.parent.exists()
|
||||||
|
|
||||||
|
|
||||||
class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
|
class ResourceFromZipsTest02(util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'data02'
|
MODULE = 'data02'
|
||||||
|
|
||||||
def test_unrelated_contents(self):
|
def test_unrelated_contents(self):
|
||||||
"""
|
"""
|
||||||
|
@ -134,7 +134,7 @@ class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeletingZipsTest(util.ZipSetupBase, unittest.TestCase):
|
class DeletingZipsTest(util.ZipSetup, unittest.TestCase):
|
||||||
"""Having accessed resources in a zip file should not keep an open
|
"""Having accessed resources in a zip file should not keep an open
|
||||||
reference to the zip.
|
reference to the zip.
|
||||||
"""
|
"""
|
||||||
|
@ -216,24 +216,20 @@ class ResourceFromNamespaceTests:
|
||||||
self.assertEqual(contents, {'binary.file'})
|
self.assertEqual(contents, {'binary.file'})
|
||||||
|
|
||||||
|
|
||||||
class ResourceFromNamespaceDiskTests(ResourceFromNamespaceTests, unittest.TestCase):
|
class ResourceFromNamespaceDiskTests(
|
||||||
site_dir = str(pathlib.Path(__file__).parent)
|
util.DiskSetup,
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
sys.path.append(cls.site_dir)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tearDownClass(cls):
|
|
||||||
sys.path.remove(cls.site_dir)
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceFromNamespaceZipTests(
|
|
||||||
util.ZipSetupBase,
|
|
||||||
ResourceFromNamespaceTests,
|
ResourceFromNamespaceTests,
|
||||||
unittest.TestCase,
|
unittest.TestCase,
|
||||||
):
|
):
|
||||||
ZIP_MODULE = 'namespacedata01'
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFromNamespaceZipTests(
|
||||||
|
util.ZipSetup,
|
||||||
|
ResourceFromNamespaceTests,
|
||||||
|
unittest.TestCase,
|
||||||
|
):
|
||||||
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
"""
|
|
||||||
Generate the zip test data files.
|
|
||||||
|
|
||||||
Run to build the tests/zipdataNN/ziptestdata.zip files from
|
|
||||||
files in tests/dataNN.
|
|
||||||
|
|
||||||
Replaces the file with the working copy, but does commit anything
|
|
||||||
to the source repo.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
>>> from unittest import mock
|
|
||||||
>>> monkeypatch = getfixture('monkeypatch')
|
|
||||||
>>> monkeypatch.setattr(zipfile, 'ZipFile', mock.MagicMock())
|
|
||||||
>>> print(); main() # print workaround for bpo-32509
|
|
||||||
<BLANKLINE>
|
|
||||||
...data01... -> ziptestdata/...
|
|
||||||
...
|
|
||||||
...data02... -> ziptestdata/...
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
suffixes = '01', '02'
|
|
||||||
tuple(map(generate, suffixes))
|
|
||||||
|
|
||||||
|
|
||||||
def generate(suffix):
|
|
||||||
root = pathlib.Path(__file__).parent.relative_to(os.getcwd())
|
|
||||||
zfpath = root / f'zipdata{suffix}/ziptestdata.zip'
|
|
||||||
with zipfile.ZipFile(zfpath, 'w') as zf:
|
|
||||||
for src, rel in walk(root / f'data{suffix}'):
|
|
||||||
dst = 'ziptestdata' / pathlib.PurePosixPath(rel.as_posix())
|
|
||||||
print(src, '->', dst)
|
|
||||||
zf.write(src, dst)
|
|
||||||
|
|
||||||
|
|
||||||
def walk(datapath):
|
|
||||||
for dirpath, dirnames, filenames in os.walk(datapath):
|
|
||||||
with contextlib.suppress(ValueError):
|
|
||||||
dirnames.remove('__pycache__')
|
|
||||||
for filename in filenames:
|
|
||||||
res = pathlib.Path(dirpath) / filename
|
|
||||||
rel = res.relative_to(datapath)
|
|
||||||
yield res, rel
|
|
||||||
|
|
||||||
|
|
||||||
__name__ == '__main__' and main()
|
|
|
@ -6,10 +6,10 @@ import types
|
||||||
import pathlib
|
import pathlib
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from . import data01
|
|
||||||
from importlib.resources.abc import ResourceReader
|
from importlib.resources.abc import ResourceReader
|
||||||
from test.support import import_helper, os_helper
|
from test.support import import_helper, os_helper
|
||||||
from . import zip as zip_
|
from . import zip as zip_
|
||||||
|
from . import _path
|
||||||
|
|
||||||
|
|
||||||
from importlib.machinery import ModuleSpec
|
from importlib.machinery import ModuleSpec
|
||||||
|
@ -68,7 +68,7 @@ def create_package(file=None, path=None, is_package=True, contents=()):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CommonTests(metaclass=abc.ABCMeta):
|
class CommonTestsBase(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
Tests shared by test_open, test_path, and test_read.
|
Tests shared by test_open, test_path, and test_read.
|
||||||
"""
|
"""
|
||||||
|
@ -84,34 +84,34 @@ class CommonTests(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
Passing in the package name should succeed.
|
Passing in the package name should succeed.
|
||||||
"""
|
"""
|
||||||
self.execute(data01.__name__, 'utf-8.file')
|
self.execute(self.data.__name__, 'utf-8.file')
|
||||||
|
|
||||||
def test_package_object(self):
|
def test_package_object(self):
|
||||||
"""
|
"""
|
||||||
Passing in the package itself should succeed.
|
Passing in the package itself should succeed.
|
||||||
"""
|
"""
|
||||||
self.execute(data01, 'utf-8.file')
|
self.execute(self.data, 'utf-8.file')
|
||||||
|
|
||||||
def test_string_path(self):
|
def test_string_path(self):
|
||||||
"""
|
"""
|
||||||
Passing in a string for the path should succeed.
|
Passing in a string for the path should succeed.
|
||||||
"""
|
"""
|
||||||
path = 'utf-8.file'
|
path = 'utf-8.file'
|
||||||
self.execute(data01, path)
|
self.execute(self.data, path)
|
||||||
|
|
||||||
def test_pathlib_path(self):
|
def test_pathlib_path(self):
|
||||||
"""
|
"""
|
||||||
Passing in a pathlib.PurePath object for the path should succeed.
|
Passing in a pathlib.PurePath object for the path should succeed.
|
||||||
"""
|
"""
|
||||||
path = pathlib.PurePath('utf-8.file')
|
path = pathlib.PurePath('utf-8.file')
|
||||||
self.execute(data01, path)
|
self.execute(self.data, path)
|
||||||
|
|
||||||
def test_importing_module_as_side_effect(self):
|
def test_importing_module_as_side_effect(self):
|
||||||
"""
|
"""
|
||||||
The anchor package can already be imported.
|
The anchor package can already be imported.
|
||||||
"""
|
"""
|
||||||
del sys.modules[data01.__name__]
|
del sys.modules[self.data.__name__]
|
||||||
self.execute(data01.__name__, 'utf-8.file')
|
self.execute(self.data.__name__, 'utf-8.file')
|
||||||
|
|
||||||
def test_missing_path(self):
|
def test_missing_path(self):
|
||||||
"""
|
"""
|
||||||
|
@ -141,24 +141,66 @@ class CommonTests(metaclass=abc.ABCMeta):
|
||||||
self.execute(package, 'utf-8.file')
|
self.execute(package, 'utf-8.file')
|
||||||
|
|
||||||
|
|
||||||
class ZipSetupBase:
|
fixtures = dict(
|
||||||
ZIP_MODULE = 'data01'
|
data01={
|
||||||
|
'__init__.py': '',
|
||||||
|
'binary.file': bytes(range(4)),
|
||||||
|
'utf-16.file': 'Hello, UTF-16 world!\n'.encode('utf-16'),
|
||||||
|
'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'),
|
||||||
|
'subdirectory': {
|
||||||
|
'__init__.py': '',
|
||||||
|
'binary.file': bytes(range(4, 8)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data02={
|
||||||
|
'__init__.py': '',
|
||||||
|
'one': {'__init__.py': '', 'resource1.txt': 'one resource'},
|
||||||
|
'two': {'__init__.py': '', 'resource2.txt': 'two resource'},
|
||||||
|
'subdirectory': {'subsubdir': {'resource.txt': 'a resource'}},
|
||||||
|
},
|
||||||
|
namespacedata01={
|
||||||
|
'binary.file': bytes(range(4)),
|
||||||
|
'utf-16.file': 'Hello, UTF-16 world!\n'.encode('utf-16'),
|
||||||
|
'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'),
|
||||||
|
'subdirectory': {
|
||||||
|
'binary.file': bytes(range(12, 16)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleSetup:
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.fixtures = contextlib.ExitStack()
|
self.fixtures = contextlib.ExitStack()
|
||||||
self.addCleanup(self.fixtures.close)
|
self.addCleanup(self.fixtures.close)
|
||||||
|
|
||||||
self.fixtures.enter_context(import_helper.isolated_modules())
|
self.fixtures.enter_context(import_helper.isolated_modules())
|
||||||
|
self.data = self.load_fixture(self.MODULE)
|
||||||
|
|
||||||
|
def load_fixture(self, module):
|
||||||
|
self.tree_on_path({module: fixtures[module]})
|
||||||
|
return importlib.import_module(module)
|
||||||
|
|
||||||
|
|
||||||
|
class ZipSetup(ModuleSetup):
|
||||||
|
MODULE = 'data01'
|
||||||
|
|
||||||
|
def tree_on_path(self, spec):
|
||||||
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
|
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
|
||||||
modules = pathlib.Path(temp_dir) / 'zipped modules.zip'
|
modules = pathlib.Path(temp_dir) / 'zipped modules.zip'
|
||||||
src_path = pathlib.Path(__file__).parent.joinpath(self.ZIP_MODULE)
|
|
||||||
self.fixtures.enter_context(
|
self.fixtures.enter_context(
|
||||||
import_helper.DirsOnSysPath(str(zip_.make_zip_file(src_path, modules)))
|
import_helper.DirsOnSysPath(str(zip_.make_zip_file(spec, modules)))
|
||||||
)
|
)
|
||||||
|
|
||||||
self.data = importlib.import_module(self.ZIP_MODULE)
|
|
||||||
|
class DiskSetup(ModuleSetup):
|
||||||
|
MODULE = 'data01'
|
||||||
|
|
||||||
|
def tree_on_path(self, spec):
|
||||||
|
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
|
||||||
|
_path.build(spec, pathlib.Path(temp_dir))
|
||||||
|
self.fixtures.enter_context(import_helper.DirsOnSysPath(temp_dir))
|
||||||
|
|
||||||
|
|
||||||
class ZipSetup(ZipSetupBase):
|
class CommonTests(DiskSetup, CommonTestsBase):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -2,29 +2,23 @@
|
||||||
Generate zip test data files.
|
Generate zip test data files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
|
||||||
def make_zip_file(src, dst):
|
def make_zip_file(tree, dst):
|
||||||
"""
|
"""
|
||||||
Zip the files in src into a new zipfile at dst.
|
Zip the files in tree into a new zipfile at dst.
|
||||||
"""
|
"""
|
||||||
with zipfile.ZipFile(dst, 'w') as zf:
|
with zipfile.ZipFile(dst, 'w') as zf:
|
||||||
for src_path, rel in walk(src):
|
for name, contents in walk(tree):
|
||||||
dst_name = src.name / pathlib.PurePosixPath(rel.as_posix())
|
zf.writestr(name, contents)
|
||||||
zf.write(src_path, dst_name)
|
|
||||||
zipfile._path.CompleteDirs.inject(zf)
|
zipfile._path.CompleteDirs.inject(zf)
|
||||||
return dst
|
return dst
|
||||||
|
|
||||||
|
|
||||||
def walk(datapath):
|
def walk(tree, prefix=''):
|
||||||
for dirpath, dirnames, filenames in os.walk(datapath):
|
for name, contents in tree.items():
|
||||||
with contextlib.suppress(ValueError):
|
if isinstance(contents, dict):
|
||||||
dirnames.remove('__pycache__')
|
yield from walk(contents, prefix=f'{prefix}{name}/')
|
||||||
for filename in filenames:
|
else:
|
||||||
res = pathlib.Path(dirpath) / filename
|
yield f'{prefix}{name}', contents
|
||||||
rel = res.relative_to(datapath)
|
|
||||||
yield res, rel
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -256,17 +256,9 @@ class zipimporter(_bootstrap_external._LoaderBasics):
|
||||||
|
|
||||||
|
|
||||||
def get_resource_reader(self, fullname):
|
def get_resource_reader(self, fullname):
|
||||||
"""Return the ResourceReader for a package in a zip file.
|
"""Return the ResourceReader for a module in a zip file."""
|
||||||
|
|
||||||
If 'fullname' is a package within the zip file, return the
|
|
||||||
'ResourceReader' object for the package. Otherwise return None.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if not self.is_package(fullname):
|
|
||||||
return None
|
|
||||||
except ZipImportError:
|
|
||||||
return None
|
|
||||||
from importlib.readers import ZipReader
|
from importlib.readers import ZipReader
|
||||||
|
|
||||||
return ZipReader(self, fullname)
|
return ZipReader(self, fullname)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2489,21 +2489,6 @@ TESTSUBDIRS= idlelib/idle_test \
|
||||||
test/test_importlib/namespace_pkgs/project3/parent/child \
|
test/test_importlib/namespace_pkgs/project3/parent/child \
|
||||||
test/test_importlib/partial \
|
test/test_importlib/partial \
|
||||||
test/test_importlib/resources \
|
test/test_importlib/resources \
|
||||||
test/test_importlib/resources/data01 \
|
|
||||||
test/test_importlib/resources/data01/subdirectory \
|
|
||||||
test/test_importlib/resources/data02 \
|
|
||||||
test/test_importlib/resources/data02/one \
|
|
||||||
test/test_importlib/resources/data02/subdirectory \
|
|
||||||
test/test_importlib/resources/data02/subdirectory/subsubdir \
|
|
||||||
test/test_importlib/resources/data02/two \
|
|
||||||
test/test_importlib/resources/data03 \
|
|
||||||
test/test_importlib/resources/data03/namespace \
|
|
||||||
test/test_importlib/resources/data03/namespace/portion1 \
|
|
||||||
test/test_importlib/resources/data03/namespace/portion2 \
|
|
||||||
test/test_importlib/resources/namespacedata01 \
|
|
||||||
test/test_importlib/resources/namespacedata01/subdirectory \
|
|
||||||
test/test_importlib/resources/zipdata01 \
|
|
||||||
test/test_importlib/resources/zipdata02 \
|
|
||||||
test/test_importlib/source \
|
test/test_importlib/source \
|
||||||
test/test_inspect \
|
test/test_inspect \
|
||||||
test/test_interpreters \
|
test/test_interpreters \
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
When working with zip archives, importlib.resources now properly honors
|
||||||
|
module-adjacent references (e.g. ``files(pkg.mod)`` and not just
|
||||||
|
``files(pkg)``).
|
Loading…
Reference in New Issue