././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8738866 importlib_resources-6.4.0/0000755000175100001770000000000014577034505015233 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/.coveragerc0000644000175100001770000000040014577034455017352 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 = # jaraco/skeleton#97 @overload if TYPE_CHECKING: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/.editorconfig0000644000175100001770000000036614577034455017721 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=1711028525.0 importlib_resources-6.4.0/.gitattributes0000644000175100001770000000003314577034455020126 0ustar00runnerdocker*.file binary *.zip binary ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8618865 importlib_resources-6.4.0/.github/0000755000175100001770000000000014577034505016573 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/.github/FUNDING.yml0000644000175100001770000000004314577034455020411 0ustar00runnerdockertidelift: pypi/importlib-resources ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/.github/dependabot.yml0000644000175100001770000000022414577034455021425 0ustar00runnerdockerversion: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "daily" allow: - dependency-type: "all" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8618865 importlib_resources-6.4.0/.github/workflows/0000755000175100001770000000000014577034505020630 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/.github/workflows/main.yml0000644000175100001770000000526514577034455022313 0ustar00runnerdockername: tests on: merge_group: push: branches-ignore: # disabled for jaraco/skeleton#103 # - gh-readonly-queue/** # Temporary merge queue-related GH-made branches pull_request: 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: matrix: python: - "3.8" - "3.11" - "3.12" platform: - ubuntu-latest - macos-latest - windows-latest include: - python: "3.9" platform: ubuntu-latest - python: "3.10" platform: ubuntu-latest - python: pypy3.10 platform: ubuntu-latest runs-on: ${{ matrix.platform }} continue-on-error: ${{ matrix.python == '3.13' }} 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=1711028525.0 importlib_resources-6.4.0/.gitignore0000644000175100001770000000222314577034455017226 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=1711028525.0 importlib_resources-6.4.0/.pre-commit-config.yaml0000644000175100001770000000016414577034455021521 0ustar00runnerdockerrepos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.8 hooks: - id: ruff - id: ruff-format ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/.readthedocs.yaml0000644000175100001770000000030414577034455020463 0ustar00runnerdockerversion: 2 python: install: - path: . extra_requirements: - docs # required boilerplate readthedocs/readthedocs.org#10401 build: os: ubuntu-lts-latest tools: python: latest ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/LICENSE0000644000175100001770000002613614577034455016254 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=1711028525.0 importlib_resources-6.4.0/NEWS.rst0000644000175100001770000002767714577034455016570 0ustar00runnerdockerv6.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=1711028548.8738866 importlib_resources-6.4.0/PKG-INFO0000644000175100001770000000751214577034505016335 0ustar00runnerdockerMetadata-Version: 2.1 Name: importlib_resources Version: 6.4.0 Summary: Read resources from Python packages Home-page: https://github.com/python/importlib_resources Author: Barry Warsaw Author-email: barry@python.org Project-URL: Documentation, https://importlib-resources.readthedocs.io/ 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.8 License-File: LICENSE Requires-Dist: zipp>=3.1.0; python_version < "3.10" Provides-Extra: testing Requires-Dist: pytest>=6; extra == "testing" Requires-Dist: pytest-checkdocs>=2.4; extra == "testing" Requires-Dist: pytest-cov; extra == "testing" Requires-Dist: pytest-mypy; platform_python_implementation != "PyPy" and extra == "testing" Requires-Dist: pytest-enabler>=2.2; extra == "testing" Requires-Dist: pytest-ruff>=0.2.1; extra == "testing" Requires-Dist: zipp>=3.17; extra == "testing" Requires-Dist: jaraco.test>=5.4; extra == "testing" Provides-Extra: docs Requires-Dist: sphinx>=3.5; extra == "docs" Requires-Dist: sphinx<7.2.5; extra == "docs" Requires-Dist: jaraco.packaging>=9.3; extra == "docs" Requires-Dist: rst.linker>=1.9; extra == "docs" Requires-Dist: furo; extra == "docs" Requires-Dist: sphinx-lint; extra == "docs" Requires-Dist: jaraco.tidelift>=1.4; extra == "docs" .. 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-2024-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=1711028525.0 importlib_resources-6.4.0/README.rst0000644000175100001770000000461314577034455016732 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-2024-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=1711028525.0 importlib_resources-6.4.0/SECURITY.md0000644000175100001770000000026414577034455017032 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=1711028525.0 importlib_resources-6.4.0/codecov.yml0000644000175100001770000000006714577034455017407 0ustar00runnerdockercodecov: token: 5eb1bc45-1b7f-43e6-8bc1-f2b02833dba9 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8618865 importlib_resources-6.4.0/docs/0000755000175100001770000000000014577034505016163 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/docs/api.rst0000644000175100001770000000075014577034455017474 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=1711028525.0 importlib_resources-6.4.0/docs/conf.py0000644000175100001770000000264214577034455017472 0ustar00runnerdockerextensions = [ '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 #|bpo-)(?P\d+)', url='http://bugs.python.org/issue{python}', ), ], ), } # Be strict about any broken references nitpicky = True nitpick_ignore = [] # 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 extensions += ['jaraco.tidelift'] nitpick_ignore.extend([ ('py:class', 'module'), ('py:class', '_io.BufferedReader'), ]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/docs/history.rst0000644000175100001770000000011614577034455020420 0ustar00runnerdocker:tocdepth: 2 .. _changes: History ******* .. include:: ../NEWS (links).rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/docs/index.rst0000644000175100001770000000332314577034455020031 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=1711028525.0 importlib_resources-6.4.0/docs/migration.rst0000644000175100001770000001342214577034455020714 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 = f.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=1711028525.0 importlib_resources-6.4.0/docs/using.rst0000644000175100001770000002102514577034455020046 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8658867 importlib_resources-6.4.0/importlib_resources/0000755000175100001770000000000014577034505021326 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/__init__.py0000644000175100001770000000077114577034455023450 0ustar00runnerdocker"""Read resources contained within a package.""" from ._common import ( as_file, files, Package, Anchor, ) 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/_adapters.py0000644000175100001770000001060214577034455023645 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/_common.py0000644000175100001770000001270314577034455023336 0ustar00runnerdockerimport os import pathlib import tempfile import functools import contextlib import types import importlib import inspect import warnings import itertools from typing import Union, Optional, 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 if reader is None: return None return reader(spec.name) # type: ignore @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 == __file__ def is_wrapper(frame_info): return frame_info.function == 'wrapper' not_this_file = itertools.filterfalse(is_this_file, inspect.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.TemporyDirectory 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/_itertools.py0000644000175100001770000000237514577034455024076 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/abc.py0000644000175100001770000001205214577034455022431 0ustar00runnerdockerimport abc import io import itertools import pathlib from typing import Any, BinaryIO, Iterable, Iterator, NoReturn, Text, Optional from typing import runtime_checkable, Protocol from .compat.py38 import StrPath __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) -> str: """ Read contents of self as text """ with self.open(encoding=encoding) 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) @abc.abstractmethod def open(self, mode='r', *args, **kwargs): """ 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) -> io.BufferedReader: 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=1711028548.8658867 importlib_resources-6.4.0/importlib_resources/compat/0000755000175100001770000000000014577034505022611 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/compat/__init__.py0000644000175100001770000000000014577034455024714 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/compat/py38.py0000644000175100001770000000034614577034455023775 0ustar00runnerdockerimport os import sys from typing import Union if sys.version_info >= (3, 9): StrPath = Union[str, os.PathLike[str]] else: # PathLike is only subscriptable at runtime in 3.9+ StrPath = Union[str, "os.PathLike[str]"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/compat/py39.py0000644000175100001770000000027014577034455023772 0ustar00runnerdockerimport sys __all__ = ['ZipPath'] if sys.version_info >= (3, 10): from zipfile import Path as ZipPath # type: ignore else: from zipp import Path as ZipPath # type: ignore ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/functional.py0000644000175100001770000000513314577034455024050 0ustar00runnerdocker"""Simplified function-based API for importlib.resources""" import warnings from ._common import files, as_file _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``. """ return _get_resource(anchor, path_names).is_file() 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8658867 importlib_resources-6.4.0/importlib_resources/future/0000755000175100001770000000000014577034505022640 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/future/__init__.py0000644000175100001770000000000014577034455024743 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/future/adapters.py0000644000175100001770000000557414577034455025034 0ustar00runnerdockerimport functools import pathlib from contextlib import suppress from types import SimpleNamespace from .. import readers, _adapters 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 # 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/py.typed0000644000175100001770000000000014577034455023017 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/readers.py0000644000175100001770000001334714577034455023341 0ustar00runnerdockerimport collections import contextlib import itertools import pathlib import operator import re import warnings 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): _, _, name = module.rpartition('.') self.prefix = loader.prefix.replace('\\', '/') + 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(*map(self._resolve, namespace_path)) @classmethod def _resolve(cls, path_str) -> abc.Traversable: 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``. """ (dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir()) return dir @classmethod def _candidate_paths(cls, path_str): yield pathlib.Path(path_str) yield from cls._resolve_zip_path(path_str) @staticmethod def _resolve_zip_path(path_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=1711028525.0 importlib_resources-6.4.0/importlib_resources/simple.py0000644000175100001770000000503014577034455023173 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 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8698866 importlib_resources-6.4.0/importlib_resources/tests/0000755000175100001770000000000014577034505022470 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/__init__.py0000644000175100001770000000000014577034455024573 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/_path.py0000644000175100001770000000241114577034455024137 0ustar00runnerdockerimport pathlib import functools from typing import Dict, Union #### # from jaraco.path 3.4.1 FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore def build(spec: FilesSpec, prefix=pathlib.Path()): """ 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", ... } ... } >>> target = getfixture('tmp_path') >>> build(spec, target) >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8') '# Some code' """ for name, contents in spec.items(): create(contents, pathlib.Path(prefix) / name) @functools.singledispatch def create(content: Union[str, bytes, FilesSpec], path): path.mkdir(exist_ok=True) build(content, prefix=path) # type: ignore @create.register def _(content: bytes, path): path.write_bytes(content) @create.register def _(content: str, path): path.write_text(content, encoding='utf-8') # end from jaraco.path #### ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8698866 importlib_resources-6.4.0/importlib_resources/tests/compat/0000755000175100001770000000000014577034505023753 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/compat/__init__.py0000644000175100001770000000000014577034455026056 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/compat/py312.py0000644000175100001770000000055414577034455025213 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/compat/py39.py0000644000175100001770000000051114577034455025132 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') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8698866 importlib_resources-6.4.0/importlib_resources/tests/data01/0000755000175100001770000000000014577034505023542 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data01/__init__.py0000644000175100001770000000000014577034455025645 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data01/binary.file0000644000175100001770000000000414577034455025665 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8698866 importlib_resources-6.4.0/importlib_resources/tests/data01/subdirectory/0000755000175100001770000000000014577034505026260 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data01/subdirectory/__init__.py0000644000175100001770000000000014577034455030363 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data01/subdirectory/binary.file0000644000175100001770000000000414577034455030403 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data01/utf-16.file0000644000175100001770000000005414577034455025430 0ustar00runnerdockerÿþHello, UTF-16 world! ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data01/utf-8.file0000644000175100001770000000002414577034455025346 0ustar00runnerdockerHello, UTF-8 world! ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8698866 importlib_resources-6.4.0/importlib_resources/tests/data02/0000755000175100001770000000000014577034505023543 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data02/__init__.py0000644000175100001770000000000014577034455025646 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8698866 importlib_resources-6.4.0/importlib_resources/tests/data02/one/0000755000175100001770000000000014577034505024324 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data02/one/__init__.py0000644000175100001770000000000014577034455026427 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data02/one/resource1.txt0000644000175100001770000000001514577034455026775 0ustar00runnerdockerone resource ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8578866 importlib_resources-6.4.0/importlib_resources/tests/data02/subdirectory/0000755000175100001770000000000014577034505026261 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8698866 importlib_resources-6.4.0/importlib_resources/tests/data02/subdirectory/subsubdir/0000755000175100001770000000000014577034505030263 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data02/subdirectory/subsubdir/resource.txt0000644000175100001770000000001214577034455032650 0ustar00runnerdockera resource././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8738866 importlib_resources-6.4.0/importlib_resources/tests/data02/two/0000755000175100001770000000000014577034505024354 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data02/two/__init__.py0000644000175100001770000000000014577034455026457 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/data02/two/resource2.txt0000644000175100001770000000001514577034455027026 0ustar00runnerdockertwo resource ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8738866 importlib_resources-6.4.0/importlib_resources/tests/namespacedata01/0000755000175100001770000000000014577034505025417 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/namespacedata01/binary.file0000644000175100001770000000000414577034455027542 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8738866 importlib_resources-6.4.0/importlib_resources/tests/namespacedata01/subdirectory/0000755000175100001770000000000014577034505030135 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/namespacedata01/subdirectory/binary.file0000644000175100001770000000000414577034455032260 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/namespacedata01/utf-16.file0000644000175100001770000000005414577034455027305 0ustar00runnerdockerÿþHello, UTF-16 world! ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/namespacedata01/utf-8.file0000644000175100001770000000002414577034455027223 0ustar00runnerdockerHello, UTF-8 world! ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_compatibilty_files.py0000644000175100001770000000636214577034455027776 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_contents.py0000644000175100001770000000164214577034455025745 0ustar00runnerdockerimport unittest import importlib_resources as resources from . import data01 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, unittest.TestCase): def setUp(self): self.data = data01 class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase): pass class ContentsNamespaceTests(ContentsTests, unittest.TestCase): expected = { # no __init__ because of namespace design 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file', } def setUp(self): from . import namespacedata01 self.data = namespacedata01 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_custom.py0000644000175100001770000000230514577034455025417 0ustar00runnerdockerimport unittest import contextlib import pathlib import importlib_resources as resources from .. import abc from ..abc import TraversableResources, ResourceReader 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_files.py0000644000175100001770000000662014577034455025213 0ustar00runnerdockerimport textwrap import unittest import warnings import importlib import contextlib import importlib_resources as resources from ..abc import Traversable from . import data01 from . import util from . import _path from .compat.py39 import os_helper from .compat.py312 import import_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, unittest.TestCase): def setUp(self): self.data = data01 class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase): pass class OpenNamespaceTests(FilesTests, unittest.TestCase): def setUp(self): from . import namespacedata01 self.data = namespacedata01 class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase): ZIP_MODULE = 'namespacedata01' class SiteDir: def setUp(self): self.fixtures = contextlib.ExitStack() self.addCleanup(self.fixtures.close) self.site_dir = self.fixtures.enter_context(os_helper.temp_dir()) self.fixtures.enter_context(import_helper.DirsOnSysPath(self.site_dir)) self.fixtures.enter_context(import_helper.isolated_modules()) class ModulesFilesTests(SiteDir, unittest.TestCase): def test_module_resources(self): """ A module can have resources found adjacent to the module. """ spec = { 'mod.py': '', 'res.txt': 'resources are the best', } _path.build(spec, self.site_dir) import mod actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8') assert actual == spec['res.txt'] class ImplicitContextFilesTests(SiteDir, unittest.TestCase): def test_implicit_files(self): """ Without any parameter, files() will infer the location as the caller. """ spec = { 'somepkg': { '__init__.py': textwrap.dedent( """ import importlib_resources as res val = res.files().joinpath('res.txt').read_text(encoding='utf-8') """ ), 'res.txt': 'resources are the best', }, } _path.build(spec, self.site_dir) assert importlib.import_module('somepkg').val == 'resources are the best' if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_functional.py0000644000175100001770000002061714577034455026255 0ustar00runnerdockerimport unittest import os import contextlib try: from test.support.warnings_helper import ignore_warnings, check_warnings except ImportError: # older Python versions from test.support import ignore_warnings, check_warnings import importlib_resources as resources # Since the functional API forwards to Traversable, we only test # filesystem resources here -- not zip files, namespace packages etc. # We do test for two kinds of Anchor, though. class StringAnchorMixin: anchor01 = 'importlib_resources.tests.data01' anchor02 = 'importlib_resources.tests.data02' class ModuleAnchorMixin: from . import data01 as anchor01 from . import data02 as anchor02 class FunctionalAPIBase: 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 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.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.assertEqual( resources.read_text( self.anchor01, 'utf-16.file', errors='backslashreplace', ), 'Hello, UTF-16 world!\n'.encode('utf-16').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.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.assertEqual( f.read(), 'Hello, UTF-16 world!\n'.encode('utf-16').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 check_warnings((".*contents.*", DeprecationWarning)): c = resources.contents(self.anchor01) self.assertGreaterEqual( set(c), {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}, ) with contextlib.ExitStack() as cm: cm.enter_context(self.assertRaises(OSError)) cm.enter_context(check_warnings((".*contents.*", DeprecationWarning))) list(resources.contents(self.anchor01, 'utf-8.file')) for path_parts in self._gen_resourcetxt_path_parts(): with contextlib.ExitStack() as cm: cm.enter_context(self.assertRaises(OSError)) cm.enter_context(check_warnings((".*contents.*", DeprecationWarning))) list(resources.contents(self.anchor01, *path_parts)) with check_warnings((".*contents.*", DeprecationWarning)): c = resources.contents(self.anchor01, 'subdirectory') self.assertGreaterEqual( set(c), {'binary.file'}, ) @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( unittest.TestCase, FunctionalAPIBase, StringAnchorMixin, ): pass class FunctionalAPITest_ModuleAnchor( unittest.TestCase, FunctionalAPIBase, ModuleAnchorMixin, ): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_open.py0000644000175100001770000000533214577034455025051 0ustar00runnerdockerimport unittest import importlib_resources as resources from . import data01 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, unittest.TestCase): def setUp(self): self.data = data01 class OpenDiskNamespaceTests(OpenTests, unittest.TestCase): def setUp(self): from . import namespacedata01 self.data = namespacedata01 class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase): pass class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase): ZIP_MODULE = 'namespacedata01' if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_path.py0000644000175100001770000000373114577034455025045 0ustar00runnerdockerimport io import pathlib import unittest import importlib_resources as resources from . import data01 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, unittest.TestCase): data = data01 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=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_read.py0000644000175100001770000000605014577034455025021 0ustar00runnerdockerimport unittest import importlib_resources as resources from . import data01 from . import util from importlib import import_module 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, unittest.TestCase): data = data01 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, unittest.TestCase): def setUp(self): from . import namespacedata01 self.data = namespacedata01 class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase): ZIP_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=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_reader.py0000644000175100001770000001161114577034455025347 0ustar00runnerdockerimport os.path import sys import pathlib import unittest from importlib import import_module from importlib_resources.readers import MultiplexedPath, NamespaceReader class MultiplexedPathTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.folder = pathlib.Path(__file__).parent / 'namespacedata01' 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): data01 = pathlib.Path(__file__).parent.joinpath('data01') contents = { path.name for path in MultiplexedPath(self.folder, 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): data01 = pathlib.Path(__file__).parent.joinpath('data01') prefix = str(data01.parent) path = MultiplexedPath(self.folder, 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): data01 = pathlib.Path(__file__).parent.joinpath('data01') data02 = pathlib.Path(__file__).parent.joinpath('data02') prefix = str(data01.parent) path = MultiplexedPath(data01, 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(unittest.TestCase): site_dir = str(pathlib.Path(__file__).parent) @classmethod def setUpClass(cls): sys.path.append(cls.site_dir) @classmethod def tearDownClass(cls): sys.path.remove(cls.site_dir) 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 = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01')) 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): namespacedata01 = import_module('namespacedata01') reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations) root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01')) self.assertIsInstance(reader.files(), MultiplexedPath) self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')") if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/test_resource.py0000644000175100001770000001721714577034455025744 0ustar00runnerdockerimport sys import unittest import importlib_resources as resources import pathlib from . import data01 from . import util from importlib import import_module 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, unittest.TestCase): def setUp(self): self.data = data01 class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase): pass def names(traversable): return {item.name for item in traversable.iterdir()} class ResourceLoaderTests(unittest.TestCase): def test_resource_contents(self): package = util.create_package( file=data01, path=data01.__file__, contents=['A', 'B', 'C'] ) self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'}) def test_is_file(self): package = util.create_package( file=data01, path=data01.__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=data01, path=data01.__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=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'] ) self.assertFalse(resources.files(package).joinpath('Z').is_file()) class ResourceCornerCaseTests(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=data01, path=data01.__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.ZipSetupBase, unittest.TestCase): ZIP_MODULE = 'data01' 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.ZipSetupBase, unittest.TestCase): ZIP_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.ZipSetupBase, 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(ResourceFromNamespaceTests, unittest.TestCase): site_dir = str(pathlib.Path(__file__).parent) @classmethod def setUpClass(cls): sys.path.append(cls.site_dir) @classmethod def tearDownClass(cls): sys.path.remove(cls.site_dir) class ResourceFromNamespaceZipTests( util.ZipSetupBase, ResourceFromNamespaceTests, unittest.TestCase, ): ZIP_MODULE = 'namespacedata01' if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/util.py0000644000175100001770000001121114577034455024017 0ustar00runnerdockerimport abc import importlib import io import sys import types import pathlib import contextlib from . import data01 from ..abc import ResourceReader from .compat.py39 import import_helper, os_helper from . import zip as zip_ from importlib.machinery import ModuleSpec 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 CommonTests(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(data01.__name__, 'utf-8.file') def test_package_object(self): """ Passing in the package itself should succeed. """ self.execute(data01, 'utf-8.file') def test_string_path(self): """ Passing in a string for the path should succeed. """ path = 'utf-8.file' self.execute(data01, 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(data01, path) def test_importing_module_as_side_effect(self): """ The anchor package can already be imported. """ del sys.modules[data01.__name__] self.execute(data01.__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') class ZipSetupBase: ZIP_MODULE = 'data01' def setUp(self): self.fixtures = contextlib.ExitStack() self.addCleanup(self.fixtures.close) self.fixtures.enter_context(import_helper.isolated_modules()) temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) modules = pathlib.Path(temp_dir) / 'zipped modules.zip' src_path = pathlib.Path(__file__).parent.joinpath(self.ZIP_MODULE) self.fixtures.enter_context( import_helper.DirsOnSysPath(str(zip_.make_zip_file(src_path, modules))) ) self.data = importlib.import_module(self.ZIP_MODULE) class ZipSetup(ZipSetupBase): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/importlib_resources/tests/zip.py0000755000175100001770000000141714577034455023656 0ustar00runnerdocker""" Generate zip test data files. """ import contextlib import os import pathlib import zipfile import zipp def make_zip_file(src, dst): """ Zip the files in src into a new zipfile at dst. """ with zipfile.ZipFile(dst, 'w') as zf: for src_path, rel in walk(src): dst_name = src.name / pathlib.PurePosixPath(rel.as_posix()) zf.write(src_path, dst_name) zipp.CompleteDirs.inject(zf) return dst def walk(datapath): for dirpath, dirnames, filenames in os.walk(datapath): with contextlib.suppress(ValueError): dirnames.remove('__pycache__') for filename in filenames: res = pathlib.Path(dirpath) / filename rel = res.relative_to(datapath) yield res, rel ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8738866 importlib_resources-6.4.0/importlib_resources.egg-info/0000755000175100001770000000000014577034505023020 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028548.0 importlib_resources-6.4.0/importlib_resources.egg-info/PKG-INFO0000644000175100001770000000751214577034504024121 0ustar00runnerdockerMetadata-Version: 2.1 Name: importlib_resources Version: 6.4.0 Summary: Read resources from Python packages Home-page: https://github.com/python/importlib_resources Author: Barry Warsaw Author-email: barry@python.org Project-URL: Documentation, https://importlib-resources.readthedocs.io/ 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.8 License-File: LICENSE Requires-Dist: zipp>=3.1.0; python_version < "3.10" Provides-Extra: testing Requires-Dist: pytest>=6; extra == "testing" Requires-Dist: pytest-checkdocs>=2.4; extra == "testing" Requires-Dist: pytest-cov; extra == "testing" Requires-Dist: pytest-mypy; platform_python_implementation != "PyPy" and extra == "testing" Requires-Dist: pytest-enabler>=2.2; extra == "testing" Requires-Dist: pytest-ruff>=0.2.1; extra == "testing" Requires-Dist: zipp>=3.17; extra == "testing" Requires-Dist: jaraco.test>=5.4; extra == "testing" Provides-Extra: docs Requires-Dist: sphinx>=3.5; extra == "docs" Requires-Dist: sphinx<7.2.5; extra == "docs" Requires-Dist: jaraco.packaging>=9.3; extra == "docs" Requires-Dist: rst.linker>=1.9; extra == "docs" Requires-Dist: furo; extra == "docs" Requires-Dist: sphinx-lint; extra == "docs" Requires-Dist: jaraco.tidelift>=1.4; extra == "docs" .. 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-2024-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=1711028548.0 importlib_resources-6.4.0/importlib_resources.egg-info/SOURCES.txt0000644000175100001770000000504314577034504024705 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 setup.cfg 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/_itertools.py importlib_resources/abc.py importlib_resources/functional.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/py38.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/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 importlib_resources/tests/data01/__init__.py importlib_resources/tests/data01/binary.file importlib_resources/tests/data01/utf-16.file importlib_resources/tests/data01/utf-8.file importlib_resources/tests/data01/subdirectory/__init__.py importlib_resources/tests/data01/subdirectory/binary.file importlib_resources/tests/data02/__init__.py importlib_resources/tests/data02/one/__init__.py importlib_resources/tests/data02/one/resource1.txt importlib_resources/tests/data02/subdirectory/subsubdir/resource.txt importlib_resources/tests/data02/two/__init__.py importlib_resources/tests/data02/two/resource2.txt importlib_resources/tests/namespacedata01/binary.file importlib_resources/tests/namespacedata01/utf-16.file importlib_resources/tests/namespacedata01/utf-8.file importlib_resources/tests/namespacedata01/subdirectory/binary.file././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028548.0 importlib_resources-6.4.0/importlib_resources.egg-info/dependency_links.txt0000644000175100001770000000000114577034504027065 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028548.0 importlib_resources-6.4.0/importlib_resources.egg-info/requires.txt0000644000175100001770000000051614577034504025421 0ustar00runnerdocker [:python_version < "3.10"] zipp>=3.1.0 [docs] sphinx>=3.5 sphinx<7.2.5 jaraco.packaging>=9.3 rst.linker>=1.9 furo sphinx-lint jaraco.tidelift>=1.4 [testing] pytest>=6 pytest-checkdocs>=2.4 pytest-cov pytest-enabler>=2.2 pytest-ruff>=0.2.1 zipp>=3.17 jaraco.test>=5.4 [testing:platform_python_implementation != "PyPy"] pytest-mypy ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028548.0 importlib_resources-6.4.0/importlib_resources.egg-info/top_level.txt0000644000175100001770000000002414577034504025545 0ustar00runnerdockerimportlib_resources ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/mypy.ini0000644000175100001770000000023214577034455016733 0ustar00runnerdocker[mypy] ignore_missing_imports = True # required to support namespace packages # https://github.com/python/mypy/issues/14057 explicit_package_bases = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/pyproject.toml0000644000175100001770000000021314577034455020147 0ustar00runnerdocker[build-system] requires = ["setuptools>=56", "setuptools_scm[toml]>=3.4.1"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/pytest.ini0000644000175100001770000000101414577034455017264 0ustar00runnerdocker[pytest] norecursedirs=dist build .tox .eggs addopts=--doctest-modules 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=1711028525.0 importlib_resources-6.4.0/ruff.toml0000644000175100001770000000060014577034455017072 0ustar00runnerdocker[lint] 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, required for quote-style = "preserve" preview = true # https://docs.astral.sh/ruff/settings/#format-quote-style quote-style = "preserve" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711028548.8738866 importlib_resources-6.4.0/setup.cfg0000644000175100001770000000202114577034505017047 0ustar00runnerdocker[metadata] name = importlib_resources author = Barry Warsaw author_email = barry@python.org description = Read resources from Python packages long_description = file: README.rst url = https://github.com/python/importlib_resources classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: Apache Software License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only project_urls = Documentation = https://importlib-resources.readthedocs.io/ [options] include_package_data = true python_requires = >=3.8 install_requires = zipp >= 3.1.0; python_version < '3.10' [options.extras_require] testing = pytest >= 6 pytest-checkdocs >= 2.4 pytest-cov pytest-mypy; \ python_implementation != "PyPy" pytest-enabler >= 2.2 pytest-ruff >= 0.2.1 zipp >= 3.17 jaraco.test >= 5.4 docs = sphinx >= 3.5 sphinx < 7.2.5 jaraco.packaging >= 9.3 rst.linker >= 1.9 furo sphinx-lint jaraco.tidelift >= 1.4 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/towncrier.toml0000644000175100001770000000005414577034455020147 0ustar00runnerdocker[tool.towncrier] title_format = "{version}" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711028525.0 importlib_resources-6.4.0/tox.ini0000644000175100001770000000250314577034455016552 0ustar00runnerdocker[testenv] description = perform primary checks (tests, style, types, coverage) deps = setenv = PYTHONWARNDEFAULTENCODING = 1 commands = pytest {posargs} usedevelop = True extras = testing [testenv:diffcov] description = run tests and check that diff from main is covered 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 = docs testing changedir = docs commands = python -m sphinx -W --keep-going . {toxinidir}/build/html python -m sphinxlint \ # workaround for sphinx-contrib/sphinx-lint#83 --jobs 1 [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