1from contextlib import suppress 2 3from . import abc 4 5 6class SpecLoaderAdapter: 7 """ 8 Adapt a package spec to adapt the underlying loader. 9 """ 10 11 def __init__(self, spec, adapter=lambda spec: spec.loader): 12 self.spec = spec 13 self.loader = adapter(spec) 14 15 def __getattr__(self, name): 16 return getattr(self.spec, name) 17 18 19class TraversableResourcesLoader: 20 """ 21 Adapt a loader to provide TraversableResources. 22 """ 23 24 def __init__(self, spec): 25 self.spec = spec 26 27 def get_resource_reader(self, name): 28 return DegenerateFiles(self.spec)._native() 29 30 31class DegenerateFiles: 32 """ 33 Adapter for an existing or non-existant resource reader 34 to provide a degenerate .files(). 35 """ 36 37 class Path(abc.Traversable): 38 def iterdir(self): 39 return iter(()) 40 41 def is_dir(self): 42 return False 43 44 is_file = exists = is_dir # type: ignore 45 46 def joinpath(self, other): 47 return DegenerateFiles.Path() 48 49 @property 50 def name(self): 51 return '' 52 53 def open(self, mode='rb', *args, **kwargs): 54 raise ValueError() 55 56 def __init__(self, spec): 57 self.spec = spec 58 59 @property 60 def _reader(self): 61 with suppress(AttributeError): 62 return self.spec.loader.get_resource_reader(self.spec.name) 63 64 def _native(self): 65 """ 66 Return the native reader if it supports files(). 67 """ 68 reader = self._reader 69 return reader if hasattr(reader, 'files') else self 70 71 def __getattr__(self, attr): 72 return getattr(self._reader, attr) 73 74 def files(self): 75 return DegenerateFiles.Path() 76 77 78def wrap_spec(package): 79 """ 80 Construct a package spec with traversable compatibility 81 on the spec/loader/reader. 82 """ 83 return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) 84