diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2021-03-04 18:43:00 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-04 18:43:00 (GMT) |
commit | 67148254146948041a77d8a2989f41b88cdb2f99 (patch) | |
tree | 036bcb818e80090b34f0c59f57f8b6946b52b21d /Lib/importlib/readers.py | |
parent | fbf75b9997e280b1220755d0a17dbed71240d42e (diff) | |
download | cpython-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.py | 82 |
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 |