8000 [3.13] gh-106531: Apply changes from importlib_resources 6.3.2 (GH-117054) by miss-islington · Pull Request #120014 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

[3.13] gh-106531: Apply changes from importlib_resources 6.3.2 (GH-117054) #120014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Lib/importlib/resources/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def package_to_anchor(func):
>>> files('a', 'b')
Traceback (most recent call last):
TypeError: files() takes from 0 to 1 positional arguments but 2 were given

Remove this compatibility in Python 3.14.
"""
undefined = object()

Expand Down
54 changes: 52 additions & 2 deletions Lib/importlib/resources/readers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import collections
import contextlib
import itertools
import pathlib
import operator
import re
import warnings
import zipfile

from . import abc
Expand Down Expand Up @@ -62,7 +65,7 @@ class MultiplexedPath(abc.Traversable):
"""

def __init__(self, *paths):
self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
self._paths = list(map(_ensure_traversable, remove_duplicates(paths)))
if not self._paths:
message = 'MultiplexedPath must contain at least one path'
raise FileNotFoundError(message)
Expand Down Expand Up @@ -130,7 +133,36 @@ class NamespaceReader(abc.TraversableResources):
def __init__(self, namespace_path):
if 'NamespacePath' not in str(namespace_path):
raise ValueError('Invalid path')
self.path = MultiplexedPath(*list(namespace_path))
self.path = MultiplexedPath(*map(self._resolve, namespace_path))

@classmethod
def _resolve(cls, path_str) -> abc.Traversable:
r"""
Given an item from a namespace path, resolve it to a Traversable.

path_str might be a directory on the filesystem or a path to a
zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or< 10000 /span>
``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
"""
(dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
return dir

@classmethod
def _candidate_paths(cls, path_str):
yield pathlib.Path(path_str)
yield from cls._resolve_zip_path(path_str)

@staticmethod
def _resolve_zip_path(path_str):
for match in reversed(list(re.finditer(r'[\\/]', path_str))):
with contextlib.suppress(
FileNotFoundError,
IsADirectoryError,
NotADirectoryError,
PermissionError,
):
inner = path_str[match.end() :].replace('\\', '/') + '/'
yield zipfile.Path(path_str[: match.start()], inner.lstrip('/'))

def resource_path(self, resource):
"""
Expand All @@ -142,3 +174,21 @@ def resource_path(self, resource):

def files(self):
return self.path


def _ensure_traversable(path):
"""
Convert deprecated string arguments to traversables (pathlib.Path).

Remove with Python 3.15.
"""
if not isinstance(path, str):
return path

warnings.warn(
"String arguments are deprecated. Pass a Traversable instead.",
DeprecationWarning,
stacklevel=3,
)

return pathlib.Path(path)
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_importlib/resources/test_contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
expected = {
# no __init__ because of namespace design
# no subdirectory as incidental difference in fixture
'binary.file',
'subdirectory',
'utf-16.file',
'utf-8.file',
}
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_importlib/resources/test_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from test.support import os_helper

from importlib import resources
from importlib.resources import abc
from importlib.resources.abc import TraversableResources, ResourceReader
from . import util

Expand Down Expand Up @@ -39,8 +40,9 @@ def setUp(self):
self.addCleanup(self.fixtures.close)

def test_custom_loader(self):
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
temp_dir = pathlib.Path(self.fixtures.enter_context(os_helper.temp_dir()))
loader = SimpleLoader(MagicResources(temp_dir))
pkg = util.create_package_from_loader(loader)
files = resources.files(pkg)
assert files is temp_dir
assert isinstance(files, abc.Traversable)
assert list(files.iterdir()) == []
14 changes: 9 additions & 5 deletions Lib/test/test_importlib/resources/test_files.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import typing
import textwrap
import unittest
import warnings
Expand Down Expand Up @@ -32,13 +31,14 @@ def test_read_text(self):
actual = files.joinpath('utf-8.file').read_text(encoding='utf-8')
assert actual == 'Hello, UTF-8 world!\n'

@unittest.skipUnless(
hasattr(typing, 'runtime_checkable'),
"Only suitable when typing supports runtime_checkable",
)
def test_traversable(self):
assert isinstance(resources.files(self.data), Traversable)

def test_joinpath_with_multiple_args(self):
files = resources.files(self.data)
binfile = files.joinpath('subdirectory', 'binary.file')
self.assertTrue(binfile.is_file())

def test_old_parameter(self):
"""
Files used to take a 'package' parameter. Make sure anyone
Expand All @@ -64,6 +64,10 @@ def setUp(self):
self.data = namespacedata01


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


class SiteDir:
def setUp(self):
self.fixtures = contextlib.ExitStack()
Expand Down
6 changes: 5 additions & 1 deletion Lib/test/test_importlib/resources/test_open.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_open_binary(self):
target = resources.files(self.data) / 'binary.file'
with target.open('rb') as fp:
result = fp.read()
self.assertEqual(result, b'\x00\x01\x02\x03')
self.assertEqual(result, bytes(range(4)))

def test_open_text_default_encoding(self):
target = resources.files(self.data) / 'utf-8.file'
Expand Down Expand Up @@ -81,5 +81,9 @@ class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
pass


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


if __name__ == '__main__':
unittest.main()
12 changes: 4 additions & 8 deletions Lib/test/test_importlib/resources/test_path.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io
import pathlib
import unittest

from importlib import resources
Expand All @@ -15,18 +16,13 @@ def execute(self, package, path):
class PathTests:
def test_reading(self):
"""
Path should be readable.

Test also implicitly verifies the returned object is a pathlib.Path
instance.
Path should be readable and a pathlib.Path instance.
"""
target = resources.files(self.data) / 'utf-8.file'
with resources.as_file(target) as path:
self.assertIsInstance(path, pathlib.Path)
self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
# pathlib.Path.read_text() was introduced in Python 3.5.
with path.open('r', encoding='utf-8') as file:
text = file.read()
self.assertEqual('Hello, UTF-8 world!\n', text)
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))


class PathDiskTests(PathTests, unittest.TestCase):
Expand Down
29 changes: 22 additions & 7 deletions Lib/test/test_importlib/resources/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def execute(self, package, path):
class ReadTests:
def test_read_bytes(self):
result = resources.files(self.data).joinpath('binary.file').read_bytes()
self.assertEqual(result, b'\0\1\2\3')
self.assertEqual(result, bytes(range(4)))

def test_read_text_default_encoding(self):
result = (
Expand Down Expand Up @@ -57,17 +57,15 @@ class ReadDiskTests(ReadTests, unittest.TestCase):

class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
def test_read_submodule_resource(self):
submodule = import_module('ziptestdata.subdirectory')
submodule = import_module('data01.subdirectory')
result = resources.files(submodule).joinpath('binary.file').read_bytes()
self.assertEqual(result, b'\0\1\2\3')
self.assertEqual(result, bytes(range(4, 8)))

def test_read_submodule_resource_by_name(self):
result = (
resources.files('ziptestdata.subdirectory')
.joinpath('binary.file')
.read_bytes()
resources.files('data01.subdirectory').joinpath('binary.file').read_bytes()
)
self.assertEqual(result, b'\0\1\2\3')
self.assertEqual(result, bytes(range(4, 8)))


class ReadNamespaceTests(ReadTests, unittest.TestCase):
Expand All @@ -77,5 +75,22 @@ def setUp(self):
self.data = namespacedata01


class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
ZIP_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)))

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__':
unittest.main()
29 changes: 15 additions & 14 deletions Lib/test/test_importlib/resources/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,28 @@
class MultiplexedPathTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
path = pathlib.Path(__file__).parent / 'namespacedata01'
cls.folder = str(path)
cls.folder = pathlib.Path(__file__).parent / 'namespacedata01'

def test_init_no_paths(self):
with self.assertRaises(FileNotFoundError):
MultiplexedPath()

def test_init_file(self):
with self.assertRaises(NotADirectoryError):
MultiplexedPath(os.path.join(self.folder, 'binary.file'))
MultiplexedPath(self.folder / 'binary.file')

def test_iterdir(self):
contents = {path.name for path in MultiplexedPath(self.folder).iterdir()}
try:
contents.remove('__pycache__')
except (KeyError, ValueError):
pass
self.assertEqual(contents, {'binary.file', 'utf-16.file', 'utf-8.file'})
self.assertEqual(
contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'}
)

def test_iterdir_duplicate(self):
data01 = os.path.abspath(os.path.join(__file__, '..', 'data01'))
data01 = pathlib.Path(__file__).parent.joinpath('data01')
contents = {
path.name for path in MultiplexedPath(self.folder, data01).iterdir()
}
Expand Down Expand Up @@ -60,17 +61,17 @@ def test_open_file(self):
path.open()

def test_join_path(self):
prefix = os.path.abspath(os.path.join(__file__, '..'))
data01 = os.path.join(prefix, 'data01')
data01 = pathlib.Path(__file__).parent.joinpath('data01')
prefix = str(data01.parent)
path = MultiplexedPath(self.folder, data01)
self.assertEqual(
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
os.path.join('namespacedata01', 'binary.file'),
)
self.assertEqual(
str(path.joinpath('subdirectory'))[len(prefix) + 1 :],
os.path.join('data01', 'subdirectory'),
)
sub = path.joinpath('subdirectory')
assert isinstance(sub, MultiplexedPath)
assert 'namespacedata01' in str(sub)
assert 'data01' in str(sub)
self.assertEqual(
str(path.joinpath('imaginary'))[len(prefix) + 1 :],
os.path.join('namespacedata01', 'imaginary'),
Expand All @@ -82,9 +83,9 @@ def test_join_path_compound(self):
assert not path.joinpath('imaginary/foo.py').exists()

def test_join_path_common_subdir(self):
prefix = os.path.abspath(os.path.join(__file__, '..'))
data01 = os.path.join(prefix, 'data01')
data02 = os.path.join(prefix, 'data02')
data01 = pathlib.Path(__file__).parent.joinpath('data01')
data02 = pathlib.Path(__file__).parent.joinpath('data02')
prefix = str(data01.parent)
path = MultiplexedPath(data01, data02)
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
self.assertEqual(
Expand Down
Loading
Loading
0