summaryrefslogtreecommitdiffstats
path: root/Lib/importlib/readers.py
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2021-03-04 18:43:00 (GMT)
committerGitHub <noreply@github.com>2021-03-04 18:43:00 (GMT)
commit67148254146948041a77d8a2989f41b88cdb2f99 (patch)
tree036bcb818e80090b34f0c59f57f8b6946b52b21d /Lib/importlib/readers.py
parentfbf75b9997e280b1220755d0a17dbed71240d42e (diff)
downloadcpython-67148254146948041a77d8a2989f41b88cdb2f99.zip
cpython-67148254146948041a77d8a2989f41b88cdb2f99.tar.gz
cpython-67148254146948041a77d8a2989f41b88cdb2f99.tar.bz2
bpo-42129: Add support for resources in namespaces (GH-24670)
* Unify behavior in ResourceReaderDefaultsTests and align with the behavior found in importlib_resources. * Equip NamespaceLoader with a NamespaceReader. * Apply changes from importlib_resources 5.0.4
Diffstat (limited to 'Lib/importlib/readers.py')
-rw-r--r--Lib/importlib/readers.py82
1 files changed, 82 insertions, 0 deletions
diff --git a/Lib/importlib/readers.py b/Lib/importlib/readers.py
index 74a63e4..535c828 100644
--- a/Lib/importlib/readers.py
+++ b/Lib/importlib/readers.py
@@ -1,8 +1,13 @@
+import collections
import zipfile
import pathlib
from . import abc
+def remove_duplicates(items):
+ return iter(collections.OrderedDict.fromkeys(items))
+
+
class FileReader(abc.TraversableResources):
def __init__(self, loader):
self.path = pathlib.Path(loader.path).parent
@@ -39,3 +44,80 @@ class ZipReader(abc.TraversableResources):
def files(self):
return zipfile.Path(self.archive, self.prefix)
+
+
+class MultiplexedPath(abc.Traversable):
+ """
+ Given a series of Traversable objects, implement a merged
+ version of the interface across all objects. Useful for
+ namespace packages which may be multihomed at a single
+ name.
+ """
+
+ def __init__(self, *paths):
+ self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
+ if not self._paths:
+ message = 'MultiplexedPath must contain at least one path'
+ raise FileNotFoundError(message)
+ if not all(path.is_dir() for path in self._paths):
+ raise NotADirectoryError('MultiplexedPath only supports directories')
+
+ def iterdir(self):
+ visited = []
+ for path in self._paths:
+ for file in path.iterdir():
+ if file.name in visited:
+ continue
+ visited.append(file.name)
+ yield file
+
+ def read_bytes(self):
+ raise FileNotFoundError(f'{self} is not a file')
+
+ def read_text(self, *args, **kwargs):
+ raise FileNotFoundError(f'{self} is not a file')
+
+ def is_dir(self):
+ return True
+
+ def is_file(self):
+ return False
+
+ def joinpath(self, child):
+ # first try to find child in current paths
+ for file in self.iterdir():
+ if file.name == child:
+ return file
+ # if it does not exist, construct it with the first path
+ return self._paths[0] / child
+
+ __truediv__ = joinpath
+
+ def open(self, *args, **kwargs):
+ raise FileNotFoundError('{} is not a file'.format(self))
+
+ def name(self):
+ return self._paths[0].name
+
+ def __repr__(self):
+ return 'MultiplexedPath({})'.format(
+ ', '.join("'{}'".format(path) for path in self._paths)
+ )
+
+
+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))
+
+ def resource_path(self, resource):
+ """
+ Return the file system path to prevent
+ `resources.path()` from creating a temporary
+ copy.
+ """
+ return str(self.path.joinpath(resource))
+
+ def files(self):
+ return self.path