Skip to content

Commit 7f7e706

Browse files
bpo-39791: Add files() to importlib.resources (GH-19722)
* bpo-39791: Update importlib.resources to support files() API (importlib_resources 1.5). * πŸ“œπŸ€– Added by blurb_it. * Add some documentation about the new objects added. Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
1 parent d10091a commit 7f7e706

File tree

7 files changed

+295
-102
lines changed

7 files changed

+295
-102
lines changed

β€ŽDoc/library/importlib.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,8 @@ ABC hierarchy::
480480

481481
.. class:: ResourceReader
482482

483+
*Superseded by TraversableReader*
484+
483485
An :term:`abstract base class` to provide the ability to read
484486
*resources*.
485487

@@ -795,6 +797,28 @@ ABC hierarchy::
795797
itself does not end in ``__init__``.
796798

797799

800+
.. class:: Traversable
801+
802+
An object with a subset of pathlib.Path methods suitable for
803+
traversing directories and opening files.
804+
805+
.. versionadded:: 3.9
806+
807+
808+
.. class:: TraversableReader
809+
810+
An abstract base class for resource readers capable of serving
811+
the ``files`` interface. Subclasses ResourceReader and provides
812+
concrete implementations of the ResourceReader's abstract
813+
methods. Therefore, any loader supplying TraversableReader
814+
also supplies ResourceReader.
815+
816+
Loaders that wish to support resource reading are expected to
817+
implement this interface.
818+
819+
.. versionadded:: 3.9
820+
821+
798822
:mod:`importlib.resources` -- Resources
799823
---------------------------------------
800824

@@ -853,6 +877,19 @@ The following types are defined.
853877

854878
The following functions are available.
855879

880+
881+
.. function:: files(package)
882+
883+
Returns an :class:`importlib.resources.abc.Traversable` object
884+
representing the resource container for the package (think directory)
885+
and its resources (think files). A Traversable may contain other
886+
containers (think subdirectories).
887+
888+
*package* is either a name or a module object which conforms to the
889+
``Package`` requirements.
890+
891+
.. versionadded:: 3.9
892+
856893
.. function:: open_binary(package, resource)
857894

858895
Open for binary reading the *resource* within *package*.

β€ŽLib/importlib/_common.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import os
2+
import pathlib
3+
import zipfile
4+
import tempfile
5+
import functools
6+
import contextlib
7+
8+
9+
def from_package(package):
10+
"""
11+
Return a Traversable object for the given package.
12+
13+
"""
14+
spec = package.__spec__
15+
return from_traversable_resources(spec) or fallback_resources(spec)
16+
17+
18+
def from_traversable_resources(spec):
19+
"""
20+
If the spec.loader implements TraversableResources,
21+
directly or implicitly, it will have a ``files()`` method.
22+
"""
23+
with contextlib.suppress(AttributeError):
24+
return spec.loader.files()
25+
26+
27+
def fallback_resources(spec):
28+
package_directory = pathlib.Path(spec.origin).parent
29+
try:
30+
archive_path = spec.loader.archive
31+
rel_path = package_directory.relative_to(archive_path)
32+
return zipfile.Path(archive_path, str(rel_path) + '/')
33+
except Exception:
34+
pass
35+
return package_directory
36+
37+
38+
@contextlib.contextmanager
39+
def _tempfile(reader, suffix=''):
40+
# Not using tempfile.NamedTemporaryFile as it leads to deeper 'try'
41+
# blocks due to the need to close the temporary file to work on Windows
42+
# properly.
43+
fd, raw_path = tempfile.mkstemp(suffix=suffix)
44+
try:
45+
os.write(fd, reader())
46+
os.close(fd)
47+
yield pathlib.Path(raw_path)
48+
finally:
49+
try:
50+
os.remove(raw_path)
51+
except FileNotFoundError:
52+
pass
53+
54+
55+
@functools.singledispatch
56+
@contextlib.contextmanager
57+
def as_file(path):
58+
"""
59+
Given a Traversable object, return that object as a
60+
path on the local file system in a context manager.
61+
"""
62+
with _tempfile(path.read_bytes, suffix=path.name) as local:
63+
yield local
64+
65+
66+
@as_file.register(pathlib.Path)
67+
@contextlib.contextmanager
68+
def _(path):
69+
"""
70+
Degenerate behavior for pathlib.Path objects.
71+
"""
72+
yield path

β€ŽLib/importlib/abc.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
_frozen_importlib_external = _bootstrap_external
1515
import abc
1616
import warnings
17+
from typing import Protocol, runtime_checkable
1718

1819

1920
def _register(abstract_cls, *classes):
@@ -386,3 +387,88 @@ def contents(self):
386387

387388

388389
_register(ResourceReader, machinery.SourceFileLoader)
390+
391+
392+
@runtime_checkable
393+
class Traversable(Protocol):
394+
"""
395+
An object with a subset of pathlib.Path methods suitable for
396+
traversing directories and opening files.
397+
"""
398+
399+
@abc.abstractmethod
400+
def iterdir(self):
401+
"""
402+
Yield Traversable objects in self
403+
"""
404+
405+
@abc.abstractmethod
406+
def read_bytes(self):
407+
"""
408+
Read contents of self as bytes
409+
"""
410+
411+
@abc.abstractmethod
412+
def read_text(self, encoding=None):
413+
"""
414+
Read contents of self as bytes
415+
"""
416+
417+
@abc.abstractmethod
418+
def is_dir(self):
419+
"""
420+
Return True if self is a dir
421+
"""
422+
423+
@abc.abstractmethod
424+
def is_file(self):
425+
"""
426+
Return True if self is a file
427+
"""
428+
429+
@abc.abstractmethod
430+
def joinpath(self, child):
431+
"""
432+
Return Traversable child in self
433+
"""
434+
435+
@abc.abstractmethod
436+
def __truediv__(self, child):
437+
"""
438+
Return Traversable child in self
439+
"""
440+
441+
@abc.abstractmethod
442+
def open(self, mode='r', *args, **kwargs):
443+
"""
444+
mode may be 'r' or 'rb' to open as text or binary. Return a handle
445+
suitable for reading (same as pathlib.Path.open).
446+
447+
When opening as text, accepts encoding parameters such as those
448+
accepted by io.TextIOWrapper.
449+
"""
450+
451+
@abc.abstractproperty
452+
def name(self):
453+
# type: () -> str
454+
"""
455+
The base name of this object without any parent references.
456+
"""
457+
458+
459+
class TraversableResources(ResourceReader):
460+
@abc.abstractmethod
461+
def files(self):
462+
"""Return a Traversable object for the loaded package."""
463+
464+
def open_resource(self, resource):
465+
return self.files().joinpath(resource).open('rb')
466+
467+
def resource_path(self, resource):
468+
raise FileNotFoundError(resource)
469+
470+
def is_resource(self, path):
471+
return self.files().joinpath(path).isfile()
472+
473+
def contents(self):
474+
return (item.name for item in self.files().iterdir())

0 commit comments

Comments
Β (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy