]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-91298: Refine traversable (apply changes from importlib_resources 5.7.1) (#91623)
authorJason R. Coombs <jaraco@jaraco.com>
Sun, 17 Apr 2022 15:10:36 +0000 (11:10 -0400)
committerGitHub <noreply@github.com>
Sun, 17 Apr 2022 15:10:36 +0000 (11:10 -0400)
* bpo-47142: Refine traversable (apply changes from importlib_resources 5.7.1)

* Replace changelog referencing github issue.

Lib/importlib/resources/abc.py
Lib/importlib/resources/simple.py
Lib/test/test_importlib/update-zips.py
Misc/NEWS.d/next/Documentation/2022-04-17-03-19-51.gh-issue-91298.NT9qHi.rst [new file with mode: 0644]

index e9efdab5ea8305a687a4c1530120b4911aa74ba5..0b7bfdc415829e15a77ac94420a0e5258e359d79 100644 (file)
@@ -1,6 +1,14 @@
 import abc
-from typing import BinaryIO, Iterable, Text
+import io
+import os
+from typing import Any, BinaryIO, Iterable, Iterator, NoReturn, Text, Optional
 from typing import runtime_checkable, Protocol
+from typing import Union
+
+
+StrPath = Union[str, os.PathLike[str]]
+
+__all__ = ["ResourceReader", "Traversable", "TraversableResources"]
 
 
 class ResourceReader(metaclass=abc.ABCMeta):
@@ -50,22 +58,25 @@ class Traversable(Protocol):
     """
     An object with a subset of pathlib.Path methods suitable for
     traversing directories and opening files.
+
+    Any exceptions that occur when accessing the backing resource
+    may propagate unaltered.
     """
 
     @abc.abstractmethod
-    def iterdir(self):
+    def iterdir(self) -> Iterator["Traversable"]:
         """
         Yield Traversable objects in self
         """
 
-    def read_bytes(self):
+    def read_bytes(self) -> bytes:
         """
         Read contents of self as bytes
         """
         with self.open('rb') as strm:
             return strm.read()
 
-    def read_text(self, encoding=None):
+    def read_text(self, encoding: Optional[str] = None) -> str:
         """
         Read contents of self as text
         """
@@ -85,12 +96,16 @@ class Traversable(Protocol):
         """
 
     @abc.abstractmethod
-    def joinpath(self, child):
+    def joinpath(self, *descendants: StrPath) -> "Traversable":
         """
-        Return Traversable child in self
+        Return Traversable resolved with any descendants applied.
+
+        Each descendant should be a path segment relative to self
+        and each may contain multiple levels separated by
+        ``posixpath.sep`` (``/``).
         """
 
-    def __truediv__(self, child):
+    def __truediv__(self, child: StrPath) -> "Traversable":
         """
         Return Traversable child in self
         """
@@ -120,17 +135,17 @@ class TraversableResources(ResourceReader):
     """
 
     @abc.abstractmethod
-    def files(self):
+    def files(self) -> "Traversable":
         """Return a Traversable object for the loaded package."""
 
-    def open_resource(self, resource):
+    def open_resource(self, resource: StrPath) -> io.BufferedReader:
         return self.files().joinpath(resource).open('rb')
 
-    def resource_path(self, resource):
+    def resource_path(self, resource: Any) -> NoReturn:
         raise FileNotFoundError(resource)
 
-    def is_resource(self, path):
+    def is_resource(self, path: StrPath) -> bool:
         return self.files().joinpath(path).is_file()
 
-    def contents(self):
+    def contents(self) -> Iterator[str]:
         return (item.name for item in self.files().iterdir())
index da073cbdb11e6c24c19a2d388c53c8842228595f..d0fbf237762c2febfb53158f9b5ee783808efce4 100644 (file)
@@ -99,10 +99,19 @@ class ResourceContainer(Traversable):
     def open(self, *args, **kwargs):
         raise IsADirectoryError()
 
-    def joinpath(self, name):
+    @staticmethod
+    def _flatten(compound_names):
+        for name in compound_names:
+            yield from name.split('/')
+
+    def joinpath(self, *descendants):
+        if not descendants:
+            return self
+        names = self._flatten(descendants)
+        target = next(names)
         return next(
-            traversable for traversable in self.iterdir() if traversable.name == name
-        )
+            traversable for traversable in self.iterdir() if traversable.name == target
+        ).joinpath(*names)
 
 
 class TraversableReader(TraversableResources, SimpleReader):
index 9ef0224ca65ca067be71deeda384c9bce3015a97..231334aa7e38b46157234314129262657edeadee 100755 (executable)
@@ -42,7 +42,7 @@ def generate(suffix):
 
 def walk(datapath):
     for dirpath, dirnames, filenames in os.walk(datapath):
-        with contextlib.suppress(KeyError):
+        with contextlib.suppress(ValueError):
             dirnames.remove('__pycache__')
         for filename in filenames:
             res = pathlib.Path(dirpath) / filename
diff --git a/Misc/NEWS.d/next/Documentation/2022-04-17-03-19-51.gh-issue-91298.NT9qHi.rst b/Misc/NEWS.d/next/Documentation/2022-04-17-03-19-51.gh-issue-91298.NT9qHi.rst
new file mode 100644 (file)
index 0000000..471a7ce
--- /dev/null
@@ -0,0 +1,2 @@
+In ``importlib.resources.abc``, refined the documentation of the Traversable
+Protocol, applying changes from importlib_resources 5.7.1.