summaryrefslogtreecommitdiffstats
path: root/Lib/importlib
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2023-02-18 21:29:22 (GMT)
committerGitHub <noreply@github.com>2023-02-18 21:29:22 (GMT)
commit5170caf3059fdacc92d7370eecb9fe4f0c5a1c76 (patch)
treeb294f3f9a122e4ffc520ec4ef22aabadc34144a3 /Lib/importlib
parent128379b8cdb88a6d3d7fed24df082c9a654b3fb8 (diff)
downloadcpython-5170caf3059fdacc92d7370eecb9fe4f0c5a1c76.zip
cpython-5170caf3059fdacc92d7370eecb9fe4f0c5a1c76.tar.gz
cpython-5170caf3059fdacc92d7370eecb9fe4f0c5a1c76.tar.bz2
gh-97930: Apply changes from importlib_resources 5.12. (GH-102010)
Diffstat (limited to 'Lib/importlib')
-rw-r--r--Lib/importlib/resources/_adapters.py4
-rw-r--r--Lib/importlib/resources/_itertools.py69
-rw-r--r--Lib/importlib/resources/readers.py36
3 files changed, 67 insertions, 42 deletions
diff --git a/Lib/importlib/resources/_adapters.py b/Lib/importlib/resources/_adapters.py
index f22f6bc..50688fb 100644
--- a/Lib/importlib/resources/_adapters.py
+++ b/Lib/importlib/resources/_adapters.py
@@ -34,9 +34,7 @@ def _io_wrapper(file, mode='r', *args, **kwargs):
return TextIOWrapper(file, *args, **kwargs)
elif mode == 'rb':
return file
- raise ValueError(
- f"Invalid mode value '{mode}', only 'r' and 'rb' are supported"
- )
+ raise ValueError(f"Invalid mode value '{mode}', only 'r' and 'rb' are supported")
class CompatibilityFiles:
diff --git a/Lib/importlib/resources/_itertools.py b/Lib/importlib/resources/_itertools.py
index cce0558..7b775ef5a 100644
--- a/Lib/importlib/resources/_itertools.py
+++ b/Lib/importlib/resources/_itertools.py
@@ -1,35 +1,38 @@
-from itertools import filterfalse
+# from more_itertools 9.0
+def only(iterable, default=None, too_long=None):
+ """If *iterable* has only one item, return it.
+ If it has zero items, return *default*.
+ If it has more than one item, raise the exception given by *too_long*,
+ which is ``ValueError`` by default.
+ >>> only([], default='missing')
+ 'missing'
+ >>> only([1])
+ 1
+ >>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ ValueError: Expected exactly one item in iterable, but got 1, 2,
+ and perhaps more.'
+ >>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ TypeError
+ Note that :func:`only` attempts to advance *iterable* twice to ensure there
+ is only one item. See :func:`spy` or :func:`peekable` to check
+ iterable contents less destructively.
+ """
+ it = iter(iterable)
+ first_value = next(it, default)
-from typing import (
- Callable,
- Iterable,
- Iterator,
- Optional,
- Set,
- TypeVar,
- Union,
-)
-
-# Type and type variable definitions
-_T = TypeVar('_T')
-_U = TypeVar('_U')
-
-
-def unique_everseen(
- iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = None
-) -> Iterator[_T]:
- "List unique elements, preserving order. Remember all elements ever seen."
- # unique_everseen('AAAABBBCCDAABBB') --> A B C D
- # unique_everseen('ABBCcAD', str.lower) --> A B C D
- seen: Set[Union[_T, _U]] = set()
- seen_add = seen.add
- if key is None:
- for element in filterfalse(seen.__contains__, iterable):
- seen_add(element)
- yield element
+ try:
+ second_value = next(it)
+ except StopIteration:
+ pass
else:
- for element in iterable:
- k = key(element)
- if k not in seen:
- seen_add(k)
- yield element
+ msg = (
+ 'Expected exactly one item in iterable, but got {!r}, {!r}, '
+ 'and perhaps more.'.format(first_value, second_value)
+ )
+ raise too_long or ValueError(msg)
+
+ return first_value
diff --git a/Lib/importlib/resources/readers.py b/Lib/importlib/resources/readers.py
index 80cb320..c3cdf76 100644
--- a/Lib/importlib/resources/readers.py
+++ b/Lib/importlib/resources/readers.py
@@ -1,11 +1,12 @@
import collections
-import operator
+import itertools
import pathlib
+import operator
import zipfile
from . import abc
-from ._itertools import unique_everseen
+from ._itertools import only
def remove_duplicates(items):
@@ -41,8 +42,10 @@ class ZipReader(abc.TraversableResources):
raise FileNotFoundError(exc.args[0])
def is_resource(self, path):
- # workaround for `zipfile.Path.is_file` returning true
- # for non-existent paths.
+ """
+ Workaround for `zipfile.Path.is_file` returning true
+ for non-existent paths.
+ """
target = self.files().joinpath(path)
return target.is_file() and target.exists()
@@ -67,8 +70,10 @@ class MultiplexedPath(abc.Traversable):
raise NotADirectoryError('MultiplexedPath only supports directories')
def iterdir(self):
- files = (file for path in self._paths for file in path.iterdir())
- return unique_everseen(files, key=operator.attrgetter('name'))
+ children = (child for path in self._paths for child in path.iterdir())
+ by_name = operator.attrgetter('name')
+ groups = itertools.groupby(sorted(children, key=by_name), key=by_name)
+ return map(self._follow, (locs for name, locs in groups))
def read_bytes(self):
raise FileNotFoundError(f'{self} is not a file')
@@ -90,6 +95,25 @@ class MultiplexedPath(abc.Traversable):
# Just return something that will not exist.
return self._paths[0].joinpath(*descendants)
+ @classmethod
+ def _follow(cls, children):
+ """
+ Construct a MultiplexedPath if needed.
+
+ If children contains a sole element, return it.
+ Otherwise, return a MultiplexedPath of the items.
+ Unless one of the items is not a Directory, then return the first.
+ """
+ subdirs, one_dir, one_file = itertools.tee(children, 3)
+
+ try:
+ return only(one_dir)
+ except ValueError:
+ try:
+ return cls(*subdirs)
+ except NotADirectoryError:
+ return next(one_file)
+
def open(self, *args, **kwargs):
raise FileNotFoundError(f'{self} is not a file')