Skip to content

Commit 3110294

Browse files
authored
Merge pull request #255 from python/feature/228-directory-of-resources
Add support for rendering a local copy of resources on the file system
2 parents b8da844 + bac6e8e commit 3110294

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

CHANGES.rst

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
v5.9.0
2+
======
3+
4+
* #228: ``as_file`` now also supports a ``Traversable``
5+
representing a directory and (when needed) renders the
6+
full tree to a temporary directory.
7+
18
v5.8.1
29
======
310

importlib_resources/_common.py

+49-1
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,30 @@ def _tempfile(
9393
pass
9494

9595

96+
def _temp_file(path):
97+
return _tempfile(path.read_bytes, suffix=path.name)
98+
99+
100+
def _is_present_dir(path: Traversable) -> bool:
101+
"""
102+
Some Traversables implement ``is_dir()`` to raise an
103+
exception (i.e. ``FileNotFoundError``) when the
104+
directory doesn't exist. This function wraps that call
105+
to always return a boolean and only return True
106+
if there's a dir and it exists.
107+
"""
108+
with contextlib.suppress(FileNotFoundError):
109+
return path.is_dir()
110+
return False
111+
112+
96113
@functools.singledispatch
97114
def as_file(path):
98115
"""
99116
Given a Traversable object, return that object as a
100117
path on the local file system in a context manager.
101118
"""
102-
return _tempfile(path.read_bytes, suffix=path.name)
119+
return _temp_dir(path) if _is_present_dir(path) else _temp_file(path)
103120

104121

105122
@as_file.register(pathlib.Path)
@@ -109,3 +126,34 @@ def _(path):
109126
Degenerate behavior for pathlib.Path objects.
110127
"""
111128
yield path
129+
130+
131+
@contextlib.contextmanager
132+
def _temp_path(dir: tempfile.TemporaryDirectory):
133+
"""
134+
Wrap tempfile.TemporyDirectory to return a pathlib object.
135+
"""
136+
with dir as result:
137+
yield pathlib.Path(result)
138+
139+
140+
@contextlib.contextmanager
141+
def _temp_dir(path):
142+
"""
143+
Given a traversable dir, recursively replicate the whole tree
144+
to the file system in a context manager.
145+
"""
146+
assert path.is_dir()
147+
with _temp_path(tempfile.TemporaryDirectory()) as temp_dir:
148+
yield _write_contents(temp_dir, path)
149+
150+
151+
def _write_contents(target, source):
152+
child = target.joinpath(source.name)
153+
if source.is_dir():
154+
child.mkdir()
155+
for item in source.iterdir():
156+
_write_contents(child, item)
157+
else:
158+
child.open('wb').write(source.read_bytes())
159+
return child

importlib_resources/tests/test_resource.py

+8
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ def test_submodule_contents_by_name(self):
111111
{'__init__.py', 'binary.file'},
112112
)
113113

114+
def test_as_file_directory(self):
115+
with resources.as_file(resources.files('ziptestdata')) as data:
116+
assert data.name == 'ziptestdata'
117+
assert data.is_dir()
118+
assert data.joinpath('subdirectory').is_dir()
119+
assert len(list(data.iterdir()))
120+
assert not data.parent.exists()
121+
114122

115123
class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
116124
ZIP_MODULE = zipdata02 # type: ignore

0 commit comments

Comments
 (0)