summaryrefslogtreecommitdiffstats
path: root/Lib/importlib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/importlib')
-rw-r--r--Lib/importlib/resources/_common.py2
-rw-r--r--Lib/importlib/resources/readers.py54
2 files changed, 54 insertions, 2 deletions
diff --git a/Lib/importlib/resources/_common.py b/Lib/importlib/resources/_common.py
index e18082f..ca5b067 100644
--- a/Lib/importlib/resources/_common.py
+++ b/Lib/importlib/resources/_common.py
@@ -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()
diff --git a/Lib/importlib/resources/readers.py b/Lib/importlib/resources/readers.py
index c3cdf76..b86cdef 100644
--- a/Lib/importlib/resources/readers.py
+++ b/Lib/importlib/resources/readers.py
@@ -1,7 +1,10 @@
import collections
+import contextlib
import itertools
import pathlib
import operator
+import re
+import warnings
import zipfile
from . import abc
@@ -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)
@@ -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):
"""
@@ -142,3 +174,21 @@ class NamespaceReader(abc.TraversableResources):
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)