1 from contextlib
import suppress
2 from io
import TextIOWrapper
7 class SpecLoaderAdapter
:
9 Adapt a package spec to adapt the underlying loader.
12 def __init__(self
, spec
, adapter
=lambda spec
: spec
.loader
):
14 self
.loader
= adapter(spec
)
16 def __getattr__(self
, name
):
17 return getattr(self
.spec
, name
)
20 class TraversableResourcesLoader
:
22 Adapt a loader to provide TraversableResources.
25 def __init__(self
, spec
):
28 def get_resource_reader(self
, name
):
29 return CompatibilityFiles(self
.spec
)._native
()
32 def _io_wrapper(file, mode
='r', *args
, **kwargs
):
34 return TextIOWrapper(file, *args
, **kwargs
)
38 "Invalid mode value '{}', only 'r' and 'rb' are supported".format(mode
)
42 class CompatibilityFiles
:
44 Adapter for an existing or non-existent resource reader
45 to provide a compatibility .files().
48 class SpecPath(abc
.Traversable
):
50 Path tied to a module spec.
51 Can be read and exposes the resource reader children.
54 def __init__(self
, spec
, reader
):
62 CompatibilityFiles
.ChildPath(self
._reader
, path
)
63 for path
in self
._reader
.contents()
71 def joinpath(self
, other
):
73 return CompatibilityFiles
.OrphanPath(other
)
74 return CompatibilityFiles
.ChildPath(self
._reader
, other
)
78 return self
._spec
.name
80 def open(self
, mode
='r', *args
, **kwargs
):
81 return _io_wrapper(self
._reader
.open_resource(None), mode
, *args
, **kwargs
)
83 class ChildPath(abc
.Traversable
):
85 Path tied to a resource reader child.
86 Can be read but doesn't expose any meaningful children.
89 def __init__(self
, reader
, name
):
97 return self
._reader
.is_resource(self
.name
)
100 return not self
.is_file()
102 def joinpath(self
, other
):
103 return CompatibilityFiles
.OrphanPath(self
.name
, other
)
109 def open(self
, mode
='r', *args
, **kwargs
):
111 self
._reader
.open_resource(self
.name
), mode
, *args
, **kwargs
114 class OrphanPath(abc
.Traversable
):
116 Orphan path, not tied to a module spec or resource reader.
117 Can't be read and doesn't expose any meaningful children.
120 def __init__(self
, *path_parts
):
121 if len(path_parts
) < 1:
122 raise ValueError('Need at least one path part to construct a path')
123 self
._path
= path_parts
133 def joinpath(self
, other
):
134 return CompatibilityFiles
.OrphanPath(*self
._path
, other
)
138 return self
._path
[-1]
140 def open(self
, mode
='r', *args
, **kwargs
):
141 raise FileNotFoundError("Can't open orphan path")
143 def __init__(self
, spec
):
148 with suppress(AttributeError):
149 return self
.spec
.loader
.get_resource_reader(self
.spec
.name
)
153 Return the native reader if it supports files().
155 reader
= self
._reader
156 return reader
if hasattr(reader
, 'files') else self
158 def __getattr__(self
, attr
):
159 return getattr(self
._reader
, attr
)
162 return CompatibilityFiles
.SpecPath(self
.spec
, self
._reader
)
165 def wrap_spec(package
):
167 Construct a package spec with traversable compatibility
168 on the spec/loader/reader.
170 return SpecLoaderAdapter(package
.__spec
__, TraversableResourcesLoader
)