Skip to content

Commit 81e7f72

Browse files
committed
Merge branch 'feature/extensible-traversable' into 'master'
Extensibility support for traversable objects Closes #77 See merge request python-devs/importlib_resources!85
2 parents 3090428 + 7146a45 commit 81e7f72

File tree

7 files changed

+64
-29
lines changed

7 files changed

+64
-29
lines changed

importlib_resources/_compat.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
from pathlib2 import Path, PurePath # type: ignore
99

1010

11+
try:
12+
from contextlib import suppress
13+
except ImportError:
14+
from contextlib2 import suppress # type: ignore
15+
16+
1117
try:
1218
from functools import singledispatch
1319
except ImportError:

importlib_resources/abc.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,21 @@ def open(self, mode='r', *args, **kwargs):
116116
When opening as text, accepts encoding parameters such as those
117117
accepted by io.TextIOWrapper.
118118
"""
119+
120+
121+
class TraversableResources(ResourceReader):
122+
@abc.abstractmethod
123+
def files(self):
124+
"""Return a Traversable object for the loaded package."""
125+
126+
def open_resource(self, resource):
127+
return self.files().joinpath(resource).open('rb')
128+
129+
def resource_path(self, resource):
130+
raise FileNotFoundError(resource)
131+
132+
def is_resource(self, path):
133+
return self.files().joinpath(path).isfile()
134+
135+
def contents(self):
136+
return (item.name for item in self.files().iterdir())

importlib_resources/docs/changelog.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
v1.3.0
66
======
7+
* Add extensibility support for non-standard loaders to supply
8+
``Traversable`` resources. Introduces a new abstract base
9+
class ``abc.TraversableResources`` that supersedes (but
10+
implements for compatibility) ``abc.ResourceReader``. Any
11+
loader that implements (implicitly or explicitly) the
12+
``TraversableResources.files`` method will be capable of
13+
supplying resources with subdirectory support. Closes #77.
714
* Preferred way to access ``as_file`` is now from top-level module.
815
``importlib_resources.trees.as_file`` is deprecated and discouraged.
916
Closes #86.

importlib_resources/docs/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ problems of ``pkg_resources``.
99

1010
In our terminology, a *resource* is a file tree that is located within an
1111
importable `Python package`_. Resources can live on the file system or in a
12-
zip file, with limited support for loader_ supporting the appropriate API for
13-
reading resources.
12+
zip file, with support for other loader_ classes that implement the appropriate
13+
API for reading resources.
1414

1515
``importlib_resources`` is a backport of Python 3.9's standard library
1616
`importlib.resources`_ module for Python 2.7, and 3.5 through 3.8. Users of

importlib_resources/docs/using.rst

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,6 @@ the ``data.one`` package, and
5555
``data`` package.
5656

5757

58-
Caveats
59-
=======
60-
61-
Subdirectory Access
62-
-------------------
63-
64-
Prior to importlib_resources 1.1 and the ``files()`` API, resources that were
65-
not direct descendents of a package's folder were inaccessible through the
66-
API, so in the example above ``resources1/resource1.1`` is not a resource of
67-
the ``data.one`` package and ``two/resource2.txt`` is not a resource of the
68-
``data`` package. Therefore, if subdirectory access is required, use the
69-
``files()`` API.
70-
71-
Resource Reader Support
72-
-----------------------
73-
74-
Due to the limitations on resource readers to access files beyond direct
75-
descendents of a package, the ``files()`` API does not rely
76-
on the importlib ResourceReader interface and thus only supports resources
77-
exposed by the built-in path and zipfile loaders. If support for arbitrary
78-
resource readers is required, the other API functions still support loading
79-
those resources.
80-
8158
Example
8259
=======
8360

@@ -186,6 +163,16 @@ manager.
186163
Both relative and absolute paths work for Python 3.7 and newer.
187164

188165

166+
Extending
167+
=========
168+
169+
Starting with Python 3.9 and ``importlib_resources`` 1.3, this package
170+
provides an interface for non-standard loaders, such as those used by
171+
executable bundlers, to supply resources. These loaders should subclass
172+
from the ``TraversableResources`` abstract class and implement the
173+
``files()`` method to return a ``Traversable`` object.
174+
175+
189176
.. rubric:: Footnotes
190177

191178
.. [#fn1] We're ignoring `PEP 420

importlib_resources/trees.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,29 @@
66

77
from ._compat import (
88
Path, package_spec, FileNotFoundError, ZipPath,
9-
singledispatch,
9+
singledispatch, suppress,
1010
)
1111

1212

1313
def from_package(package):
14-
"""Return a Traversable object for the given package"""
14+
"""
15+
Return a Traversable object for the given package.
16+
17+
"""
1518
spec = package_spec(package)
19+
return from_traversable_resources(spec) or fallback_resources(spec)
20+
21+
22+
def from_traversable_resources(spec):
23+
"""
24+
If the spec.loader implements TraversableResources,
25+
directly or implicitly, it will have a ``files()`` method.
26+
"""
27+
with suppress(AttributeError):
28+
return spec.loader.files()
29+
30+
31+
def fallback_resources(spec):
1632
package_directory = Path(spec.origin).parent
1733
try:
1834
archive_path = spec.loader.archive
@@ -44,7 +60,7 @@ def _tempfile(reader):
4460
@contextlib.contextmanager
4561
def as_file(path):
4662
"""
47-
Given a path-like object, return that object as a
63+
Given a Traversable object, return that object as a
4864
path on the local file system in a context manager.
4965
"""
5066
with _tempfile(path.read_bytes) as local:
@@ -55,6 +71,6 @@ def as_file(path):
5571
@contextlib.contextmanager
5672
def _(path):
5773
"""
58-
Degenerate behavior for pathlib.Path objects
74+
Degenerate behavior for pathlib.Path objects.
5975
"""
6076
yield path

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ install_requires =
2222
zipp >= 0.4; python_version < '3.8'
2323
singledispatch; python_version < '3.4'
2424
importlib_metadata; python_version < '3.8'
25+
contextlib2; python_version < '3'
2526
setup_requires = setuptools_scm[toml] >= 3.4.1
2627
packages = find:
2728

0 commit comments

Comments
 (0)