././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1735930311.4373128 importlib_resources-6.5.2/0000755000175100001660000000000014736030707015231 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.coveragerc0000644000175100001660000000063514736030670017355 0ustar00runnerdocker[run] omit = # leading `*/` for pytest-dev/pytest-cov#456 */.tox/* */_itertools.py */_legacy.py */simple.py */_path.py disable_warnings = couldnt-parse [report] show_missing = True exclude_also = # Exclude common false positives per # https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion # Ref jaraco/skeleton#97 and jaraco/skeleton#135 class .*\bProtocol\): if TYPE_CHECKING: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.editorconfig0000644000175100001660000000036614736030670017712 0ustar00runnerdockerroot = true [*] charset = utf-8 indent_style = tab indent_size = 4 insert_final_newline = true end_of_line = lf [*.py] indent_style = space max_line_length = 88 [*.{yml,yaml}] indent_style = space indent_size = 2 [*.rst] indent_style = space ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.gitattributes0000644000175100001660000000003314736030670020117 0ustar00runnerdocker*.file binary *.zip binary ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1735930311.426313 importlib_resources-6.5.2/.github/0000755000175100001660000000000014736030707016571 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.github/FUNDING.yml0000644000175100001660000000004314736030670020402 0ustar00runnerdockertidelift: pypi/importlib-resources ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.github/dependabot.yml0000644000175100001660000000022414736030670021416 0ustar00runnerdockerversion: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "daily" allow: - dependency-type: "all" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1735930311.426313 importlib_resources-6.5.2/.github/workflows/0000755000175100001660000000000014736030707020626 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.github/workflows/main.yml0000644000175100001660000000566714736030670022312 0ustar00runnerdockername: tests on: merge_group: push: branches-ignore: # temporary GH branches relating to merge queues (jaraco/skeleton#93) - gh-readonly-queue/** tags: # required if branches-ignore is supplied (jaraco/skeleton#103) - '**' pull_request: workflow_dispatch: permissions: contents: read env: # Environment variable to support color support (jaraco/skeleton#66) FORCE_COLOR: 1 # Suppress noisy pip warnings PIP_DISABLE_PIP_VERSION_CHECK: 'true' PIP_NO_PYTHON_VERSION_WARNING: 'true' PIP_NO_WARN_SCRIPT_LOCATION: 'true' # Ensure tests can sense settings about the environment TOX_OVERRIDE: >- testenv.pass_env+=GITHUB_*,FORCE_COLOR jobs: test: strategy: # https://blog.jaraco.com/efficient-use-of-ci-resources/ matrix: python: - "3.9" - "3.13" platform: - ubuntu-latest - macos-latest - windows-latest include: - python: "3.10" platform: ubuntu-latest - python: "3.11" platform: ubuntu-latest - python: "3.12" platform: ubuntu-latest - python: "3.14" platform: ubuntu-latest - python: pypy3.10 platform: ubuntu-latest runs-on: ${{ matrix.platform }} continue-on-error: ${{ matrix.python == '3.14' }} steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} allow-prereleases: true - name: Install tox run: python -m pip install tox - name: Run run: tox collateral: strategy: fail-fast: false matrix: job: - diffcov - docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v4 with: python-version: 3.x - name: Install tox run: python -m pip install tox - name: Eval ${{ matrix.job }} run: tox -e ${{ matrix.job }} check: # This job does nothing and is only used for the branch protection if: always() needs: - test - collateral runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} release: permissions: contents: write needs: - check if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: python-version: 3.x - name: Install tox run: python -m pip install tox - name: Run run: tox -e release env: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.gitignore0000644000175100001660000000222314736030670017217 0ustar00runnerdocker# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # dotenv .env # virtualenv .venv venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ /diffcov.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.pre-commit-config.yaml0000644000175100001660000000022614736030670021511 0ustar00runnerdockerrepos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.7.1 hooks: - id: ruff args: [--fix, --unsafe-fixes] - id: ruff-format ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/.readthedocs.yaml0000644000175100001660000000051614736030670020461 0ustar00runnerdockerversion: 2 python: install: - path: . extra_requirements: - doc # required boilerplate readthedocs/readthedocs.org#10401 build: os: ubuntu-lts-latest tools: python: latest # post-checkout job to ensure the clone isn't shallow jaraco/skeleton#114 jobs: post_checkout: - git fetch --unshallow || true ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/LICENSE0000644000175100001660000002613614736030670016245 0ustar00runnerdocker Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/NEWS.rst0000644000175100001660000003155314736030670016545 0ustar00runnerdockerv6.5.2 ====== Bugfixes -------- - Replaced reference to typing_extensions with stdlib Literal. (#323) v6.5.1 ====== Bugfixes -------- - Updated ``Traversable.read_text()`` to reflect the ``errors`` parameter (python/cpython#127012). (#321) v6.5.0 ====== Features -------- - Add type annotations for Traversable.open. (#317) - Require Python 3.9 or later. v6.4.5 ====== Bugfixes -------- - Omit sentinel values from a namespace path. (#311) v6.4.4 ====== No significant changes. v6.4.3 ====== Bugfixes -------- - When inferring the caller in ``files()`` correctly detect one's own module even when the resources package source is not present. (python/cpython#123085) v6.4.2 ====== Bugfixes -------- - Merged fix for UTF-16 BOM handling in functional tests. (#312) v6.4.1 ====== Bugfixes -------- - When constructing ZipReader, only append the name if the indicated module is a package. (python/cpython#121735) v6.4.0 ====== Features -------- - The functions ``is_resource()``, ``open_binary()``, ``open_text()``, ``path()``, ``read_binary()``, and ``read_text()`` are un-deprecated, and support subdirectories via multiple positional arguments. The ``contents()`` function also allows subdirectories, but remains deprecated. (#303) - Deferred select imports in for a speedup (python/cpython#109829). v6.3.2 ====== Bugfixes -------- - Restored expectation that local standard readers are preferred over degenerate readers. (#298) v6.3.1 ====== Bugfixes -------- - Restored expectation that stdlib readers are suppressed on Python 3.10. (#257) v6.3.0 ====== Features -------- - Add ``Anchor`` to ``importlib.resources`` (in order for the code to comply with the documentation) v6.2.0 ====== Features -------- - Future compatibility adapters now ensure that standard library readers are replaced without overriding non-standard readers. (#295) v6.1.3 ====== No significant changes. v6.1.2 ====== Bugfixes -------- - Fixed NotADirectoryError when calling files on a subdirectory of a namespace package. (#293) v6.1.1 ====== Bugfixes -------- - Added missed stream argument in simple.ResourceHandle. Ref python/cpython#111775. v6.1.0 ====== Features -------- - MultiplexedPath now expects Traversable paths. String arguments to MultiplexedPath are now deprecated. Bugfixes -------- - Enabled support for resources in namespace packages in zip files. (#287) v6.0.1 ====== Bugfixes -------- - Restored Apache license. (#285) v6.0.0 ====== Deprecations and Removals ------------------------- - Removed legacy functions deprecated in 5.3. (#80) v5.13.0 ======= Features -------- - Require Python 3.8 or later. v5.12.0 ======= * #257: ``importlib_resources`` (backport) now gives precedence to built-in readers (file system, zip, namespace packages), providing forward-compatibility of behaviors like ``MultiplexedPath``. v5.11.1 ======= v5.10.4 ======= * #280: Fixed one more ``EncodingWarning`` in test suite. v5.11.0 ======= * #265: ``MultiplexedPath`` now honors multiple subdirectories in ``iterdir`` and ``joinpath``. v5.10.3 ======= * Packaging refresh, including fixing EncodingWarnings and some tests cleanup. v5.10.2 ======= * #274: Prefer ``write_bytes`` to context manager as proposed in gh-100586. v5.10.1 ======= * #274: Fixed ``ResourceWarning`` in ``_common``. v5.10.0 ======= * #203: Lifted restriction on modules passed to ``files``. Now modules need not be a package and if a non-package module is passed, resources will be resolved adjacent to those modules, even for modules not found in any package. For example, ``files(import_module('mod.py'))`` will resolve resources found at the root. The parameter to files was renamed from 'package' to 'anchor', with a compatibility shim for those passing by keyword. * #259: ``files`` no longer requires the anchor to be specified and can infer the anchor from the caller's scope (defaults to the caller's module). v5.9.0 ====== * #228: ``as_file`` now also supports a ``Traversable`` representing a directory and (when needed) renders the full tree to a temporary directory. v5.8.1 ====== * #253: In ``MultiplexedPath``, restore expectation that a compound path with a non-existent directory does not raise an exception. v5.8.0 ====== * #250: Now ``Traversable.joinpath`` provides a concrete implementation, replacing the implementation in ``.simple`` and converging with the behavior in ``MultiplexedPath``. v5.7.1 ====== * #249: In ``simple.ResourceContainer.joinpath``, honor names split by ``posixpath.sep``. v5.7.0 ====== * #248: ``abc.Traversable.joinpath`` now allows for multiple arguments and specifies that ``posixpath.sep`` is allowed in any argument to accept multiple arguments, matching the behavior found in ``zipfile.Path`` and ``pathlib.Path``. ``simple.ResourceContainer`` now honors this behavior. v5.6.0 ====== * #244: Add type declarations in ABCs. v5.5.0 ====== * Require Python 3.7 or later. * #243: Fix error when no ``__pycache__`` directories exist when testing ``update-zips``. v5.4.0 ====== * #80: Test suite now relies entirely on the traversable API. v5.3.0 ====== * #80: Now raise a ``DeprecationWarning`` for all legacy functions. Instead, users should rely on the ``files()`` API introduced in importlib_resources 1.3. See `Migrating from Legacy `_ for guidance on avoiding the deprecated functions. v5.2.3 ====== * Updated readme to reflect current behavior and show which versions correspond to which behavior in CPython. v5.0.7 ====== * bpo-45419: Correct ``DegenerateFiles.Path`` ``.name`` and ``.open()`` interfaces to match ``Traversable``. v5.2.2 ====== * #234: Fix refleak in ``as_file`` caught by CPython tests. v5.2.1 ====== * bpo-38291: Avoid DeprecationWarning on ``typing.io``. v5.2.0 ====== * #80 via #221: Legacy API (``path``, ``contents``, ...) is now supported entirely by the ``.files()`` API with a compatibility shim supplied for resource loaders without that functionality. v5.0.6 ====== * bpo-38693: Prefer f-strings to ``.format`` calls. v5.1.4 ====== * #225: Require `zipp 3.1.0 `_ or later on Python prior to 3.10 to incorporate those fixes. v5.0.5 ====== * #216: Make MultiplexedPath.name a property per the spec. v5.1.3 ====== * Refresh packaging and improve tests. * #216: Make MultiplexedPath.name a property per the spec. v5.1.2 ====== * Re-release with changes from 5.0.4. v5.0.4 ====== * Fixed non-hermetic test in test_reader, revealed by GH-24670. v5.1.1 ====== * Re-release with changes from 5.0.3. v5.0.3 ====== * Simplified DegenerateFiles.Path. v5.0.2 ====== * #214: Added ``_adapters`` module to ensure that degenerate ``files`` behavior can be made available for legacy loaders whose resource readers don't implement it. Fixes issue where backport compatibility module was masking this fallback behavior only to discover the defect when applying changes to CPython. v5.1.0 ====== * Added ``simple`` module implementing adapters from a low-level resource reader interface to a ``TraversableResources`` interface. Closes #90. v5.0.1 ====== * Remove pyinstaller hook for hidden 'trees' module. v5.0.0 ====== * Removed ``importlib_resources.trees``, deprecated since 1.3.0. v4.1.1 ====== * Fixed badges in README. v4.1.0 ====== * #209: Adopt `jaraco/skeleton `_. * Cleaned up some straggling Python 2 compatibility code. * Refreshed test zip files without .pyc and .pyo files. v4.0.0 ====== * #108: Drop support for Python 2.7. Now requires Python 3.6+. v3.3.1 ====== * Minor cleanup. v3.3.0 ====== * #107: Drop support for Python 3.5. Now requires Python 2.7 or 3.6+. v3.2.1 ====== * #200: Minor fixes and improved tests for namespace package support. v3.2.0 ====== * #68: Resources in PEP 420 Namespace packages are now supported. v3.1.1 ====== * bpo-41490: ``contents`` is now also more aggressive about consuming any iterator from the ``Reader``. v3.1.0 ====== * #110 and bpo-41490: ``path`` method is more aggressive about releasing handles to zipfile objects early, enabling use-cases like ``certifi`` to leave the context open but delete the underlying zip file. v3.0.0 ====== * Package no longer exposes ``importlib_resources.__version__``. Users that wish to inspect the version of ``importlib_resources`` should instead invoke ``.version('importlib_resources')`` from ``importlib-metadata`` ( `stdlib `_ or `backport `_) directly. This change eliminates the dependency on ``importlib_metadata``. Closes #100. * Package now always includes its data. Closes #93. * Declare hidden imports for PyInstaller. Closes #101. v2.0.1 ====== * Select pathlib and contextlib imports based on Python version and avoid pulling in deprecated [pathlib](https://pypi.org/project/pathlib). Closes #97. v2.0.0 ====== * Loaders are no longer expected to implement the ``abc.TraversableResources`` interface, but are instead expected to return ``TraversableResources`` from their ``get_resource_reader`` method. v1.5.0 ====== * Traversable is now a Protocol instead of an Abstract Base Class (Python 2.7 and Python 3.8+). * Traversable objects now require a ``.name`` property. v1.4.0 ====== * #79: Temporary files created will now reflect the filename of their origin. v1.3.1 ====== * For improved compatibility, ``importlib_resources.trees`` is now imported implicitly. Closes #88. v1.3.0 ====== * Add extensibility support for non-standard loaders to supply ``Traversable`` resources. Introduces a new abstract base class ``abc.TraversableResources`` that supersedes (but implements for compatibility) ``abc.ResourceReader``. Any loader that implements (implicitly or explicitly) the ``TraversableResources.files`` method will be capable of supplying resources with subdirectory support. Closes #77. * Preferred way to access ``as_file`` is now from top-level module. ``importlib_resources.trees.as_file`` is deprecated and discouraged. Closes #86. * Moved ``Traversable`` abc to ``abc`` module. Closes #87. v1.2.0 ====== * Traversable now requires an ``open`` method. Closes #81. * Fixed error on ``Python 3.5.{0,3}``. Closes #83. * Updated packaging to resolve version from package metadata. Closes #82. v1.1.0 ====== * Add support for retrieving resources from subdirectories of packages through the new ``files()`` function, which returns a ``Traversable`` object with ``joinpath`` and ``read_*`` interfaces matching those of ``pathlib.Path`` objects. This new function supersedes all of the previous functionality as it provides a more general-purpose access to a package's resources. With this function, subdirectories are supported (Closes #58). The documentation has been updated to reflect that this function is now the preferred interface for loading package resources. It does not, however, support resources from arbitrary loaders. It currently only supports resources from file system path and zipfile packages (a consequence of the ResourceReader interface only operating on Python packages). 1.0.2 ===== * Fix ``setup_requires`` and ``install_requires`` metadata in ``setup.cfg``. Given by Anthony Sottile. 1.0.1 ===== * Update Trove classifiers. Closes #63 1.0 === * Backport fix for test isolation from Python 3.8/3.7. Closes #61 0.8 === * Strip ``importlib_resources.__version__``. Closes #56 * Fix a metadata problem with older setuptools. Closes #57 * Add an ``__all__`` to ``importlib_resources``. Closes #59 0.7 === * Fix ``setup.cfg`` metadata bug. Closes #55 0.6 === * Move everything from ``pyproject.toml`` to ``setup.cfg``, with the added benefit of fixing the PyPI metadata. Closes #54 * Turn off mypy's ``strict_optional`` setting for now. 0.5 === * Resynchronize with Python 3.7; changes the return type of ``contents()`` to be an ``Iterable``. Closes #52 0.4 === * Correctly find resources in subpackages inside a zip file. Closes #51 0.3 === * The API, implementation, and documentation is synchronized with the Python 3.7 standard library. Closes #47 * When run under Python 3.7 this API shadows the stdlib versions. Closes #50 0.2 === * **Backward incompatible change**. Split the ``open()`` and ``read()`` calls into separate binary and text versions, i.e. ``open_binary()``, ``open_text()``, ``read_binary()``, and ``read_text()``. Closes #41 * Fix a bug where unrelated resources could be returned from ``contents()``. Closes #44 * Correctly prevent namespace packages from containing resources. Closes #20 0.1 === * Initial release. .. Local Variables: mode: change-log-mode indent-tabs-mode: nil sentence-end-double-space: t fill-column: 78 coding: utf-8 End: ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1735930311.4373128 importlib_resources-6.5.2/PKG-INFO0000644000175100001660000000755314736030707016340 0ustar00runnerdockerMetadata-Version: 2.1 Name: importlib_resources Version: 6.5.2 Summary: Read resources from Python packages Author-email: Barry Warsaw Maintainer-email: "Jason R. Coombs" Project-URL: Source, https://github.com/python/importlib_resources Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: zipp>=3.1.0; python_version < "3.10" Provides-Extra: test Requires-Dist: pytest!=8.1.*,>=6; extra == "test" Requires-Dist: zipp>=3.17; extra == "test" Requires-Dist: jaraco.test>=5.4; extra == "test" Provides-Extra: doc Requires-Dist: sphinx>=3.5; extra == "doc" Requires-Dist: jaraco.packaging>=9.3; extra == "doc" Requires-Dist: rst.linker>=1.9; extra == "doc" Requires-Dist: furo; extra == "doc" Requires-Dist: sphinx-lint; extra == "doc" Requires-Dist: jaraco.tidelift>=1.4; extra == "doc" Provides-Extra: check Requires-Dist: pytest-checkdocs>=2.4; extra == "check" Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check" Provides-Extra: cover Requires-Dist: pytest-cov; extra == "cover" Provides-Extra: enabler Requires-Dist: pytest-enabler>=2.2; extra == "enabler" Provides-Extra: type Requires-Dist: pytest-mypy; extra == "type" .. image:: https://img.shields.io/pypi/v/importlib_resources.svg :target: https://pypi.org/project/importlib_resources .. image:: https://img.shields.io/pypi/pyversions/importlib_resources.svg .. image:: https://github.com/python/importlib_resources/actions/workflows/main.yml/badge.svg :target: https://github.com/python/importlib_resources/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff .. image:: https://readthedocs.org/projects/importlib-resources/badge/?version=latest :target: https://importlib-resources.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2025-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/importlib-resources :target: https://tidelift.com/subscription/pkg/pypi-importlib-resources?utm_source=pypi-importlib-resources&utm_medium=readme ``importlib_resources`` is a backport of Python standard library `importlib.resources `_ module for older Pythons. The key goal of this module is to replace parts of `pkg_resources `_ with a solution in Python's stdlib that relies on well-defined APIs. This makes reading resources included in packages easier, with more stable and consistent semantics. Compatibility ============= New features are introduced in this third-party library and later merged into CPython. The following table indicates which versions of this library were contributed to different versions in the standard library: .. list-table:: :header-rows: 1 * - importlib_resources - stdlib * - 6.0 - 3.13 * - 5.12 - 3.12 * - 5.7 - 3.11 * - 5.0 - 3.10 * - 1.3 - 3.9 * - 0.5 (?) - 3.7 For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/README.rst0000644000175100001660000000461314736030670016723 0ustar00runnerdocker.. image:: https://img.shields.io/pypi/v/importlib_resources.svg :target: https://pypi.org/project/importlib_resources .. image:: https://img.shields.io/pypi/pyversions/importlib_resources.svg .. image:: https://github.com/python/importlib_resources/actions/workflows/main.yml/badge.svg :target: https://github.com/python/importlib_resources/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff .. image:: https://readthedocs.org/projects/importlib-resources/badge/?version=latest :target: https://importlib-resources.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2025-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/importlib-resources :target: https://tidelift.com/subscription/pkg/pypi-importlib-resources?utm_source=pypi-importlib-resources&utm_medium=readme ``importlib_resources`` is a backport of Python standard library `importlib.resources `_ module for older Pythons. The key goal of this module is to replace parts of `pkg_resources `_ with a solution in Python's stdlib that relies on well-defined APIs. This makes reading resources included in packages easier, with more stable and consistent semantics. Compatibility ============= New features are introduced in this third-party library and later merged into CPython. The following table indicates which versions of this library were contributed to different versions in the standard library: .. list-table:: :header-rows: 1 * - importlib_resources - stdlib * - 6.0 - 3.13 * - 5.12 - 3.12 * - 5.7 - 3.11 * - 5.0 - 3.10 * - 1.3 - 3.9 * - 0.5 (?) - 3.7 For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/SECURITY.md0000644000175100001660000000026414736030670017023 0ustar00runnerdocker# Security Contact To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/codecov.yml0000644000175100001660000000006714736030670017400 0ustar00runnerdockercodecov: token: 5eb1bc45-1b7f-43e6-8bc1-f2b02833dba9 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1735930311.4273129 importlib_resources-6.5.2/docs/0000755000175100001660000000000014736030707016161 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/docs/api.rst0000644000175100001660000000075014736030670017465 0ustar00runnerdocker============= API Reference ============= ``importlib_resources`` module ------------------------------ .. automodule:: importlib_resources :members: :undoc-members: :show-inheritance: .. automodule:: importlib_resources.abc :members: :undoc-members: :show-inheritance: .. automodule:: importlib_resources.readers :members: :undoc-members: :show-inheritance: .. automodule:: importlib_resources.simple :members: :undoc-members: :show-inheritance: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/docs/conf.py0000644000175100001660000000364214736030670017464 0ustar00runnerdockerfrom __future__ import annotations extensions = [ 'sphinx.ext.autodoc', 'jaraco.packaging.sphinx', ] master_doc = "index" html_theme = "furo" # Link dates and other references in the changelog extensions += ['rst.linker'] link_files = { '../NEWS.rst': dict( using=dict(GH='https://github.com'), replace=[ dict( pattern=r'(Issue #|\B#)(?P\d+)', url='{package_url}/issues/{issue}', ), dict( pattern=r'(?m:^((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n)', with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', ), dict( pattern=r'PEP[- ](?P\d+)', url='https://peps.python.org/pep-{pep_number:0>4}/', ), dict( pattern=r'(python/cpython#|Python #)(?P\d+)', url='https://github.com/python/cpython/issues/{python}', ), dict( pattern=r'bpo-(?P\d+)', url='http://bugs.python.org/issue{bpo}', ), ], ), } # Be strict about any broken references nitpicky = True nitpick_ignore: list[tuple[str, str]] = [] # Include Python intersphinx mapping to prevent failures # jaraco/skeleton#51 extensions += ['sphinx.ext.intersphinx'] intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), } # Preserve authored syntax for defaults autodoc_preserve_defaults = True # Add support for linking usernames, PyPI projects, Wikipedia pages github_url = 'https://github.com/' extlinks = { 'user': (f'{github_url}%s', '@%s'), 'pypi': ('https://pypi.org/project/%s', '%s'), 'wiki': ('https://wikipedia.org/wiki/%s', '%s'), } extensions += ['sphinx.ext.extlinks'] # local extensions += ['jaraco.tidelift'] nitpick_ignore.extend([ ('py:class', 'module'), ('py:class', '_io.BufferedReader'), ]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/docs/history.rst0000644000175100001660000000011614736030670020411 0ustar00runnerdocker:tocdepth: 2 .. _changes: History ******* .. include:: ../NEWS (links).rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/docs/index.rst0000644000175100001660000000332314736030670020022 0ustar00runnerdockerWelcome to |project| documentation! =================================== .. sidebar-links:: :home: :pypi: ``importlib_resources`` is a library which provides for access to *resources* in Python packages. It provides functionality similar to ``pkg_resources`` `Basic Resource Access`_ API, but without all of the overhead and performance problems of ``pkg_resources``. In our terminology, a *resource* is a file tree that is located alongside an importable `Python module`_. Resources can live on the file system or in a zip file, with support for other loader_ classes that implement the appropriate API for reading resources. ``importlib_resources`` supplies a backport of :mod:`importlib.resources`, enabling early access to features of future Python versions and making functionality available for older Python versions. Users are encouraged to use the Python standard library where suitable and fall back to this library for future compatibility. Developers looking for detailed API descriptions should refer to the standard library documentation. The documentation here includes a general :ref:`usage ` guide and a :ref:`migration ` guide for projects that want to adopt ``importlib_resources`` instead of ``pkg_resources``. .. toctree:: :maxdepth: 2 :caption: Contents: using api migration history .. tidelift-referral-banner:: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _`Basic Resource Access`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access .. _`Python module`: https://docs.python.org/3/glossary.html#term-module .. _loader: https://docs.python.org/3/reference/import.html#finders-and-loaders ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/docs/migration.rst0000644000175100001660000001342414736030670020707 0ustar00runnerdocker.. _migration: ================= Migration guide ================= The following guide will help you migrate common ``pkg_resources`` APIs to ``importlib_resources``. Only a small number of the most common APIs are supported by ``importlib_resources``, so projects that use other features (e.g. entry points) will have to find other solutions. ``importlib_resources`` primarily supports the following `basic resource access`_ APIs: * ``pkg_resources.resource_filename()`` * ``pkg_resources.resource_stream()`` * ``pkg_resources.resource_string()`` * ``pkg_resources.resource_listdir()`` * ``pkg_resources.resource_isdir()`` Note that although the steps below provide a drop-in replacement for the above methods, for many use-cases, a better approach is to use the ``Traversable`` path from ``files()`` directly. pkg_resources.resource_filename() ================================= ``resource_filename()`` is one of the more interesting APIs because it guarantees that the return value names a file on the file system. This means that if the resource is in a zip file, ``pkg_resources`` will extract the file and return the name of the temporary file it created. The problem is that ``pkg_resources`` also *implicitly* cleans up this temporary file, without control over its lifetime by the programmer. ``importlib_resources`` takes a different approach. Its equivalent API is the ``files()`` function, which returns a Traversable object implementing a subset of the :py:class:`pathlib.Path` interface suitable for reading the contents and provides a wrapper for creating a temporary file on the system in a context whose lifetime is managed by the user. Note though that if the resource is *already* on the file system, ``importlib_resources`` still returns a context manager, but nothing needs to get cleaned up. Here's an example from ``pkg_resources``:: path = pkg_resources.resource_filename('my.package', 'resource.dat') The best way to convert this is with the following idiom:: ref = importlib_resources.files('my.package') / 'resource.dat' with importlib_resources.as_file(ref) as path: # Do something with path. After the with-statement exits, any # temporary file created will be immediately cleaned up. That's all fine if you only need the file temporarily, but what if you need it to stick around for a while? One way of doing this is to use an :py:class:`contextlib.ExitStack` instance and manage the resource explicitly:: from contextlib import ExitStack file_manager = ExitStack() ref = importlib_resources.files('my.package') / 'resource.dat' path = file_manager.enter_context( importlib_resources.as_file(ref)) Now ``path`` will continue to exist until you explicitly call ``file_manager.close()``. What if you want the file to exist until the process exits, or you can't pass ``file_manager`` around in your code? Use an :py:mod:`atexit` handler:: import atexit file_manager = ExitStack() atexit.register(file_manager.close) ref = importlib_resources.files('my.package') / 'resource.dat' path = file_manager.enter_context( importlib_resources.as_file(ref)) Assuming your Python interpreter exits gracefully, the temporary file will be cleaned up when Python exits. pkg_resources.resource_stream() =============================== ``pkg_resources.resource_stream()`` returns a readable file-like object opened in binary mode. When you read from the returned file-like object, you get bytes. E.g.:: with pkg_resources.resource_stream('my.package', 'resource.dat') as fp: my_bytes = fp.read() The equivalent code in ``importlib_resources`` is pretty straightforward:: ref = importlib_resources.files('my.package').joinpath('resource.dat') with ref.open('rb') as fp: my_bytes = fp.read() pkg_resources.resource_string() =============================== In Python 2, ``pkg_resources.resource_string()`` returns the contents of a resource as a ``str``. In Python 3, this function is a misnomer; it actually returns the contents of the named resource as ``bytes``. That's why the following example is often written for clarity as:: from pkg_resources import resource_string as resource_bytes contents = resource_bytes('my.package', 'resource.dat') This can be easily rewritten like so:: ref = importlib_resources.files('my.package').joinpath('resource.dat') contents = ref.read_bytes() pkg_resources.resource_listdir() ================================ This function lists the entries in the package, both files and directories, but it does not recurse into subdirectories, e.g.:: for entry in pkg_resources.resource_listdir('my.package', 'subpackage'): print(entry) This is easily rewritten using the following idiom:: for entry in importlib_resources.files('my.package.subpackage').iterdir(): print(entry.name) Note: * ``Traversable.iterdir()`` returns *all* the entries in the subpackage, i.e. both resources (files) and non-resources (directories). * ``Traversable.iterdir()`` returns additional traversable objects, which if directories can also be iterated over (recursively). * ``Traversable.iterdir()``, like ``pathlib.Path`` returns an iterator, not a concrete sequence. * The order in which the elements are returned is undefined. pkg_resources.resource_isdir() ============================== You can ask ``pkg_resources`` to tell you whether a particular resource inside a package is a directory or not:: if pkg_resources.resource_isdir('my.package', 'resource'): print('A directory') The ``importlib_resources`` equivalent is straightforward:: if importlib_resources.files('my.package').joinpath('resource').is_dir(): print('A directory') .. _`basic resource access`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/docs/using.rst0000644000175100001660000002102514736030670020037 0ustar00runnerdocker.. _using: =========================== Using importlib_resources =========================== ``importlib_resources`` is a library that leverages Python's import system to provide access to *resources* within *packages* and alongside *modules*. Given that this library is built on top of the import system, it is highly efficient and easy to use. This library's philosophy is that, if one can import a module, one can access resources associated with that module. Resources can be opened or read, in either binary or text mode. What exactly do we mean by "a resource"? It's easiest to think about the metaphor of files and directories on the file system, though it's important to keep in mind that this is just a metaphor. Resources and packages **do not** have to exist as physical files and directories on the file system. If you have a file system layout such as:: data/ __init__.py one/ __init__.py resource1.txt module1.py resources1/ resource1.1.txt two/ __init__.py resource2.txt standalone.py resource3.txt then the directories are ``data``, ``data/one``, and ``data/two``. Each of these are also Python packages by virtue of the fact that they all contain ``__init__.py`` files. That means that in Python, all of these import statements work:: import data import data.one from data import two Each import statement gives you a Python *module* corresponding to the ``__init__.py`` file in each of the respective directories. These modules are packages since packages are just special module instances that have an additional attribute, namely a ``__path__`` [#fn1]_. In this analogy then, resources are just files or directories contained in a package directory, so ``data/one/resource1.txt`` and ``data/two/resource2.txt`` are both resources, as are the ``__init__.py`` files in all the directories. Resources in packages are always accessed relative to the package that they live in. ``resource1.txt`` and ``resources1/resource1.1.txt`` are resources within the ``data.one`` package, and ``two/resource2.txt`` is a resource within the ``data`` package. Resources may also be referenced relative to another *anchor*, a module in a package (``data.one.module1``) or a standalone module (``standalone``). In this case, resources are loaded from the same loader that loaded that module. Example ======= Let's say you are writing an email parsing library and in your test suite you have a sample email message in a file called ``message.eml``. You would like to access the contents of this file for your tests, so you put this in your project under the ``email/tests/data/message.eml`` path. Let's say your unit tests live in ``email/tests/test_email.py``. Your test could read the data file by doing something like:: data_dir = os.path.join(os.path.dirname(__file__), 'tests', 'data') data_path = os.path.join(data_dir, 'message.eml') with open(data_path, encoding='utf-8') as fp: eml = fp.read() But there's a problem with this! The use of ``__file__`` doesn't work if your package lives inside a zip file, since in that case this code does not live on the file system. You could use the `pkg_resources API`_ like so:: # In Python 3, resource_string() actually returns bytes! from pkg_resources import resource_string as resource_bytes eml = resource_bytes('email.tests.data', 'message.eml').decode('utf-8') This requires you to make Python packages of both ``email/tests`` and ``email/tests/data``, by placing an empty ``__init__.py`` files in each of those directories. The problem with the ``pkg_resources`` approach is that, depending on the packages in your environment, ``pkg_resources`` can be expensive just to import. This behavior can have a serious negative impact on things like command line startup time for Python implement commands. ``importlib_resources`` solves this performance challenge by being built entirely on the back of the stdlib :py:mod:`importlib`. By taking advantage of all the efficiencies in Python's import system, and the fact that it's built into Python, using ``importlib_resources`` can be much more performant. The equivalent code using ``importlib_resources`` would look like:: from importlib_resources import files # Reads contents with UTF-8 encoding and returns str. eml = files('email.tests.data').joinpath('message.eml').read_text() Anchors ======= The ``importlib_resources`` ``files`` API takes an *anchor* as its first parameter, which can either be a package name (as a ``str``) or an actual module object. If a string is passed in, it must name an importable Python module, which is imported prior to loading any resources. Thus the above example could also be written as:: import email.tests.data eml = files(email.tests.data).joinpath('message.eml').read_text() Namespace Packages ================== ``importlib_resources`` supports namespace packages as anchors just like any other package. Similar to modules in a namespace package, resources in a namespace package are not allowed to collide by name. For example, if two packages both expose ``nspkg/data/foo.txt``, those resources are unsupported by this library. The package will also likely experience problems due to the collision with installers. It's perfectly valid, however, for two packages to present different resources in the same namespace package, regular package, or subdirectory. For example, one package could expose ``nspkg/data/foo.txt`` and another expose ``nspkg/data/bar.txt`` and those two packages could be installed into separate paths, and the resources should be queryable:: data = importlib_resources.files('nspkg').joinpath('data') data.joinpath('foo.txt').read_text() data.joinpath('bar.txt').read_text() File system or zip file ======================= A consumer need not worry whether any given package is on the file system or in a zip file, as the ``importlib_resources`` APIs abstracts those details. Sometimes though, the user needs a path to an actual file on the file system. For example, some SSL APIs require a certificate file to be specified by a real file system path, and C's ``dlopen()`` function also requires a real file system path. To support this need, ``importlib_resources`` provides an API to extract the resource from a zip file to a temporary file or folder and return the file system path to this materialized resource as a :py:class:`pathlib.Path` object. In order to properly clean up this temporary file, what's actually returned is a context manager for use in a ``with``-statement:: from importlib_resources import files, as_file source = files(email.tests.data).joinpath('message.eml') with as_file(source) as eml: third_party_api_requiring_file_system_path(eml) Use all the standard :py:mod:`contextlib` APIs to manage this context manager. Migrating from Legacy ===================== Starting with Python 3.9 and ``importlib_resources`` 1.4, this package introduced the ``files()`` API, to be preferred over the legacy API, i.e. the functions ``open_binary``, ``open_text``, ``path``, ``contents``, ``read_text``, ``read_binary``, and ``is_resource``. To port to the ``files()`` API, refer to the `_legacy module `_ to see simple wrappers that enable drop-in replacement based on the preferred API, and either copy those or adapt the usage to utilize the ``files`` and `Traversable `_ interfaces directly. Extending ========= Starting with Python 3.9 and ``importlib_resources`` 2.0, this package provides an interface for non-standard loaders, such as those used by executable bundlers, to supply resources. These loaders should supply a ``get_resource_reader`` method, which is passed a module name and should return a ``TraversableResources`` instance. .. rubric:: Footnotes .. [#fn1] As of `PEP 451 `_ this information is also available on the module's ``__spec__.submodule_search_locations`` attribute, which will not be ``None`` for packages. .. _`pkg_resources API`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access .. _`loader`: https://docs.python.org/3/reference/import.html#finders-and-loaders .. _`ResourceReader`: https://docs.python.org/3.7/library/importlib.html#importlib.abc.ResourceReader ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1735930311.429313 importlib_resources-6.5.2/importlib_resources/0000755000175100001660000000000014736030707021324 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/__init__.py0000644000175100001660000000127414736030670023440 0ustar00runnerdocker""" Read resources contained within a package. This codebase is shared between importlib.resources in the stdlib and importlib_resources in PyPI. See https://github.com/python/importlib_metadata/wiki/Development-Methodology for more detail. """ from ._common import ( Anchor, Package, as_file, files, ) from ._functional import ( contents, is_resource, open_binary, open_text, path, read_binary, read_text, ) from .abc import ResourceReader __all__ = [ 'Package', 'Anchor', 'ResourceReader', 'as_file', 'files', 'contents', 'is_resource', 'open_binary', 'open_text', 'path', 'read_binary', 'read_text', ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/_adapters.py0000644000175100001660000001060214736030670023636 0ustar00runnerdockerfrom contextlib import suppress from io import TextIOWrapper from . import abc class SpecLoaderAdapter: """ Adapt a package spec to adapt the underlying loader. """ def __init__(self, spec, adapter=lambda spec: spec.loader): self.spec = spec self.loader = adapter(spec) def __getattr__(self, name): return getattr(self.spec, name) class TraversableResourcesLoader: """ Adapt a loader to provide TraversableResources. """ def __init__(self, spec): self.spec = spec def get_resource_reader(self, name): return CompatibilityFiles(self.spec)._native() def _io_wrapper(file, mode='r', *args, **kwargs): if mode == 'r': return TextIOWrapper(file, *args, **kwargs) elif mode == 'rb': return file raise ValueError(f"Invalid mode value '{mode}', only 'r' and 'rb' are supported") class CompatibilityFiles: """ Adapter for an existing or non-existent resource reader to provide a compatibility .files(). """ class SpecPath(abc.Traversable): """ Path tied to a module spec. Can be read and exposes the resource reader children. """ def __init__(self, spec, reader): self._spec = spec self._reader = reader def iterdir(self): if not self._reader: return iter(()) return iter( CompatibilityFiles.ChildPath(self._reader, path) for path in self._reader.contents() ) def is_file(self): return False is_dir = is_file def joinpath(self, other): if not self._reader: return CompatibilityFiles.OrphanPath(other) return CompatibilityFiles.ChildPath(self._reader, other) @property def name(self): return self._spec.name def open(self, mode='r', *args, **kwargs): return _io_wrapper(self._reader.open_resource(None), mode, *args, **kwargs) class ChildPath(abc.Traversable): """ Path tied to a resource reader child. Can be read but doesn't expose any meaningful children. """ def __init__(self, reader, name): self._reader = reader self._name = name def iterdir(self): return iter(()) def is_file(self): return self._reader.is_resource(self.name) def is_dir(self): return not self.is_file() def joinpath(self, other): return CompatibilityFiles.OrphanPath(self.name, other) @property def name(self): return self._name def open(self, mode='r', *args, **kwargs): return _io_wrapper( self._reader.open_resource(self.name), mode, *args, **kwargs ) class OrphanPath(abc.Traversable): """ Orphan path, not tied to a module spec or resource reader. Can't be read and doesn't expose any meaningful children. """ def __init__(self, *path_parts): if len(path_parts) < 1: raise ValueError('Need at least one path part to construct a path') self._path = path_parts def iterdir(self): return iter(()) def is_file(self): return False is_dir = is_file def joinpath(self, other): return CompatibilityFiles.OrphanPath(*self._path, other) @property def name(self): return self._path[-1] def open(self, mode='r', *args, **kwargs): raise FileNotFoundError("Can't open orphan path") def __init__(self, spec): self.spec = spec @property def _reader(self): with suppress(AttributeError): return self.spec.loader.get_resource_reader(self.spec.name) def _native(self): """ Return the native reader if it supports files(). """ reader = self._reader return reader if hasattr(reader, 'files') else self def __getattr__(self, attr): return getattr(self._reader, attr) def files(self): return CompatibilityFiles.SpecPath(self.spec, self._reader) def wrap_spec(package): """ Construct a package spec with traversable compatibility on the spec/loader/reader. """ return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/_common.py0000644000175100001660000001277014736030670023333 0ustar00runnerdockerimport contextlib import functools import importlib import inspect import itertools import os import pathlib import tempfile import types import warnings from typing import Optional, Union, cast from .abc import ResourceReader, Traversable Package = Union[types.ModuleType, str] Anchor = Package def package_to_anchor(func): """ Replace 'package' parameter as 'anchor' and warn about the change. Other errors should fall through. >>> files('a', 'b') Traceback (most recent call last): TypeError: files() takes from 0 to 1 positional arguments but 2 were given Remove this compatibility in Python 3.14. """ undefined = object() @functools.wraps(func) def wrapper(anchor=undefined, package=undefined): if package is not undefined: if anchor is not undefined: return func(anchor, package) warnings.warn( "First parameter to files is renamed to 'anchor'", DeprecationWarning, stacklevel=2, ) return func(package) elif anchor is undefined: return func() return func(anchor) return wrapper @package_to_anchor def files(anchor: Optional[Anchor] = None) -> Traversable: """ Get a Traversable resource for an anchor. """ return from_package(resolve(anchor)) def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]: """ Return the package's loader if it's a ResourceReader. """ # We can't use # a issubclass() check here because apparently abc.'s __subclasscheck__() # hook wants to create a weak reference to the object, but # zipimport.zipimporter does not support weak references, resulting in a # TypeError. That seems terrible. spec = package.__spec__ reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore[union-attr] if reader is None: return None return reader(spec.name) # type: ignore[union-attr] @functools.singledispatch def resolve(cand: Optional[Anchor]) -> types.ModuleType: return cast(types.ModuleType, cand) @resolve.register def _(cand: str) -> types.ModuleType: return importlib.import_module(cand) @resolve.register def _(cand: None) -> types.ModuleType: return resolve(_infer_caller().f_globals['__name__']) def _infer_caller(): """ Walk the stack and find the frame of the first caller not in this module. """ def is_this_file(frame_info): return frame_info.filename == stack[0].filename def is_wrapper(frame_info): return frame_info.function == 'wrapper' stack = inspect.stack() not_this_file = itertools.filterfalse(is_this_file, stack) # also exclude 'wrapper' due to singledispatch in the call stack callers = itertools.filterfalse(is_wrapper, not_this_file) return next(callers).frame def from_package(package: types.ModuleType): """ Return a Traversable object for the given package. """ # deferred for performance (python/cpython#109829) from .future.adapters import wrap_spec spec = wrap_spec(package) reader = spec.loader.get_resource_reader(spec.name) return reader.files() @contextlib.contextmanager def _tempfile( reader, suffix='', # gh-93353: Keep a reference to call os.remove() in late Python # finalization. *, _os_remove=os.remove, ): # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try' # blocks due to the need to close the temporary file to work on Windows # properly. fd, raw_path = tempfile.mkstemp(suffix=suffix) try: try: os.write(fd, reader()) finally: os.close(fd) del reader yield pathlib.Path(raw_path) finally: try: _os_remove(raw_path) except FileNotFoundError: pass def _temp_file(path): return _tempfile(path.read_bytes, suffix=path.name) def _is_present_dir(path: Traversable) -> bool: """ Some Traversables implement ``is_dir()`` to raise an exception (i.e. ``FileNotFoundError``) when the directory doesn't exist. This function wraps that call to always return a boolean and only return True if there's a dir and it exists. """ with contextlib.suppress(FileNotFoundError): return path.is_dir() return False @functools.singledispatch def as_file(path): """ Given a Traversable object, return that object as a path on the local file system in a context manager. """ return _temp_dir(path) if _is_present_dir(path) else _temp_file(path) @as_file.register(pathlib.Path) @contextlib.contextmanager def _(path): """ Degenerate behavior for pathlib.Path objects. """ yield path @contextlib.contextmanager def _temp_path(dir: tempfile.TemporaryDirectory): """ Wrap tempfile.TemporaryDirectory to return a pathlib object. """ with dir as result: yield pathlib.Path(result) @contextlib.contextmanager def _temp_dir(path): """ Given a traversable dir, recursively replicate the whole tree to the file system in a context manager. """ assert path.is_dir() with _temp_path(tempfile.TemporaryDirectory()) as temp_dir: yield _write_contents(temp_dir, path) def _write_contents(target, source): child = target.joinpath(source.name) if source.is_dir(): child.mkdir() for item in source.iterdir(): _write_contents(child, item) else: child.write_bytes(source.read_bytes()) return child ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/_functional.py0000644000175100001660000000526714736030670024210 0ustar00runnerdocker"""Simplified function-based API for importlib.resources""" import warnings from ._common import as_file, files from .abc import TraversalError _MISSING = object() def open_binary(anchor, *path_names): """Open for binary reading the *resource* within *package*.""" return _get_resource(anchor, path_names).open('rb') def open_text(anchor, *path_names, encoding=_MISSING, errors='strict'): """Open for text reading the *resource* within *package*.""" encoding = _get_encoding_arg(path_names, encoding) resource = _get_resource(anchor, path_names) return resource.open('r', encoding=encoding, errors=errors) def read_binary(anchor, *path_names): """Read and return contents of *resource* within *package* as bytes.""" return _get_resource(anchor, path_names).read_bytes() def read_text(anchor, *path_names, encoding=_MISSING, errors='strict'): """Read and return contents of *resource* within *package* as str.""" encoding = _get_encoding_arg(path_names, encoding) resource = _get_resource(anchor, path_names) return resource.read_text(encoding=encoding, errors=errors) def path(anchor, *path_names): """Return the path to the *resource* as an actual file system path.""" return as_file(_get_resource(anchor, path_names)) def is_resource(anchor, *path_names): """Return ``True`` if there is a resource named *name* in the package, Otherwise returns ``False``. """ try: return _get_resource(anchor, path_names).is_file() except TraversalError: return False def contents(anchor, *path_names): """Return an iterable over the named resources within the package. The iterable returns :class:`str` resources (e.g. files). The iterable does not recurse into subdirectories. """ warnings.warn( "importlib.resources.contents is deprecated. " "Use files(anchor).iterdir() instead.", DeprecationWarning, stacklevel=1, ) return (resource.name for resource in _get_resource(anchor, path_names).iterdir()) def _get_encoding_arg(path_names, encoding): # For compatibility with versions where *encoding* was a positional # argument, it needs to be given explicitly when there are multiple # *path_names*. # This limitation can be removed in Python 3.15. if encoding is _MISSING: if len(path_names) > 1: raise TypeError( "'encoding' argument required with multiple path names", ) else: return 'utf-8' return encoding def _get_resource(anchor, path_names): if anchor is None: raise TypeError("anchor must be module or string, got None") return files(anchor).joinpath(*path_names) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/_itertools.py0000644000175100001660000000237514736030670024067 0ustar00runnerdocker# from more_itertools 9.0 def only(iterable, default=None, too_long=None): """If *iterable* has only one item, return it. If it has zero items, return *default*. If it has more than one item, raise the exception given by *too_long*, which is ``ValueError`` by default. >>> only([], default='missing') 'missing' >>> only([1]) 1 >>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValueError: Expected exactly one item in iterable, but got 1, 2, and perhaps more.' >>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TypeError Note that :func:`only` attempts to advance *iterable* twice to ensure there is only one item. See :func:`spy` or :func:`peekable` to check iterable contents less destructively. """ it = iter(iterable) first_value = next(it, default) try: second_value = next(it) except StopIteration: pass else: msg = ( 'Expected exactly one item in iterable, but got {!r}, {!r}, ' 'and perhaps more.'.format(first_value, second_value) ) raise too_long or ValueError(msg) return first_value ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/abc.py0000644000175100001660000001265514736030670022433 0ustar00runnerdockerimport abc import itertools import os import pathlib from typing import ( Any, BinaryIO, Iterable, Iterator, NoReturn, Literal, Optional, Protocol, Text, TextIO, Union, overload, runtime_checkable, ) StrPath = Union[str, os.PathLike[str]] __all__ = ["ResourceReader", "Traversable", "TraversableResources"] class ResourceReader(metaclass=abc.ABCMeta): """Abstract base class for loaders to provide resource reading support.""" @abc.abstractmethod def open_resource(self, resource: Text) -> BinaryIO: """Return an opened, file-like object for binary reading. The 'resource' argument is expected to represent only a file name. If the resource cannot be found, FileNotFoundError is raised. """ # This deliberately raises FileNotFoundError instead of # NotImplementedError so that if this method is accidentally called, # it'll still do the right thing. raise FileNotFoundError @abc.abstractmethod def resource_path(self, resource: Text) -> Text: """Return the file system path to the specified resource. The 'resource' argument is expected to represent only a file name. If the resource does not exist on the file system, raise FileNotFoundError. """ # This deliberately raises FileNotFoundError instead of # NotImplementedError so that if this method is accidentally called, # it'll still do the right thing. raise FileNotFoundError @abc.abstractmethod def is_resource(self, path: Text) -> bool: """Return True if the named 'path' is a resource. Files are resources, directories are not. """ raise FileNotFoundError @abc.abstractmethod def contents(self) -> Iterable[str]: """Return an iterable of entries in `package`.""" raise FileNotFoundError class TraversalError(Exception): pass @runtime_checkable class Traversable(Protocol): """ An object with a subset of pathlib.Path methods suitable for traversing directories and opening files. Any exceptions that occur when accessing the backing resource may propagate unaltered. """ @abc.abstractmethod def iterdir(self) -> Iterator["Traversable"]: """ Yield Traversable objects in self """ def read_bytes(self) -> bytes: """ Read contents of self as bytes """ with self.open('rb') as strm: return strm.read() def read_text( self, encoding: Optional[str] = None, errors: Optional[str] = None ) -> str: """ Read contents of self as text """ with self.open(encoding=encoding, errors=errors) as strm: return strm.read() @abc.abstractmethod def is_dir(self) -> bool: """ Return True if self is a directory """ @abc.abstractmethod def is_file(self) -> bool: """ Return True if self is a file """ def joinpath(self, *descendants: StrPath) -> "Traversable": """ Return Traversable resolved with any descendants applied. Each descendant should be a path segment relative to self and each may contain multiple levels separated by ``posixpath.sep`` (``/``). """ if not descendants: return self names = itertools.chain.from_iterable( path.parts for path in map(pathlib.PurePosixPath, descendants) ) target = next(names) matches = ( traversable for traversable in self.iterdir() if traversable.name == target ) try: match = next(matches) except StopIteration: raise TraversalError( "Target not found during traversal.", target, list(names) ) return match.joinpath(*names) def __truediv__(self, child: StrPath) -> "Traversable": """ Return Traversable child in self """ return self.joinpath(child) @overload def open(self, mode: Literal['r'] = 'r', *args: Any, **kwargs: Any) -> TextIO: ... @overload def open(self, mode: Literal['rb'], *args: Any, **kwargs: Any) -> BinaryIO: ... @abc.abstractmethod def open( self, mode: str = 'r', *args: Any, **kwargs: Any ) -> Union[TextIO, BinaryIO]: """ mode may be 'r' or 'rb' to open as text or binary. Return a handle suitable for reading (same as pathlib.Path.open). When opening as text, accepts encoding parameters such as those accepted by io.TextIOWrapper. """ @property @abc.abstractmethod def name(self) -> str: """ The base name of this object without any parent references. """ class TraversableResources(ResourceReader): """ The required interface for providing traversable resources. """ @abc.abstractmethod def files(self) -> "Traversable": """Return a Traversable object for the loaded package.""" def open_resource(self, resource: StrPath) -> BinaryIO: return self.files().joinpath(resource).open('rb') def resource_path(self, resource: Any) -> NoReturn: raise FileNotFoundError(resource) def is_resource(self, path: StrPath) -> bool: return self.files().joinpath(path).is_file() def contents(self) -> Iterator[str]: return (item.name for item in self.files().iterdir()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1735930311.4313128 importlib_resources-6.5.2/importlib_resources/compat/0000755000175100001660000000000014736030707022607 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/compat/__init__.py0000644000175100001660000000000014736030670024705 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/compat/py39.py0000644000175100001660000000022714736030670023765 0ustar00runnerdockerimport sys __all__ = ['ZipPath'] if sys.version_info >= (3, 10): from zipfile import Path as ZipPath else: from zipp import Path as ZipPath ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1735930311.4313128 importlib_resources-6.5.2/importlib_resources/future/0000755000175100001660000000000014736030707022636 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/future/__init__.py0000644000175100001660000000000014736030670024734 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/future/adapters.py0000644000175100001660000000632514736030670025020 0ustar00runnerdockerimport functools import pathlib from contextlib import suppress from types import SimpleNamespace from .. import _adapters, readers def _block_standard(reader_getter): """ Wrap _adapters.TraversableResourcesLoader.get_resource_reader and intercept any standard library readers. """ @functools.wraps(reader_getter) def wrapper(*args, **kwargs): """ If the reader is from the standard library, return None to allow allow likely newer implementations in this library to take precedence. """ try: reader = reader_getter(*args, **kwargs) except NotADirectoryError: # MultiplexedPath may fail on zip subdirectory return except ValueError as exc: # NamespaceReader in stdlib may fail for editable installs # (python/importlib_resources#311, python/importlib_resources#318) # Remove after bugfix applied to Python 3.13. if "not enough values to unpack" not in str(exc): raise return # Python 3.10+ mod_name = reader.__class__.__module__ if mod_name.startswith('importlib.') and mod_name.endswith('readers'): return # Python 3.8, 3.9 if isinstance(reader, _adapters.CompatibilityFiles) and ( reader.spec.loader.__class__.__module__.startswith('zipimport') or reader.spec.loader.__class__.__module__.startswith( '_frozen_importlib_external' ) ): return return reader return wrapper def _skip_degenerate(reader): """ Mask any degenerate reader. Ref #298. """ is_degenerate = ( isinstance(reader, _adapters.CompatibilityFiles) and not reader._reader ) return reader if not is_degenerate else None class TraversableResourcesLoader(_adapters.TraversableResourcesLoader): """ Adapt loaders to provide TraversableResources and other compatibility. Ensures the readers from importlib_resources are preferred over stdlib readers. """ def get_resource_reader(self, name): return ( _skip_degenerate(_block_standard(super().get_resource_reader)(name)) or self._standard_reader() or super().get_resource_reader(name) ) def _standard_reader(self): return self._zip_reader() or self._namespace_reader() or self._file_reader() def _zip_reader(self): with suppress(AttributeError): return readers.ZipReader(self.spec.loader, self.spec.name) def _namespace_reader(self): with suppress(AttributeError, ValueError): return readers.NamespaceReader(self.spec.submodule_search_locations) def _file_reader(self): try: path = pathlib.Path(self.spec.origin) except TypeError: return None if path.exists(): return readers.FileReader(SimpleNamespace(path=path)) def wrap_spec(package): """ Override _adapters.wrap_spec to use TraversableResourcesLoader from above. Ensures that future behavior is always available on older Pythons. """ return _adapters.SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/py.typed0000644000175100001660000000000014736030670023010 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/readers.py0000644000175100001660000001415214736030670023325 0ustar00runnerdockerfrom __future__ import annotations import collections import contextlib import itertools import operator import pathlib import re import warnings from collections.abc import Iterator from . import abc from ._itertools import only from .compat.py39 import ZipPath def remove_duplicates(items): return iter(collections.OrderedDict.fromkeys(items)) class FileReader(abc.TraversableResources): def __init__(self, loader): self.path = pathlib.Path(loader.path).parent def resource_path(self, resource): """ Return the file system path to prevent `resources.path()` from creating a temporary copy. """ return str(self.path.joinpath(resource)) def files(self): return self.path class ZipReader(abc.TraversableResources): def __init__(self, loader, module): self.prefix = loader.prefix.replace('\\', '/') if loader.is_package(module): _, _, name = module.rpartition('.') self.prefix += name + '/' self.archive = loader.archive def open_resource(self, resource): try: return super().open_resource(resource) except KeyError as exc: raise FileNotFoundError(exc.args[0]) def is_resource(self, path): """ Workaround for `zipfile.Path.is_file` returning true for non-existent paths. """ target = self.files().joinpath(path) return target.is_file() and target.exists() def files(self): return ZipPath(self.archive, self.prefix) class MultiplexedPath(abc.Traversable): """ Given a series of Traversable objects, implement a merged version of the interface across all objects. Useful for namespace packages which may be multihomed at a single name. """ def __init__(self, *paths): self._paths = list(map(_ensure_traversable, remove_duplicates(paths))) if not self._paths: message = 'MultiplexedPath must contain at least one path' raise FileNotFoundError(message) if not all(path.is_dir() for path in self._paths): raise NotADirectoryError('MultiplexedPath only supports directories') def iterdir(self): children = (child for path in self._paths for child in path.iterdir()) by_name = operator.attrgetter('name') groups = itertools.groupby(sorted(children, key=by_name), key=by_name) return map(self._follow, (locs for name, locs in groups)) def read_bytes(self): raise FileNotFoundError(f'{self} is not a file') def read_text(self, *args, **kwargs): raise FileNotFoundError(f'{self} is not a file') def is_dir(self): return True def is_file(self): return False def joinpath(self, *descendants): try: return super().joinpath(*descendants) except abc.TraversalError: # One of the paths did not resolve (a directory does not exist). # Just return something that will not exist. return self._paths[0].joinpath(*descendants) @classmethod def _follow(cls, children): """ Construct a MultiplexedPath if needed. If children contains a sole element, return it. Otherwise, return a MultiplexedPath of the items. Unless one of the items is not a Directory, then return the first. """ subdirs, one_dir, one_file = itertools.tee(children, 3) try: return only(one_dir) except ValueError: try: return cls(*subdirs) except NotADirectoryError: return next(one_file) def open(self, *args, **kwargs): raise FileNotFoundError(f'{self} is not a file') @property def name(self): return self._paths[0].name def __repr__(self): paths = ', '.join(f"'{path}'" for path in self._paths) return f'MultiplexedPath({paths})' class NamespaceReader(abc.TraversableResources): def __init__(self, namespace_path): if 'NamespacePath' not in str(namespace_path): raise ValueError('Invalid path') self.path = MultiplexedPath(*filter(bool, map(self._resolve, namespace_path))) @classmethod def _resolve(cls, path_str) -> abc.Traversable | None: r""" Given an item from a namespace path, resolve it to a Traversable. path_str might be a directory on the filesystem or a path to a zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or ``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``. path_str might also be a sentinel used by editable packages to trigger other behaviors (see python/importlib_resources#311). In that case, return None. """ dirs = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir()) return next(dirs, None) @classmethod def _candidate_paths(cls, path_str: str) -> Iterator[abc.Traversable]: yield pathlib.Path(path_str) yield from cls._resolve_zip_path(path_str) @staticmethod def _resolve_zip_path(path_str: str): for match in reversed(list(re.finditer(r'[\\/]', path_str))): with contextlib.suppress( FileNotFoundError, IsADirectoryError, NotADirectoryError, PermissionError, ): inner = path_str[match.end() :].replace('\\', '/') + '/' yield ZipPath(path_str[: match.start()], inner.lstrip('/')) def resource_path(self, resource): """ Return the file system path to prevent `resources.path()` from creating a temporary copy. """ return str(self.path.joinpath(resource)) def files(self): return self.path def _ensure_traversable(path): """ Convert deprecated string arguments to traversables (pathlib.Path). Remove with Python 3.15. """ if not isinstance(path, str): return path warnings.warn( "String arguments are deprecated. Pass a Traversable instead.", DeprecationWarning, stacklevel=3, ) return pathlib.Path(path) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/simple.py0000644000175100001660000000503614736030670023172 0ustar00runnerdocker""" Interface adapters for low-level readers. """ import abc import io import itertools from typing import BinaryIO, List from .abc import Traversable, TraversableResources class SimpleReader(abc.ABC): """ The minimum, low-level interface required from a resource provider. """ @property @abc.abstractmethod def package(self) -> str: """ The name of the package for which this reader loads resources. """ @abc.abstractmethod def children(self) -> List['SimpleReader']: """ Obtain an iterable of SimpleReader for available child containers (e.g. directories). """ @abc.abstractmethod def resources(self) -> List[str]: """ Obtain available named resources for this virtual package. """ @abc.abstractmethod def open_binary(self, resource: str) -> BinaryIO: """ Obtain a File-like for a named resource. """ @property def name(self): return self.package.split('.')[-1] class ResourceContainer(Traversable): """ Traversable container for a package's resources via its reader. """ def __init__(self, reader: SimpleReader): self.reader = reader def is_dir(self): return True def is_file(self): return False def iterdir(self): files = (ResourceHandle(self, name) for name in self.reader.resources) dirs = map(ResourceContainer, self.reader.children()) return itertools.chain(files, dirs) def open(self, *args, **kwargs): raise IsADirectoryError() class ResourceHandle(Traversable): """ Handle to a named resource in a ResourceReader. """ def __init__(self, parent: ResourceContainer, name: str): self.parent = parent self.name = name # type: ignore[misc] def is_file(self): return True def is_dir(self): return False def open(self, mode='r', *args, **kwargs): stream = self.parent.reader.open_binary(self.name) if 'b' not in mode: stream = io.TextIOWrapper(stream, *args, **kwargs) return stream def joinpath(self, name): raise RuntimeError("Cannot traverse into a resource") class TraversableReader(TraversableResources, SimpleReader): """ A TraversableResources based on SimpleReader. Resource providers may derive from this class to provide the TraversableResources interface by supplying the SimpleReader interface. """ def files(self): return ResourceContainer(self) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1735930311.433313 importlib_resources-6.5.2/importlib_resources/tests/0000755000175100001660000000000014736030707022466 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/__init__.py0000644000175100001660000000000014736030670024564 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/_path.py0000644000175100001660000000431214736030670024132 0ustar00runnerdockerimport functools import pathlib from typing import Dict, Protocol, Union, runtime_checkable #### # from jaraco.path 3.7.1 class Symlink(str): """ A string indicating the target of a symlink. """ FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']] @runtime_checkable class TreeMaker(Protocol): def __truediv__(self, *args, **kwargs): ... # pragma: no cover def mkdir(self, **kwargs): ... # pragma: no cover def write_text(self, content, **kwargs): ... # pragma: no cover def write_bytes(self, content): ... # pragma: no cover def symlink_to(self, target): ... # pragma: no cover def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker: return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore[return-value] def build( spec: FilesSpec, prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore[assignment] ): """ Build a set of files/directories, as described by the spec. Each key represents a pathname, and the value represents the content. Content may be a nested directory. >>> spec = { ... 'README.txt': "A README file", ... "foo": { ... "__init__.py": "", ... "bar": { ... "__init__.py": "", ... }, ... "baz.py": "# Some code", ... "bar.py": Symlink("baz.py"), ... }, ... "bing": Symlink("foo"), ... } >>> target = getfixture('tmp_path') >>> build(spec, target) >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8') '# Some code' >>> target.joinpath('bing/bar.py').read_text(encoding='utf-8') '# Some code' """ for name, contents in spec.items(): create(contents, _ensure_tree_maker(prefix) / name) @functools.singledispatch def create(content: Union[str, bytes, FilesSpec], path): path.mkdir(exist_ok=True) build(content, prefix=path) # type: ignore[arg-type] @create.register def _(content: bytes, path): path.write_bytes(content) @create.register def _(content: str, path): path.write_text(content, encoding='utf-8') @create.register def _(content: Symlink, path): path.symlink_to(content) # end from jaraco.path #### ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1735930311.4343128 importlib_resources-6.5.2/importlib_resources/tests/compat/0000755000175100001660000000000014736030707023751 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/compat/__init__.py0000644000175100001660000000000014736030670026047 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/compat/py312.py0000644000175100001660000000055414736030670025204 0ustar00runnerdockerimport contextlib from .py39 import import_helper @contextlib.contextmanager def isolated_modules(): """ Save modules on entry and cleanup on exit. """ (saved,) = import_helper.modules_setup() try: yield finally: import_helper.modules_cleanup(saved) vars(import_helper).setdefault('isolated_modules', isolated_modules) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/compat/py39.py0000644000175100001660000000067114736030670025132 0ustar00runnerdocker""" Backward-compatability shims to support Python 3.9 and earlier. """ from jaraco.test.cpython import from_test_support, try_import import_helper = try_import('import_helper') or from_test_support( 'modules_setup', 'modules_cleanup', 'DirsOnSysPath' ) os_helper = try_import('os_helper') or from_test_support('temp_dir') warnings_helper = try_import('warnings_helper') or from_test_support( 'ignore_warnings', 'check_warnings' ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_compatibilty_files.py0000644000175100001660000000636114736030670027766 0ustar00runnerdockerimport io import unittest import importlib_resources as resources from importlib_resources._adapters import ( CompatibilityFiles, wrap_spec, ) from . import util class CompatibilityFilesTests(unittest.TestCase): @property def package(self): bytes_data = io.BytesIO(b'Hello, world!') return util.create_package( file=bytes_data, path='some_path', contents=('a', 'b', 'c'), ) @property def files(self): return resources.files(self.package) def test_spec_path_iter(self): self.assertEqual( sorted(path.name for path in self.files.iterdir()), ['a', 'b', 'c'], ) def test_child_path_iter(self): self.assertEqual(list((self.files / 'a').iterdir()), []) def test_orphan_path_iter(self): self.assertEqual(list((self.files / 'a' / 'a').iterdir()), []) self.assertEqual(list((self.files / 'a' / 'a' / 'a').iterdir()), []) def test_spec_path_is(self): self.assertFalse(self.files.is_file()) self.assertFalse(self.files.is_dir()) def test_child_path_is(self): self.assertTrue((self.files / 'a').is_file()) self.assertFalse((self.files / 'a').is_dir()) def test_orphan_path_is(self): self.assertFalse((self.files / 'a' / 'a').is_file()) self.assertFalse((self.files / 'a' / 'a').is_dir()) self.assertFalse((self.files / 'a' / 'a' / 'a').is_file()) self.assertFalse((self.files / 'a' / 'a' / 'a').is_dir()) def test_spec_path_name(self): self.assertEqual(self.files.name, 'testingpackage') def test_child_path_name(self): self.assertEqual((self.files / 'a').name, 'a') def test_orphan_path_name(self): self.assertEqual((self.files / 'a' / 'b').name, 'b') self.assertEqual((self.files / 'a' / 'b' / 'c').name, 'c') def test_spec_path_open(self): self.assertEqual(self.files.read_bytes(), b'Hello, world!') self.assertEqual(self.files.read_text(encoding='utf-8'), 'Hello, world!') def test_child_path_open(self): self.assertEqual((self.files / 'a').read_bytes(), b'Hello, world!') self.assertEqual( (self.files / 'a').read_text(encoding='utf-8'), 'Hello, world!' ) def test_orphan_path_open(self): with self.assertRaises(FileNotFoundError): (self.files / 'a' / 'b').read_bytes() with self.assertRaises(FileNotFoundError): (self.files / 'a' / 'b' / 'c').read_bytes() def test_open_invalid_mode(self): with self.assertRaises(ValueError): self.files.open('0') def test_orphan_path_invalid(self): with self.assertRaises(ValueError): CompatibilityFiles.OrphanPath() def test_wrap_spec(self): spec = wrap_spec(self.package) self.assertIsInstance(spec.loader.get_resource_reader(None), CompatibilityFiles) class CompatibilityFilesNoReaderTests(unittest.TestCase): @property def package(self): return util.create_package_from_loader(None) @property def files(self): return resources.files(self.package) def test_spec_path_joinpath(self): self.assertIsInstance(self.files / 'a', CompatibilityFiles.OrphanPath) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_contents.py0000644000175100001660000000150614736030670025735 0ustar00runnerdockerimport unittest import importlib_resources as resources from . import util class ContentsTests: expected = { '__init__.py', 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file', } def test_contents(self): contents = {path.name for path in resources.files(self.data).iterdir()} assert self.expected <= contents class ContentsDiskTests(ContentsTests, util.DiskSetup, unittest.TestCase): pass class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase): pass class ContentsNamespaceTests(ContentsTests, util.DiskSetup, unittest.TestCase): MODULE = 'namespacedata01' expected = { # no __init__ because of namespace design 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file', } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_custom.py0000644000175100001660000000230614736030670025411 0ustar00runnerdockerimport contextlib import pathlib import unittest import importlib_resources as resources from .. import abc from ..abc import ResourceReader, TraversableResources from . import util from .compat.py39 import os_helper class SimpleLoader: """ A simple loader that only implements a resource reader. """ def __init__(self, reader: ResourceReader): self.reader = reader def get_resource_reader(self, package): return self.reader class MagicResources(TraversableResources): """ Magically returns the resources at path. """ def __init__(self, path: pathlib.Path): self.path = path def files(self): return self.path class CustomTraversableResourcesTests(unittest.TestCase): def setUp(self): self.fixtures = contextlib.ExitStack() self.addCleanup(self.fixtures.close) def test_custom_loader(self): temp_dir = pathlib.Path(self.fixtures.enter_context(os_helper.temp_dir())) loader = SimpleLoader(MagicResources(temp_dir)) pkg = util.create_package_from_loader(loader) files = resources.files(pkg) assert isinstance(files, abc.Traversable) assert list(files.iterdir()) == [] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_files.py0000644000175100001660000001315514736030670025205 0ustar00runnerdockerimport contextlib import importlib import pathlib import py_compile import textwrap import unittest import warnings import importlib_resources as resources from ..abc import Traversable from . import util from .compat.py39 import import_helper, os_helper @contextlib.contextmanager def suppress_known_deprecation(): with warnings.catch_warnings(record=True) as ctx: warnings.simplefilter('default', category=DeprecationWarning) yield ctx class FilesTests: def test_read_bytes(self): files = resources.files(self.data) actual = files.joinpath('utf-8.file').read_bytes() assert actual == b'Hello, UTF-8 world!\n' def test_read_text(self): files = resources.files(self.data) actual = files.joinpath('utf-8.file').read_text(encoding='utf-8') assert actual == 'Hello, UTF-8 world!\n' def test_traversable(self): assert isinstance(resources.files(self.data), Traversable) def test_joinpath_with_multiple_args(self): files = resources.files(self.data) binfile = files.joinpath('subdirectory', 'binary.file') self.assertTrue(binfile.is_file()) def test_old_parameter(self): """ Files used to take a 'package' parameter. Make sure anyone passing by name is still supported. """ with suppress_known_deprecation(): resources.files(package=self.data) class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase): pass class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase): pass class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase): MODULE = 'namespacedata01' def test_non_paths_in_dunder_path(self): """ Non-path items in a namespace package's ``__path__`` are ignored. As reported in python/importlib_resources#311, some tools like Setuptools, when creating editable packages, will inject non-paths into a namespace package's ``__path__``, a sentinel like ``__editable__.sample_namespace-1.0.finder.__path_hook__`` to cause the ``PathEntryFinder`` to be called when searching for packages. In that case, resources should still be loadable. """ import namespacedata01 # type: ignore[import-not-found] namespacedata01.__path__.append( '__editable__.sample_namespace-1.0.finder.__path_hook__' ) resources.files(namespacedata01) class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase): ZIP_MODULE = 'namespacedata01' class DirectSpec: """ Override behavior of ModuleSetup to write a full spec directly. """ MODULE = 'unused' def load_fixture(self, name): self.tree_on_path(self.spec) class ModulesFiles: spec = { 'mod.py': '', 'res.txt': 'resources are the best', } def test_module_resources(self): """ A module can have resources found adjacent to the module. """ import mod # type: ignore[import-not-found] actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8') assert actual == self.spec['res.txt'] class ModuleFilesDiskTests(DirectSpec, util.DiskSetup, ModulesFiles, unittest.TestCase): pass class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.TestCase): pass class ImplicitContextFiles: set_val = textwrap.dedent( f""" import {resources.__name__} as res val = res.files().joinpath('res.txt').read_text(encoding='utf-8') """ ) spec = { 'somepkg': { '__init__.py': set_val, 'submod.py': set_val, 'res.txt': 'resources are the best', }, 'frozenpkg': { '__init__.py': set_val.replace(resources.__name__, 'c_resources'), 'res.txt': 'resources are the best', }, } def test_implicit_files_package(self): """ Without any parameter, files() will infer the location as the caller. """ assert importlib.import_module('somepkg').val == 'resources are the best' def test_implicit_files_submodule(self): """ Without any parameter, files() will infer the location as the caller. """ assert importlib.import_module('somepkg.submod').val == 'resources are the best' def _compile_importlib(self): """ Make a compiled-only copy of the importlib resources package. Currently only code is copied, as importlib resources doesn't itself have any resources. """ bin_site = self.fixtures.enter_context(os_helper.temp_dir()) c_resources = pathlib.Path(bin_site, 'c_resources') sources = pathlib.Path(resources.__file__).parent for source_path in sources.glob('**/*.py'): c_path = c_resources.joinpath(source_path.relative_to(sources)).with_suffix( '.pyc' ) py_compile.compile(source_path, c_path) self.fixtures.enter_context(import_helper.DirsOnSysPath(bin_site)) def test_implicit_files_with_compiled_importlib(self): """ Caller detection works for compiled-only resources module. python/cpython#123085 """ self._compile_importlib() assert importlib.import_module('frozenpkg').val == 'resources are the best' class ImplicitContextFilesDiskTests( DirectSpec, util.DiskSetup, ImplicitContextFiles, unittest.TestCase ): pass class ImplicitContextFilesZipTests( DirectSpec, util.ZipSetup, ImplicitContextFiles, unittest.TestCase ): pass if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_functional.py0000644000175100001660000002155614736030670026251 0ustar00runnerdockerimport importlib import os import unittest import importlib_resources as resources from . import util from .compat.py39 import warnings_helper class StringAnchorMixin: anchor01 = 'data01' anchor02 = 'data02' class ModuleAnchorMixin: @property def anchor01(self): return importlib.import_module('data01') @property def anchor02(self): return importlib.import_module('data02') class FunctionalAPIBase: def setUp(self): super().setUp() self.load_fixture('data02') def _gen_resourcetxt_path_parts(self): """Yield various names of a text file in anchor02, each in a subTest""" for path_parts in ( ('subdirectory', 'subsubdir', 'resource.txt'), ('subdirectory/subsubdir/resource.txt',), ('subdirectory/subsubdir', 'resource.txt'), ): with self.subTest(path_parts=path_parts): yield path_parts def assertEndsWith(self, string, suffix): """Assert that `string` ends with `suffix`. Used to ignore an architecture-specific UTF-16 byte-order mark.""" self.assertEqual(string[-len(suffix) :], suffix) def test_read_text(self): self.assertEqual( resources.read_text(self.anchor01, 'utf-8.file'), 'Hello, UTF-8 world!\n', ) self.assertEqual( resources.read_text( self.anchor02, 'subdirectory', 'subsubdir', 'resource.txt', encoding='utf-8', ), 'a resource', ) for path_parts in self._gen_resourcetxt_path_parts(): self.assertEqual( resources.read_text( self.anchor02, *path_parts, encoding='utf-8', ), 'a resource', ) # Use generic OSError, since e.g. attempting to read a directory can # fail with PermissionError rather than IsADirectoryError with self.assertRaises(OSError): resources.read_text(self.anchor01) with self.assertRaises((OSError, resources.abc.TraversalError)): resources.read_text(self.anchor01, 'no-such-file') with self.assertRaises(UnicodeDecodeError): resources.read_text(self.anchor01, 'utf-16.file') self.assertEqual( resources.read_text( self.anchor01, 'binary.file', encoding='latin1', ), '\x00\x01\x02\x03', ) self.assertEndsWith( # ignore the BOM resources.read_text( self.anchor01, 'utf-16.file', errors='backslashreplace', ), 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode( errors='backslashreplace', ), ) def test_read_binary(self): self.assertEqual( resources.read_binary(self.anchor01, 'utf-8.file'), b'Hello, UTF-8 world!\n', ) for path_parts in self._gen_resourcetxt_path_parts(): self.assertEqual( resources.read_binary(self.anchor02, *path_parts), b'a resource', ) def test_open_text(self): with resources.open_text(self.anchor01, 'utf-8.file') as f: self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') for path_parts in self._gen_resourcetxt_path_parts(): with resources.open_text( self.anchor02, *path_parts, encoding='utf-8', ) as f: self.assertEqual(f.read(), 'a resource') # Use generic OSError, since e.g. attempting to read a directory can # fail with PermissionError rather than IsADirectoryError with self.assertRaises(OSError): resources.open_text(self.anchor01) with self.assertRaises((OSError, resources.abc.TraversalError)): resources.open_text(self.anchor01, 'no-such-file') with resources.open_text(self.anchor01, 'utf-16.file') as f: with self.assertRaises(UnicodeDecodeError): f.read() with resources.open_text( self.anchor01, 'binary.file', encoding='latin1', ) as f: self.assertEqual(f.read(), '\x00\x01\x02\x03') with resources.open_text( self.anchor01, 'utf-16.file', errors='backslashreplace', ) as f: self.assertEndsWith( # ignore the BOM f.read(), 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode( errors='backslashreplace', ), ) def test_open_binary(self): with resources.open_binary(self.anchor01, 'utf-8.file') as f: self.assertEqual(f.read(), b'Hello, UTF-8 world!\n') for path_parts in self._gen_resourcetxt_path_parts(): with resources.open_binary( self.anchor02, *path_parts, ) as f: self.assertEqual(f.read(), b'a resource') def test_path(self): with resources.path(self.anchor01, 'utf-8.file') as path: with open(str(path), encoding='utf-8') as f: self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') with resources.path(self.anchor01) as path: with open(os.path.join(path, 'utf-8.file'), encoding='utf-8') as f: self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') def test_is_resource(self): is_resource = resources.is_resource self.assertTrue(is_resource(self.anchor01, 'utf-8.file')) self.assertFalse(is_resource(self.anchor01, 'no_such_file')) self.assertFalse(is_resource(self.anchor01)) self.assertFalse(is_resource(self.anchor01, 'subdirectory')) for path_parts in self._gen_resourcetxt_path_parts(): self.assertTrue(is_resource(self.anchor02, *path_parts)) def test_contents(self): with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)): c = resources.contents(self.anchor01) self.assertGreaterEqual( set(c), {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}, ) with ( self.assertRaises(OSError), warnings_helper.check_warnings(( ".*contents.*", DeprecationWarning, )), ): list(resources.contents(self.anchor01, 'utf-8.file')) for path_parts in self._gen_resourcetxt_path_parts(): with ( self.assertRaises((OSError, resources.abc.TraversalError)), warnings_helper.check_warnings(( ".*contents.*", DeprecationWarning, )), ): list(resources.contents(self.anchor01, *path_parts)) with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)): c = resources.contents(self.anchor01, 'subdirectory') self.assertGreaterEqual( set(c), {'binary.file'}, ) @warnings_helper.ignore_warnings(category=DeprecationWarning) def test_common_errors(self): for func in ( resources.read_text, resources.read_binary, resources.open_text, resources.open_binary, resources.path, resources.is_resource, resources.contents, ): with self.subTest(func=func): # Rejecting None anchor with self.assertRaises(TypeError): func(None) # Rejecting invalid anchor type with self.assertRaises((TypeError, AttributeError)): func(1234) # Unknown module with self.assertRaises(ModuleNotFoundError): func('$missing module$') def test_text_errors(self): for func in ( resources.read_text, resources.open_text, ): with self.subTest(func=func): # Multiple path arguments need explicit encoding argument. with self.assertRaises(TypeError): func( self.anchor02, 'subdirectory', 'subsubdir', 'resource.txt', ) class FunctionalAPITest_StringAnchor_Disk( StringAnchorMixin, FunctionalAPIBase, util.DiskSetup, unittest.TestCase, ): pass class FunctionalAPITest_ModuleAnchor_Disk( ModuleAnchorMixin, FunctionalAPIBase, util.DiskSetup, unittest.TestCase, ): pass class FunctionalAPITest_StringAnchor_Memory( StringAnchorMixin, FunctionalAPIBase, util.MemorySetup, unittest.TestCase, ): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_open.py0000644000175100001660000000517214736030670025044 0ustar00runnerdockerimport unittest import importlib_resources as resources from . import util class CommonBinaryTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): target = resources.files(package).joinpath(path) with target.open('rb'): pass class CommonTextTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): target = resources.files(package).joinpath(path) with target.open(encoding='utf-8'): pass class OpenTests: def test_open_binary(self): target = resources.files(self.data) / 'binary.file' with target.open('rb') as fp: result = fp.read() self.assertEqual(result, bytes(range(4))) def test_open_text_default_encoding(self): target = resources.files(self.data) / 'utf-8.file' with target.open(encoding='utf-8') as fp: result = fp.read() self.assertEqual(result, 'Hello, UTF-8 world!\n') def test_open_text_given_encoding(self): target = resources.files(self.data) / 'utf-16.file' with target.open(encoding='utf-16', errors='strict') as fp: result = fp.read() self.assertEqual(result, 'Hello, UTF-16 world!\n') def test_open_text_with_errors(self): """ Raises UnicodeError without the 'errors' argument. """ target = resources.files(self.data) / 'utf-16.file' with target.open(encoding='utf-8', errors='strict') as fp: self.assertRaises(UnicodeError, fp.read) with target.open(encoding='utf-8', errors='ignore') as fp: result = fp.read() self.assertEqual( result, 'H\x00e\x00l\x00l\x00o\x00,\x00 ' '\x00U\x00T\x00F\x00-\x001\x006\x00 ' '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', ) def test_open_binary_FileNotFoundError(self): target = resources.files(self.data) / 'does-not-exist' with self.assertRaises(FileNotFoundError): target.open('rb') def test_open_text_FileNotFoundError(self): target = resources.files(self.data) / 'does-not-exist' with self.assertRaises(FileNotFoundError): target.open(encoding='utf-8') class OpenDiskTests(OpenTests, util.DiskSetup, unittest.TestCase): pass class OpenDiskNamespaceTests(OpenTests, util.DiskSetup, unittest.TestCase): MODULE = 'namespacedata01' class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase): pass class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase): MODULE = 'namespacedata01' if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_path.py0000644000175100001660000000370214736030670025034 0ustar00runnerdockerimport io import pathlib import unittest import importlib_resources as resources from . import util class CommonTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): with resources.as_file(resources.files(package).joinpath(path)): pass class PathTests: def test_reading(self): """ Path should be readable and a pathlib.Path instance. """ target = resources.files(self.data) / 'utf-8.file' with resources.as_file(target) as path: self.assertIsInstance(path, pathlib.Path) self.assertTrue(path.name.endswith("utf-8.file"), repr(path)) self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8')) class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase): def test_natural_path(self): """ Guarantee the internal implementation detail that file-system-backed resources do not get the tempdir treatment. """ target = resources.files(self.data) / 'utf-8.file' with resources.as_file(target) as path: assert 'data' in str(path) class PathMemoryTests(PathTests, unittest.TestCase): def setUp(self): file = io.BytesIO(b'Hello, UTF-8 world!\n') self.addCleanup(file.close) self.data = util.create_package( file=file, path=FileNotFoundError("package exists only in memory") ) self.data.__spec__.origin = None self.data.__spec__.has_location = False class PathZipTests(PathTests, util.ZipSetup, unittest.TestCase): def test_remove_in_context_manager(self): """ It is not an error if the file that was temporarily stashed on the file system is removed inside the `with` stanza. """ target = resources.files(self.data) / 'utf-8.file' with resources.as_file(target) as path: path.unlink() if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_read.py0000644000175100001660000000574614736030670025025 0ustar00runnerdockerimport unittest from importlib import import_module import importlib_resources as resources from . import util class CommonBinaryTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): resources.files(package).joinpath(path).read_bytes() class CommonTextTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): resources.files(package).joinpath(path).read_text(encoding='utf-8') class ReadTests: def test_read_bytes(self): result = resources.files(self.data).joinpath('binary.file').read_bytes() self.assertEqual(result, bytes(range(4))) def test_read_text_default_encoding(self): result = ( resources.files(self.data) .joinpath('utf-8.file') .read_text(encoding='utf-8') ) self.assertEqual(result, 'Hello, UTF-8 world!\n') def test_read_text_given_encoding(self): result = ( resources.files(self.data) .joinpath('utf-16.file') .read_text(encoding='utf-16') ) self.assertEqual(result, 'Hello, UTF-16 world!\n') def test_read_text_with_errors(self): """ Raises UnicodeError without the 'errors' argument. """ target = resources.files(self.data) / 'utf-16.file' self.assertRaises(UnicodeError, target.read_text, encoding='utf-8') result = target.read_text(encoding='utf-8', errors='ignore') self.assertEqual( result, 'H\x00e\x00l\x00l\x00o\x00,\x00 ' '\x00U\x00T\x00F\x00-\x001\x006\x00 ' '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', ) class ReadDiskTests(ReadTests, util.DiskSetup, unittest.TestCase): pass class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase): def test_read_submodule_resource(self): submodule = import_module('data01.subdirectory') result = resources.files(submodule).joinpath('binary.file').read_bytes() self.assertEqual(result, bytes(range(4, 8))) def test_read_submodule_resource_by_name(self): result = ( resources.files('data01.subdirectory').joinpath('binary.file').read_bytes() ) self.assertEqual(result, bytes(range(4, 8))) class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase): MODULE = 'namespacedata01' class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase): MODULE = 'namespacedata01' def test_read_submodule_resource(self): submodule = import_module('namespacedata01.subdirectory') result = resources.files(submodule).joinpath('binary.file').read_bytes() self.assertEqual(result, bytes(range(12, 16))) def test_read_submodule_resource_by_name(self): result = ( resources.files('namespacedata01.subdirectory') .joinpath('binary.file') .read_bytes() ) self.assertEqual(result, bytes(range(12, 16))) if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_reader.py0000644000175100001660000001105714736030670025344 0ustar00runnerdockerimport os.path import pathlib import unittest from importlib import import_module from importlib_resources.readers import MultiplexedPath, NamespaceReader from . import util class MultiplexedPathTest(util.DiskSetup, unittest.TestCase): MODULE = 'namespacedata01' def setUp(self): super().setUp() self.folder = pathlib.Path(self.data.__path__[0]) self.data01 = pathlib.Path(self.load_fixture('data01').__file__).parent self.data02 = pathlib.Path(self.load_fixture('data02').__file__).parent def test_init_no_paths(self): with self.assertRaises(FileNotFoundError): MultiplexedPath() def test_init_file(self): with self.assertRaises(NotADirectoryError): MultiplexedPath(self.folder / 'binary.file') def test_iterdir(self): contents = {path.name for path in MultiplexedPath(self.folder).iterdir()} try: contents.remove('__pycache__') except (KeyError, ValueError): pass self.assertEqual( contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'} ) def test_iterdir_duplicate(self): contents = { path.name for path in MultiplexedPath(self.folder, self.data01).iterdir() } for remove in ('__pycache__', '__init__.pyc'): try: contents.remove(remove) except (KeyError, ValueError): pass self.assertEqual( contents, {'__init__.py', 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file'}, ) def test_is_dir(self): self.assertEqual(MultiplexedPath(self.folder).is_dir(), True) def test_is_file(self): self.assertEqual(MultiplexedPath(self.folder).is_file(), False) def test_open_file(self): path = MultiplexedPath(self.folder) with self.assertRaises(FileNotFoundError): path.read_bytes() with self.assertRaises(FileNotFoundError): path.read_text() with self.assertRaises(FileNotFoundError): path.open() def test_join_path(self): prefix = str(self.folder.parent) path = MultiplexedPath(self.folder, self.data01) self.assertEqual( str(path.joinpath('binary.file'))[len(prefix) + 1 :], os.path.join('namespacedata01', 'binary.file'), ) sub = path.joinpath('subdirectory') assert isinstance(sub, MultiplexedPath) assert 'namespacedata01' in str(sub) assert 'data01' in str(sub) self.assertEqual( str(path.joinpath('imaginary'))[len(prefix) + 1 :], os.path.join('namespacedata01', 'imaginary'), ) self.assertEqual(path.joinpath(), path) def test_join_path_compound(self): path = MultiplexedPath(self.folder) assert not path.joinpath('imaginary/foo.py').exists() def test_join_path_common_subdir(self): prefix = str(self.data02.parent) path = MultiplexedPath(self.data01, self.data02) self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath) self.assertEqual( str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :], os.path.join('data02', 'subdirectory', 'subsubdir'), ) def test_repr(self): self.assertEqual( repr(MultiplexedPath(self.folder)), f"MultiplexedPath('{self.folder}')", ) def test_name(self): self.assertEqual( MultiplexedPath(self.folder).name, os.path.basename(self.folder), ) class NamespaceReaderTest(util.DiskSetup, unittest.TestCase): MODULE = 'namespacedata01' def test_init_error(self): with self.assertRaises(ValueError): NamespaceReader(['path1', 'path2']) def test_resource_path(self): namespacedata01 = import_module('namespacedata01') reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations) root = self.data.__path__[0] self.assertEqual( reader.resource_path('binary.file'), os.path.join(root, 'binary.file') ) self.assertEqual( reader.resource_path('imaginary'), os.path.join(root, 'imaginary') ) def test_files(self): reader = NamespaceReader(self.data.__spec__.submodule_search_locations) root = self.data.__path__[0] self.assertIsInstance(reader.files(), MultiplexedPath) self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')") if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_resource.py0000644000175100001660000001700714736030670025732 0ustar00runnerdockerimport unittest from importlib import import_module import importlib_resources as resources from . import util class ResourceTests: # Subclasses are expected to set the `data` attribute. def test_is_file_exists(self): target = resources.files(self.data) / 'binary.file' self.assertTrue(target.is_file()) def test_is_file_missing(self): target = resources.files(self.data) / 'not-a-file' self.assertFalse(target.is_file()) def test_is_dir(self): target = resources.files(self.data) / 'subdirectory' self.assertFalse(target.is_file()) self.assertTrue(target.is_dir()) class ResourceDiskTests(ResourceTests, util.DiskSetup, unittest.TestCase): pass class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase): pass def names(traversable): return {item.name for item in traversable.iterdir()} class ResourceLoaderTests(util.DiskSetup, unittest.TestCase): def test_resource_contents(self): package = util.create_package( file=self.data, path=self.data.__file__, contents=['A', 'B', 'C'] ) self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'}) def test_is_file(self): package = util.create_package( file=self.data, path=self.data.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'], ) self.assertTrue(resources.files(package).joinpath('B').is_file()) def test_is_dir(self): package = util.create_package( file=self.data, path=self.data.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'], ) self.assertTrue(resources.files(package).joinpath('D').is_dir()) def test_resource_missing(self): package = util.create_package( file=self.data, path=self.data.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'], ) self.assertFalse(resources.files(package).joinpath('Z').is_file()) class ResourceCornerCaseTests(util.DiskSetup, unittest.TestCase): def test_package_has_no_reader_fallback(self): """ Test odd ball packages which: # 1. Do not have a ResourceReader as a loader # 2. Are not on the file system # 3. Are not in a zip file """ module = util.create_package( file=self.data, path=self.data.__file__, contents=['A', 'B', 'C'] ) # Give the module a dummy loader. module.__loader__ = object() # Give the module a dummy origin. module.__file__ = '/path/which/shall/not/be/named' module.__spec__.loader = module.__loader__ module.__spec__.origin = module.__file__ self.assertFalse(resources.files(module).joinpath('A').is_file()) class ResourceFromZipsTest01(util.ZipSetup, unittest.TestCase): def test_is_submodule_resource(self): submodule = import_module('data01.subdirectory') self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file()) def test_read_submodule_resource_by_name(self): self.assertTrue( resources.files('data01.subdirectory').joinpath('binary.file').is_file() ) def test_submodule_contents(self): submodule = import_module('data01.subdirectory') self.assertEqual( names(resources.files(submodule)), {'__init__.py', 'binary.file'} ) def test_submodule_contents_by_name(self): self.assertEqual( names(resources.files('data01.subdirectory')), {'__init__.py', 'binary.file'}, ) def test_as_file_directory(self): with resources.as_file(resources.files('data01')) as data: assert data.name == 'data01' assert data.is_dir() assert data.joinpath('subdirectory').is_dir() assert len(list(data.iterdir())) assert not data.parent.exists() class ResourceFromZipsTest02(util.ZipSetup, unittest.TestCase): MODULE = 'data02' def test_unrelated_contents(self): """ Test thata zip with two unrelated subpackages return distinct resources. Ref python/importlib_resources#44. """ self.assertEqual( names(resources.files('data02.one')), {'__init__.py', 'resource1.txt'}, ) self.assertEqual( names(resources.files('data02.two')), {'__init__.py', 'resource2.txt'}, ) class DeletingZipsTest(util.ZipSetup, unittest.TestCase): """Having accessed resources in a zip file should not keep an open reference to the zip. """ def test_iterdir_does_not_keep_open(self): [item.name for item in resources.files('data01').iterdir()] def test_is_file_does_not_keep_open(self): resources.files('data01').joinpath('binary.file').is_file() def test_is_file_failure_does_not_keep_open(self): resources.files('data01').joinpath('not-present').is_file() @unittest.skip("Desired but not supported.") def test_as_file_does_not_keep_open(self): # pragma: no cover resources.as_file(resources.files('data01') / 'binary.file') def test_entered_path_does_not_keep_open(self): """ Mimic what certifi does on import to make its bundle available for the process duration. """ resources.as_file(resources.files('data01') / 'binary.file').__enter__() def test_read_binary_does_not_keep_open(self): resources.files('data01').joinpath('binary.file').read_bytes() def test_read_text_does_not_keep_open(self): resources.files('data01').joinpath('utf-8.file').read_text(encoding='utf-8') class ResourceFromNamespaceTests: def test_is_submodule_resource(self): self.assertTrue( resources.files(import_module('namespacedata01')) .joinpath('binary.file') .is_file() ) def test_read_submodule_resource_by_name(self): self.assertTrue( resources.files('namespacedata01').joinpath('binary.file').is_file() ) def test_submodule_contents(self): contents = names(resources.files(import_module('namespacedata01'))) try: contents.remove('__pycache__') except KeyError: pass self.assertEqual( contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'} ) def test_submodule_contents_by_name(self): contents = names(resources.files('namespacedata01')) try: contents.remove('__pycache__') except KeyError: pass self.assertEqual( contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'} ) def test_submodule_sub_contents(self): contents = names(resources.files(import_module('namespacedata01.subdirectory'))) try: contents.remove('__pycache__') except KeyError: pass self.assertEqual(contents, {'binary.file'}) def test_submodule_sub_contents_by_name(self): contents = names(resources.files('namespacedata01.subdirectory')) try: contents.remove('__pycache__') except KeyError: pass self.assertEqual(contents, {'binary.file'}) class ResourceFromNamespaceDiskTests( util.DiskSetup, ResourceFromNamespaceTests, unittest.TestCase, ): MODULE = 'namespacedata01' class ResourceFromNamespaceZipTests( util.ZipSetup, ResourceFromNamespaceTests, unittest.TestCase, ): MODULE = 'namespacedata01' if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/test_util.py0000644000175100001660000000212114736030670025047 0ustar00runnerdockerimport unittest from .util import MemorySetup, Traversable class TestMemoryTraversableImplementation(unittest.TestCase): def test_concrete_methods_are_not_overridden(self): """`MemoryTraversable` must not override `Traversable` concrete methods. This test is not an attempt to enforce a particular `Traversable` protocol; it merely catches changes in the `Traversable` abstract/concrete methods that have not been mirrored in the `MemoryTraversable` subclass. """ traversable_concrete_methods = { method for method, value in Traversable.__dict__.items() if callable(value) and method not in Traversable.__abstractmethods__ } memory_traversable_concrete_methods = { method for method, value in MemorySetup.MemoryTraversable.__dict__.items() if callable(value) and not method.startswith("__") } overridden_methods = ( memory_traversable_concrete_methods & traversable_concrete_methods ) assert not overridden_methods ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/util.py0000644000175100001660000002311414736030670024015 0ustar00runnerdockerimport abc import contextlib import functools import importlib import io import pathlib import sys import types from importlib.machinery import ModuleSpec from ..abc import ResourceReader, Traversable, TraversableResources from . import _path from . import zip as zip_ from .compat.py39 import import_helper, os_helper class Reader(ResourceReader): def __init__(self, **kwargs): vars(self).update(kwargs) def get_resource_reader(self, package): return self def open_resource(self, path): self._path = path if isinstance(self.file, Exception): raise self.file return self.file def resource_path(self, path_): self._path = path_ if isinstance(self.path, Exception): raise self.path return self.path def is_resource(self, path_): self._path = path_ if isinstance(self.path, Exception): raise self.path def part(entry): return entry.split('/') return any( len(parts) == 1 and parts[0] == path_ for parts in map(part, self._contents) ) def contents(self): if isinstance(self.path, Exception): raise self.path yield from self._contents def create_package_from_loader(loader, is_package=True): name = 'testingpackage' module = types.ModuleType(name) spec = ModuleSpec(name, loader, origin='does-not-exist', is_package=is_package) module.__spec__ = spec module.__loader__ = loader return module def create_package(file=None, path=None, is_package=True, contents=()): return create_package_from_loader( Reader(file=file, path=path, _contents=contents), is_package, ) class CommonTestsBase(metaclass=abc.ABCMeta): """ Tests shared by test_open, test_path, and test_read. """ @abc.abstractmethod def execute(self, package, path): """ Call the pertinent legacy API function (e.g. open_text, path) on package and path. """ def test_package_name(self): """ Passing in the package name should succeed. """ self.execute(self.data.__name__, 'utf-8.file') def test_package_object(self): """ Passing in the package itself should succeed. """ self.execute(self.data, 'utf-8.file') def test_string_path(self): """ Passing in a string for the path should succeed. """ path = 'utf-8.file' self.execute(self.data, path) def test_pathlib_path(self): """ Passing in a pathlib.PurePath object for the path should succeed. """ path = pathlib.PurePath('utf-8.file') self.execute(self.data, path) def test_importing_module_as_side_effect(self): """ The anchor package can already be imported. """ del sys.modules[self.data.__name__] self.execute(self.data.__name__, 'utf-8.file') def test_missing_path(self): """ Attempting to open or read or request the path for a non-existent path should succeed if open_resource can return a viable data stream. """ bytes_data = io.BytesIO(b'Hello, world!') package = create_package(file=bytes_data, path=FileNotFoundError()) self.execute(package, 'utf-8.file') self.assertEqual(package.__loader__._path, 'utf-8.file') def test_extant_path(self): # Attempting to open or read or request the path when the # path does exist should still succeed. Does not assert # anything about the result. bytes_data = io.BytesIO(b'Hello, world!') # any path that exists path = __file__ package = create_package(file=bytes_data, path=path) self.execute(package, 'utf-8.file') self.assertEqual(package.__loader__._path, 'utf-8.file') def test_useless_loader(self): package = create_package(file=FileNotFoundError(), path=FileNotFoundError()) with self.assertRaises(FileNotFoundError): self.execute(package, 'utf-8.file') fixtures = dict( data01={ '__init__.py': '', 'binary.file': bytes(range(4)), 'utf-16.file': '\ufeffHello, UTF-16 world!\n'.encode('utf-16-le'), 'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'), 'subdirectory': { '__init__.py': '', 'binary.file': bytes(range(4, 8)), }, }, data02={ '__init__.py': '', 'one': {'__init__.py': '', 'resource1.txt': 'one resource'}, 'two': {'__init__.py': '', 'resource2.txt': 'two resource'}, 'subdirectory': {'subsubdir': {'resource.txt': 'a resource'}}, }, namespacedata01={ 'binary.file': bytes(range(4)), 'utf-16.file': '\ufeffHello, UTF-16 world!\n'.encode('utf-16-le'), 'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'), 'subdirectory': { 'binary.file': bytes(range(12, 16)), }, }, ) class ModuleSetup: def setUp(self): self.fixtures = contextlib.ExitStack() self.addCleanup(self.fixtures.close) self.fixtures.enter_context(import_helper.isolated_modules()) self.data = self.load_fixture(self.MODULE) def load_fixture(self, module): self.tree_on_path({module: fixtures[module]}) return importlib.import_module(module) class ZipSetup(ModuleSetup): MODULE = 'data01' def tree_on_path(self, spec): temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) modules = pathlib.Path(temp_dir) / 'zipped modules.zip' self.fixtures.enter_context( import_helper.DirsOnSysPath(str(zip_.make_zip_file(spec, modules))) ) class DiskSetup(ModuleSetup): MODULE = 'data01' def tree_on_path(self, spec): temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) _path.build(spec, pathlib.Path(temp_dir)) self.fixtures.enter_context(import_helper.DirsOnSysPath(temp_dir)) class MemorySetup(ModuleSetup): """Support loading a module in memory.""" MODULE = 'data01' def load_fixture(self, module): self.fixtures.enter_context(self.augment_sys_metapath(module)) return importlib.import_module(module) @contextlib.contextmanager def augment_sys_metapath(self, module): finder_instance = self.MemoryFinder(module) sys.meta_path.append(finder_instance) yield sys.meta_path.remove(finder_instance) class MemoryFinder(importlib.abc.MetaPathFinder): def __init__(self, module): self._module = module def find_spec(self, fullname, path, target=None): if fullname != self._module: return None return importlib.machinery.ModuleSpec( name=fullname, loader=MemorySetup.MemoryLoader(self._module), is_package=True, ) class MemoryLoader(importlib.abc.Loader): def __init__(self, module): self._module = module def exec_module(self, module): pass def get_resource_reader(self, fullname): return MemorySetup.MemoryTraversableResources(self._module, fullname) class MemoryTraversableResources(TraversableResources): def __init__(self, module, fullname): self._module = module self._fullname = fullname def files(self): return MemorySetup.MemoryTraversable(self._module, self._fullname) class MemoryTraversable(Traversable): """Implement only the abstract methods of `Traversable`. Besides `.__init__()`, no other methods may be implemented or overridden. This is critical for validating the concrete `Traversable` implementations. """ def __init__(self, module, fullname): self._module = module self._fullname = fullname def _resolve(self): """ Fully traverse the `fixtures` dictionary. This should be wrapped in a `try/except KeyError` but it is not currently needed and lowers the code coverage numbers. """ path = pathlib.PurePosixPath(self._fullname) return functools.reduce(lambda d, p: d[p], path.parts, fixtures) def iterdir(self): directory = self._resolve() if not isinstance(directory, dict): # Filesystem openers raise OSError, and that exception is mirrored here. raise OSError(f"{self._fullname} is not a directory") for path in directory: yield MemorySetup.MemoryTraversable( self._module, f"{self._fullname}/{path}" ) def is_dir(self) -> bool: return isinstance(self._resolve(), dict) def is_file(self) -> bool: return not self.is_dir() def open(self, mode='r', encoding=None, errors=None, *_, **__): contents = self._resolve() if isinstance(contents, dict): # Filesystem openers raise OSError when attempting to open a directory, # and that exception is mirrored here. raise OSError(f"{self._fullname} is a directory") if isinstance(contents, str): contents = contents.encode("utf-8") result = io.BytesIO(contents) if "b" in mode: return result return io.TextIOWrapper(result, encoding=encoding, errors=errors) @property def name(self): return pathlib.PurePosixPath(self._fullname).name class CommonTests(DiskSetup, CommonTestsBase): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/importlib_resources/tests/zip.py0000755000175100001660000000110114736030670023635 0ustar00runnerdocker""" Generate zip test data files. """ import zipfile import zipp def make_zip_file(tree, dst): """ Zip the files in tree into a new zipfile at dst. """ with zipfile.ZipFile(dst, 'w') as zf: for name, contents in walk(tree): zf.writestr(name, contents) zipp.CompleteDirs.inject(zf) return dst def walk(tree, prefix=''): for name, contents in tree.items(): if isinstance(contents, dict): yield from walk(contents, prefix=f'{prefix}{name}/') else: yield f'{prefix}{name}', contents ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1735930311.4343128 importlib_resources-6.5.2/importlib_resources.egg-info/0000755000175100001660000000000014736030707023016 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930311.0 importlib_resources-6.5.2/importlib_resources.egg-info/PKG-INFO0000644000175100001660000000755314736030707024125 0ustar00runnerdockerMetadata-Version: 2.1 Name: importlib_resources Version: 6.5.2 Summary: Read resources from Python packages Author-email: Barry Warsaw Maintainer-email: "Jason R. Coombs" Project-URL: Source, https://github.com/python/importlib_resources Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: zipp>=3.1.0; python_version < "3.10" Provides-Extra: test Requires-Dist: pytest!=8.1.*,>=6; extra == "test" Requires-Dist: zipp>=3.17; extra == "test" Requires-Dist: jaraco.test>=5.4; extra == "test" Provides-Extra: doc Requires-Dist: sphinx>=3.5; extra == "doc" Requires-Dist: jaraco.packaging>=9.3; extra == "doc" Requires-Dist: rst.linker>=1.9; extra == "doc" Requires-Dist: furo; extra == "doc" Requires-Dist: sphinx-lint; extra == "doc" Requires-Dist: jaraco.tidelift>=1.4; extra == "doc" Provides-Extra: check Requires-Dist: pytest-checkdocs>=2.4; extra == "check" Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check" Provides-Extra: cover Requires-Dist: pytest-cov; extra == "cover" Provides-Extra: enabler Requires-Dist: pytest-enabler>=2.2; extra == "enabler" Provides-Extra: type Requires-Dist: pytest-mypy; extra == "type" .. image:: https://img.shields.io/pypi/v/importlib_resources.svg :target: https://pypi.org/project/importlib_resources .. image:: https://img.shields.io/pypi/pyversions/importlib_resources.svg .. image:: https://github.com/python/importlib_resources/actions/workflows/main.yml/badge.svg :target: https://github.com/python/importlib_resources/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff .. image:: https://readthedocs.org/projects/importlib-resources/badge/?version=latest :target: https://importlib-resources.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2025-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/importlib-resources :target: https://tidelift.com/subscription/pkg/pypi-importlib-resources?utm_source=pypi-importlib-resources&utm_medium=readme ``importlib_resources`` is a backport of Python standard library `importlib.resources `_ module for older Pythons. The key goal of this module is to replace parts of `pkg_resources `_ with a solution in Python's stdlib that relies on well-defined APIs. This makes reading resources included in packages easier, with more stable and consistent semantics. Compatibility ============= New features are introduced in this third-party library and later merged into CPython. The following table indicates which versions of this library were contributed to different versions in the standard library: .. list-table:: :header-rows: 1 * - importlib_resources - stdlib * - 6.0 - 3.13 * - 5.12 - 3.12 * - 5.7 - 3.11 * - 5.0 - 3.10 * - 1.3 - 3.9 * - 0.5 (?) - 3.7 For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930311.0 importlib_resources-6.5.2/importlib_resources.egg-info/SOURCES.txt0000644000175100001660000000333114736030707024702 0ustar00runnerdocker.coveragerc .editorconfig .gitattributes .gitignore .pre-commit-config.yaml .readthedocs.yaml LICENSE NEWS.rst README.rst SECURITY.md codecov.yml mypy.ini pyproject.toml pytest.ini ruff.toml towncrier.toml tox.ini .github/FUNDING.yml .github/dependabot.yml .github/workflows/main.yml docs/api.rst docs/conf.py docs/history.rst docs/index.rst docs/migration.rst docs/using.rst importlib_resources/__init__.py importlib_resources/_adapters.py importlib_resources/_common.py importlib_resources/_functional.py importlib_resources/_itertools.py importlib_resources/abc.py importlib_resources/py.typed importlib_resources/readers.py importlib_resources/simple.py importlib_resources.egg-info/PKG-INFO importlib_resources.egg-info/SOURCES.txt importlib_resources.egg-info/dependency_links.txt importlib_resources.egg-info/requires.txt importlib_resources.egg-info/top_level.txt importlib_resources/compat/__init__.py importlib_resources/compat/py39.py importlib_resources/future/__init__.py importlib_resources/future/adapters.py importlib_resources/tests/__init__.py importlib_resources/tests/_path.py importlib_resources/tests/test_compatibilty_files.py importlib_resources/tests/test_contents.py importlib_resources/tests/test_custom.py importlib_resources/tests/test_files.py importlib_resources/tests/test_functional.py importlib_resources/tests/test_open.py importlib_resources/tests/test_path.py importlib_resources/tests/test_read.py importlib_resources/tests/test_reader.py importlib_resources/tests/test_resource.py importlib_resources/tests/test_util.py importlib_resources/tests/util.py importlib_resources/tests/zip.py importlib_resources/tests/compat/__init__.py importlib_resources/tests/compat/py312.py importlib_resources/tests/compat/py39.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930311.0 importlib_resources-6.5.2/importlib_resources.egg-info/dependency_links.txt0000644000175100001660000000000114736030707027064 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930311.0 importlib_resources-6.5.2/importlib_resources.egg-info/requires.txt0000644000175100001660000000053014736030707025414 0ustar00runnerdocker [:python_version < "3.10"] zipp>=3.1.0 [check] pytest-checkdocs>=2.4 [check:sys_platform != "cygwin"] pytest-ruff>=0.2.1 [cover] pytest-cov [doc] sphinx>=3.5 jaraco.packaging>=9.3 rst.linker>=1.9 furo sphinx-lint jaraco.tidelift>=1.4 [enabler] pytest-enabler>=2.2 [test] pytest!=8.1.*,>=6 zipp>=3.17 jaraco.test>=5.4 [type] pytest-mypy ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930311.0 importlib_resources-6.5.2/importlib_resources.egg-info/top_level.txt0000644000175100001660000000002414736030707025544 0ustar00runnerdockerimportlib_resources ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/mypy.ini0000644000175100001660000000077714736030670016742 0ustar00runnerdocker[mypy] # Is the project well-typed? strict = False # Early opt-in even when strict = False warn_unused_ignores = True warn_redundant_casts = True enable_error_code = ignore-without-code # Support namespace packages per https://github.com/python/mypy/issues/14057 explicit_package_bases = True disable_error_code = # Disable due to many false positives overload-overlap, # jaraco/zipp#123 [mypy-zipp] ignore_missing_imports = True # jaraco/jaraco.test#7 [mypy-jaraco.test.*] ignore_missing_imports = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/pyproject.toml0000644000175100001660000000247514736030670020154 0ustar00runnerdocker[build-system] requires = ["setuptools>=61.2", "setuptools_scm[toml]>=3.4.1"] build-backend = "setuptools.build_meta" [project] name = "importlib_resources" authors = [ { name = "Barry Warsaw", email = "barry@python.org" }, ] maintainers = [ { name = "Jason R. Coombs", email = "jaraco@jaraco.com" }, ] description = "Read resources from Python packages" readme = "README.rst" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", ] requires-python = ">=3.9" dependencies = [ "zipp >= 3.1.0; python_version < '3.10'", ] dynamic = ["version"] [project.urls] Source = "https://github.com/python/importlib_resources" [project.optional-dependencies] test = [ # upstream "pytest >= 6, != 8.1.*", # local "zipp >= 3.17", "jaraco.test >= 5.4", ] doc = [ # upstream "sphinx >= 3.5", "jaraco.packaging >= 9.3", "rst.linker >= 1.9", "furo", "sphinx-lint", # tidelift "jaraco.tidelift >= 1.4", # local ] check = [ "pytest-checkdocs >= 2.4", "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", ] cover = [ "pytest-cov", ] enabler = [ "pytest-enabler >= 2.2", ] type = [ # upstream "pytest-mypy", # local ] [tool.setuptools_scm] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/pytest.ini0000644000175100001660000000111014736030670017252 0ustar00runnerdocker[pytest] norecursedirs=dist build .tox .eggs addopts= --doctest-modules --import-mode importlib consider_namespace_packages=true filterwarnings= ## upstream # Ensure ResourceWarnings are emitted default::ResourceWarning # realpython/pytest-mypy#152 ignore:'encoding' argument not specified::pytest_mypy # python/cpython#100750 ignore:'encoding' argument not specified::platform # pypa/build#615 ignore:'encoding' argument not specified::build.env # dateutil/dateutil#1284 ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:dateutil.tz.tz ## end upstream ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/ruff.toml0000644000175100001660000000117114736030670017067 0ustar00runnerdocker# extend pyproject.toml for requires-python (workaround astral-sh/ruff#10299) extend = "pyproject.toml" [lint] extend-select = [ "C901", "PERF401", "W", ] ignore = [ # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules "W191", "E111", "E114", "E117", "D206", "D300", "Q000", "Q001", "Q002", "Q003", "COM812", "COM819", "ISC001", "ISC002", ] [format] # Enable preview to get hugged parenthesis unwrapping and other nice surprises # See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373 preview = true # https://docs.astral.sh/ruff/settings/#format_quote-style quote-style = "preserve" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1735930311.4373128 importlib_resources-6.5.2/setup.cfg0000644000175100001660000000004614736030707017052 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/towncrier.toml0000644000175100001660000000005414736030670020140 0ustar00runnerdocker[tool.towncrier] title_format = "{version}" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735930296.0 importlib_resources-6.5.2/tox.ini0000644000175100001660000000245414736030670016550 0ustar00runnerdocker[testenv] description = perform primary checks (tests, style, types, coverage) deps = setenv = PYTHONWARNDEFAULTENCODING = 1 commands = pytest {posargs} usedevelop = True extras = test check cover enabler type [testenv:diffcov] description = run tests and check that diff from main is covered deps = {[testenv]deps} diff-cover commands = pytest {posargs} --cov-report xml diff-cover coverage.xml --compare-branch=origin/main --html-report diffcov.html diff-cover coverage.xml --compare-branch=origin/main --fail-under=100 [testenv:docs] description = build the documentation extras = doc test changedir = docs commands = python -m sphinx -W --keep-going . {toxinidir}/build/html python -m sphinxlint [testenv:finalize] description = assemble changelog and tag a release skip_install = True deps = towncrier jaraco.develop >= 7.23 pass_env = * commands = python -m jaraco.develop.finalize [testenv:release] description = publish the package to PyPI and GitHub skip_install = True deps = build twine>=3 jaraco.develop>=7.1 pass_env = TWINE_PASSWORD GITHUB_TOKEN setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" python -m build python -m twine upload dist/* python -m jaraco.develop.create-github-release