Skip to content

Commit cf4b282

Browse files
jaracoestyxx
authored andcommitted
pythongh-106531: Apply changes from importlib_resources 6.3.2 (python#117054)
Apply changes from importlib_resources 6.3.2.
1 parent c062f58 commit cf4b282

File tree

16 files changed

+231
-146
lines changed

16 files changed

+231
-146
lines changed

Lib/importlib/resources/_common.py

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def package_to_anchor(func):
2525
>>> files('a', 'b')
2626
Traceback (most recent call last):
2727
TypeError: files() takes from 0 to 1 positional arguments but 2 were given
28+
29+
Remove this compatibility in Python 3.14.
2830
"""
2931
undefined = object()
3032

Lib/importlib/resources/readers.py

+52-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import collections
2+
import contextlib
23
import itertools
34
import pathlib
45
import operator
6+
import re
7+
import warnings
58
import zipfile
69

710
from . import abc
@@ -62,7 +65,7 @@ class MultiplexedPath(abc.Traversable):
6265
"""
6366

6467
def __init__(self, *paths):
65-
self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
68+
self._paths = list(map(_ensure_traversable, remove_duplicates(paths)))
6669
if not self._paths:
6770
message = 'MultiplexedPath must contain at least one path'
6871
raise FileNotFoundError(message)
@@ -130,7 +133,36 @@ class NamespaceReader(abc.TraversableResources):
130133
def __init__(self, namespace_path):
131134
if 'NamespacePath' not in str(namespace_path):
132135
raise ValueError('Invalid path')
133-
self.path = MultiplexedPath(*list(namespace_path))
136+
self.path = MultiplexedPath(*map(self._resolve, namespace_path))
137+
138+
@classmethod
139+
def _resolve(cls, path_str) -> abc.Traversable:
140+
r"""
141+
Given an item from a namespace path, resolve it to a Traversable.
142+
143+
path_str might be a directory on the filesystem or a path to a
144+
zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or
145+
``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
146+
"""
147+
(dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
148+
return dir
149+
150+
@classmethod
151+
def _candidate_paths(cls, path_str):
152+
yield pathlib.Path(path_str)
153+
yield from cls._resolve_zip_path(path_str)
154+
155+
@staticmethod
156+
def _resolve_zip_path(path_str):
157+
for match in reversed(list(re.finditer(r'[\\/]', path_str))):
158+
with contextlib.suppress(
159+
FileNotFoundError,
160+
IsADirectoryError,
161+
NotADirectoryError,
162+
PermissionError,
163+
):
164+
inner = path_str[match.end() :].replace('\\', '/') + '/'
165+
yield zipfile.Path(path_str[: match.start()], inner.lstrip('/'))
134166

135167
def resource_path(self, resource):
136168
"""
@@ -142,3 +174,21 @@ def resource_path(self, resource):
142174

143175
def files(self):
144176
return self.path
177+
178+
179+
def _ensure_traversable(path):
180+
"""
181+
Convert deprecated string arguments to traversables (pathlib.Path).
182+
183+
Remove with Python 3.15.
184+
"""
185+
if not isinstance(path, str):
186+
return path
187+
188+
warnings.warn(
189+
"String arguments are deprecated. Pass a Traversable instead.",
190+
DeprecationWarning,
191+
stacklevel=3,
192+
)
193+
194+
return pathlib.Path(path)
Binary file not shown.

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

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+


Lib/test/test_importlib/resources/test_contents.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
3131
class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
3232
expected = {
3333
# no __init__ because of namespace design
34-
# no subdirectory as incidental difference in fixture
3534
'binary.file',
35+
'subdirectory',
3636
'utf-16.file',
3737
'utf-8.file',
3838
}

Lib/test/test_importlib/resources/test_custom.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from test.support import os_helper
66

77
from importlib import resources
8+
from importlib.resources import abc
89
from importlib.resources.abc import TraversableResources, ResourceReader
910
from . import util
1011

@@ -39,8 +40,9 @@ def setUp(self):
3940
self.addCleanup(self.fixtures.close)
4041

4142
def test_custom_loader(self):
42-
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
43+
temp_dir = pathlib.Path(self.fixtures.enter_context(os_helper.temp_dir()))
4344
loader = SimpleLoader(MagicResources(temp_dir))
4445
pkg = util.create_package_from_loader(loader)
4546
files = resources.files(pkg)
46-
assert files is temp_dir
47+
assert isinstance(files, abc.Traversable)
48+
assert list(files.iterdir()) == []

Lib/test/test_importlib/resources/test_files.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import typing
21
import textwrap
32
import unittest
43
import warnings
@@ -32,13 +31,14 @@ def test_read_text(self):
3231
actual = files.joinpath('utf-8.file').read_text(encoding='utf-8')
3332
assert actual == 'Hello, UTF-8 world!\n'
3433

35-
@unittest.skipUnless(
36-
hasattr(typing, 'runtime_checkable'),
37-
"Only suitable when typing supports runtime_checkable",
38-
)
3934
def test_traversable(self):
4035
assert isinstance(resources.files(self.data), Traversable)
4136

37+
def test_joinpath_with_multiple_args(self):
38+
files = resources.files(self.data)
39+
binfile = files.joinpath('subdirectory', 'binary.file')
40+
self.assertTrue(binfile.is_file())
41+
4242
def test_old_parameter(self):
4343
"""
4444
Files used to take a 'package' parameter. Make sure anyone
@@ -64,6 +64,10 @@ def setUp(self):
6464
self.data = namespacedata01
6565

6666

67+
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
68+
ZIP_MODULE = 'namespacedata01'
69+
70+
6771
class SiteDir:
6872
def setUp(self):
6973
self.fixtures = contextlib.ExitStack()

Lib/test/test_importlib/resources/test_open.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_open_binary(self):
2424
target = resources.files(self.data) / 'binary.file'
2525
with target.open('rb') as fp:
2626
result = fp.read()
27-
self.assertEqual(result, b'\x00\x01\x02\x03')
27+
self.assertEqual(result, bytes(range(4)))
2828

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

8383

84+
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
85+
ZIP_MODULE = 'namespacedata01'
86+
87+
8488
if __name__ == '__main__':
8589
unittest.main()

Lib/test/test_importlib/resources/test_path.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import io
2+
import pathlib
23
import unittest
34

45
from importlib import resources
@@ -15,18 +16,13 @@ def execute(self, package, path):
1516
class PathTests:
1617
def test_reading(self):
1718
"""
18-
Path should be readable.
19-
20-
Test also implicitly verifies the returned object is a pathlib.Path
21-
instance.
19+
Path should be readable and a pathlib.Path instance.
2220
"""
2321
target = resources.files(self.data) / 'utf-8.file'
2422
with resources.as_file(target) as path:
23+
self.assertIsInstance(path, pathlib.Path)
2524
self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
26-
# pathlib.Path.read_text() was introduced in Python 3.5.
27-
with path.open('r', encoding='utf-8') as file:
28-
text = file.read()
29-
self.assertEqual('Hello, UTF-8 world!\n', text)
25+
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
3026

3127

3228
class PathDiskTests(PathTests, unittest.TestCase):

Lib/test/test_importlib/resources/test_read.py

+22-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def execute(self, package, path):
1818
class ReadTests:
1919
def test_read_bytes(self):
2020
result = resources.files(self.data).joinpath('binary.file').read_bytes()
21-
self.assertEqual(result, b'\0\1\2\3')
21+
self.assertEqual(result, bytes(range(4)))
2222

2323
def test_read_text_default_encoding(self):
2424
result = (
@@ -57,17 +57,15 @@ class ReadDiskTests(ReadTests, unittest.TestCase):
5757

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

6464
def test_read_submodule_resource_by_name(self):
6565
result = (
66-
resources.files('ziptestdata.subdirectory')
67-
.joinpath('binary.file')
68-
.read_bytes()
66+
resources.files('data01.subdirectory').joinpath('binary.file').read_bytes()
6967
)
70-
self.assertEqual(result, b'\0\1\2\3')
68+
self.assertEqual(result, bytes(range(4, 8)))
7169

7270

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

7977

78+
class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
79+
ZIP_MODULE = 'namespacedata01'
80+
81+
def test_read_submodule_resource(self):
82+
submodule = import_module('namespacedata01.subdirectory')
83+
result = resources.files(submodule).joinpath('binary.file').read_bytes()
84+
self.assertEqual(result, bytes(range(12, 16)))
85+
86+
def test_read_submodule_resource_by_name(self):
87+
result = (
88+
resources.files('namespacedata01.subdirectory')
89+
.joinpath('binary.file')
90+
.read_bytes()
91+
)
92+
self.assertEqual(result, bytes(range(12, 16)))
93+
94+
8095
if __name__ == '__main__':
8196
unittest.main()

Lib/test/test_importlib/resources/test_reader.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,28 @@
1010
class MultiplexedPathTest(unittest.TestCase):
1111
@classmethod
1212
def setUpClass(cls):
13-
path = pathlib.Path(__file__).parent / 'namespacedata01'
14-
cls.folder = str(path)
13+
cls.folder = pathlib.Path(__file__).parent / 'namespacedata01'
1514

1615
def test_init_no_paths(self):
1716
with self.assertRaises(FileNotFoundError):
1817
MultiplexedPath()
1918

2019
def test_init_file(self):
2120
with self.assertRaises(NotADirectoryError):
22-
MultiplexedPath(os.path.join(self.folder, 'binary.file'))
21+
MultiplexedPath(self.folder / 'binary.file')
2322

2423
def test_iterdir(self):
2524
contents = {path.name for path in MultiplexedPath(self.folder).iterdir()}
2625
try:
2726
contents.remove('__pycache__')
2827
except (KeyError, ValueError):
2928
pass
30-
self.assertEqual(contents, {'binary.file', 'utf-16.file', 'utf-8.file'})
29+
self.assertEqual(
30+
contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'}
31+
)
3132

3233
def test_iterdir_duplicate(self):
33-
data01 = os.path.abspath(os.path.join(__file__, '..', 'data01'))
34+
data01 = pathlib.Path(__file__).parent.joinpath('data01')
3435
contents = {
3536
path.name for path in MultiplexedPath(self.folder, data01).iterdir()
3637
}
@@ -60,17 +61,17 @@ def test_open_file(self):
6061
path.open()
6162

6263
def test_join_path(self):
63-
prefix = os.path.abspath(os.path.join(__file__, '..'))
64-
data01 = os.path.join(prefix, 'data01')
64+
data01 = pathlib.Path(__file__).parent.joinpath('data01')
65+
prefix = str(data01.parent)
6566
path = MultiplexedPath(self.folder, data01)
6667
self.assertEqual(
6768
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
6869
os.path.join('namespacedata01', 'binary.file'),
6970
)
70-
self.assertEqual(
71-
str(path.joinpath('subdirectory'))[len(prefix) + 1 :],
72-
os.path.join('data01', 'subdirectory'),
73-
)
71+
sub = path.joinpath('subdirectory')
72+
assert isinstance(sub, MultiplexedPath)
73+
assert 'namespacedata01' in str(sub)
74+
assert 'data01' in str(sub)
7475
self.assertEqual(
7576
str(path.joinpath('imaginary'))[len(prefix) + 1 :],
7677
os.path.join('namespacedata01', 'imaginary'),
@@ -82,9 +83,9 @@ def test_join_path_compound(self):
8283
assert not path.joinpath('imaginary/foo.py').exists()
8384

8485
def test_join_path_common_subdir(self):
85-
prefix = os.path.abspath(os.path.join(__file__, '..'))
86-
data01 = os.path.join(prefix, 'data01')
87-
data02 = os.path.join(prefix, 'data02')
86+
data01 = pathlib.Path(__file__).parent.joinpath('data01')
87+
data02 = pathlib.Path(__file__).parent.joinpath('data02')
88+
prefix = str(data01.parent)
8889
path = MultiplexedPath(data01, data02)
8990
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
9091
self.assertEqual(

0 commit comments

Comments
 (0)