Skip to content

Commit

Permalink
Apply changes from importlib_resources 6.3.2.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Mar 20, 2024
1 parent d5ebf8b commit 98465e0
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 146 deletions.
2 changes: 2 additions & 0 deletions Lib/importlib/resources/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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
``/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

0 comments on commit 98465e0

Please sign in to comment.