diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 054b5e59..00000000 --- a/.coveragerc +++ /dev/null @@ -1,19 +0,0 @@ -[run] -omit = - # leading `*/` for pytest-dev/pytest-cov#456 - */.tox/* - */_itertools.py - */_legacy.py - */simple.py - */_path.py -disable_warnings = - couldnt-parse - -[report] -show_missing = True -exclude_also = - # Exclude common false positives per - # https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion - # Ref jaraco/skeleton#97 and jaraco/skeleton#135 - class .*\bProtocol\): - if TYPE_CHECKING: diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 304196f8..00000000 --- a/.editorconfig +++ /dev/null @@ -1,19 +0,0 @@ -root = 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 diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index e6805180..00000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.file binary -*.zip binary diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 8ebda610..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -tidelift: pypi/importlib-resources diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 53513eee..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: tests - -on: - merge_group: - push: - branches-ignore: - # temporary GH branches relating to merge queues (jaraco/skeleton#93) - - gh-readonly-queue/** - tags: - # required if branches-ignore is supplied (jaraco/skeleton#103) - - '**' - pull_request: - workflow_dispatch: - -permissions: - contents: read - -env: - # Environment variable to support color support (jaraco/skeleton#66) - FORCE_COLOR: 1 - - # Suppress noisy pip warnings - PIP_DISABLE_PIP_VERSION_CHECK: 'true' - PIP_NO_WARN_SCRIPT_LOCATION: 'true' - - # Ensure tests can sense settings about the environment - TOX_OVERRIDE: >- - testenv.pass_env+=GITHUB_*,FORCE_COLOR - - -jobs: - test: - strategy: - # https://blog.jaraco.com/efficient-use-of-ci-resources/ - matrix: - python: - - "3.9" - - "3.13" - platform: - - ubuntu-latest - - macos-latest - - windows-latest - include: - - python: "3.10" - platform: ubuntu-latest - - python: "3.11" - platform: ubuntu-latest - - python: "3.12" - platform: ubuntu-latest - - python: "3.14" - platform: ubuntu-latest - - python: pypy3.10 - platform: ubuntu-latest - runs-on: ${{ matrix.platform }} - continue-on-error: ${{ matrix.python == '3.14' }} - steps: - - uses: actions/checkout@v4 - - name: Install build dependencies - # Install dependencies for building packages on pre-release Pythons - # jaraco/skeleton#161 - if: matrix.python == '3.14' && matrix.platform == 'ubuntu-latest' - run: | - sudo apt update - sudo apt install -y libxml2-dev libxslt-dev - - name: Setup Python - uses: actions/setup-python@v5 - 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@v5 - 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@v5 - 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 }} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 87274500..00000000 --- a/.gitignore +++ /dev/null @@ -1,102 +0,0 @@ -# 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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 633e3648..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -repos: -- repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.9 - hooks: - - id: ruff - args: [--fix, --unsafe-fixes] - - id: ruff-format diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index 72437063..00000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: 2 -python: - install: - - path: . - extra_requirements: - - doc - -sphinx: - configuration: docs/conf.py - -# required boilerplate readthedocs/readthedocs.org#10401 -build: - os: ubuntu-lts-latest - tools: - python: latest - # post-checkout job to ensure the clone isn't shallow jaraco/skeleton#114 - jobs: - post_checkout: - - git fetch --unshallow || true diff --git a/NEWS.rst b/NEWS.rst deleted file mode 100644 index a7c95e80..00000000 --- a/NEWS.rst +++ /dev/null @@ -1,636 +0,0 @@ -v6.5.2 -====== - -Bugfixes --------- - -- Replaced reference to typing_extensions with stdlib Literal. (#323) - - -v6.5.1 -====== - -Bugfixes --------- - -- Updated ``Traversable.read_text()`` to reflect the ``errors`` parameter (python/cpython#127012). (#321) - - -v6.5.0 -====== - -Features --------- - -- Add type annotations for Traversable.open. (#317) -- Require Python 3.9 or later. - - -v6.4.5 -====== - -Bugfixes --------- - -- Omit sentinel values from a namespace path. (#311) - - -v6.4.4 -====== - -No significant changes. - - -v6.4.3 -====== - -Bugfixes --------- - -- When inferring the caller in ``files()`` correctly detect one's own module even when the resources package source is not present. (python/cpython#123085) - - -v6.4.2 -====== - -Bugfixes --------- - -- Merged fix for UTF-16 BOM handling in functional tests. (#312) - - -v6.4.1 -====== - -Bugfixes --------- - -- When constructing ZipReader, only append the name if the indicated module is a package. (python/cpython#121735) - - -v6.4.0 -====== - -Features --------- - -- The functions - ``is_resource()``, - ``open_binary()``, - ``open_text()``, - ``path()``, - ``read_binary()``, and - ``read_text()`` are un-deprecated, and support - subdirectories via multiple positional arguments. - The ``contents()`` function also allows subdirectories, - but remains deprecated. (#303) -- Deferred select imports in for a speedup (python/cpython#109829). - - -v6.3.2 -====== - -Bugfixes --------- - -- Restored expectation that local standard readers are preferred over degenerate readers. (#298) - - -v6.3.1 -====== - -Bugfixes --------- - -- Restored expectation that stdlib readers are suppressed on Python 3.10. (#257) - - -v6.3.0 -====== - -Features --------- - -- Add ``Anchor`` to ``importlib.resources`` (in order for the code to comply with the documentation) - - -v6.2.0 -====== - -Features --------- - -- Future compatibility adapters now ensure that standard library readers are replaced without overriding non-standard readers. (#295) - - -v6.1.3 -====== - -No significant changes. - - -v6.1.2 -====== - -Bugfixes --------- - -- Fixed NotADirectoryError when calling files on a subdirectory of a namespace package. (#293) - - -v6.1.1 -====== - -Bugfixes --------- - -- Added missed stream argument in simple.ResourceHandle. Ref python/cpython#111775. - - -v6.1.0 -====== - -Features --------- - -- MultiplexedPath now expects Traversable paths. String arguments to MultiplexedPath are now deprecated. - - -Bugfixes --------- - -- Enabled support for resources in namespace packages in zip files. (#287) - - -v6.0.1 -====== - -Bugfixes --------- - -- Restored Apache license. (#285) - - -v6.0.0 -====== - -Deprecations and Removals -------------------------- - -- Removed legacy functions deprecated in 5.3. (#80) - - -v5.13.0 -======= - -Features --------- - -- Require Python 3.8 or later. - - -v5.12.0 -======= - -* #257: ``importlib_resources`` (backport) now gives - precedence to built-in readers (file system, zip, - namespace packages), providing forward-compatibility - of behaviors like ``MultiplexedPath``. - -v5.11.1 -======= - -v5.10.4 -======= - -* #280: Fixed one more ``EncodingWarning`` in test suite. - -v5.11.0 -======= - -* #265: ``MultiplexedPath`` now honors multiple subdirectories - in ``iterdir`` and ``joinpath``. - -v5.10.3 -======= - -* Packaging refresh, including fixing EncodingWarnings - and some tests cleanup. - -v5.10.2 -======= - -* #274: Prefer ``write_bytes`` to context manager as - proposed in gh-100586. - -v5.10.1 -======= - -* #274: Fixed ``ResourceWarning`` in ``_common``. - -v5.10.0 -======= - -* #203: Lifted restriction on modules passed to ``files``. - Now modules need not be a package and if a non-package - module is passed, resources will be resolved adjacent to - those modules, even for modules not found in any package. - For example, ``files(import_module('mod.py'))`` will - resolve resources found at the root. The parameter to - files was renamed from 'package' to 'anchor', with a - compatibility shim for those passing by keyword. - -* #259: ``files`` no longer requires the anchor to be - specified and can infer the anchor from the caller's scope - (defaults to the caller's module). - -v5.9.0 -====== - -* #228: ``as_file`` now also supports a ``Traversable`` - representing a directory and (when needed) renders the - full tree to a temporary directory. - -v5.8.1 -====== - -* #253: In ``MultiplexedPath``, restore expectation that - a compound path with a non-existent directory does not - raise an exception. - -v5.8.0 -====== - -* #250: Now ``Traversable.joinpath`` provides a concrete - implementation, replacing the implementation in ``.simple`` - and converging with the behavior in ``MultiplexedPath``. - -v5.7.1 -====== - -* #249: In ``simple.ResourceContainer.joinpath``, honor - names split by ``posixpath.sep``. - -v5.7.0 -====== - -* #248: ``abc.Traversable.joinpath`` now allows for multiple - arguments and specifies that ``posixpath.sep`` is allowed - in any argument to accept multiple arguments, matching the - behavior found in ``zipfile.Path`` and ``pathlib.Path``. - - ``simple.ResourceContainer`` now honors this behavior. - -v5.6.0 -====== - -* #244: Add type declarations in ABCs. - -v5.5.0 -====== - -* Require Python 3.7 or later. -* #243: Fix error when no ``__pycache__`` directories exist - when testing ``update-zips``. - -v5.4.0 -====== - -* #80: Test suite now relies entirely on the traversable - API. - -v5.3.0 -====== - -* #80: Now raise a ``DeprecationWarning`` for all legacy - functions. Instead, users should rely on the ``files()`` - API introduced in importlib_resources 1.3. See - `Migrating from Legacy `_ - for guidance on avoiding the deprecated functions. - -v5.2.3 -====== - -* Updated readme to reflect current behavior and show - which versions correspond to which behavior in CPython. - -v5.0.7 -====== - -* bpo-45419: Correct ``DegenerateFiles.Path`` ``.name`` - and ``.open()`` interfaces to match ``Traversable``. - -v5.2.2 -====== - -* #234: Fix refleak in ``as_file`` caught by CPython tests. - -v5.2.1 -====== - -* bpo-38291: Avoid DeprecationWarning on ``typing.io``. - -v5.2.0 -====== - -* #80 via #221: Legacy API (``path``, ``contents``, ...) - is now supported entirely by the ``.files()`` API with - a compatibility shim supplied for resource loaders without - that functionality. - -v5.0.6 -====== - -* bpo-38693: Prefer f-strings to ``.format`` calls. - -v5.1.4 -====== - -* #225: Require - `zipp 3.1.0 `_ - or later on Python prior to 3.10 to incorporate those fixes. - -v5.0.5 -====== - -* #216: Make MultiplexedPath.name a property per the - spec. - -v5.1.3 -====== - -* Refresh packaging and improve tests. -* #216: Make MultiplexedPath.name a property per the - spec. - -v5.1.2 -====== - -* Re-release with changes from 5.0.4. - -v5.0.4 -====== - -* Fixed non-hermetic test in test_reader, revealed by - GH-24670. - -v5.1.1 -====== - -* Re-release with changes from 5.0.3. - -v5.0.3 -====== - -* Simplified DegenerateFiles.Path. - -v5.0.2 -====== - -* #214: Added ``_adapters`` module to ensure that degenerate - ``files`` behavior can be made available for legacy loaders - whose resource readers don't implement it. Fixes issue where - backport compatibility module was masking this fallback - behavior only to discover the defect when applying changes to - CPython. - -v5.1.0 -====== - -* Added ``simple`` module implementing adapters from - a low-level resource reader interface to a - ``TraversableResources`` interface. Closes #90. - -v5.0.1 -====== - -* Remove pyinstaller hook for hidden 'trees' module. - -v5.0.0 -====== - -* Removed ``importlib_resources.trees``, deprecated since 1.3.0. - -v4.1.1 -====== - -* Fixed badges in README. - -v4.1.0 -====== - -* #209: Adopt - `jaraco/skeleton `_. - -* Cleaned up some straggling Python 2 compatibility code. - -* Refreshed test zip files without .pyc and .pyo files. - -v4.0.0 -====== - -* #108: Drop support for Python 2.7. Now requires Python 3.6+. - -v3.3.1 -====== - -* Minor cleanup. - -v3.3.0 -====== - -* #107: Drop support for Python 3.5. Now requires Python 2.7 or 3.6+. - -v3.2.1 -====== - -* #200: Minor fixes and improved tests for namespace package support. - -v3.2.0 -====== - -* #68: Resources in PEP 420 Namespace packages are now supported. - -v3.1.1 -====== - -* bpo-41490: ``contents`` is now also more aggressive about - consuming any iterator from the ``Reader``. - -v3.1.0 -====== - -* #110 and bpo-41490: ``path`` method is more aggressive about - releasing handles to zipfile objects early, enabling use-cases - like ``certifi`` to leave the context open but delete the underlying - zip file. - -v3.0.0 -====== - -* Package no longer exposes ``importlib_resources.__version__``. - Users that wish to inspect the version of ``importlib_resources`` - should instead invoke ``.version('importlib_resources')`` from - ``importlib-metadata`` ( - `stdlib `_ - or `backport `_) - directly. This change eliminates the dependency on - ``importlib_metadata``. Closes #100. -* Package now always includes its data. Closes #93. -* Declare hidden imports for PyInstaller. Closes #101. - -v2.0.1 -====== - -* Select pathlib and contextlib imports based on Python version - and avoid pulling in deprecated - [pathlib](https://pypi.org/project/pathlib). Closes #97. - -v2.0.0 -====== - -* Loaders are no longer expected to implement the - ``abc.TraversableResources`` interface, but are instead - expected to return ``TraversableResources`` from their - ``get_resource_reader`` method. - -v1.5.0 -====== - -* Traversable is now a Protocol instead of an Abstract Base - Class (Python 2.7 and Python 3.8+). - -* Traversable objects now require a ``.name`` property. - -v1.4.0 -====== - -* #79: Temporary files created will now reflect the filename of - their origin. - -v1.3.1 -====== - -* For improved compatibility, ``importlib_resources.trees`` is - now imported implicitly. Closes #88. - -v1.3.0 -====== - -* Add extensibility support for non-standard loaders to supply - ``Traversable`` resources. Introduces a new abstract base - class ``abc.TraversableResources`` that supersedes (but - implements for compatibility) ``abc.ResourceReader``. Any - loader that implements (implicitly or explicitly) the - ``TraversableResources.files`` method will be capable of - supplying resources with subdirectory support. Closes #77. -* Preferred way to access ``as_file`` is now from top-level module. - ``importlib_resources.trees.as_file`` is deprecated and discouraged. - Closes #86. -* Moved ``Traversable`` abc to ``abc`` module. Closes #87. - -v1.2.0 -====== - -* Traversable now requires an ``open`` method. Closes #81. -* Fixed error on ``Python 3.5.{0,3}``. Closes #83. -* Updated packaging to resolve version from package metadata. - Closes #82. - -v1.1.0 -====== - -* Add support for retrieving resources from subdirectories of packages - through the new ``files()`` function, which returns a ``Traversable`` - object with ``joinpath`` and ``read_*`` interfaces matching those - of ``pathlib.Path`` objects. This new function supersedes all of the - previous functionality as it provides a more general-purpose access - to a package's resources. - - With this function, subdirectories are supported (Closes #58). - - The - documentation has been updated to reflect that this function is now - the preferred interface for loading package resources. It does not, - however, support resources from arbitrary loaders. It currently only - supports resources from file system path and zipfile packages (a - consequence of the ResourceReader interface only operating on - Python packages). - -1.0.2 -===== - -* Fix ``setup_requires`` and ``install_requires`` metadata in ``setup.cfg``. - Given by Anthony Sottile. - -1.0.1 -===== - -* Update Trove classifiers. Closes #63 - -1.0 -=== - -* Backport fix for test isolation from Python 3.8/3.7. Closes #61 - -0.8 -=== - -* Strip ``importlib_resources.__version__``. Closes #56 -* Fix a metadata problem with older setuptools. Closes #57 -* Add an ``__all__`` to ``importlib_resources``. Closes #59 - -0.7 -=== - -* Fix ``setup.cfg`` metadata bug. Closes #55 - -0.6 -=== - -* Move everything from ``pyproject.toml`` to ``setup.cfg``, with the added - benefit of fixing the PyPI metadata. Closes #54 -* Turn off mypy's ``strict_optional`` setting for now. - -0.5 -=== - -* Resynchronize with Python 3.7; changes the return type of ``contents()`` to - be an ``Iterable``. Closes #52 - -0.4 -=== - -* Correctly find resources in subpackages inside a zip file. Closes #51 - -0.3 -=== - -* The API, implementation, and documentation is synchronized with the Python - 3.7 standard library. Closes #47 -* When run under Python 3.7 this API shadows the stdlib versions. Closes #50 - -0.2 -=== - -* **Backward incompatible change**. Split the ``open()`` and ``read()`` calls - into separate binary and text versions, i.e. ``open_binary()``, - ``open_text()``, ``read_binary()``, and ``read_text()``. Closes #41 -* Fix a bug where unrelated resources could be returned from ``contents()``. - Closes #44 -* Correctly prevent namespace packages from containing resources. Closes #20 - -0.1 -=== - -* Initial release. - - -.. - Local Variables: - mode: change-log-mode - indent-tabs-mode: nil - sentence-end-double-space: t - fill-column: 78 - coding: utf-8 - End: diff --git a/README.rst b/README.rst deleted file mode 100644 index 30fa5b1e..00000000 --- a/README.rst +++ /dev/null @@ -1,66 +0,0 @@ -.. 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/astral-sh/ruff/main/assets/badge/v2.json - :target: https://github.com/astral-sh/ruff - :alt: Ruff - -.. image:: https://readthedocs.org/projects/importlib-resources/badge/?version=latest - :target: https://importlib-resources.readthedocs.io/en/latest/?badge=latest - -.. image:: https://img.shields.io/badge/skeleton-2025-informational - :target: https://blog.jaraco.com/skeleton - -.. image:: https://tidelift.com/badges/package/pypi/importlib-resources - :target: https://tidelift.com/subscription/pkg/pypi-importlib-resources?utm_source=pypi-importlib-resources&utm_medium=readme - -``importlib_resources`` is a backport of Python standard library -`importlib.resources -`_ -module for older Pythons. - -The key goal of this module is to replace parts of `pkg_resources -`_ with a -solution in Python's stdlib that relies on well-defined APIs. This makes -reading resources included in packages easier, with more stable and consistent -semantics. - -Compatibility -============= - -New features are introduced in this third-party library and later merged -into CPython. The following table indicates which versions of this library -were contributed to different versions in the standard library: - -.. list-table:: - :header-rows: 1 - - * - importlib_resources - - stdlib - * - 6.0 - - 3.13 - * - 5.12 - - 3.12 - * - 5.7 - - 3.11 - * - 5.0 - - 3.10 - * - 1.3 - - 3.9 - * - 0.5 (?) - - 3.7 - -For Enterprise -============== - -Available as part of the Tidelift Subscription. - -This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. - -`Learn more `_. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 54f99acb..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,3 +0,0 @@ -# Security Contact - -To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 66c7f4bd..00000000 --- a/codecov.yml +++ /dev/null @@ -1,2 +0,0 @@ -codecov: - token: 5eb1bc45-1b7f-43e6-8bc1-f2b02833dba9 diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 739be9ed..00000000 --- a/docs/api.rst +++ /dev/null @@ -1,26 +0,0 @@ -============= -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: diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 570346b6..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -extensions = [ - 'sphinx.ext.autodoc', - 'jaraco.packaging.sphinx', -] - -master_doc = "index" -html_theme = "furo" - -# Link dates and other references in the changelog -extensions += ['rst.linker'] -link_files = { - '../NEWS.rst': dict( - using=dict(GH='https://github.com'), - replace=[ - dict( - pattern=r'(Issue #|\B#)(?P\d+)', - url='{package_url}/issues/{issue}', - ), - dict( - pattern=r'(?m:^((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n)', - with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', - ), - dict( - pattern=r'PEP[- ](?P\d+)', - url='https://peps.python.org/pep-{pep_number:0>4}/', - ), - dict( - pattern=r'(python/cpython#|Python #)(?P\d+)', - url='https://github.com/python/cpython/issues/{python}', - ), - dict( - pattern=r'bpo-(?P\d+)', - url='http://bugs.python.org/issue{bpo}', - ), - ], - ), -} - -# Be strict about any broken references -nitpicky = True -nitpick_ignore: list[tuple[str, str]] = [] - -# Include Python intersphinx mapping to prevent failures -# jaraco/skeleton#51 -extensions += ['sphinx.ext.intersphinx'] -intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), -} - -# Preserve authored syntax for defaults -autodoc_preserve_defaults = True - -# Add support for linking usernames, PyPI projects, Wikipedia pages -github_url = 'https://github.com/' -extlinks = { - 'user': (f'{github_url}%s', '@%s'), - 'pypi': ('https://pypi.org/project/%s', '%s'), - 'wiki': ('https://wikipedia.org/wiki/%s', '%s'), -} -extensions += ['sphinx.ext.extlinks'] - -# local - -extensions += ['jaraco.tidelift'] - -nitpick_ignore.extend([ - ('py:class', 'module'), - ('py:class', '_io.BufferedReader'), -]) diff --git a/docs/history.rst b/docs/history.rst deleted file mode 100644 index 5bdc2320..00000000 --- a/docs/history.rst +++ /dev/null @@ -1,8 +0,0 @@ -:tocdepth: 2 - -.. _changes: - -History -******* - -.. include:: ../NEWS (links).rst diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index eee051cc..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,52 +0,0 @@ -Welcome 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 diff --git a/docs/migration.rst b/docs/migration.rst deleted file mode 100644 index 5121d068..00000000 --- a/docs/migration.rst +++ /dev/null @@ -1,155 +0,0 @@ -.. _migration: - -================= - Migration guide -================= - -The following guide will help you migrate common ``pkg_resources`` APIs to -``importlib_resources``. Only a small number of the most common APIs are -supported by ``importlib_resources``, so projects that use other features -(e.g. entry points) will have to find other solutions. -``importlib_resources`` primarily supports the following `basic resource -access`_ APIs: - -* ``pkg_resources.resource_filename()`` -* ``pkg_resources.resource_stream()`` -* ``pkg_resources.resource_string()`` -* ``pkg_resources.resource_listdir()`` -* ``pkg_resources.resource_isdir()`` - -Note that although the steps below provide a drop-in replacement for the -above methods, for many use-cases, a better approach is to use the -``Traversable`` path from ``files()`` directly. - - -pkg_resources.resource_filename() -================================= - -``resource_filename()`` is one of the more interesting APIs because it -guarantees that the return value names a file on the file system. This means -that if the resource is in a zip file, ``pkg_resources`` will extract the -file and return the name of the temporary file it created. The problem is -that ``pkg_resources`` also *implicitly* cleans up this temporary file, -without control over its lifetime by the programmer. - -``importlib_resources`` takes a different approach. Its equivalent API is the -``files()`` function, which returns a Traversable object implementing a -subset of the -:py:class:`pathlib.Path` interface suitable for reading the contents and -provides a wrapper for creating a temporary file on the system in a -context whose lifetime is managed by the user. Note though -that if the resource is *already* on the file system, ``importlib_resources`` -still returns a context manager, but nothing needs to get cleaned up. - -Here's an example from ``pkg_resources``:: - - path = pkg_resources.resource_filename('my.package', 'resource.dat') - -The best way to convert this is with the following idiom:: - - ref = importlib_resources.files('my.package') / 'resource.dat' - with importlib_resources.as_file(ref) as path: - # Do something with path. After the with-statement exits, any - # temporary file created will be immediately cleaned up. - -That's all fine if you only need the file temporarily, but what if you need it -to stick around for a while? One way of doing this is to use an -:py:class:`contextlib.ExitStack` instance and manage the resource explicitly:: - - from contextlib import ExitStack - file_manager = ExitStack() - ref = importlib_resources.files('my.package') / 'resource.dat' - path = file_manager.enter_context( - importlib_resources.as_file(ref)) - -Now ``path`` will continue to exist until you explicitly call -``file_manager.close()``. What if you want the file to exist until the -process exits, or you can't pass ``file_manager`` around in your code? Use an -:py:mod:`atexit` handler:: - - import atexit - file_manager = ExitStack() - atexit.register(file_manager.close) - ref = importlib_resources.files('my.package') / 'resource.dat' - path = file_manager.enter_context( - importlib_resources.as_file(ref)) - -Assuming your Python interpreter exits gracefully, the temporary file will be -cleaned up when Python exits. - - -pkg_resources.resource_stream() -=============================== - -``pkg_resources.resource_stream()`` returns a readable file-like object opened -in binary mode. When you read from the returned file-like object, you get -bytes. E.g.:: - - with pkg_resources.resource_stream('my.package', 'resource.dat') as fp: - my_bytes = fp.read() - -The equivalent code in ``importlib_resources`` is pretty straightforward:: - - ref = importlib_resources.files('my.package').joinpath('resource.dat') - with ref.open('rb') as fp: - my_bytes = fp.read() - - -pkg_resources.resource_string() -=============================== - -In Python 2, ``pkg_resources.resource_string()`` returns the contents of a -resource as a ``str``. In Python 3, this function is a misnomer; it actually -returns the contents of the named resource as ``bytes``. That's why the -following example is often written for clarity as:: - - from pkg_resources import resource_string as resource_bytes - contents = resource_bytes('my.package', 'resource.dat') - -This can be easily rewritten like so:: - - ref = importlib_resources.files('my.package').joinpath('resource.dat') - contents = ref.read_bytes() - - -pkg_resources.resource_listdir() -================================ - -This function lists the entries in the package, both files and directories, -but it does not recurse into subdirectories, e.g.:: - - for entry in pkg_resources.resource_listdir('my.package', 'subpackage'): - print(entry) - -This is easily rewritten using the following idiom:: - - for entry in importlib_resources.files('my.package.subpackage').iterdir(): - print(entry.name) - -Note: - -* ``Traversable.iterdir()`` returns *all* the entries in the - subpackage, i.e. both resources (files) and non-resources (directories). -* ``Traversable.iterdir()`` returns additional traversable objects, which if - directories can also be iterated over (recursively). -* ``Traversable.iterdir()``, like ``pathlib.Path`` returns an iterator, not a - concrete sequence. -* The order in which the elements are returned is undefined. - - -pkg_resources.resource_isdir() -============================== - -You can ask ``pkg_resources`` to tell you whether a particular resource inside -a package is a directory or not:: - - if pkg_resources.resource_isdir('my.package', 'resource'): - print('A directory') - -The ``importlib_resources`` equivalent is straightforward:: - - if importlib_resources.files('my.package').joinpath('resource').is_dir(): - print('A directory') - - -.. _`basic resource access`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access diff --git a/docs/using.rst b/docs/using.rst deleted file mode 100644 index 0c66810f..00000000 --- a/docs/using.rst +++ /dev/null @@ -1,207 +0,0 @@ -.. _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 diff --git a/hydra_plugins/dummy_plugin.py b/hydra_plugins/dummy_plugin.py new file mode 100644 index 00000000..033bbcda --- /dev/null +++ b/hydra_plugins/dummy_plugin.py @@ -0,0 +1,7 @@ +from hydra.core.config_search_path import ConfigSearchPath +from hydra.plugins.search_path_plugin import SearchPathPlugin + + +class Dummy(SearchPathPlugin): + def manipulate_search_path(self, search_path: ConfigSearchPath) -> None: + search_path.append(provider="dummy-search-path-plugin", path="pkg://test/config") \ No newline at end of file diff --git a/importlib_resources/__init__.py b/importlib_resources/__init__.py deleted file mode 100644 index 27d6c7f8..00000000 --- a/importlib_resources/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Read resources contained within a package. - -This codebase is shared between importlib.resources in the stdlib -and importlib_resources in PyPI. See -https://github.com/python/importlib_metadata/wiki/Development-Methodology -for more detail. -""" - -from ._common import ( - Anchor, - Package, - as_file, - files, -) -from ._functional import ( - contents, - is_resource, - open_binary, - open_text, - path, - read_binary, - read_text, -) -from .abc import ResourceReader - -__all__ = [ - 'Package', - 'Anchor', - 'ResourceReader', - 'as_file', - 'files', - 'contents', - 'is_resource', - 'open_binary', - 'open_text', - 'path', - 'read_binary', - 'read_text', -] diff --git a/importlib_resources/_adapters.py b/importlib_resources/_adapters.py deleted file mode 100644 index 50688fbb..00000000 --- a/importlib_resources/_adapters.py +++ /dev/null @@ -1,168 +0,0 @@ -from 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) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py deleted file mode 100644 index 5f41c265..00000000 --- a/importlib_resources/_common.py +++ /dev/null @@ -1,211 +0,0 @@ -import contextlib -import functools -import importlib -import inspect -import itertools -import os -import pathlib -import tempfile -import types -import warnings -from typing import Optional, Union, cast - -from .abc import ResourceReader, Traversable - -Package = Union[types.ModuleType, str] -Anchor = Package - - -def package_to_anchor(func): - """ - Replace 'package' parameter as 'anchor' and warn about the change. - - Other errors should fall through. - - >>> files('a', 'b') - Traceback (most recent call last): - TypeError: files() takes from 0 to 1 positional arguments but 2 were given - - Remove this compatibility in Python 3.14. - """ - undefined = object() - - @functools.wraps(func) - def wrapper(anchor=undefined, package=undefined): - if package is not undefined: - if anchor is not undefined: - return func(anchor, package) - warnings.warn( - "First parameter to files is renamed to 'anchor'", - DeprecationWarning, - stacklevel=2, - ) - return func(package) - elif anchor is undefined: - return func() - return func(anchor) - - return wrapper - - -@package_to_anchor -def files(anchor: Optional[Anchor] = None) -> Traversable: - """ - Get a Traversable resource for an anchor. - """ - return from_package(resolve(anchor)) - - -def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]: - """ - Return the package's loader if it's a ResourceReader. - """ - # We can't use - # a issubclass() check here because apparently abc.'s __subclasscheck__() - # hook wants to create a weak reference to the object, but - # zipimport.zipimporter does not support weak references, resulting in a - # TypeError. That seems terrible. - spec = package.__spec__ - reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore[union-attr] - if reader is None: - return None - return reader(spec.name) # type: ignore[union-attr] - - -@functools.singledispatch -def resolve(cand: Optional[Anchor]) -> types.ModuleType: - return cast(types.ModuleType, cand) - - -@resolve.register -def _(cand: str) -> types.ModuleType: - return importlib.import_module(cand) - - -@resolve.register -def _(cand: None) -> types.ModuleType: - return resolve(_infer_caller().f_globals['__name__']) - - -def _infer_caller(): - """ - Walk the stack and find the frame of the first caller not in this module. - """ - - def is_this_file(frame_info): - return frame_info.filename == stack[0].filename - - def is_wrapper(frame_info): - return frame_info.function == 'wrapper' - - stack = inspect.stack() - not_this_file = itertools.filterfalse(is_this_file, stack) - # also exclude 'wrapper' due to singledispatch in the call stack - callers = itertools.filterfalse(is_wrapper, not_this_file) - return next(callers).frame - - -def from_package(package: types.ModuleType): - """ - Return a Traversable object for the given package. - - """ - # deferred for performance (python/cpython#109829) - from .future.adapters import wrap_spec - - spec = wrap_spec(package) - reader = spec.loader.get_resource_reader(spec.name) - return reader.files() - - -@contextlib.contextmanager -def _tempfile( - reader, - suffix='', - # gh-93353: Keep a reference to call os.remove() in late Python - # finalization. - *, - _os_remove=os.remove, -): - # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try' - # blocks due to the need to close the temporary file to work on Windows - # properly. - fd, raw_path = tempfile.mkstemp(suffix=suffix) - try: - try: - os.write(fd, reader()) - finally: - os.close(fd) - del reader - yield pathlib.Path(raw_path) - finally: - try: - _os_remove(raw_path) - except FileNotFoundError: - pass - - -def _temp_file(path): - return _tempfile(path.read_bytes, suffix=path.name) - - -def _is_present_dir(path: Traversable) -> bool: - """ - Some Traversables implement ``is_dir()`` to raise an - exception (i.e. ``FileNotFoundError``) when the - directory doesn't exist. This function wraps that call - to always return a boolean and only return True - if there's a dir and it exists. - """ - with contextlib.suppress(FileNotFoundError): - return path.is_dir() - return False - - -@functools.singledispatch -def as_file(path): - """ - Given a Traversable object, return that object as a - path on the local file system in a context manager. - """ - return _temp_dir(path) if _is_present_dir(path) else _temp_file(path) - - -@as_file.register(pathlib.Path) -@contextlib.contextmanager -def _(path): - """ - Degenerate behavior for pathlib.Path objects. - """ - yield path - - -@contextlib.contextmanager -def _temp_path(dir: tempfile.TemporaryDirectory): - """ - Wrap tempfile.TemporaryDirectory to return a pathlib object. - """ - with dir as result: - yield pathlib.Path(result) - - -@contextlib.contextmanager -def _temp_dir(path): - """ - Given a traversable dir, recursively replicate the whole tree - to the file system in a context manager. - """ - assert path.is_dir() - with _temp_path(tempfile.TemporaryDirectory()) as temp_dir: - yield _write_contents(temp_dir, path) - - -def _write_contents(target, source): - child = target.joinpath(source.name) - if source.is_dir(): - child.mkdir() - for item in source.iterdir(): - _write_contents(child, item) - else: - child.write_bytes(source.read_bytes()) - return child diff --git a/importlib_resources/_functional.py b/importlib_resources/_functional.py deleted file mode 100644 index b08a5c6e..00000000 --- a/importlib_resources/_functional.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Simplified function-based API for importlib.resources""" - -import warnings - -from ._common import as_file, files -from .abc import TraversalError - -_MISSING = object() - - -def open_binary(anchor, *path_names): - """Open for binary reading the *resource* within *package*.""" - return _get_resource(anchor, path_names).open('rb') - - -def open_text(anchor, *path_names, encoding=_MISSING, errors='strict'): - """Open for text reading the *resource* within *package*.""" - encoding = _get_encoding_arg(path_names, encoding) - resource = _get_resource(anchor, path_names) - return resource.open('r', encoding=encoding, errors=errors) - - -def read_binary(anchor, *path_names): - """Read and return contents of *resource* within *package* as bytes.""" - return _get_resource(anchor, path_names).read_bytes() - - -def read_text(anchor, *path_names, encoding=_MISSING, errors='strict'): - """Read and return contents of *resource* within *package* as str.""" - encoding = _get_encoding_arg(path_names, encoding) - resource = _get_resource(anchor, path_names) - return resource.read_text(encoding=encoding, errors=errors) - - -def path(anchor, *path_names): - """Return the path to the *resource* as an actual file system path.""" - return as_file(_get_resource(anchor, path_names)) - - -def is_resource(anchor, *path_names): - """Return ``True`` if there is a resource named *name* in the package, - - Otherwise returns ``False``. - """ - try: - return _get_resource(anchor, path_names).is_file() - except TraversalError: - return False - - -def contents(anchor, *path_names): - """Return an iterable over the named resources within the package. - - The iterable returns :class:`str` resources (e.g. files). - The iterable does not recurse into subdirectories. - """ - warnings.warn( - "importlib.resources.contents is deprecated. " - "Use files(anchor).iterdir() instead.", - DeprecationWarning, - stacklevel=1, - ) - return (resource.name for resource in _get_resource(anchor, path_names).iterdir()) - - -def _get_encoding_arg(path_names, encoding): - # For compatibility with versions where *encoding* was a positional - # argument, it needs to be given explicitly when there are multiple - # *path_names*. - # This limitation can be removed in Python 3.15. - if encoding is _MISSING: - if len(path_names) > 1: - raise TypeError( - "'encoding' argument required with multiple path names", - ) - else: - return 'utf-8' - return encoding - - -def _get_resource(anchor, path_names): - if anchor is None: - raise TypeError("anchor must be module or string, got None") - return files(anchor).joinpath(*path_names) diff --git a/importlib_resources/_itertools.py b/importlib_resources/_itertools.py deleted file mode 100644 index 7b775ef5..00000000 --- a/importlib_resources/_itertools.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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 diff --git a/importlib_resources/abc.py b/importlib_resources/abc.py deleted file mode 100644 index 883d3328..00000000 --- a/importlib_resources/abc.py +++ /dev/null @@ -1,193 +0,0 @@ -import abc -import itertools -import os -import pathlib -from typing import ( - Any, - BinaryIO, - Iterable, - Iterator, - NoReturn, - Literal, - Optional, - Protocol, - Text, - TextIO, - Union, - overload, - runtime_checkable, -) - -StrPath = Union[str, os.PathLike[str]] - -__all__ = ["ResourceReader", "Traversable", "TraversableResources"] - - -class ResourceReader(metaclass=abc.ABCMeta): - """Abstract base class for loaders to provide resource reading support.""" - - @abc.abstractmethod - def open_resource(self, resource: Text) -> BinaryIO: - """Return an opened, file-like object for binary reading. - - The 'resource' argument is expected to represent only a file name. - If the resource cannot be found, FileNotFoundError is raised. - """ - # This deliberately raises FileNotFoundError instead of - # NotImplementedError so that if this method is accidentally called, - # it'll still do the right thing. - raise FileNotFoundError - - @abc.abstractmethod - def resource_path(self, resource: Text) -> Text: - """Return the file system path to the specified resource. - - The 'resource' argument is expected to represent only a file name. - If the resource does not exist on the file system, raise - FileNotFoundError. - """ - # This deliberately raises FileNotFoundError instead of - # NotImplementedError so that if this method is accidentally called, - # it'll still do the right thing. - raise FileNotFoundError - - @abc.abstractmethod - def is_resource(self, path: Text) -> bool: - """Return True if the named 'path' is a resource. - - Files are resources, directories are not. - """ - raise FileNotFoundError - - @abc.abstractmethod - def contents(self) -> Iterable[str]: - """Return an iterable of entries in `package`.""" - raise FileNotFoundError - - -class TraversalError(Exception): - pass - - -@runtime_checkable -class Traversable(Protocol): - """ - An object with a subset of pathlib.Path methods suitable for - traversing directories and opening files. - - Any exceptions that occur when accessing the backing resource - may propagate unaltered. - """ - - @abc.abstractmethod - def iterdir(self) -> Iterator["Traversable"]: - """ - Yield Traversable objects in self - """ - - def read_bytes(self) -> bytes: - """ - Read contents of self as bytes - """ - with self.open('rb') as strm: - return strm.read() - - def read_text( - self, encoding: Optional[str] = None, errors: Optional[str] = None - ) -> str: - """ - Read contents of self as text - """ - with self.open(encoding=encoding, errors=errors) as strm: - return strm.read() - - @abc.abstractmethod - def is_dir(self) -> bool: - """ - Return True if self is a directory - """ - - @abc.abstractmethod - def is_file(self) -> bool: - """ - Return True if self is a file - """ - - def joinpath(self, *descendants: StrPath) -> "Traversable": - """ - Return Traversable resolved with any descendants applied. - - Each descendant should be a path segment relative to self - and each may contain multiple levels separated by - ``posixpath.sep`` (``/``). - """ - if not descendants: - return self - names = itertools.chain.from_iterable( - path.parts for path in map(pathlib.PurePosixPath, descendants) - ) - target = next(names) - matches = ( - traversable for traversable in self.iterdir() if traversable.name == target - ) - try: - match = next(matches) - except StopIteration: - raise TraversalError( - "Target not found during traversal.", target, list(names) - ) - return match.joinpath(*names) - - def __truediv__(self, child: StrPath) -> "Traversable": - """ - Return Traversable child in self - """ - return self.joinpath(child) - - @overload - def open(self, mode: Literal['r'] = 'r', *args: Any, **kwargs: Any) -> TextIO: ... - - @overload - def open(self, mode: Literal['rb'], *args: Any, **kwargs: Any) -> BinaryIO: ... - - @abc.abstractmethod - def open( - self, mode: str = 'r', *args: Any, **kwargs: Any - ) -> Union[TextIO, BinaryIO]: - """ - mode may be 'r' or 'rb' to open as text or binary. Return a handle - suitable for reading (same as pathlib.Path.open). - - When opening as text, accepts encoding parameters such as those - accepted by io.TextIOWrapper. - """ - - @property - @abc.abstractmethod - def name(self) -> str: - """ - The base name of this object without any parent references. - """ - - -class TraversableResources(ResourceReader): - """ - The required interface for providing traversable - resources. - """ - - @abc.abstractmethod - def files(self) -> "Traversable": - """Return a Traversable object for the loaded package.""" - - def open_resource(self, resource: StrPath) -> BinaryIO: - return self.files().joinpath(resource).open('rb') - - def resource_path(self, resource: Any) -> NoReturn: - raise FileNotFoundError(resource) - - def is_resource(self, path: StrPath) -> bool: - return self.files().joinpath(path).is_file() - - def contents(self) -> Iterator[str]: - return (item.name for item in self.files().iterdir()) diff --git a/importlib_resources/compat/py39.py b/importlib_resources/compat/py39.py deleted file mode 100644 index 684d3c63..00000000 --- a/importlib_resources/compat/py39.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys - -__all__ = ['ZipPath'] - - -if sys.version_info >= (3, 10): - from zipfile import Path as ZipPath -else: - from zipp import Path as ZipPath diff --git a/importlib_resources/future/adapters.py b/importlib_resources/future/adapters.py deleted file mode 100644 index 239e52b7..00000000 --- a/importlib_resources/future/adapters.py +++ /dev/null @@ -1,102 +0,0 @@ -import functools -import pathlib -from contextlib import suppress -from types import SimpleNamespace - -from .. import _adapters, readers - - -def _block_standard(reader_getter): - """ - Wrap _adapters.TraversableResourcesLoader.get_resource_reader - and intercept any standard library readers. - """ - - @functools.wraps(reader_getter) - def wrapper(*args, **kwargs): - """ - If the reader is from the standard library, return None to allow - allow likely newer implementations in this library to take precedence. - """ - try: - reader = reader_getter(*args, **kwargs) - except NotADirectoryError: - # MultiplexedPath may fail on zip subdirectory - return - except ValueError as exc: - # NamespaceReader in stdlib may fail for editable installs - # (python/importlib_resources#311, python/importlib_resources#318) - # Remove after bugfix applied to Python 3.13. - if "not enough values to unpack" not in str(exc): - raise - return - # Python 3.10+ - mod_name = reader.__class__.__module__ - if mod_name.startswith('importlib.') and mod_name.endswith('readers'): - return - # Python 3.8, 3.9 - if isinstance(reader, _adapters.CompatibilityFiles) and ( - reader.spec.loader.__class__.__module__.startswith('zipimport') - or reader.spec.loader.__class__.__module__.startswith( - '_frozen_importlib_external' - ) - ): - return - return reader - - return wrapper - - -def _skip_degenerate(reader): - """ - Mask any degenerate reader. Ref #298. - """ - is_degenerate = ( - isinstance(reader, _adapters.CompatibilityFiles) and not reader._reader - ) - return reader if not is_degenerate else None - - -class TraversableResourcesLoader(_adapters.TraversableResourcesLoader): - """ - Adapt loaders to provide TraversableResources and other - compatibility. - - Ensures the readers from importlib_resources are preferred - over stdlib readers. - """ - - def get_resource_reader(self, name): - return ( - _skip_degenerate(_block_standard(super().get_resource_reader)(name)) - or self._standard_reader() - or super().get_resource_reader(name) - ) - - def _standard_reader(self): - return self._zip_reader() or self._namespace_reader() or self._file_reader() - - def _zip_reader(self): - with suppress(AttributeError): - return readers.ZipReader(self.spec.loader, self.spec.name) - - def _namespace_reader(self): - with suppress(AttributeError, ValueError): - return readers.NamespaceReader(self.spec.submodule_search_locations) - - def _file_reader(self): - try: - path = pathlib.Path(self.spec.origin) - except TypeError: - return None - if path.exists(): - return readers.FileReader(SimpleNamespace(path=path)) - - -def wrap_spec(package): - """ - Override _adapters.wrap_spec to use TraversableResourcesLoader - from above. Ensures that future behavior is always available on older - Pythons. - """ - return _adapters.SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) diff --git a/importlib_resources/py.typed b/importlib_resources/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/importlib_resources/readers.py b/importlib_resources/readers.py deleted file mode 100644 index 99884b6a..00000000 --- a/importlib_resources/readers.py +++ /dev/null @@ -1,202 +0,0 @@ -from __future__ import annotations - -import collections -import contextlib -import itertools -import operator -import pathlib -import re -import warnings -from collections.abc import Iterator - -from . import abc -from ._itertools import only -from .compat.py39 import ZipPath - - -def remove_duplicates(items): - return iter(collections.OrderedDict.fromkeys(items)) - - -class FileReader(abc.TraversableResources): - def __init__(self, loader): - self.path = pathlib.Path(loader.path).parent - - def resource_path(self, resource): - """ - Return the file system path to prevent - `resources.path()` from creating a temporary - copy. - """ - return str(self.path.joinpath(resource)) - - def files(self): - return self.path - - -class ZipReader(abc.TraversableResources): - def __init__(self, loader, module): - self.prefix = loader.prefix.replace('\\', '/') - if loader.is_package(module): - _, _, name = module.rpartition('.') - self.prefix += name + '/' - self.archive = loader.archive - - def open_resource(self, resource): - try: - return super().open_resource(resource) - except KeyError as exc: - raise FileNotFoundError(exc.args[0]) - - def is_resource(self, path): - """ - Workaround for `zipfile.Path.is_file` returning true - for non-existent paths. - """ - target = self.files().joinpath(path) - return target.is_file() and target.exists() - - def files(self): - return ZipPath(self.archive, self.prefix) - - -class MultiplexedPath(abc.Traversable): - """ - Given a series of Traversable objects, implement a merged - version of the interface across all objects. Useful for - namespace packages which may be multihomed at a single - name. - """ - - def __init__(self, *paths): - self._paths = list(map(_ensure_traversable, remove_duplicates(paths))) - if not self._paths: - message = 'MultiplexedPath must contain at least one path' - raise FileNotFoundError(message) - if not all(path.is_dir() for path in self._paths): - raise NotADirectoryError('MultiplexedPath only supports directories') - - def iterdir(self): - children = (child for path in self._paths for child in path.iterdir()) - by_name = operator.attrgetter('name') - groups = itertools.groupby(sorted(children, key=by_name), key=by_name) - return map(self._follow, (locs for name, locs in groups)) - - def read_bytes(self): - raise FileNotFoundError(f'{self} is not a file') - - def read_text(self, *args, **kwargs): - raise FileNotFoundError(f'{self} is not a file') - - def is_dir(self): - return True - - def is_file(self): - return False - - def joinpath(self, *descendants): - try: - return super().joinpath(*descendants) - except abc.TraversalError: - # One of the paths did not resolve (a directory does not exist). - # Just return something that will not exist. - return self._paths[0].joinpath(*descendants) - - @classmethod - def _follow(cls, children): - """ - Construct a MultiplexedPath if needed. - - If children contains a sole element, return it. - Otherwise, return a MultiplexedPath of the items. - Unless one of the items is not a Directory, then return the first. - """ - subdirs, one_dir, one_file = itertools.tee(children, 3) - - try: - return only(one_dir) - except ValueError: - try: - return cls(*subdirs) - except NotADirectoryError: - return next(one_file) - - def open(self, *args, **kwargs): - raise FileNotFoundError(f'{self} is not a file') - - @property - def name(self): - return self._paths[0].name - - def __repr__(self): - paths = ', '.join(f"'{path}'" for path in self._paths) - return f'MultiplexedPath({paths})' - - -class NamespaceReader(abc.TraversableResources): - def __init__(self, namespace_path): - if 'NamespacePath' not in str(namespace_path): - raise ValueError('Invalid path') - self.path = MultiplexedPath(*filter(bool, map(self._resolve, namespace_path))) - - @classmethod - def _resolve(cls, path_str) -> abc.Traversable | None: - r""" - Given an item from a namespace path, resolve it to a Traversable. - - path_str might be a directory on the filesystem or a path to a - zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or - ``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``. - - path_str might also be a sentinel used by editable packages to - trigger other behaviors (see python/importlib_resources#311). - In that case, return None. - """ - dirs = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir()) - return next(dirs, None) - - @classmethod - def _candidate_paths(cls, path_str: str) -> Iterator[abc.Traversable]: - yield pathlib.Path(path_str) - yield from cls._resolve_zip_path(path_str) - - @staticmethod - def _resolve_zip_path(path_str: str): - for match in reversed(list(re.finditer(r'[\\/]', path_str))): - with contextlib.suppress( - FileNotFoundError, - IsADirectoryError, - NotADirectoryError, - PermissionError, - ): - inner = path_str[match.end() :].replace('\\', '/') + '/' - yield ZipPath(path_str[: match.start()], inner.lstrip('/')) - - def resource_path(self, resource): - """ - Return the file system path to prevent - `resources.path()` from creating a temporary - copy. - """ - return str(self.path.joinpath(resource)) - - def files(self): - return self.path - - -def _ensure_traversable(path): - """ - Convert deprecated string arguments to traversables (pathlib.Path). - - Remove with Python 3.15. - """ - if not isinstance(path, str): - return path - - warnings.warn( - "String arguments are deprecated. Pass a Traversable instead.", - DeprecationWarning, - stacklevel=3, - ) - - return pathlib.Path(path) diff --git a/importlib_resources/simple.py b/importlib_resources/simple.py deleted file mode 100644 index 2e75299b..00000000 --- a/importlib_resources/simple.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Interface adapters for low-level readers. -""" - -import abc -import io -import itertools -from typing import BinaryIO, List - -from .abc import Traversable, TraversableResources - - -class SimpleReader(abc.ABC): - """ - The minimum, low-level interface required from a resource - provider. - """ - - @property - @abc.abstractmethod - def package(self) -> str: - """ - The name of the package for which this reader loads resources. - """ - - @abc.abstractmethod - def children(self) -> List['SimpleReader']: - """ - Obtain an iterable of SimpleReader for available - child containers (e.g. directories). - """ - - @abc.abstractmethod - def resources(self) -> List[str]: - """ - Obtain available named resources for this virtual package. - """ - - @abc.abstractmethod - def open_binary(self, resource: str) -> BinaryIO: - """ - Obtain a File-like for a named resource. - """ - - @property - def name(self): - return self.package.split('.')[-1] - - -class ResourceContainer(Traversable): - """ - Traversable container for a package's resources via its reader. - """ - - def __init__(self, reader: SimpleReader): - self.reader = reader - - def is_dir(self): - return True - - def is_file(self): - return False - - def iterdir(self): - files = (ResourceHandle(self, name) for name in self.reader.resources) - dirs = map(ResourceContainer, self.reader.children()) - return itertools.chain(files, dirs) - - def open(self, *args, **kwargs): - raise IsADirectoryError() - - -class ResourceHandle(Traversable): - """ - Handle to a named resource in a ResourceReader. - """ - - def __init__(self, parent: ResourceContainer, name: str): - self.parent = parent - self.name = name # type: ignore[misc] - - def is_file(self): - return True - - def is_dir(self): - return False - - def open(self, mode='r', *args, **kwargs): - stream = self.parent.reader.open_binary(self.name) - if 'b' not in mode: - stream = io.TextIOWrapper(stream, *args, **kwargs) - return stream - - def joinpath(self, name): - raise RuntimeError("Cannot traverse into a resource") - - -class TraversableReader(TraversableResources, SimpleReader): - """ - A TraversableResources based on SimpleReader. Resource providers - may derive from this class to provide the TraversableResources - interface by supplying the SimpleReader interface. - """ - - def files(self): - return ResourceContainer(self) diff --git a/importlib_resources/tests/__init__.py b/importlib_resources/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/importlib_resources/tests/_path.py b/importlib_resources/tests/_path.py deleted file mode 100644 index 0033983d..00000000 --- a/importlib_resources/tests/_path.py +++ /dev/null @@ -1,90 +0,0 @@ -import functools -import pathlib -from typing import Dict, Protocol, Union, runtime_checkable - -#### -# from jaraco.path 3.7.1 - - -class Symlink(str): - """ - A string indicating the target of a symlink. - """ - - -FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']] - - -@runtime_checkable -class TreeMaker(Protocol): - def __truediv__(self, *args, **kwargs): ... # pragma: no cover - - def mkdir(self, **kwargs): ... # pragma: no cover - - def write_text(self, content, **kwargs): ... # pragma: no cover - - def write_bytes(self, content): ... # pragma: no cover - - def symlink_to(self, target): ... # pragma: no cover - - -def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker: - return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore[return-value] - - -def build( - spec: FilesSpec, - prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore[assignment] -): - """ - Build a set of files/directories, as described by the spec. - - Each key represents a pathname, and the value represents - the content. Content may be a nested directory. - - >>> spec = { - ... 'README.txt': "A README file", - ... "foo": { - ... "__init__.py": "", - ... "bar": { - ... "__init__.py": "", - ... }, - ... "baz.py": "# Some code", - ... "bar.py": Symlink("baz.py"), - ... }, - ... "bing": Symlink("foo"), - ... } - >>> target = getfixture('tmp_path') - >>> build(spec, target) - >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8') - '# Some code' - >>> target.joinpath('bing/bar.py').read_text(encoding='utf-8') - '# Some code' - """ - for name, contents in spec.items(): - create(contents, _ensure_tree_maker(prefix) / name) - - -@functools.singledispatch -def create(content: Union[str, bytes, FilesSpec], path): - path.mkdir(exist_ok=True) - build(content, prefix=path) # type: ignore[arg-type] - - -@create.register -def _(content: bytes, path): - path.write_bytes(content) - - -@create.register -def _(content: str, path): - path.write_text(content, encoding='utf-8') - - -@create.register -def _(content: Symlink, path): - path.symlink_to(content) - - -# end from jaraco.path -#### diff --git a/importlib_resources/tests/compat/__init__.py b/importlib_resources/tests/compat/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/importlib_resources/tests/compat/py312.py b/importlib_resources/tests/compat/py312.py deleted file mode 100644 index ea9a58ba..00000000 --- a/importlib_resources/tests/compat/py312.py +++ /dev/null @@ -1,18 +0,0 @@ -import 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) diff --git a/importlib_resources/tests/compat/py39.py b/importlib_resources/tests/compat/py39.py deleted file mode 100644 index e01d276b..00000000 --- a/importlib_resources/tests/compat/py39.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Backward-compatability shims to support Python 3.9 and earlier. -""" - -from jaraco.test.cpython import from_test_support, try_import - -import_helper = try_import('import_helper') or from_test_support( - 'modules_setup', 'modules_cleanup', 'DirsOnSysPath' -) -os_helper = try_import('os_helper') or from_test_support('temp_dir') -warnings_helper = try_import('warnings_helper') or from_test_support( - 'ignore_warnings', 'check_warnings' -) diff --git a/importlib_resources/tests/test_compatibilty_files.py b/importlib_resources/tests/test_compatibilty_files.py deleted file mode 100644 index e8aac284..00000000 --- a/importlib_resources/tests/test_compatibilty_files.py +++ /dev/null @@ -1,103 +0,0 @@ -import 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) diff --git a/importlib_resources/tests/test_contents.py b/importlib_resources/tests/test_contents.py deleted file mode 100644 index dcb872ec..00000000 --- a/importlib_resources/tests/test_contents.py +++ /dev/null @@ -1,39 +0,0 @@ -import unittest - -import importlib_resources as resources - -from . import util - - -class ContentsTests: - expected = { - '__init__.py', - 'binary.file', - 'subdirectory', - 'utf-16.file', - 'utf-8.file', - } - - def test_contents(self): - contents = {path.name for path in resources.files(self.data).iterdir()} - assert self.expected <= contents - - -class ContentsDiskTests(ContentsTests, util.DiskSetup, unittest.TestCase): - pass - - -class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase): - pass - - -class ContentsNamespaceTests(ContentsTests, util.DiskSetup, unittest.TestCase): - MODULE = 'namespacedata01' - - expected = { - # no __init__ because of namespace design - 'binary.file', - 'subdirectory', - 'utf-16.file', - 'utf-8.file', - } diff --git a/importlib_resources/tests/test_custom.py b/importlib_resources/tests/test_custom.py deleted file mode 100644 index 25ae0e75..00000000 --- a/importlib_resources/tests/test_custom.py +++ /dev/null @@ -1,48 +0,0 @@ -import contextlib -import pathlib -import unittest - -import importlib_resources as resources - -from .. import abc -from ..abc import ResourceReader, TraversableResources -from . import util -from .compat.py39 import os_helper - - -class SimpleLoader: - """ - A simple loader that only implements a resource reader. - """ - - def __init__(self, reader: ResourceReader): - self.reader = reader - - def get_resource_reader(self, package): - return self.reader - - -class MagicResources(TraversableResources): - """ - Magically returns the resources at path. - """ - - def __init__(self, path: pathlib.Path): - self.path = path - - def files(self): - return self.path - - -class CustomTraversableResourcesTests(unittest.TestCase): - def setUp(self): - self.fixtures = contextlib.ExitStack() - self.addCleanup(self.fixtures.close) - - def test_custom_loader(self): - temp_dir = pathlib.Path(self.fixtures.enter_context(os_helper.temp_dir())) - loader = SimpleLoader(MagicResources(temp_dir)) - pkg = util.create_package_from_loader(loader) - files = resources.files(pkg) - assert isinstance(files, abc.Traversable) - assert list(files.iterdir()) == [] diff --git a/importlib_resources/tests/test_files.py b/importlib_resources/tests/test_files.py deleted file mode 100644 index be206603..00000000 --- a/importlib_resources/tests/test_files.py +++ /dev/null @@ -1,194 +0,0 @@ -import contextlib -import importlib -import pathlib -import py_compile -import textwrap -import unittest -import warnings - -import importlib_resources as resources - -from ..abc import Traversable -from . import util -from .compat.py39 import import_helper, os_helper - - -@contextlib.contextmanager -def suppress_known_deprecation(): - with warnings.catch_warnings(record=True) as ctx: - warnings.simplefilter('default', category=DeprecationWarning) - yield ctx - - -class FilesTests: - def test_read_bytes(self): - files = resources.files(self.data) - actual = files.joinpath('utf-8.file').read_bytes() - assert actual == b'Hello, UTF-8 world!\n' - - def test_read_text(self): - files = resources.files(self.data) - actual = files.joinpath('utf-8.file').read_text(encoding='utf-8') - assert actual == 'Hello, UTF-8 world!\n' - - def test_traversable(self): - assert isinstance(resources.files(self.data), Traversable) - - def test_joinpath_with_multiple_args(self): - files = resources.files(self.data) - binfile = files.joinpath('subdirectory', 'binary.file') - self.assertTrue(binfile.is_file()) - - def test_old_parameter(self): - """ - Files used to take a 'package' parameter. Make sure anyone - passing by name is still supported. - """ - with suppress_known_deprecation(): - resources.files(package=self.data) - - -class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase): - pass - - -class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase): - pass - - -class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase): - MODULE = 'namespacedata01' - - def test_non_paths_in_dunder_path(self): - """ - Non-path items in a namespace package's ``__path__`` are ignored. - - As reported in python/importlib_resources#311, some tools - like Setuptools, when creating editable packages, will inject - non-paths into a namespace package's ``__path__``, a - sentinel like - ``__editable__.sample_namespace-1.0.finder.__path_hook__`` - to cause the ``PathEntryFinder`` to be called when searching - for packages. In that case, resources should still be loadable. - """ - import namespacedata01 # type: ignore[import-not-found] - - namespacedata01.__path__.append( - '__editable__.sample_namespace-1.0.finder.__path_hook__' - ) - - resources.files(namespacedata01) - - -class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase): - ZIP_MODULE = 'namespacedata01' - - -class DirectSpec: - """ - Override behavior of ModuleSetup to write a full spec directly. - """ - - MODULE = 'unused' - - def load_fixture(self, name): - self.tree_on_path(self.spec) - - -class ModulesFiles: - spec = { - 'mod.py': '', - 'res.txt': 'resources are the best', - } - - def test_module_resources(self): - """ - A module can have resources found adjacent to the module. - """ - import mod # type: ignore[import-not-found] - - actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8') - assert actual == self.spec['res.txt'] - - -class ModuleFilesDiskTests(DirectSpec, util.DiskSetup, ModulesFiles, unittest.TestCase): - pass - - -class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.TestCase): - pass - - -class ImplicitContextFiles: - set_val = textwrap.dedent( - f""" - import {resources.__name__} as res - val = res.files().joinpath('res.txt').read_text(encoding='utf-8') - """ - ) - spec = { - 'somepkg': { - '__init__.py': set_val, - 'submod.py': set_val, - 'res.txt': 'resources are the best', - }, - 'frozenpkg': { - '__init__.py': set_val.replace(resources.__name__, 'c_resources'), - 'res.txt': 'resources are the best', - }, - } - - def test_implicit_files_package(self): - """ - Without any parameter, files() will infer the location as the caller. - """ - assert importlib.import_module('somepkg').val == 'resources are the best' - - def test_implicit_files_submodule(self): - """ - Without any parameter, files() will infer the location as the caller. - """ - assert importlib.import_module('somepkg.submod').val == 'resources are the best' - - def _compile_importlib(self): - """ - Make a compiled-only copy of the importlib resources package. - - Currently only code is copied, as importlib resources doesn't itself - have any resources. - """ - bin_site = self.fixtures.enter_context(os_helper.temp_dir()) - c_resources = pathlib.Path(bin_site, 'c_resources') - sources = pathlib.Path(resources.__file__).parent - - for source_path in sources.glob('**/*.py'): - c_path = c_resources.joinpath(source_path.relative_to(sources)).with_suffix( - '.pyc' - ) - py_compile.compile(source_path, c_path) - self.fixtures.enter_context(import_helper.DirsOnSysPath(bin_site)) - - def test_implicit_files_with_compiled_importlib(self): - """ - Caller detection works for compiled-only resources module. - - python/cpython#123085 - """ - self._compile_importlib() - assert importlib.import_module('frozenpkg').val == 'resources are the best' - - -class ImplicitContextFilesDiskTests( - DirectSpec, util.DiskSetup, ImplicitContextFiles, unittest.TestCase -): - pass - - -class ImplicitContextFilesZipTests( - DirectSpec, util.ZipSetup, ImplicitContextFiles, unittest.TestCase -): - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/importlib_resources/tests/test_functional.py b/importlib_resources/tests/test_functional.py deleted file mode 100644 index 9eb2d815..00000000 --- a/importlib_resources/tests/test_functional.py +++ /dev/null @@ -1,267 +0,0 @@ -import importlib -import os -import unittest - -import importlib_resources as resources - -from . import util -from .compat.py39 import warnings_helper - - -class StringAnchorMixin: - anchor01 = 'data01' - anchor02 = 'data02' - - -class ModuleAnchorMixin: - @property - def anchor01(self): - return importlib.import_module('data01') - - @property - def anchor02(self): - return importlib.import_module('data02') - - -class FunctionalAPIBase: - def setUp(self): - super().setUp() - self.load_fixture('data02') - - def _gen_resourcetxt_path_parts(self): - """Yield various names of a text file in anchor02, each in a subTest""" - for path_parts in ( - ('subdirectory', 'subsubdir', 'resource.txt'), - ('subdirectory/subsubdir/resource.txt',), - ('subdirectory/subsubdir', 'resource.txt'), - ): - with self.subTest(path_parts=path_parts): - yield path_parts - - def assertEndsWith(self, string, suffix): - """Assert that `string` ends with `suffix`. - - Used to ignore an architecture-specific UTF-16 byte-order mark.""" - self.assertEqual(string[-len(suffix) :], suffix) - - def test_read_text(self): - self.assertEqual( - resources.read_text(self.anchor01, 'utf-8.file'), - 'Hello, UTF-8 world!\n', - ) - self.assertEqual( - resources.read_text( - self.anchor02, - 'subdirectory', - 'subsubdir', - 'resource.txt', - encoding='utf-8', - ), - 'a resource', - ) - for path_parts in self._gen_resourcetxt_path_parts(): - self.assertEqual( - resources.read_text( - self.anchor02, - *path_parts, - encoding='utf-8', - ), - 'a resource', - ) - # Use generic OSError, since e.g. attempting to read a directory can - # fail with PermissionError rather than IsADirectoryError - with self.assertRaises(OSError): - resources.read_text(self.anchor01) - with self.assertRaises((OSError, resources.abc.TraversalError)): - resources.read_text(self.anchor01, 'no-such-file') - with self.assertRaises(UnicodeDecodeError): - resources.read_text(self.anchor01, 'utf-16.file') - self.assertEqual( - resources.read_text( - self.anchor01, - 'binary.file', - encoding='latin1', - ), - '\x00\x01\x02\x03', - ) - self.assertEndsWith( # ignore the BOM - resources.read_text( - self.anchor01, - 'utf-16.file', - errors='backslashreplace', - ), - 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode( - errors='backslashreplace', - ), - ) - - def test_read_binary(self): - self.assertEqual( - resources.read_binary(self.anchor01, 'utf-8.file'), - b'Hello, UTF-8 world!\n', - ) - for path_parts in self._gen_resourcetxt_path_parts(): - self.assertEqual( - resources.read_binary(self.anchor02, *path_parts), - b'a resource', - ) - - def test_open_text(self): - with resources.open_text(self.anchor01, 'utf-8.file') as f: - self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') - for path_parts in self._gen_resourcetxt_path_parts(): - with resources.open_text( - self.anchor02, - *path_parts, - encoding='utf-8', - ) as f: - self.assertEqual(f.read(), 'a resource') - # Use generic OSError, since e.g. attempting to read a directory can - # fail with PermissionError rather than IsADirectoryError - with self.assertRaises(OSError): - resources.open_text(self.anchor01) - with self.assertRaises((OSError, resources.abc.TraversalError)): - resources.open_text(self.anchor01, 'no-such-file') - with resources.open_text(self.anchor01, 'utf-16.file') as f: - with self.assertRaises(UnicodeDecodeError): - f.read() - with resources.open_text( - self.anchor01, - 'binary.file', - encoding='latin1', - ) as f: - self.assertEqual(f.read(), '\x00\x01\x02\x03') - with resources.open_text( - self.anchor01, - 'utf-16.file', - errors='backslashreplace', - ) as f: - self.assertEndsWith( # ignore the BOM - f.read(), - 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode( - errors='backslashreplace', - ), - ) - - def test_open_binary(self): - with resources.open_binary(self.anchor01, 'utf-8.file') as f: - self.assertEqual(f.read(), b'Hello, UTF-8 world!\n') - for path_parts in self._gen_resourcetxt_path_parts(): - with resources.open_binary( - self.anchor02, - *path_parts, - ) as f: - self.assertEqual(f.read(), b'a resource') - - def test_path(self): - with resources.path(self.anchor01, 'utf-8.file') as path: - with open(str(path), encoding='utf-8') as f: - self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') - with resources.path(self.anchor01) as path: - with open(os.path.join(path, 'utf-8.file'), encoding='utf-8') as f: - self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') - - def test_is_resource(self): - is_resource = resources.is_resource - self.assertTrue(is_resource(self.anchor01, 'utf-8.file')) - self.assertFalse(is_resource(self.anchor01, 'no_such_file')) - self.assertFalse(is_resource(self.anchor01)) - self.assertFalse(is_resource(self.anchor01, 'subdirectory')) - for path_parts in self._gen_resourcetxt_path_parts(): - self.assertTrue(is_resource(self.anchor02, *path_parts)) - - def test_contents(self): - with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)): - c = resources.contents(self.anchor01) - self.assertGreaterEqual( - set(c), - {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}, - ) - with ( - self.assertRaises(OSError), - warnings_helper.check_warnings(( - ".*contents.*", - DeprecationWarning, - )), - ): - list(resources.contents(self.anchor01, 'utf-8.file')) - - for path_parts in self._gen_resourcetxt_path_parts(): - with ( - self.assertRaises((OSError, resources.abc.TraversalError)), - warnings_helper.check_warnings(( - ".*contents.*", - DeprecationWarning, - )), - ): - list(resources.contents(self.anchor01, *path_parts)) - with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)): - c = resources.contents(self.anchor01, 'subdirectory') - self.assertGreaterEqual( - set(c), - {'binary.file'}, - ) - - @warnings_helper.ignore_warnings(category=DeprecationWarning) - def test_common_errors(self): - for func in ( - resources.read_text, - resources.read_binary, - resources.open_text, - resources.open_binary, - resources.path, - resources.is_resource, - resources.contents, - ): - with self.subTest(func=func): - # Rejecting None anchor - with self.assertRaises(TypeError): - func(None) - # Rejecting invalid anchor type - with self.assertRaises((TypeError, AttributeError)): - func(1234) - # Unknown module - with self.assertRaises(ModuleNotFoundError): - func('$missing module$') - - def test_text_errors(self): - for func in ( - resources.read_text, - resources.open_text, - ): - with self.subTest(func=func): - # Multiple path arguments need explicit encoding argument. - with self.assertRaises(TypeError): - func( - self.anchor02, - 'subdirectory', - 'subsubdir', - 'resource.txt', - ) - - -class FunctionalAPITest_StringAnchor_Disk( - StringAnchorMixin, - FunctionalAPIBase, - util.DiskSetup, - unittest.TestCase, -): - pass - - -class FunctionalAPITest_ModuleAnchor_Disk( - ModuleAnchorMixin, - FunctionalAPIBase, - util.DiskSetup, - unittest.TestCase, -): - pass - - -class FunctionalAPITest_StringAnchor_Memory( - StringAnchorMixin, - FunctionalAPIBase, - util.MemorySetup, - unittest.TestCase, -): - pass diff --git a/importlib_resources/tests/test_open.py b/importlib_resources/tests/test_open.py deleted file mode 100644 index 8a4b68e3..00000000 --- a/importlib_resources/tests/test_open.py +++ /dev/null @@ -1,85 +0,0 @@ -import unittest - -import importlib_resources as resources - -from . import util - - -class CommonBinaryTests(util.CommonTests, unittest.TestCase): - def execute(self, package, path): - target = resources.files(package).joinpath(path) - with target.open('rb'): - pass - - -class CommonTextTests(util.CommonTests, unittest.TestCase): - def execute(self, package, path): - target = resources.files(package).joinpath(path) - with target.open(encoding='utf-8'): - pass - - -class OpenTests: - def test_open_binary(self): - target = resources.files(self.data) / 'binary.file' - with target.open('rb') as fp: - result = fp.read() - self.assertEqual(result, bytes(range(4))) - - def test_open_text_default_encoding(self): - target = resources.files(self.data) / 'utf-8.file' - with target.open(encoding='utf-8') as fp: - result = fp.read() - self.assertEqual(result, 'Hello, UTF-8 world!\n') - - def test_open_text_given_encoding(self): - target = resources.files(self.data) / 'utf-16.file' - with target.open(encoding='utf-16', errors='strict') as fp: - result = fp.read() - self.assertEqual(result, 'Hello, UTF-16 world!\n') - - def test_open_text_with_errors(self): - """ - Raises UnicodeError without the 'errors' argument. - """ - target = resources.files(self.data) / 'utf-16.file' - with target.open(encoding='utf-8', errors='strict') as fp: - self.assertRaises(UnicodeError, fp.read) - with target.open(encoding='utf-8', errors='ignore') as fp: - result = fp.read() - self.assertEqual( - result, - 'H\x00e\x00l\x00l\x00o\x00,\x00 ' - '\x00U\x00T\x00F\x00-\x001\x006\x00 ' - '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', - ) - - def test_open_binary_FileNotFoundError(self): - target = resources.files(self.data) / 'does-not-exist' - with self.assertRaises(FileNotFoundError): - target.open('rb') - - def test_open_text_FileNotFoundError(self): - target = resources.files(self.data) / 'does-not-exist' - with self.assertRaises(FileNotFoundError): - target.open(encoding='utf-8') - - -class OpenDiskTests(OpenTests, util.DiskSetup, unittest.TestCase): - pass - - -class OpenDiskNamespaceTests(OpenTests, util.DiskSetup, unittest.TestCase): - MODULE = 'namespacedata01' - - -class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase): - pass - - -class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase): - MODULE = 'namespacedata01' - - -if __name__ == '__main__': - unittest.main() diff --git a/importlib_resources/tests/test_path.py b/importlib_resources/tests/test_path.py deleted file mode 100644 index 0be673d2..00000000 --- a/importlib_resources/tests/test_path.py +++ /dev/null @@ -1,63 +0,0 @@ -import io -import pathlib -import unittest - -import importlib_resources as resources - -from . import util - - -class CommonTests(util.CommonTests, unittest.TestCase): - def execute(self, package, path): - with resources.as_file(resources.files(package).joinpath(path)): - pass - - -class PathTests: - def test_reading(self): - """ - Path should be readable and a pathlib.Path instance. - """ - target = resources.files(self.data) / 'utf-8.file' - with resources.as_file(target) as path: - self.assertIsInstance(path, pathlib.Path) - self.assertTrue(path.name.endswith("utf-8.file"), repr(path)) - self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8')) - - -class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase): - def test_natural_path(self): - """ - Guarantee the internal implementation detail that - file-system-backed resources do not get the tempdir - treatment. - """ - target = resources.files(self.data) / 'utf-8.file' - with resources.as_file(target) as path: - assert 'data' in str(path) - - -class PathMemoryTests(PathTests, unittest.TestCase): - def setUp(self): - file = io.BytesIO(b'Hello, UTF-8 world!\n') - self.addCleanup(file.close) - self.data = util.create_package( - file=file, path=FileNotFoundError("package exists only in memory") - ) - self.data.__spec__.origin = None - self.data.__spec__.has_location = False - - -class PathZipTests(PathTests, util.ZipSetup, unittest.TestCase): - def test_remove_in_context_manager(self): - """ - It is not an error if the file that was temporarily stashed on the - file system is removed inside the `with` stanza. - """ - target = resources.files(self.data) / 'utf-8.file' - with resources.as_file(target) as path: - path.unlink() - - -if __name__ == '__main__': - unittest.main() diff --git a/importlib_resources/tests/test_read.py b/importlib_resources/tests/test_read.py deleted file mode 100644 index 216c8feb..00000000 --- a/importlib_resources/tests/test_read.py +++ /dev/null @@ -1,94 +0,0 @@ -import unittest -from importlib import import_module - -import importlib_resources as resources - -from . import util - - -class CommonBinaryTests(util.CommonTests, unittest.TestCase): - def execute(self, package, path): - resources.files(package).joinpath(path).read_bytes() - - -class CommonTextTests(util.CommonTests, unittest.TestCase): - def execute(self, package, path): - resources.files(package).joinpath(path).read_text(encoding='utf-8') - - -class ReadTests: - def test_read_bytes(self): - result = resources.files(self.data).joinpath('binary.file').read_bytes() - self.assertEqual(result, bytes(range(4))) - - def test_read_text_default_encoding(self): - result = ( - resources.files(self.data) - .joinpath('utf-8.file') - .read_text(encoding='utf-8') - ) - self.assertEqual(result, 'Hello, UTF-8 world!\n') - - def test_read_text_given_encoding(self): - result = ( - resources.files(self.data) - .joinpath('utf-16.file') - .read_text(encoding='utf-16') - ) - self.assertEqual(result, 'Hello, UTF-16 world!\n') - - def test_read_text_with_errors(self): - """ - Raises UnicodeError without the 'errors' argument. - """ - target = resources.files(self.data) / 'utf-16.file' - self.assertRaises(UnicodeError, target.read_text, encoding='utf-8') - result = target.read_text(encoding='utf-8', errors='ignore') - self.assertEqual( - result, - 'H\x00e\x00l\x00l\x00o\x00,\x00 ' - '\x00U\x00T\x00F\x00-\x001\x006\x00 ' - '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', - ) - - -class ReadDiskTests(ReadTests, util.DiskSetup, unittest.TestCase): - pass - - -class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase): - def test_read_submodule_resource(self): - submodule = import_module('data01.subdirectory') - result = resources.files(submodule).joinpath('binary.file').read_bytes() - self.assertEqual(result, bytes(range(4, 8))) - - def test_read_submodule_resource_by_name(self): - result = ( - resources.files('data01.subdirectory').joinpath('binary.file').read_bytes() - ) - self.assertEqual(result, bytes(range(4, 8))) - - -class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase): - MODULE = 'namespacedata01' - - -class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase): - MODULE = 'namespacedata01' - - def test_read_submodule_resource(self): - submodule = import_module('namespacedata01.subdirectory') - result = resources.files(submodule).joinpath('binary.file').read_bytes() - self.assertEqual(result, bytes(range(12, 16))) - - def test_read_submodule_resource_by_name(self): - result = ( - resources.files('namespacedata01.subdirectory') - .joinpath('binary.file') - .read_bytes() - ) - self.assertEqual(result, bytes(range(12, 16))) - - -if __name__ == '__main__': - unittest.main() diff --git a/importlib_resources/tests/test_reader.py b/importlib_resources/tests/test_reader.py deleted file mode 100644 index f8cfd8de..00000000 --- a/importlib_resources/tests/test_reader.py +++ /dev/null @@ -1,137 +0,0 @@ -import os.path -import pathlib -import unittest -from importlib import import_module - -from importlib_resources.readers import MultiplexedPath, NamespaceReader - -from . import util - - -class MultiplexedPathTest(util.DiskSetup, unittest.TestCase): - MODULE = 'namespacedata01' - - def setUp(self): - super().setUp() - self.folder = pathlib.Path(self.data.__path__[0]) - self.data01 = pathlib.Path(self.load_fixture('data01').__file__).parent - self.data02 = pathlib.Path(self.load_fixture('data02').__file__).parent - - def test_init_no_paths(self): - with self.assertRaises(FileNotFoundError): - MultiplexedPath() - - def test_init_file(self): - with self.assertRaises(NotADirectoryError): - MultiplexedPath(self.folder / 'binary.file') - - def test_iterdir(self): - contents = {path.name for path in MultiplexedPath(self.folder).iterdir()} - try: - contents.remove('__pycache__') - except (KeyError, ValueError): - pass - self.assertEqual( - contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'} - ) - - def test_iterdir_duplicate(self): - contents = { - path.name for path in MultiplexedPath(self.folder, self.data01).iterdir() - } - for remove in ('__pycache__', '__init__.pyc'): - try: - contents.remove(remove) - except (KeyError, ValueError): - pass - self.assertEqual( - contents, - {'__init__.py', 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file'}, - ) - - def test_is_dir(self): - self.assertEqual(MultiplexedPath(self.folder).is_dir(), True) - - def test_is_file(self): - self.assertEqual(MultiplexedPath(self.folder).is_file(), False) - - def test_open_file(self): - path = MultiplexedPath(self.folder) - with self.assertRaises(FileNotFoundError): - path.read_bytes() - with self.assertRaises(FileNotFoundError): - path.read_text() - with self.assertRaises(FileNotFoundError): - path.open() - - def test_join_path(self): - prefix = str(self.folder.parent) - path = MultiplexedPath(self.folder, self.data01) - self.assertEqual( - str(path.joinpath('binary.file'))[len(prefix) + 1 :], - os.path.join('namespacedata01', 'binary.file'), - ) - sub = path.joinpath('subdirectory') - assert isinstance(sub, MultiplexedPath) - assert 'namespacedata01' in str(sub) - assert 'data01' in str(sub) - self.assertEqual( - str(path.joinpath('imaginary'))[len(prefix) + 1 :], - os.path.join('namespacedata01', 'imaginary'), - ) - self.assertEqual(path.joinpath(), path) - - def test_join_path_compound(self): - path = MultiplexedPath(self.folder) - assert not path.joinpath('imaginary/foo.py').exists() - - def test_join_path_common_subdir(self): - prefix = str(self.data02.parent) - path = MultiplexedPath(self.data01, self.data02) - self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath) - self.assertEqual( - str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :], - os.path.join('data02', 'subdirectory', 'subsubdir'), - ) - - def test_repr(self): - self.assertEqual( - repr(MultiplexedPath(self.folder)), - f"MultiplexedPath('{self.folder}')", - ) - - def test_name(self): - self.assertEqual( - MultiplexedPath(self.folder).name, - os.path.basename(self.folder), - ) - - -class NamespaceReaderTest(util.DiskSetup, unittest.TestCase): - MODULE = 'namespacedata01' - - def test_init_error(self): - with self.assertRaises(ValueError): - NamespaceReader(['path1', 'path2']) - - def test_resource_path(self): - namespacedata01 = import_module('namespacedata01') - reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations) - - root = self.data.__path__[0] - self.assertEqual( - reader.resource_path('binary.file'), os.path.join(root, 'binary.file') - ) - self.assertEqual( - reader.resource_path('imaginary'), os.path.join(root, 'imaginary') - ) - - def test_files(self): - reader = NamespaceReader(self.data.__spec__.submodule_search_locations) - root = self.data.__path__[0] - self.assertIsInstance(reader.files(), MultiplexedPath) - self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')") - - -if __name__ == '__main__': - unittest.main() diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py deleted file mode 100644 index c80afdc7..00000000 --- a/importlib_resources/tests/test_resource.py +++ /dev/null @@ -1,238 +0,0 @@ -import unittest -from importlib import import_module - -import importlib_resources as resources - -from . import util - - -class ResourceTests: - # Subclasses are expected to set the `data` attribute. - - def test_is_file_exists(self): - target = resources.files(self.data) / 'binary.file' - self.assertTrue(target.is_file()) - - def test_is_file_missing(self): - target = resources.files(self.data) / 'not-a-file' - self.assertFalse(target.is_file()) - - def test_is_dir(self): - target = resources.files(self.data) / 'subdirectory' - self.assertFalse(target.is_file()) - self.assertTrue(target.is_dir()) - - -class ResourceDiskTests(ResourceTests, util.DiskSetup, unittest.TestCase): - pass - - -class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase): - pass - - -def names(traversable): - return {item.name for item in traversable.iterdir()} - - -class ResourceLoaderTests(util.DiskSetup, unittest.TestCase): - def test_resource_contents(self): - package = util.create_package( - file=self.data, path=self.data.__file__, contents=['A', 'B', 'C'] - ) - self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'}) - - def test_is_file(self): - package = util.create_package( - file=self.data, - path=self.data.__file__, - contents=['A', 'B', 'C', 'D/E', 'D/F'], - ) - self.assertTrue(resources.files(package).joinpath('B').is_file()) - - def test_is_dir(self): - package = util.create_package( - file=self.data, - path=self.data.__file__, - contents=['A', 'B', 'C', 'D/E', 'D/F'], - ) - self.assertTrue(resources.files(package).joinpath('D').is_dir()) - - def test_resource_missing(self): - package = util.create_package( - file=self.data, - path=self.data.__file__, - contents=['A', 'B', 'C', 'D/E', 'D/F'], - ) - self.assertFalse(resources.files(package).joinpath('Z').is_file()) - - -class ResourceCornerCaseTests(util.DiskSetup, unittest.TestCase): - def test_package_has_no_reader_fallback(self): - """ - Test odd ball packages which: - # 1. Do not have a ResourceReader as a loader - # 2. Are not on the file system - # 3. Are not in a zip file - """ - module = util.create_package( - file=self.data, path=self.data.__file__, contents=['A', 'B', 'C'] - ) - # Give the module a dummy loader. - module.__loader__ = object() - # Give the module a dummy origin. - module.__file__ = '/path/which/shall/not/be/named' - module.__spec__.loader = module.__loader__ - module.__spec__.origin = module.__file__ - self.assertFalse(resources.files(module).joinpath('A').is_file()) - - -class ResourceFromZipsTest01(util.ZipSetup, unittest.TestCase): - def test_is_submodule_resource(self): - submodule = import_module('data01.subdirectory') - self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file()) - - def test_read_submodule_resource_by_name(self): - self.assertTrue( - resources.files('data01.subdirectory').joinpath('binary.file').is_file() - ) - - def test_submodule_contents(self): - submodule = import_module('data01.subdirectory') - self.assertEqual( - names(resources.files(submodule)), {'__init__.py', 'binary.file'} - ) - - def test_submodule_contents_by_name(self): - self.assertEqual( - names(resources.files('data01.subdirectory')), - {'__init__.py', 'binary.file'}, - ) - - def test_as_file_directory(self): - with resources.as_file(resources.files('data01')) as data: - assert data.name == 'data01' - assert data.is_dir() - assert data.joinpath('subdirectory').is_dir() - assert len(list(data.iterdir())) - assert not data.parent.exists() - - -class ResourceFromZipsTest02(util.ZipSetup, unittest.TestCase): - MODULE = 'data02' - - def test_unrelated_contents(self): - """ - Test thata zip with two unrelated subpackages return - distinct resources. Ref python/importlib_resources#44. - """ - self.assertEqual( - names(resources.files('data02.one')), - {'__init__.py', 'resource1.txt'}, - ) - self.assertEqual( - names(resources.files('data02.two')), - {'__init__.py', 'resource2.txt'}, - ) - - -class DeletingZipsTest(util.ZipSetup, unittest.TestCase): - """Having accessed resources in a zip file should not keep an open - reference to the zip. - """ - - def test_iterdir_does_not_keep_open(self): - [item.name for item in resources.files('data01').iterdir()] - - def test_is_file_does_not_keep_open(self): - resources.files('data01').joinpath('binary.file').is_file() - - def test_is_file_failure_does_not_keep_open(self): - resources.files('data01').joinpath('not-present').is_file() - - @unittest.skip("Desired but not supported.") - def test_as_file_does_not_keep_open(self): # pragma: no cover - resources.as_file(resources.files('data01') / 'binary.file') - - def test_entered_path_does_not_keep_open(self): - """ - Mimic what certifi does on import to make its bundle - available for the process duration. - """ - resources.as_file(resources.files('data01') / 'binary.file').__enter__() - - def test_read_binary_does_not_keep_open(self): - resources.files('data01').joinpath('binary.file').read_bytes() - - def test_read_text_does_not_keep_open(self): - resources.files('data01').joinpath('utf-8.file').read_text(encoding='utf-8') - - -class ResourceFromNamespaceTests: - def test_is_submodule_resource(self): - self.assertTrue( - resources.files(import_module('namespacedata01')) - .joinpath('binary.file') - .is_file() - ) - - def test_read_submodule_resource_by_name(self): - self.assertTrue( - resources.files('namespacedata01').joinpath('binary.file').is_file() - ) - - def test_submodule_contents(self): - contents = names(resources.files(import_module('namespacedata01'))) - try: - contents.remove('__pycache__') - except KeyError: - pass - self.assertEqual( - contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'} - ) - - def test_submodule_contents_by_name(self): - contents = names(resources.files('namespacedata01')) - try: - contents.remove('__pycache__') - except KeyError: - pass - self.assertEqual( - contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'} - ) - - def test_submodule_sub_contents(self): - contents = names(resources.files(import_module('namespacedata01.subdirectory'))) - try: - contents.remove('__pycache__') - except KeyError: - pass - self.assertEqual(contents, {'binary.file'}) - - def test_submodule_sub_contents_by_name(self): - contents = names(resources.files('namespacedata01.subdirectory')) - try: - contents.remove('__pycache__') - except KeyError: - pass - self.assertEqual(contents, {'binary.file'}) - - -class ResourceFromNamespaceDiskTests( - util.DiskSetup, - ResourceFromNamespaceTests, - unittest.TestCase, -): - MODULE = 'namespacedata01' - - -class ResourceFromNamespaceZipTests( - util.ZipSetup, - ResourceFromNamespaceTests, - unittest.TestCase, -): - MODULE = 'namespacedata01' - - -if __name__ == '__main__': - unittest.main() diff --git a/importlib_resources/tests/test_util.py b/importlib_resources/tests/test_util.py deleted file mode 100644 index de304b6f..00000000 --- a/importlib_resources/tests/test_util.py +++ /dev/null @@ -1,29 +0,0 @@ -import unittest - -from .util import MemorySetup, Traversable - - -class TestMemoryTraversableImplementation(unittest.TestCase): - def test_concrete_methods_are_not_overridden(self): - """`MemoryTraversable` must not override `Traversable` concrete methods. - - This test is not an attempt to enforce a particular `Traversable` protocol; - it merely catches changes in the `Traversable` abstract/concrete methods - that have not been mirrored in the `MemoryTraversable` subclass. - """ - - traversable_concrete_methods = { - method - for method, value in Traversable.__dict__.items() - if callable(value) and method not in Traversable.__abstractmethods__ - } - memory_traversable_concrete_methods = { - method - for method, value in MemorySetup.MemoryTraversable.__dict__.items() - if callable(value) and not method.startswith("__") - } - overridden_methods = ( - memory_traversable_concrete_methods & traversable_concrete_methods - ) - - assert not overridden_methods diff --git a/importlib_resources/tests/util.py b/importlib_resources/tests/util.py deleted file mode 100644 index 0340c150..00000000 --- a/importlib_resources/tests/util.py +++ /dev/null @@ -1,308 +0,0 @@ -import abc -import contextlib -import functools -import importlib -import io -import pathlib -import sys -import types -from importlib.machinery import ModuleSpec - -from ..abc import ResourceReader, Traversable, TraversableResources -from . import _path -from . import zip as zip_ -from .compat.py39 import import_helper, os_helper - - -class Reader(ResourceReader): - def __init__(self, **kwargs): - vars(self).update(kwargs) - - def get_resource_reader(self, package): - return self - - def open_resource(self, path): - self._path = path - if isinstance(self.file, Exception): - raise self.file - return self.file - - def resource_path(self, path_): - self._path = path_ - if isinstance(self.path, Exception): - raise self.path - return self.path - - def is_resource(self, path_): - self._path = path_ - if isinstance(self.path, Exception): - raise self.path - - def part(entry): - return entry.split('/') - - return any( - len(parts) == 1 and parts[0] == path_ for parts in map(part, self._contents) - ) - - def contents(self): - if isinstance(self.path, Exception): - raise self.path - yield from self._contents - - -def create_package_from_loader(loader, is_package=True): - name = 'testingpackage' - module = types.ModuleType(name) - spec = ModuleSpec(name, loader, origin='does-not-exist', is_package=is_package) - module.__spec__ = spec - module.__loader__ = loader - return module - - -def create_package(file=None, path=None, is_package=True, contents=()): - return create_package_from_loader( - Reader(file=file, path=path, _contents=contents), - is_package, - ) - - -class CommonTestsBase(metaclass=abc.ABCMeta): - """ - Tests shared by test_open, test_path, and test_read. - """ - - @abc.abstractmethod - def execute(self, package, path): - """ - Call the pertinent legacy API function (e.g. open_text, path) - on package and path. - """ - - def test_package_name(self): - """ - Passing in the package name should succeed. - """ - self.execute(self.data.__name__, 'utf-8.file') - - def test_package_object(self): - """ - Passing in the package itself should succeed. - """ - self.execute(self.data, 'utf-8.file') - - def test_string_path(self): - """ - Passing in a string for the path should succeed. - """ - path = 'utf-8.file' - self.execute(self.data, path) - - def test_pathlib_path(self): - """ - Passing in a pathlib.PurePath object for the path should succeed. - """ - path = pathlib.PurePath('utf-8.file') - self.execute(self.data, path) - - def test_importing_module_as_side_effect(self): - """ - The anchor package can already be imported. - """ - del sys.modules[self.data.__name__] - self.execute(self.data.__name__, 'utf-8.file') - - def test_missing_path(self): - """ - Attempting to open or read or request the path for a - non-existent path should succeed if open_resource - can return a viable data stream. - """ - bytes_data = io.BytesIO(b'Hello, world!') - package = create_package(file=bytes_data, path=FileNotFoundError()) - self.execute(package, 'utf-8.file') - self.assertEqual(package.__loader__._path, 'utf-8.file') - - def test_extant_path(self): - # Attempting to open or read or request the path when the - # path does exist should still succeed. Does not assert - # anything about the result. - bytes_data = io.BytesIO(b'Hello, world!') - # any path that exists - path = __file__ - package = create_package(file=bytes_data, path=path) - self.execute(package, 'utf-8.file') - self.assertEqual(package.__loader__._path, 'utf-8.file') - - def test_useless_loader(self): - package = create_package(file=FileNotFoundError(), path=FileNotFoundError()) - with self.assertRaises(FileNotFoundError): - self.execute(package, 'utf-8.file') - - -fixtures = dict( - data01={ - '__init__.py': '', - 'binary.file': bytes(range(4)), - 'utf-16.file': '\ufeffHello, UTF-16 world!\n'.encode('utf-16-le'), - 'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'), - 'subdirectory': { - '__init__.py': '', - 'binary.file': bytes(range(4, 8)), - }, - }, - data02={ - '__init__.py': '', - 'one': {'__init__.py': '', 'resource1.txt': 'one resource'}, - 'two': {'__init__.py': '', 'resource2.txt': 'two resource'}, - 'subdirectory': {'subsubdir': {'resource.txt': 'a resource'}}, - }, - namespacedata01={ - 'binary.file': bytes(range(4)), - 'utf-16.file': '\ufeffHello, UTF-16 world!\n'.encode('utf-16-le'), - 'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'), - 'subdirectory': { - 'binary.file': bytes(range(12, 16)), - }, - }, -) - - -class ModuleSetup: - def setUp(self): - self.fixtures = contextlib.ExitStack() - self.addCleanup(self.fixtures.close) - - self.fixtures.enter_context(import_helper.isolated_modules()) - self.data = self.load_fixture(self.MODULE) - - def load_fixture(self, module): - self.tree_on_path({module: fixtures[module]}) - return importlib.import_module(module) - - -class ZipSetup(ModuleSetup): - MODULE = 'data01' - - def tree_on_path(self, spec): - temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) - modules = pathlib.Path(temp_dir) / 'zipped modules.zip' - self.fixtures.enter_context( - import_helper.DirsOnSysPath(str(zip_.make_zip_file(spec, modules))) - ) - - -class DiskSetup(ModuleSetup): - MODULE = 'data01' - - def tree_on_path(self, spec): - temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) - _path.build(spec, pathlib.Path(temp_dir)) - self.fixtures.enter_context(import_helper.DirsOnSysPath(temp_dir)) - - -class MemorySetup(ModuleSetup): - """Support loading a module in memory.""" - - MODULE = 'data01' - - def load_fixture(self, module): - self.fixtures.enter_context(self.augment_sys_metapath(module)) - return importlib.import_module(module) - - @contextlib.contextmanager - def augment_sys_metapath(self, module): - finder_instance = self.MemoryFinder(module) - sys.meta_path.append(finder_instance) - yield - sys.meta_path.remove(finder_instance) - - class MemoryFinder(importlib.abc.MetaPathFinder): - def __init__(self, module): - self._module = module - - def find_spec(self, fullname, path, target=None): - if fullname != self._module: - return None - - return importlib.machinery.ModuleSpec( - name=fullname, - loader=MemorySetup.MemoryLoader(self._module), - is_package=True, - ) - - class MemoryLoader(importlib.abc.Loader): - def __init__(self, module): - self._module = module - - def exec_module(self, module): - pass - - def get_resource_reader(self, fullname): - return MemorySetup.MemoryTraversableResources(self._module, fullname) - - class MemoryTraversableResources(TraversableResources): - def __init__(self, module, fullname): - self._module = module - self._fullname = fullname - - def files(self): - return MemorySetup.MemoryTraversable(self._module, self._fullname) - - class MemoryTraversable(Traversable): - """Implement only the abstract methods of `Traversable`. - - Besides `.__init__()`, no other methods may be implemented or overridden. - This is critical for validating the concrete `Traversable` implementations. - """ - - def __init__(self, module, fullname): - self._module = module - self._fullname = fullname - - def _resolve(self): - """ - Fully traverse the `fixtures` dictionary. - - This should be wrapped in a `try/except KeyError` - but it is not currently needed and lowers the code coverage numbers. - """ - path = pathlib.PurePosixPath(self._fullname) - return functools.reduce(lambda d, p: d[p], path.parts, fixtures) - - def iterdir(self): - directory = self._resolve() - if not isinstance(directory, dict): - # Filesystem openers raise OSError, and that exception is mirrored here. - raise OSError(f"{self._fullname} is not a directory") - for path in directory: - yield MemorySetup.MemoryTraversable( - self._module, f"{self._fullname}/{path}" - ) - - def is_dir(self) -> bool: - return isinstance(self._resolve(), dict) - - def is_file(self) -> bool: - return not self.is_dir() - - def open(self, mode='r', encoding=None, errors=None, *_, **__): - contents = self._resolve() - if isinstance(contents, dict): - # Filesystem openers raise OSError when attempting to open a directory, - # and that exception is mirrored here. - raise OSError(f"{self._fullname} is a directory") - if isinstance(contents, str): - contents = contents.encode("utf-8") - result = io.BytesIO(contents) - if "b" in mode: - return result - return io.TextIOWrapper(result, encoding=encoding, errors=errors) - - @property - def name(self): - return pathlib.PurePosixPath(self._fullname).name - - -class CommonTests(DiskSetup, CommonTestsBase): - pass diff --git a/importlib_resources/tests/zip.py b/importlib_resources/tests/zip.py deleted file mode 100755 index 51ee5648..00000000 --- a/importlib_resources/tests/zip.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Generate zip test data files. -""" - -import zipfile - -import zipp - - -def make_zip_file(tree, dst): - """ - Zip the files in tree into a new zipfile at dst. - """ - with zipfile.ZipFile(dst, 'w') as zf: - for name, contents in walk(tree): - zf.writestr(name, contents) - zipp.CompleteDirs.inject(zf) - return dst - - -def walk(tree, prefix=''): - for name, contents in tree.items(): - if isinstance(contents, dict): - yield from walk(contents, prefix=f'{prefix}{name}/') - else: - yield f'{prefix}{name}', contents diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index d8f1411c..00000000 --- a/mypy.ini +++ /dev/null @@ -1,23 +0,0 @@ -[mypy] -# Is the project well-typed? -strict = False - -# Early opt-in even when strict = False -warn_unused_ignores = True -warn_redundant_casts = True -enable_error_code = ignore-without-code - -# Support namespace packages per https://github.com/python/mypy/issues/14057 -explicit_package_bases = True - -disable_error_code = - # Disable due to many false positives - overload-overlap, - -# jaraco/zipp#123 -[mypy-zipp] -ignore_missing_imports = True - -# jaraco/jaraco.test#7 -[mypy-jaraco.test.*] -ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 312957fd..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,81 +0,0 @@ -[build-system] -requires = [ - "setuptools>=77", - "setuptools_scm[toml]>=3.4.1", - # jaraco/skeleton#174 - "coherent.licensed", -] -build-backend = "setuptools.build_meta" - -[project] -name = "importlib_resources" -authors = [ - { name = "Barry Warsaw", email = "barry@python.org" }, -] -maintainers = [ - { name = "Jason R. Coombs", email = "jaraco@jaraco.com" }, -] -description = "Read resources from Python packages" -readme = "README.rst" -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", -] -requires-python = ">=3.9" -license = "Apache-2.0" -dependencies = [ - "zipp >= 3.1.0; python_version < '3.10'", -] -dynamic = ["version"] - -[project.urls] -Source = "https://github.com/python/importlib_resources" - -[project.optional-dependencies] -test = [ - # upstream - "pytest >= 6, != 8.1.*", - - # local - "zipp >= 3.17", - "jaraco.test >= 5.4", -] - -doc = [ - # upstream - "sphinx >= 3.5", - "jaraco.packaging >= 9.3", - "rst.linker >= 1.9", - "furo", - "sphinx-lint", - - # tidelift - "jaraco.tidelift >= 1.4", - - # local -] - -check = [ - "pytest-checkdocs >= 2.4", - "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", -] - -cover = [ - "pytest-cov", -] - -enabler = [ - "pytest-enabler >= 2.2", -] - -type = [ - # upstream - "pytest-mypy", - - # local -] - - -[tool.setuptools_scm] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 9a0f3bce..00000000 --- a/pytest.ini +++ /dev/null @@ -1,25 +0,0 @@ -[pytest] -norecursedirs=dist build .tox .eggs -addopts= - --doctest-modules - --import-mode importlib -consider_namespace_packages=true -filterwarnings= - ## upstream - - # Ensure ResourceWarnings are emitted - default::ResourceWarning - - # realpython/pytest-mypy#152 - ignore:'encoding' argument not specified::pytest_mypy - - # python/cpython#100750 - ignore:'encoding' argument not specified::platform - - # pypa/build#615 - ignore:'encoding' argument not specified::build.env - - # dateutil/dateutil#1284 - ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:dateutil.tz.tz - - ## end upstream diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index 63c0825f..00000000 --- a/ruff.toml +++ /dev/null @@ -1,51 +0,0 @@ -[lint] -extend-select = [ - # upstream - - "C901", # complex-structure - "I", # isort - "PERF401", # manual-list-comprehension - - # Ensure modern type annotation syntax and best practices - # Not including those covered by type-checkers or exclusive to Python 3.11+ - "FA", # flake8-future-annotations - "F404", # late-future-import - "PYI", # flake8-pyi - "UP006", # non-pep585-annotation - "UP007", # non-pep604-annotation - "UP010", # unnecessary-future-import - "UP035", # deprecated-import - "UP037", # quoted-annotation - "UP043", # unnecessary-default-type-args - - # local -] -ignore = [ - # upstream - - # Typeshed rejects complex or non-literal defaults for maintenance and testing reasons, - # irrelevant to this project. - "PYI011", # typed-argument-default-in-stub - # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules - "W191", - "E111", - "E114", - "E117", - "D206", - "D300", - "Q000", - "Q001", - "Q002", - "Q003", - "COM812", - "COM819", - - # local -] - -[format] -# Enable preview to get hugged parenthesis unwrapping and other nice surprises -# See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373 -preview = true -# https://docs.astral.sh/ruff/settings/#format_quote-style -quote-style = "preserve" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..ec2bf6f8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,10 @@ +[metadata] +name = test + +[options] +python_requires = >= 3.8 +packages = test +install_requires = + hydra-core==1.1.0 + hydra-joblib-launcher==1.1.5 + importlib_resources==5.8.0 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..bac24a43 --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +import setuptools + +if __name__ == "__main__": + setuptools.setup() diff --git a/importlib_resources/compat/__init__.py b/test/__init__.py similarity index 100% rename from importlib_resources/compat/__init__.py rename to test/__init__.py diff --git a/importlib_resources/future/__init__.py b/test/config/__init__.py similarity index 100% rename from importlib_resources/future/__init__.py rename to test/config/__init__.py diff --git a/test/config/run.yaml b/test/config/run.yaml new file mode 100644 index 00000000..e75235df --- /dev/null +++ b/test/config/run.yaml @@ -0,0 +1,2 @@ +defaults: + - override hydra/launcher: joblib \ No newline at end of file diff --git a/test/test.py b/test/test.py new file mode 100644 index 00000000..01cec9c2 --- /dev/null +++ b/test/test.py @@ -0,0 +1,8 @@ +import hydra + +@hydra.main(config_path='.', config_name='run') +def test(cfg): + pass + +if __name__ == "__main__": + test() \ No newline at end of file diff --git a/towncrier.toml b/towncrier.toml deleted file mode 100644 index 6fa480e4..00000000 --- a/towncrier.toml +++ /dev/null @@ -1,2 +0,0 @@ -[tool.towncrier] -title_format = "{version}" diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 14243051..00000000 --- a/tox.ini +++ /dev/null @@ -1,63 +0,0 @@ -[testenv] -description = perform primary checks (tests, style, types, coverage) -deps = -setenv = - PYTHONWARNDEFAULTENCODING = 1 -commands = - pytest {posargs} -usedevelop = True -extras = - test - check - cover - enabler - type - -[testenv:diffcov] -description = run tests and check that diff from main is covered -deps = - {[testenv]deps} - diff-cover -commands = - pytest {posargs} --cov-report xml - diff-cover coverage.xml --compare-branch=origin/main --html-report diffcov.html - diff-cover coverage.xml --compare-branch=origin/main --fail-under=100 - -[testenv:docs] -description = build the documentation -extras = - doc - test -changedir = docs -commands = - python -m sphinx -W --keep-going . {toxinidir}/build/html - python -m sphinxlint - -[testenv:finalize] -description = assemble changelog and tag a release -skip_install = True -deps = - towncrier - jaraco.develop >= 7.23 -pass_env = * -commands = - python -m jaraco.develop.finalize - - -[testenv:release] -description = publish the package to PyPI and GitHub -skip_install = True -deps = - build - twine>=3 - jaraco.develop>=7.1 -pass_env = - TWINE_PASSWORD - GITHUB_TOKEN -setenv = - TWINE_USERNAME = {env:TWINE_USERNAME:__token__} -commands = - python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" - python -m build - python -m twine upload dist/* - python -m jaraco.develop.create-github-release