Skip to content

Commit 586b9ed

Browse files
committed
Add importlib_resources to lib
1 parent d89f417 commit 586b9ed

File tree

10 files changed

+939
-0
lines changed

10 files changed

+939
-0
lines changed

lib/importlib_resources/__init__.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Read resources contained within a package."""
2+
3+
from ._common import (
4+
as_file,
5+
files,
6+
Package,
7+
)
8+
9+
from ._legacy import (
10+
contents,
11+
open_binary,
12+
read_binary,
13+
open_text,
14+
read_text,
15+
is_resource,
16+
path,
17+
Resource,
18+
)
19+
20+
from importlib_resources.abc import ResourceReader
21+
22+
23+
__all__ = [
24+
'Package',
25+
'Resource',
26+
'ResourceReader',
27+
'as_file',
28+
'contents',
29+
'files',
30+
'is_resource',
31+
'open_binary',
32+
'open_text',
33+
'path',
34+
'read_binary',
35+
'read_text',
36+
]

lib/importlib_resources/_adapters.py

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
from contextlib import suppress
2+
from io import TextIOWrapper
3+
4+
from . import abc
5+
6+
7+
class SpecLoaderAdapter:
8+
"""
9+
Adapt a package spec to adapt the underlying loader.
10+
"""
11+
12+
def __init__(self, spec, adapter=lambda spec: spec.loader):
13+
self.spec = spec
14+
self.loader = adapter(spec)
15+
16+
def __getattr__(self, name):
17+
return getattr(self.spec, name)
18+
19+
20+
class TraversableResourcesLoader:
21+
"""
22+
Adapt a loader to provide TraversableResources.
23+
"""
24+
25+
def __init__(self, spec):
26+
self.spec = spec
27+
28+
def get_resource_reader(self, name):
29+
return CompatibilityFiles(self.spec)._native()
30+
31+
32+
def _io_wrapper(file, mode='r', *args, **kwargs):
33+
if mode == 'r':
34+
return TextIOWrapper(file, *args, **kwargs)
35+
elif mode == 'rb':
36+
return file
37+
raise ValueError(
38+
"Invalid mode value '{}', only 'r' and 'rb' are supported".format(mode)
39+
)
40+
41+
42+
class CompatibilityFiles:
43+
"""
44+
Adapter for an existing or non-existent resource reader
45+
to provide a compatibility .files().
46+
"""
47+
48+
class SpecPath(abc.Traversable):
49+
"""
50+
Path tied to a module spec.
51+
Can be read and exposes the resource reader children.
52+
"""
53+
54+
def __init__(self, spec, reader):
55+
self._spec = spec
56+
self._reader = reader
57+
58+
def iterdir(self):
59+
if not self._reader:
60+
return iter(())
61+
return iter(
62+
CompatibilityFiles.ChildPath(self._reader, path)
63+
for path in self._reader.contents()
64+
)
65+
66+
def is_file(self):
67+
return False
68+
69+
is_dir = is_file
70+
71+
def joinpath(self, other):
72+
if not self._reader:
73+
return CompatibilityFiles.OrphanPath(other)
74+
return CompatibilityFiles.ChildPath(self._reader, other)
75+
76+
@property
77+
def name(self):
78+
return self._spec.name
79+
80+
def open(self, mode='r', *args, **kwargs):
81+
return _io_wrapper(self._reader.open_resource(None), mode, *args, **kwargs)
82+
83+
class ChildPath(abc.Traversable):
84+
"""
85+
Path tied to a resource reader child.
86+
Can be read but doesn't expose any meaningful children.
87+
"""
88+
89+
def __init__(self, reader, name):
90+
self._reader = reader
91+
self._name = name
92+
93+
def iterdir(self):
94+
return iter(())
95+
96+
def is_file(self):
97+
return self._reader.is_resource(self.name)
98+
99+
def is_dir(self):
100+
return not self.is_file()
101+
102+
def joinpath(self, other):
103+
return CompatibilityFiles.OrphanPath(self.name, other)
104+
105+
@property
106+
def name(self):
107+
return self._name
108+
109+
def open(self, mode='r', *args, **kwargs):
110+
return _io_wrapper(
111+
self._reader.open_resource(self.name), mode, *args, **kwargs
112+
)
113+
114+
class OrphanPath(abc.Traversable):
115+
"""
116+
Orphan path, not tied to a module spec or resource reader.
117+
Can't be read and doesn't expose any meaningful children.
118+
"""
119+
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
124+
125+
def iterdir(self):
126+
return iter(())
127+
128+
def is_file(self):
129+
return False
130+
131+
is_dir = is_file
132+
133+
def joinpath(self, other):
134+
return CompatibilityFiles.OrphanPath(*self._path, other)
135+
136+
@property
137+
def name(self):
138+
return self._path[-1]
139+
140+
def open(self, mode='r', *args, **kwargs):
141+
raise FileNotFoundError("Can't open orphan path")
142+
143+
def __init__(self, spec):
144+
self.spec = spec
145+
146+
@property
147+
def _reader(self):
148+
with suppress(AttributeError):
149+
return self.spec.loader.get_resource_reader(self.spec.name)
150+
151+
def _native(self):
152+
"""
153+
Return the native reader if it supports files().
154+
"""
155+
reader = self._reader
156+
return reader if hasattr(reader, 'files') else self
157+
158+
def __getattr__(self, attr):
159+
return getattr(self._reader, attr)
160+
161+
def files(self):
162+
return CompatibilityFiles.SpecPath(self.spec, self._reader)
163+
164+
165+
def wrap_spec(package):
166+
"""
167+
Construct a package spec with traversable compatibility
168+
on the spec/loader/reader.
169+
"""
170+
return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)

lib/importlib_resources/_common.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import os
2+
import pathlib
3+
import tempfile
4+
import functools
5+
import contextlib
6+
import types
7+
import importlib
8+
9+
from typing import Union, Optional
10+
from .abc import ResourceReader, Traversable
11+
12+
from ._compat import wrap_spec
13+
14+
Package = Union[types.ModuleType, str]
15+
16+
17+
def files(package):
18+
# type: (Package) -> Traversable
19+
"""
20+
Get a Traversable resource from a package
21+
"""
22+
return from_package(get_package(package))
23+
24+
25+
def get_resource_reader(package):
26+
# type: (types.ModuleType) -> Optional[ResourceReader]
27+
"""
28+
Return the package's loader if it's a ResourceReader.
29+
"""
30+
# We can't use
31+
# a issubclass() check here because apparently abc.'s __subclasscheck__()
32+
# hook wants to create a weak reference to the object, but
33+
# zipimport.zipimporter does not support weak references, resulting in a
34+
# TypeError. That seems terrible.
35+
spec = package.__spec__
36+
reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore
37+
if reader is None:
38+
return None
39+
return reader(spec.name) # type: ignore
40+
41+
42+
def resolve(cand):
43+
# type: (Package) -> types.ModuleType
44+
return cand if isinstance(cand, types.ModuleType) else importlib.import_module(cand)
45+
46+
47+
def get_package(package):
48+
# type: (Package) -> types.ModuleType
49+
"""Take a package name or module object and return the module.
50+
51+
Raise an exception if the resolved module is not a package.
52+
"""
53+
resolved = resolve(package)
54+
if wrap_spec(resolved).submodule_search_locations is None:
55+
raise TypeError(f'{package!r} is not a package')
56+
return resolved
57+
58+
59+
def from_package(package):
60+
"""
61+
Return a Traversable object for the given package.
62+
63+
"""
64+
spec = wrap_spec(package)
65+
reader = spec.loader.get_resource_reader(spec.name)
66+
return reader.files()
67+
68+
69+
@contextlib.contextmanager
70+
def _tempfile(reader, suffix=''):
71+
# Not using tempfile.NamedTemporaryFile as it leads to deeper 'try'
72+
# blocks due to the need to close the temporary file to work on Windows
73+
# properly.
74+
fd, raw_path = tempfile.mkstemp(suffix=suffix)
75+
try:
76+
try:
77+
os.write(fd, reader())
78+
finally:
79+
os.close(fd)
80+
del reader
81+
yield pathlib.Path(raw_path)
82+
finally:
83+
try:
84+
os.remove(raw_path)
85+
except FileNotFoundError:
86+
pass
87+
88+
89+
@functools.singledispatch
90+
def as_file(path):
91+
"""
92+
Given a Traversable object, return that object as a
93+
path on the local file system in a context manager.
94+
"""
95+
return _tempfile(path.read_bytes, suffix=path.name)
96+
97+
98+
@as_file.register(pathlib.Path)
99+
@contextlib.contextmanager
100+
def _(path):
101+
"""
102+
Degenerate behavior for pathlib.Path objects.
103+
"""
104+
yield path

0 commit comments

Comments
 (0)