| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d10091a commit 7f7e706
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -480,6 +480,8 @@ ABC hierarchy:: | |||
| 480 | 480 | ||
| 481 | 481 | .. class:: ResourceReader | |
| 482 | 482 | ||
| 483 | + *Superseded by TraversableReader* | ||
| 484 | + | ||
| 483 | 485 | An :term:`abstract base class` to provide the ability to read | |
| 484 | 486 | *resources*. | |
| 485 | 487 | ||
@@ -795,6 +797,28 @@ ABC hierarchy:: | |||
| 795 | 797 | itself does not end in ``__init__``. | |
| 796 | 798 | ||
| 797 | 799 | ||
| 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 | + | ||
| 798 | 822 | :mod:`importlib.resources` -- Resources | |
| 799 | 823 | --------------------------------------- | |
| 800 | 824 | ||
@@ -853,6 +877,19 @@ The following types are defined. | |||
| 853 | 877 | ||
| 854 | 878 | The following functions are available. | |
| 855 | 879 | ||
| 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 | + | ||
| 856 | 893 | .. function:: open_binary(package, resource) | |
| 857 | 894 | ||
| 858 | 895 | Open for binary reading the *resource* within *package*. | |
| Original file line number | Diff line number | Diff 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 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,6 +14,7 @@ | |||
| 14 | 14 | _frozen_importlib_external = _bootstrap_external | |
| 15 | 15 | import abc | |
| 16 | 16 | import warnings | |
| 17 | + from typing import Protocol, runtime_checkable | ||
| 17 | 18 | ||
| 18 | 19 | ||
| 19 | 20 | def _register(abstract_cls, *classes): | |
@@ -386,3 +387,88 @@ def contents(self): | |||
| 386 | 387 | ||
| 387 | 388 | ||
| 388 | 389 | _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()) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments