From e5a32a7d89103b4aedecefa3ab4c3524bab2388b Mon Sep 17 00:00:00 2001 From: Gatsik <74517072+Gatsik@users.noreply.github.com> Date: Sat, 17 Aug 2024 01:05:56 +0300 Subject: [PATCH] gh-121735: Allow to list package resources inside zip by module name which is inside that package --- Lib/importlib/resources/readers.py | 4 +++- .../resources/data04/package/__init__.py | 0 .../resources/data04/package/module.py | 0 .../resources/data04/package/resource.txt | 1 + .../test_importlib/resources/test_resource.py | 21 +++++++++++++++++++ Lib/zipimport.py | 14 ++++++++----- 6 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 Lib/test/test_importlib/resources/data04/package/__init__.py create mode 100644 Lib/test/test_importlib/resources/data04/package/module.py create mode 100644 Lib/test/test_importlib/resources/data04/package/resource.txt diff --git a/Lib/importlib/resources/readers.py b/Lib/importlib/resources/readers.py index b86cdeff57c4c2..21b4f621f0f0c4 100644 --- a/Lib/importlib/resources/readers.py +++ b/Lib/importlib/resources/readers.py @@ -35,7 +35,9 @@ 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('\\', '/') + name + if not self.prefix.endswith('/'): + self.prefix += '/' self.archive = loader.archive def open_resource(self, resource): diff --git a/Lib/test/test_importlib/resources/data04/package/__init__.py b/Lib/test/test_importlib/resources/data04/package/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/test_importlib/resources/data04/package/module.py b/Lib/test/test_importlib/resources/data04/package/module.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/test_importlib/resources/data04/package/resource.txt b/Lib/test/test_importlib/resources/data04/package/resource.txt new file mode 100644 index 00000000000000..1269488f7fb1f4 --- /dev/null +++ b/Lib/test/test_importlib/resources/data04/package/resource.txt @@ -0,0 +1 @@ +data diff --git a/Lib/test/test_importlib/resources/test_resource.py b/Lib/test/test_importlib/resources/test_resource.py index d1d45d9b4617f3..39a7794c72b4a0 100644 --- a/Lib/test/test_importlib/resources/test_resource.py +++ b/Lib/test/test_importlib/resources/test_resource.py @@ -134,6 +134,27 @@ def test_unrelated_contents(self): ) +class ResourceFromZipsTest04(util.ZipSetupBase, unittest.TestCase): + ZIP_MODULE = 'data04' + + def test_package_resources(self): + self.assertEqual( + names(resources.files('data04.package')), + {'__init__.py', 'module.py', 'resource.txt'}, + ) + + def test_resources_of_module_inside_package(self): + """ + A module inside a package can have resources found adjacent to the module. + """ + package_files = resources.files('data04.package.module') + self.assertEqual( + names(package_files), + {'__init__.py', 'module.py', 'resource.txt'}, + ) + self.assertEqual(package_files.joinpath('resource.txt').read_text(), 'data\n') + + class DeletingZipsTest(util.ZipSetupBase, unittest.TestCase): """Having accessed resources in a zip file should not keep an open reference to the zip. diff --git a/Lib/zipimport.py b/Lib/zipimport.py index f2724dd0268358..fe282bad55bdb8 100644 --- a/Lib/zipimport.py +++ b/Lib/zipimport.py @@ -256,18 +256,22 @@ def load_module(self, fullname): def get_resource_reader(self, fullname): - """Return the ResourceReader for a package in a zip file. + """Return the ResourceReader for a package/module in a zip file. - If 'fullname' is a package within the zip file, return the + If 'fullname' is a package or a module within a package within the zip file, return the 'ResourceReader' object for the package. Otherwise return None. """ try: - if not self.is_package(fullname): + if self.is_package(fullname): + package = fullname + elif self.is_package("."): + package = "." + else: return None except ZipImportError: return None from importlib.readers import ZipReader - return ZipReader(self, fullname) + return ZipReader(self, package) def _get_files(self): @@ -307,7 +311,7 @@ def __repr__(self): # Given a module name, return the potential file path in the # archive (without extension). def _get_module_path(self, fullname): - return self.prefix + fullname.rpartition('.')[2] + return (self.prefix + fullname.rpartition('.')[2]).rstrip(path_sep) # Does this path represent a directory? def _is_dir(self, path):