diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 49fe2faf..00000000 --- a/.coveragerc +++ /dev/null @@ -1,11 +0,0 @@ -[run] -omit = - # leading `*/` for pytest-dev/pytest-cov#456 - */.tox/* - tests/* - prepare/* - */_itertools.py - exercises.py - -[report] -show_missing = True diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 6385b573..00000000 --- a/.editorconfig +++ /dev/null @@ -1,15 +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 - -[*.{yml,yaml}] -indent_style = space -indent_size = 2 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 542d2986..00000000 --- a/.flake8 +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -max-line-length = 88 - -# jaraco/skeleton#34 -max-complexity = 10 - -extend-ignore = - # Black creates whitespace before colon - E203 -enable-extensions = U4 diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 89ff3396..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "daily" - allow: - - dependency-type: "all" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index e19dccf0..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: tests - -on: [push, pull_request] - -jobs: - test: - strategy: - matrix: - python: - - 3.6 - - 3.9 - - 3.10.0-alpha - 3.10.99 - platform: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v2 - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - name: Install tox - run: | - python -m pip install tox - - name: Run tests - run: tox - - diffcov: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: Install tox - run: | - python -m pip install tox - - name: Evaluate coverage - run: tox - env: - TOXENV: diffcov - - release: - needs: test - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: Install tox - run: | - python -m pip install tox - - name: Release - 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 ae864d61..00000000 --- a/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -build -/coverage.xml -/diffcov.html -htmlcov -importlib_metadata.egg-info -.mypy_cache -/.coverage -/.DS_Store -artifacts -.eggs -.doctrees -dist -pip-wheel-metadata diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index c15ab0c9..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,10 +0,0 @@ -repos: -- repo: https://github.com/psf/black - rev: 20.8b1 - hooks: - - id: black - -- repo: https://github.com/asottile/blacken-docs - rev: v1.9.1 - hooks: - - id: blacken-docs diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index cc698548..00000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -python: - install: - - path: . - extra_requirements: - - docs diff --git a/CHANGES.rst b/CHANGES.rst deleted file mode 100644 index d4e66469..00000000 --- a/CHANGES.rst +++ /dev/null @@ -1,615 +0,0 @@ -v4.6.3 -====== - -* Moved workaround for #327 to ``_compat`` module. - -v4.6.2 -====== - -* bpo-44784: Avoid errors in test suite when - DeprecationWarnings are treated as errors. - -v4.6.1 -====== - -* #327: Deprecation warnings now honor call stack variance - on PyPy. - -v4.6.0 -====== - -* #326: Performance tests now rely on - `pytest-perf `_. - To disable these tests, which require network access - and a git checkout, pass ``-p no:perf`` to pytest. - -v4.5.0 -====== - -* #319: Remove ``SelectableGroups`` deprecation exception - for flake8. - -v4.4.0 -====== - -* #300: Restore compatibility in the result from - ``Distribution.entry_points`` (``EntryPoints``) to honor - expectations in older implementations and issuing - deprecation warnings for these cases: - - - ``EntryPoints`` objects are once again mutable, allowing - for ``sort()`` and other list-based mutation operations. - Avoid deprecation warnings by casting to a - mutable sequence (e.g. - ``list(dist.entry_points).sort()``). - - - ``EntryPoints`` results once again allow - for access by index. To avoid deprecation warnings, - cast the result to a Sequence first - (e.g. ``tuple(dist.entry_points)[0]``). - -v4.3.1 -====== - -* #320: Fix issue where normalized name for eggs was - incorrectly solicited, leading to metadata being - unavailable for eggs. - -v4.3.0 -====== - -* #317: De-duplication of distributions no longer requires - loading the full metadata for ``PathDistribution`` objects, - entry point loading performance by ~10x. - -v4.2.0 -====== - -* Prefer f-strings to ``.format`` calls. - -v4.1.0 -====== - -* #312: Add support for metadata 2.2 (``Dynamic`` field). - -* #315: Add ``SimplePath`` protocol for interface clarity - in ``PathDistribution``. - -v4.0.1 -====== - -* #306: Clearer guidance about compatibility in readme. - -v4.0.0 -====== - -* #304: ``PackageMetadata`` as returned by ``metadata()`` - and ``Distribution.metadata()`` now provides normalized - metadata honoring PEP 566: - - - If a long description is provided in the payload of the - RFC 822 value, it can be retrieved as the ``Description`` - field. - - Any multi-line values in the metadata will be returned as - such. - - For any multi-line values, line continuation characters - are removed. This backward-incompatible change means - that any projects relying on the RFC 822 line continuation - characters being present must be tolerant to them having - been removed. - - Add a ``json`` property that provides the metadata - converted to a JSON-compatible form per PEP 566. - - -v3.10.1 -======= - -* Minor tweaks from CPython. - -v3.10.0 -======= - -* #295: Internal refactoring to unify section parsing logic. - -v3.9.1 -====== - -* #296: Exclude 'prepare' package. -* #297: Fix ValueError when entry points contains comments. - -v3.9.0 -====== - -* Use of Mapping (dict) interfaces on ``SelectableGroups`` - is now flagged as deprecated. Instead, users are advised - to use the select interface for future compatibility. - - Suppress the warning with this filter: - ``ignore:SelectableGroups dict interface``. - - Or with this invocation in the Python environment: - ``warnings.filterwarnings('ignore', 'SelectableGroups dict interface')``. - - Preferably, switch to the ``select`` interface introduced - in 3.7.0. See the - `entry points documentation `_ and changelog for the 3.6 - release below for more detail. - - For some use-cases, especially those that rely on - ``importlib.metadata`` in Python 3.8 and 3.9 or - those relying on older ``importlib_metadata`` (especially - on Python 3.5 and earlier), - `backports.entry_points_selectable `_ - was created to ease the transition. Please have a look - at that project if simply relying on importlib_metadata 3.6+ - is not straightforward. Background in #298. - -* #283: Entry point parsing no longer relies on ConfigParser - and instead uses a custom, one-pass parser to load the - config, resulting in a ~20% performance improvement when - loading entry points. - -v3.8.2 -====== - -* #293: Re-enabled lazy evaluation of path lookup through - a FreezableDefaultDict. - -v3.8.1 -====== - -* #293: Workaround for error in distribution search. - -v3.8.0 -====== - -* #290: Add mtime-based caching for ``FastPath`` and its - lookups, dramatically increasing performance for repeated - distribution lookups. - -v3.7.3 -====== - -* Docs enhancements and cleanup following review in - `GH-24782 `_. - -v3.7.2 -====== - -* Cleaned up cruft in entry_points docstring. - -v3.7.1 -====== - -* Internal refactoring to facilitate ``entry_points() -> dict`` - deprecation. - -v3.7.0 -====== - -* #131: Added ``packages_distributions`` to conveniently - resolve a top-level package or module to its distribution(s). - -v3.6.0 -====== - -* #284: Introduces new ``EntryPoints`` object, a tuple of - ``EntryPoint`` objects but with convenience properties for - selecting and inspecting the results: - - - ``.select()`` accepts ``group`` or ``name`` keyword - parameters and returns a new ``EntryPoints`` tuple - with only those that match the selection. - - ``.groups`` property presents all of the group names. - - ``.names`` property presents the names of the entry points. - - Item access (e.g. ``eps[name]``) retrieves a single - entry point by name. - - ``entry_points`` now accepts "selection parameters", - same as ``EntryPoint.select()``. - - ``entry_points()`` now provides a future-compatible - ``SelectableGroups`` object that supplies the above interface - (except item access) but remains a dict for compatibility. - - In the future, ``entry_points()`` will return an - ``EntryPoints`` object for all entry points. - - If passing selection parameters to ``entry_points``, the - future behavior is invoked and an ``EntryPoints`` is the - result. - -* #284: Construction of entry points using - ``dict([EntryPoint, ...])`` is now deprecated and raises - an appropriate DeprecationWarning and will be removed in - a future version. - -* #300: ``Distribution.entry_points`` now presents as an - ``EntryPoints`` object and access by index is no longer - allowed. If access by index is required, cast to a sequence - first. - -v3.5.0 -====== - -* #280: ``entry_points`` now only returns entry points for - unique distributions (by name). - -v3.4.0 -====== - -* #10: Project now declares itself as being typed. -* #272: Additional performance enhancements to distribution - discovery. -* #111: For PyPA projects, add test ensuring that - ``MetadataPathFinder._search_paths`` honors the needed - interface. Method is still private. - -v3.3.0 -====== - -* #265: ``EntryPoint`` objects now expose a ``.dist`` object - referencing the ``Distribution`` when constructed from a - Distribution. - -v3.2.0 -====== - -* The object returned by ``metadata()`` now has a - formally-defined protocol called ``PackageMetadata`` - with declared support for the ``.get_all()`` method. - Fixes #126. - -v3.1.1 -====== - -v2.1.1 -====== - -* #261: Restored compatibility for package discovery for - metadata without version in the name and for legacy - eggs. - -v3.1.0 -====== - -* Merge with 2.1.0. - -v2.1.0 -====== - -* #253: When querying for package metadata, the lookup - now honors - `package normalization rules `_. - -v3.0.0 -====== - -* Require Python 3.6 or later. - -v2.0.0 -====== - -* ``importlib_metadata`` no longer presents a - ``__version__`` attribute. Consumers wishing to - resolve the version of the package should query it - directly with - ``importlib_metadata.version('importlib-metadata')``. - Closes #71. - -v1.7.0 -====== - -* ``PathNotFoundError`` now has a custom ``__str__`` - mentioning "package metadata" being missing to help - guide users to the cause when the package is installed - but no metadata is present. Closes #124. - -v1.6.1 -====== - -* Added ``Distribution._local()`` as a provisional - demonstration of how to load metadata for a local - package. Implicitly requires that - `pep517 `_ is - installed. Ref #42. -* Ensure inputs to FastPath are Unicode. Closes #121. -* Tests now rely on ``importlib.resources.files`` (and - backport) instead of the older ``path`` function. -* Support any iterable from ``find_distributions``. - Closes #122. - -v1.6.0 -====== - -* Added ``module`` and ``attr`` attributes to ``EntryPoint`` - -v1.5.2 -====== - -* Fix redundant entries from ``FastPath.zip_children``. - Closes #117. - -v1.5.1 -====== - -* Improve reliability and consistency of compatibility - imports for contextlib and pathlib when running tests. - Closes #116. - -v1.5.0 -====== - -* Additional performance optimizations in FastPath now - saves an additional 20% on a typical call. -* Correct for issue where PyOxidizer finder has no - ``__module__`` attribute. Closes #110. - -v1.4.0 -====== - -* Through careful optimization, ``distribution()`` is - 3-4x faster. Thanks to Antony Lee for the - contribution. Closes #95. - -* When searching through ``sys.path``, if any error - occurs attempting to list a path entry, that entry - is skipped, making the system much more lenient - to errors. Closes #94. - -v1.3.0 -====== - -* Improve custom finders documentation. Closes #105. - -v1.2.0 -====== - -* Once again, drop support for Python 3.4. Ref #104. - -v1.1.3 -====== - -* Restored support for Python 3.4 due to improper version - compatibility declarations in the v1.1.0 and v1.1.1 - releases. Closes #104. - -v1.1.2 -====== - -* Repaired project metadata to correctly declare the - ``python_requires`` directive. Closes #103. - -v1.1.1 -====== - -* Fixed ``repr(EntryPoint)`` on PyPy 3 also. Closes #102. - -v1.1.0 -====== - -* Dropped support for Python 3.4. -* EntryPoints are now pickleable. Closes #96. -* Fixed ``repr(EntryPoint)`` on PyPy 2. Closes #97. - -v1.0.0 -====== - -* Project adopts semver for versioning. - -* Removed compatibility shim introduced in 0.23. - -* For better compatibility with the stdlib implementation and to - avoid the same distributions being discovered by the stdlib and - backport implementations, the backport now disables the - stdlib DistributionFinder during initialization (import time). - Closes #91 and closes #100. - -0.23 -==== - -* Added a compatibility shim to prevent failures on beta releases - of Python before the signature changed to accept the - "context" parameter on find_distributions. This workaround - will have a limited lifespan, not to extend beyond release of - Python 3.8 final. - -0.22 -==== - -* Renamed ``package`` parameter to ``distribution_name`` - as `recommended `_ - in the following functions: ``distribution``, ``metadata``, - ``version``, ``files``, and ``requires``. This - backward-incompatible change is expected to have little impact - as these functions are assumed to be primarily used with - positional parameters. - -0.21 -==== - -* ``importlib.metadata`` now exposes the ``DistributionFinder`` - metaclass and references it in the docs for extending the - search algorithm. -* Add ``Distribution.at`` for constructing a Distribution object - from a known metadata directory on the file system. Closes #80. -* Distribution finders now receive a context object that - supplies ``.path`` and ``.name`` properties. This change - introduces a fundamental backward incompatibility for - any projects implementing a ``find_distributions`` method - on a ``MetaPathFinder``. This new layer of abstraction - allows this context to be supplied directly or constructed - on demand and opens the opportunity for a - ``find_distributions`` method to solicit additional - context from the caller. Closes #85. - -0.20 -==== - -* Clarify in the docs that calls to ``.files`` could return - ``None`` when the metadata is not present. Closes #69. -* Return all requirements and not just the first for dist-info - packages. Closes #67. - -0.19 -==== - -* Restrain over-eager egg metadata resolution. -* Add support for entry points with colons in the name. Closes #75. - -0.18 -==== - -* Parse entry points case sensitively. Closes #68 -* Add a version constraint on the backport configparser package. Closes #66 - -0.17 -==== - -* Fix a permission problem in the tests on Windows. - -0.16 -==== - -* Don't crash if there exists an EGG-INFO directory on sys.path. - -0.15 -==== - -* Fix documentation. - -0.14 -==== - -* Removed ``local_distribution`` function from the API. - **This backward-incompatible change removes this - behavior summarily**. Projects should remove their - reliance on this behavior. A replacement behavior is - under review in the `pep517 project - `_. Closes #42. - -0.13 -==== - -* Update docstrings to match PEP 8. Closes #63. -* Merged modules into one module. Closes #62. - -0.12 -==== - -* Add support for eggs. !65; Closes #19. - -0.11 -==== - -* Support generic zip files (not just wheels). Closes #59 -* Support zip files with multiple distributions in them. Closes #60 -* Fully expose the public API in ``importlib_metadata.__all__``. - -0.10 -==== - -* The ``Distribution`` ABC is now officially part of the public API. - Closes #37. -* Fixed support for older single file egg-info formats. Closes #43. -* Fixed a testing bug when ``$CWD`` has spaces in the path. Closes #50. -* Add Python 3.8 to the ``tox`` testing matrix. - -0.9 -=== - -* Fixed issue where entry points without an attribute would raise an - Exception. Closes #40. -* Removed unused ``name`` parameter from ``entry_points()``. Closes #44. -* ``DistributionFinder`` classes must now be instantiated before - being placed on ``sys.meta_path``. - -0.8 -=== - -* This library can now discover/enumerate all installed packages. **This - backward-incompatible change alters the protocol finders must - implement to support distribution package discovery.** Closes #24. -* The signature of ``find_distributions()`` on custom installer finders - should now accept two parameters, ``name`` and ``path`` and - these parameters must supply defaults. -* The ``entry_points()`` method no longer accepts a package name - but instead returns all entry points in a dictionary keyed by the - ``EntryPoint.group``. The ``resolve`` method has been removed. Instead, - call ``EntryPoint.load()``, which has the same semantics as - ``pkg_resources`` and ``entrypoints``. **This is a backward incompatible - change.** -* Metadata is now always returned as Unicode text regardless of - Python version. Closes #29. -* This library can now discover metadata for a 'local' package (found - in the current-working directory). Closes #27. -* Added ``files()`` function for resolving files from a distribution. -* Added a new ``requires()`` function, which returns the requirements - for a package suitable for parsing by - ``packaging.requirements.Requirement``. Closes #18. -* The top-level ``read_text()`` function has been removed. Use - ``PackagePath.read_text()`` on instances returned by the ``files()`` - function. **This is a backward incompatible change.** -* Release dates are now automatically injected into the changelog - based on SCM tags. - -0.7 -=== - -* Fixed issue where packages with dashes in their names would - not be discovered. Closes #21. -* Distribution lookup is now case-insensitive. Closes #20. -* Wheel distributions can no longer be discovered by their module - name. Like Path distributions, they must be indicated by their - distribution package name. - -0.6 -=== - -* Removed ``importlib_metadata.distribution`` function. Now - the public interface is primarily the utility functions exposed - in ``importlib_metadata.__all__``. Closes #14. -* Added two new utility functions ``read_text`` and - ``metadata``. - -0.5 -=== - -* Updated README and removed details about Distribution - class, now considered private. Closes #15. -* Added test suite support for Python 3.4+. -* Fixed SyntaxErrors on Python 3.4 and 3.5. !12 -* Fixed errors on Windows joining Path elements. !15 - -0.4 -=== - -* Housekeeping. - -0.3 -=== - -* Added usage documentation. Closes #8 -* Add support for getting metadata from wheels on ``sys.path``. Closes #9 - -0.2 -=== - -* Added ``importlib_metadata.entry_points()``. Closes #1 -* Added ``importlib_metadata.resolve()``. Closes #12 -* Add support for Python 2.7. Closes #4 - -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/docs/using.rst b/Doc/library/importlib.metadata.rst similarity index 76% rename from docs/using.rst rename to Doc/library/importlib.metadata.rst index 481ff4ee..c4c4a7db 100644 --- a/docs/using.rst +++ b/Doc/library/importlib.metadata.rst @@ -1,15 +1,24 @@ .. _using: ================================= - Using :mod:`!importlib_metadata` + Using :mod:`!importlib.metadata` ================================= -``importlib_metadata`` is a library that provides for access to installed +.. module:: importlib.metadata + :synopsis: The implementation of the importlib metadata. + +.. versionadded:: 3.8 +.. versionchanged:: 3.10 + ``importlib.metadata`` is no longer provisional. + +**Source code:** :source:`Lib/importlib/metadata/__init__.py` + +``importlib.metadata`` is a library that provides for access to installed package metadata. Built in part on Python's import system, this library intends to replace similar functionality in the `entry point API`_ and `metadata API`_ of ``pkg_resources``. Along with :mod:`importlib.resources` in Python 3.7 -and newer (backported as :doc:`importlib_resources ` for older versions of +and newer (backported as `importlib_resources`_ for older versions of Python), this can eliminate the need to use the older and less efficient ``pkg_resources`` package. @@ -28,18 +37,21 @@ Overview Let's say you wanted to get the version string for a package you've installed using ``pip``. We start by creating a virtual environment and installing -something into it:: +something into it: + +.. code-block:: shell-session $ python3 -m venv example $ source example/bin/activate - (example) $ pip install importlib_metadata (example) $ pip install wheel -You can get the version string for ``wheel`` by running the following:: +You can get the version string for ``wheel`` by running the following: + +.. code-block:: pycon (example) $ python - >>> from importlib_metadata import version - >>> version('wheel') + >>> from importlib.metadata import version # doctest: +SKIP + >>> version('wheel') # doctest: +SKIP '0.32.3' You can also get the set of entry points keyed by group, such as @@ -48,7 +60,7 @@ sequence of :ref:`EntryPoint ` objects. You can get the :ref:`metadata for a distribution `:: - >>> list(metadata('wheel')) + >>> list(metadata('wheel')) # doctest: +SKIP ['Metadata-Version', 'Name', 'Version', 'Summary', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', 'Project-URL', 'Project-URL', 'Project-URL', 'Keywords', 'Platform', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Requires-Python', 'Provides-Extra', 'Requires-Dist', 'Requires-Dist'] You can also get a :ref:`distribution's version number `, list its @@ -76,55 +88,55 @@ a ``.load()`` method to resolve the value. There are also ``.module``, Query all entry points:: - >>> eps = entry_points() + >>> eps = entry_points() # doctest: +SKIP The ``entry_points()`` function returns an ``EntryPoints`` object, a sequence of all ``EntryPoint`` objects with ``names`` and ``groups`` attributes for convenience:: - >>> sorted(eps.groups) + >>> sorted(eps.groups) # doctest: +SKIP ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation'] ``EntryPoints`` has a ``select`` method to select entry points matching specific properties. Select entry points in the ``console_scripts`` group:: - >>> scripts = eps.select(group='console_scripts') + >>> scripts = eps.select(group='console_scripts') # doctest: +SKIP Equivalently, since ``entry_points`` passes keyword arguments through to select:: - >>> scripts = entry_points(group='console_scripts') + >>> scripts = entry_points(group='console_scripts') # doctest: +SKIP Pick out a specific script named "wheel" (found in the wheel project):: - >>> 'wheel' in scripts.names + >>> 'wheel' in scripts.names # doctest: +SKIP True - >>> wheel = scripts['wheel'] + >>> wheel = scripts['wheel'] # doctest: +SKIP Equivalently, query for that entry point during selection:: - >>> (wheel,) = entry_points(group='console_scripts', name='wheel') - >>> (wheel,) = entry_points().select(group='console_scripts', name='wheel') + >>> (wheel,) = entry_points(group='console_scripts', name='wheel') # doctest: +SKIP + >>> (wheel,) = entry_points().select(group='console_scripts', name='wheel') # doctest: +SKIP Inspect the resolved entry point:: - >>> wheel + >>> wheel # doctest: +SKIP EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts') - >>> wheel.module + >>> wheel.module # doctest: +SKIP 'wheel.cli' - >>> wheel.attr + >>> wheel.attr # doctest: +SKIP 'main' - >>> wheel.extras + >>> wheel.extras # doctest: +SKIP [] - >>> main = wheel.load() - >>> main + >>> main = wheel.load() # doctest: +SKIP + >>> main # doctest: +SKIP The ``group`` and ``name`` are arbitrary values defined by the package author and usually a client will wish to resolve all entry points for a particular group. Read `the setuptools docs -`_ +`_ for more information on entry points, their definition, and usage. *Compatibility Note* @@ -147,21 +159,28 @@ Distribution metadata Every distribution includes some metadata, which you can extract using the ``metadata()`` function:: - >>> wheel_metadata = metadata('wheel') + >>> wheel_metadata = metadata('wheel') # doctest: +SKIP The keys of the returned data structure, a ``PackageMetadata``, name the metadata keywords, and the values are returned unparsed from the distribution metadata:: - >>> wheel_metadata['Requires-Python'] + >>> wheel_metadata['Requires-Python'] # doctest: +SKIP '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' ``PackageMetadata`` also presents a ``json`` attribute that returns -all the metadata in a JSON-compatible form per PEP 566:: +all the metadata in a JSON-compatible form per :PEP:`566`:: >>> wheel_metadata.json['requires_python'] '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' +.. versionchanged:: 3.10 + The ``Description`` is now included in the metadata when presented + through the payload. Line continuation characters have been removed. + +.. versionadded:: 3.10 + The ``json`` attribute was added. + .. _version: @@ -171,7 +190,7 @@ Distribution versions The ``version()`` function is the quickest way to get a distribution's version number, as a string:: - >>> version('wheel') + >>> version('wheel') # doctest: +SKIP '0.32.3' @@ -186,19 +205,19 @@ files installed by this distribution. Each file object returned is a ``PackagePath``, a :class:`pathlib.PurePath` derived object with additional ``dist``, ``size``, and ``hash`` properties as indicated by the metadata. For example:: - >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0] - >>> util + >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0] # doctest: +SKIP + >>> util # doctest: +SKIP PackagePath('wheel/util.py') - >>> util.size + >>> util.size # doctest: +SKIP 859 - >>> util.dist - - >>> util.hash + >>> util.dist # doctest: +SKIP + + >>> util.hash # doctest: +SKIP Once you have the file, you can also read its contents:: - >>> print(util.read_text()) + >>> print(util.read_text()) # doctest: +SKIP import base64 import sys ... @@ -229,20 +248,23 @@ Distribution requirements To get the full set of requirements for a distribution, use the ``requires()`` function:: - >>> requires('wheel') + >>> requires('wheel') # doctest: +SKIP ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] Package distributions --------------------- -A convience method to resolve the distribution or +A convenience method to resolve the distribution or distributions (in the case of a namespace package) for top-level Python packages or modules:: >>> packages_distributions() {'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...} +.. versionadded:: 3.10 + +.. _distributions: Distributions ============= @@ -252,21 +274,21 @@ of that information from the ``Distribution`` class. A ``Distribution`` is an abstract object that represents the metadata for a Python package. You can get the ``Distribution`` instance:: - >>> from importlib_metadata import distribution - >>> dist = distribution('wheel') + >>> from importlib.metadata import distribution # doctest: +SKIP + >>> dist = distribution('wheel') # doctest: +SKIP Thus, an alternative way to get the version number is through the ``Distribution`` instance:: - >>> dist.version + >>> dist.version # doctest: +SKIP '0.32.3' There are all kinds of additional metadata available on the ``Distribution`` instance:: - >>> dist.metadata['Requires-Python'] + >>> dist.metadata['Requires-Python'] # doctest: +SKIP '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' - >>> dist.metadata['License'] + >>> dist.metadata['License'] # doctest: +SKIP 'MIT' The full set of available metadata is not described here. See :pep:`566` @@ -278,17 +300,17 @@ Extending the search algorithm Because package metadata is not available through :data:`sys.path` searches, or package loaders directly, the metadata for a package is found through import -system `finders`_. To find a distribution package's metadata, +system :ref:`finders `. To find a distribution package's metadata, ``importlib.metadata`` queries the list of :term:`meta path finders ` on :data:`sys.meta_path`. -By default ``importlib_metadata`` installs a finder for distribution packages -found on the file system. This finder doesn't actually find any *packages*, -but it can find the packages' metadata. +The default ``PathFinder`` for Python includes a hook that calls into +``importlib.metadata.MetadataPathFinder`` for finding distributions +loaded from typical file-system-based paths. The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the interface expected of finders by Python's import system. -``importlib_metadata`` extends this protocol by looking for an optional +``importlib.metadata`` extends this protocol by looking for an optional ``find_distributions`` callable on the finders from :data:`sys.meta_path` and presents this extended interface as the ``DistributionFinder`` abstract base class, which defines this abstract @@ -313,7 +335,4 @@ a custom finder, return instances of this derived ``Distribution`` in the .. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points .. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api -.. _`finders`: https://docs.python.org/3/reference/import.html#finders-and-loaders - - -.. rubric:: Footnotes +.. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html diff --git a/LICENSE b/LICENSE deleted file mode 100644 index be7e092b..00000000 --- a/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2017-2019 Jason R. Coombs, Barry Warsaw - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/importlib_metadata/__init__.py b/Lib/importlib/metadata/__init__.py similarity index 91% rename from importlib_metadata/__init__.py rename to Lib/importlib/metadata/__init__.py index 9f5bf347..682067d3 100644 --- a/importlib_metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -3,9 +3,9 @@ import abc import csv import sys -import zipp import email import pathlib +import zipfile import operator import textwrap import warnings @@ -15,14 +15,9 @@ import collections from . import _adapters, _meta +from ._meta import PackageMetadata from ._collections import FreezableDefaultDict, Pair -from ._compat import ( - NullFinder, - PyPy_repr, - install, - pypy_partial, -) -from ._functools import method_cache +from ._functools import method_cache, pass_none from ._itertools import unique_everseen from ._meta import PackageMetadata, SimplePath @@ -127,19 +122,27 @@ def valid(line): class EntryPoint( - PyPy_repr, collections.namedtuple('EntryPointBase', 'name value group') -): + collections.namedtuple('EntryPointBase', 'name value group')): """An entry point as defined by Python packaging conventions. See `the packaging docs on entry points `_ for more information. + + >>> ep = EntryPoint( + ... name=None, group=None, value='package.module:attr [extra1, extra2]') + >>> ep.module + 'package.module' + >>> ep.attr + 'attr' + >>> ep.extras + ['extra1', 'extra2'] """ pattern = re.compile( r'(?P[\w.]+)\s*' - r'(:\s*(?P[\w.]+))?\s*' - r'(?P\[.*\])?\s*$' + r'(:\s*(?P[\w.]+)\s*)?' + r'((?P\[.*\])\s*)?$' ) """ A regular expression describing the syntax for an entry point, @@ -182,7 +185,7 @@ def attr(self): @property def extras(self): match = self.pattern.match(self.value) - return list(re.finditer(r'\w+', match.group('extras') or '')) + return re.findall(r'\w+', match.group('extras') or '') def _for(self, dist): self.dist = dist @@ -206,6 +209,25 @@ def __reduce__(self): ) def matches(self, **params): + """ + EntryPoint matches the given parameters. + + >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong [extra1, extra2]') + >>> ep.matches(group='foo') + True + >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]') + True + >>> ep.matches(group='foo', name='other') + False + >>> ep.matches() + True + >>> ep.matches(extras=['extra1', 'extra2']) + True + >>> ep.matches(module='bing') + True + >>> ep.matches(attr='bong') + True + """ attrs = (getattr(self, param) for param in params) return all(map(operator.eq, params.values(), attrs)) @@ -242,11 +264,13 @@ class DeprecatedList(list): 1 """ + __slots__ = () + _warn = functools.partial( warnings.warn, "EntryPoints list interface is deprecated. Cast to list if needed.", DeprecationWarning, - stacklevel=pypy_partial(2), + stacklevel=2, ) def __setitem__(self, *args, **kwargs): @@ -395,7 +419,7 @@ class Deprecated: warnings.warn, "SelectableGroups dict interface is deprecated. Use select.", DeprecationWarning, - stacklevel=pypy_partial(2), + stacklevel=2, ) def __getitem__(self, name): @@ -568,7 +592,7 @@ def _local(cls, root='.'): source_dir=root, system=system, ) - return PathDistribution(zipp.Path(meta.build_as_zip(builder))) + return PathDistribution(zipfile.Path(meta.build_as_zip(builder))) @property def metadata(self) -> _meta.PackageMetadata: @@ -654,7 +678,7 @@ def _read_dist_info_reqs(self): def _read_egg_info_reqs(self): source = self.read_text('requires.txt') - return source and self._deps_from_requires_text(source) + return None if source is None else self._deps_from_requires_text(source) @classmethod def _deps_from_requires_text(cls, source): @@ -675,7 +699,7 @@ def _convert_egg_info_reqs_to_simple_reqs(sections): def make_condition(name): return name and f'extra == "{name}"' - def parse_condition(section): + def quoted_marker(section): section = section or '' extra, sep, markers = section.partition(':') if extra and markers: @@ -683,8 +707,17 @@ def parse_condition(section): conditions = list(filter(None, [markers, make_condition(extra)])) return '; ' + ' and '.join(conditions) if conditions else '' + def url_req_space(req): + """ + PEP 508 requires a space between the url_spec and the quoted_marker. + Ref python/importlib_metadata#357. + """ + # '@' is uniquely indicative of a url_req. + return ' ' * ('@' in req) + for section in sections: - yield section.value + parse_condition(section.name) + space = url_req_space(section.value) + yield section.value + space + quoted_marker(section.name) class DistributionFinder(MetaPathFinder): @@ -746,20 +779,20 @@ def __new__(cls, root): return super().__new__(cls) def __init__(self, root): - self.root = str(root) + self.root = root def joinpath(self, child): return pathlib.Path(self.root, child) def children(self): with suppress(Exception): - return os.listdir(self.root or '') + return os.listdir(self.root or '.') with suppress(Exception): return self.zip_children() return [] def zip_children(self): - zip_path = zipp.Path(self.root) + zip_path = zipfile.Path(self.root) names = zip_path.root.namelist() self.joinpath = zip_path.joinpath @@ -849,15 +882,9 @@ def __bool__(self): return bool(self.name) -@install -class MetadataPathFinder(NullFinder, DistributionFinder): - """A degenerate finder for distribution packages on the file system. - - This finder supplies only a find_distributions() method for versions - of Python that do not have a PathFinder find_distributions(). - """ - - def find_distributions(self, context=DistributionFinder.Context()): +class MetadataPathFinder(DistributionFinder): + @classmethod + def find_distributions(cls, context=DistributionFinder.Context()): """ Find distributions. @@ -866,7 +893,7 @@ def find_distributions(self, context=DistributionFinder.Context()): (or all names if ``None`` indicated) along the paths in the list of directories ``context.path``. """ - found = self._search_paths(context.name, context.path) + found = cls._search_paths(context.name, context.path) return map(PathDistribution, found) @classmethod @@ -911,13 +938,25 @@ def _normalized_name(self): normalized name from the file system path. """ stem = os.path.basename(str(self._path)) - return self._name_from_stem(stem) or super()._normalized_name + return ( + pass_none(Prepared.normalize)(self._name_from_stem(stem)) + or super()._normalized_name + ) - def _name_from_stem(self, stem): - name, ext = os.path.splitext(stem) + @staticmethod + def _name_from_stem(stem): + """ + >>> PathDistribution._name_from_stem('foo-3.0.egg-info') + 'foo' + >>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info') + 'CherryPy' + >>> PathDistribution._name_from_stem('face.egg-info') + 'face' + """ + filename, ext = os.path.splitext(stem) if ext not in ('.dist-info', '.egg-info'): return - name, sep, rest = stem.partition('-') + name, sep, rest = filename.partition('-') return name diff --git a/importlib_metadata/_adapters.py b/Lib/importlib/metadata/_adapters.py similarity index 100% rename from importlib_metadata/_adapters.py rename to Lib/importlib/metadata/_adapters.py diff --git a/importlib_metadata/_collections.py b/Lib/importlib/metadata/_collections.py similarity index 100% rename from importlib_metadata/_collections.py rename to Lib/importlib/metadata/_collections.py diff --git a/importlib_metadata/_functools.py b/Lib/importlib/metadata/_functools.py similarity index 86% rename from importlib_metadata/_functools.py rename to Lib/importlib/metadata/_functools.py index 73f50d00..71f66bd0 100644 --- a/importlib_metadata/_functools.py +++ b/Lib/importlib/metadata/_functools.py @@ -83,3 +83,22 @@ def wrapper(self, *args, **kwargs): wrapper.cache_clear = lambda: None return wrapper + + +# From jaraco.functools 3.3 +def pass_none(func): + """ + Wrap func so it's not called if its first param is None + + >>> print_text = pass_none(print) + >>> print_text('text') + text + >>> print_text(None) + """ + + @functools.wraps(func) + def wrapper(param, *args, **kwargs): + if param is not None: + return func(param, *args, **kwargs) + + return wrapper diff --git a/importlib_metadata/_itertools.py b/Lib/importlib/metadata/_itertools.py similarity index 100% rename from importlib_metadata/_itertools.py rename to Lib/importlib/metadata/_itertools.py diff --git a/importlib_metadata/_meta.py b/Lib/importlib/metadata/_meta.py similarity index 92% rename from importlib_metadata/_meta.py rename to Lib/importlib/metadata/_meta.py index dd68c429..1a6edbf9 100644 --- a/importlib_metadata/_meta.py +++ b/Lib/importlib/metadata/_meta.py @@ -1,5 +1,4 @@ -from ._compat import Protocol -from typing import Any, Dict, Iterator, List, TypeVar, Union +from typing import Any, Dict, Iterator, List, Protocol, TypeVar, Union _T = TypeVar("_T") diff --git a/importlib_metadata/_text.py b/Lib/importlib/metadata/_text.py similarity index 100% rename from importlib_metadata/_text.py rename to Lib/importlib/metadata/_text.py diff --git a/docs/__init__.py b/Lib/test/test_importlib/data/__init__.py similarity index 100% rename from docs/__init__.py rename to Lib/test/test_importlib/data/__init__.py diff --git a/tests/data/example-21.12-py3-none-any.whl b/Lib/test/test_importlib/data/example-21.12-py3-none-any.whl similarity index 100% rename from tests/data/example-21.12-py3-none-any.whl rename to Lib/test/test_importlib/data/example-21.12-py3-none-any.whl diff --git a/tests/data/example-21.12-py3.6.egg b/Lib/test/test_importlib/data/example-21.12-py3.6.egg similarity index 100% rename from tests/data/example-21.12-py3.6.egg rename to Lib/test/test_importlib/data/example-21.12-py3.6.egg diff --git a/tests/fixtures.py b/Lib/test/test_importlib/fixtures.py similarity index 99% rename from tests/fixtures.py rename to Lib/test/test_importlib/fixtures.py index 8ef54f84..12ed07d3 100644 --- a/tests/fixtures.py +++ b/Lib/test/test_importlib/fixtures.py @@ -7,7 +7,7 @@ import textwrap import contextlib -from .py39compat import FS_NONASCII +from test.support.os_helper import FS_NONASCII from typing import Dict, Union diff --git a/Lib/test/test_importlib/stubs.py b/Lib/test/test_importlib/stubs.py new file mode 100644 index 00000000..e5b011c3 --- /dev/null +++ b/Lib/test/test_importlib/stubs.py @@ -0,0 +1,10 @@ +import unittest + + +class fake_filesystem_unittest: + """ + Stubbed version of the pyfakefs module + """ + class TestCase(unittest.TestCase): + def setUpPyfakefs(self): + self.skipTest("pyfakefs not available") diff --git a/tests/test_main.py b/Lib/test/test_importlib/test_main.py similarity index 85% rename from tests/test_main.py rename to Lib/test/test_importlib/test_main.py index f7c9c518..52cb6371 100644 --- a/tests/test_main.py +++ b/Lib/test/test_importlib/test_main.py @@ -4,15 +4,17 @@ import textwrap import unittest import warnings -import importlib -import importlib_metadata -import pyfakefs.fake_filesystem_unittest as ffs +import importlib.metadata + +try: + import pyfakefs.fake_filesystem_unittest as ffs +except ImportError: + from .stubs import fake_filesystem_unittest as ffs from . import fixtures -from importlib_metadata import ( +from importlib.metadata import ( Distribution, EntryPoint, - MetadataPathFinder, PackageNotFoundError, distributions, entry_points, @@ -34,12 +36,10 @@ def test_for_name_does_not_exist(self): Distribution.from_name('does-not-exist') def test_package_not_found_mentions_metadata(self): - """ - When a package is not found, that could indicate that the - packgae is not installed or that it is installed without - metadata. Ensure the exception mentions metadata to help - guide users toward the cause. See #124. - """ + # When a package is not found, that could indicate that the + # packgae is not installed or that it is installed without + # metadata. Ensure the exception mentions metadata to help + # guide users toward the cause. See #124. with self.assertRaises(PackageNotFoundError) as ctx: Distribution.from_name('does-not-exist') @@ -47,7 +47,6 @@ def test_package_not_found_mentions_metadata(self): def test_new_style_classes(self): self.assertIsInstance(Distribution, type) - self.assertIsInstance(MetadataPathFinder, type) class ImportTests(fixtures.DistInfoPkg, unittest.TestCase): @@ -68,10 +67,10 @@ def test_entrypoint_with_colon_in_name(self): def test_resolve_without_attr(self): ep = EntryPoint( name='ep', - value='importlib_metadata', + value='importlib.metadata', group='grp', ) - assert ep.load() is importlib_metadata + assert ep.load() is importlib.metadata class NameNormalizationTests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): @@ -89,10 +88,8 @@ def pkg_with_dashes(site_dir): return 'my-pkg' def test_dashes_in_dist_name_found_as_underscores(self): - """ - For a package with a dash in the name, the dist-info metadata - uses underscores in the name. Ensure the metadata loads. - """ + # For a package with a dash in the name, the dist-info metadata + # uses underscores in the name. Ensure the metadata loads. pkg_name = self.pkg_with_dashes(self.site_dir) assert version(pkg_name) == '1.0' @@ -110,9 +107,7 @@ def pkg_with_mixed_case(site_dir): return 'CherryPy' def test_dist_name_found_as_any_case(self): - """ - Ensure the metadata loads when queried with any case. - """ + # Ensure the metadata loads when queried with any case. pkg_name = self.pkg_with_mixed_case(self.site_dir) assert version(pkg_name) == '1.0' assert version(pkg_name.lower()) == '1.0' @@ -201,7 +196,7 @@ def test_discovery(self): Discovering distributions should succeed even if there is an invalid path on sys.path. """ - importlib_metadata.distributions() + importlib.metadata.distributions() class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase): @@ -217,13 +212,13 @@ def test_discovery(self): Discovering distributions should succeed even if there is an invalid path on sys.path. """ - list(importlib_metadata.distributions()) + list(importlib.metadata.distributions()) class TestEntryPoints(unittest.TestCase): def __init__(self, *args): super(TestEntryPoints, self).__init__(*args) - self.ep = importlib_metadata.EntryPoint('name', 'value', 'group') + self.ep = importlib.metadata.EntryPoint('name', 'value', 'group') def test_entry_point_pickleable(self): revived = pickle.loads(pickle.dumps(self.ep)) @@ -240,13 +235,11 @@ def test_repr(self): assert "'name'" in repr(self.ep) def test_hashable(self): - """EntryPoints should be hashable""" + # EntryPoints should be hashable. hash(self.ep) def test_json_dump(self): - """ - json should not expect to be able to dump an EntryPoint - """ + # json should not expect to be able to dump an EntryPoint. with self.assertRaises(Exception): with warnings.catch_warnings(record=True): json.dumps(self.ep) @@ -258,9 +251,7 @@ def test_attr(self): assert self.ep.attr is None def test_sortable(self): - """ - EntryPoint objects are sortable, but result is undefined. - """ + # EntryPoint objects are sortable, but result is undefined. sorted( [ EntryPoint('b', 'val', 'group'), @@ -273,10 +264,8 @@ class FileSystem( fixtures.OnSysPath, fixtures.SiteDir, fixtures.FileBuilder, unittest.TestCase ): def test_unicode_dir_on_sys_path(self): - """ - Ensure a Unicode subdirectory of a directory on sys.path - does not crash. - """ + # Ensure a Unicode subdirectory of a directory on sys.path + # does not crash. fixtures.build_files( {self.unicode_filename(): {}}, prefix=self.site_dir, diff --git a/tests/test_api.py b/Lib/test/test_importlib/test_metadata_api.py similarity index 87% rename from tests/test_api.py rename to Lib/test/test_importlib/test_metadata_api.py index 7ebcc1d8..ab482817 100644 --- a/tests/test_api.py +++ b/Lib/test/test_importlib/test_metadata_api.py @@ -6,7 +6,7 @@ import contextlib from . import fixtures -from importlib_metadata import ( +from importlib.metadata import ( Distribution, PackageNotFoundError, distribution, @@ -89,15 +89,15 @@ def test_entry_points_distribution(self): self.assertIn(ep.dist.name, ('distinfo-pkg', 'egginfo-pkg')) self.assertEqual(ep.dist.version, "1.0.0") - def test_entry_points_unique_packages(self): + def test_entry_points_unique_packages_normalized(self): """ Entry points should only be exposed for the first package - on sys.path with a given name. + on sys.path with a given name (even when normalized). """ alt_site_dir = self.fixtures.enter_context(fixtures.tempdir()) self.fixtures.enter_context(self.add_sys_path(alt_site_dir)) alt_pkg = { - "distinfo_pkg-1.1.0.dist-info": { + "DistInfo_pkg-1.1.0.dist-info": { "METADATA": """ Name: distinfo-pkg Version: 1.1.0 @@ -125,11 +125,9 @@ def test_entry_points_missing_group(self): assert entry_points(group='missing') == () def test_entry_points_dict_construction(self): - """ - Prior versions of entry_points() returned simple lists and - allowed casting those lists into maps by name using ``dict()``. - Capture this now deprecated use-case. - """ + # Prior versions of entry_points() returned simple lists and + # allowed casting those lists into maps by name using ``dict()``. + # Capture this now deprecated use-case. with suppress_known_deprecation() as caught: eps = dict(entry_points(group='entries')) @@ -158,11 +156,9 @@ def test_entry_points_by_index(self): assert "Accessing entry points by index is deprecated" in str(expected) def test_entry_points_groups_getitem(self): - """ - Prior versions of entry_points() returned a dict. Ensure - that callers using '.__getitem__()' are supported but warned to - migrate. - """ + # Prior versions of entry_points() returned a dict. Ensure + # that callers using '.__getitem__()' are supported but warned to + # migrate. with suppress_known_deprecation(): entry_points()['entries'] == entry_points(group='entries') @@ -170,16 +166,19 @@ def test_entry_points_groups_getitem(self): entry_points()['missing'] def test_entry_points_groups_get(self): - """ - Prior versions of entry_points() returned a dict. Ensure - that callers using '.get()' are supported but warned to - migrate. - """ + # Prior versions of entry_points() returned a dict. Ensure + # that callers using '.get()' are supported but warned to + # migrate. with suppress_known_deprecation(): entry_points().get('missing', 'default') == 'default' entry_points().get('entries', 'default') == entry_points()['entries'] entry_points().get('missing', ()) == () + def test_entry_points_allows_no_attributes(self): + ep = entry_points().select(group='entries', name='main') + with self.assertRaises(AttributeError): + ep.foo = 4 + def test_metadata_for_this_package(self): md = metadata('egginfo-pkg') assert md['author'] == 'Steven Ma' @@ -188,10 +187,6 @@ def test_metadata_for_this_package(self): classifiers = md.get_all('Classifier') assert 'Topic :: Software Development :: Libraries' in classifiers - def test_importlib_metadata_version(self): - resolved = version('importlib-metadata') - assert re.match(self.version_pattern, resolved) - @staticmethod def _test_files(files): root = files[0].root @@ -206,11 +201,7 @@ def _test_files(files): file.read_text() def test_file_hash_repr(self): - try: - assertRegex = self.assertRegex - except AttributeError: - # Python 2 - assertRegex = self.assertRegexpMatches + assertRegex = self.assertRegex util = [p for p in files('distinfo-pkg') if p.name == 'mod.py'][0] assertRegex(repr(util.hash), '') @@ -233,6 +224,16 @@ def test_requires_egg_info(self): assert len(deps) == 2 assert any(dep == 'wheel >= 1.0; python_version >= "2.7"' for dep in deps) + def test_requires_egg_info_empty(self): + fixtures.build_files( + { + 'requires.txt': '', + }, + self.site_dir.joinpath('egginfo_pkg.egg-info'), + ) + deps = requires('egginfo-pkg') + assert deps == [] + def test_requires_dist_info(self): deps = requires('distinfo-pkg') assert len(deps) == 2 @@ -251,6 +252,7 @@ def test_more_complex_deps_requires_text(self): [extra1] dep4 + dep6@ git+https://example.com/python/dep.git@v1.0.0 [extra2:python_version < "3"] dep5 @@ -263,6 +265,7 @@ def test_more_complex_deps_requires_text(self): 'dep3; python_version < "3"', 'dep4; extra == "extra1"', 'dep5; (python_version < "3") and extra == "extra2"', + 'dep6@ git+https://example.com/python/dep.git@v1.0.0 ; extra == "extra1"', ] # It's important that the environment marker expression be # wrapped in parentheses to avoid the following 'and' binding more @@ -314,7 +317,7 @@ def test_find_distributions_specified_path(self): assert any(dist.metadata['Name'] == 'distinfo-pkg' for dist in dists) def test_distribution_at_pathlib(self): - """Demonstrate how to load metadata direct from a directory.""" + # Demonstrate how to load metadata direct from a directory. dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info' dist = Distribution.at(dist_info_path) assert dist.version == '1.0.0' diff --git a/tests/test_zip.py b/Lib/test/test_importlib/test_zip.py similarity index 90% rename from tests/test_zip.py rename to Lib/test/test_importlib/test_zip.py index 8a4c9458..bf16a3b9 100644 --- a/tests/test_zip.py +++ b/Lib/test/test_importlib/test_zip.py @@ -2,7 +2,7 @@ import unittest from contextlib import ExitStack -from importlib_metadata import ( +from importlib.metadata import ( PackageNotFoundError, distribution, distributions, @@ -10,18 +10,14 @@ files, version, ) +from importlib import resources -try: - from importlib import resources - - getattr(resources, 'files') - getattr(resources, 'as_file') -except (ImportError, AttributeError): - import importlib_resources as resources # type: ignore +from test.support import requires_zlib +@requires_zlib() class TestZip(unittest.TestCase): - root = 'tests.data' + root = 'test.test_importlib.data' def _fixture_on_path(self, filename): pkg_file = resources.files(self.root).joinpath(filename) @@ -67,6 +63,7 @@ def test_one_distribution(self): assert len(dists) == 1 +@requires_zlib() class TestEgg(TestZip): def setUp(self): # Find the path to the example-*.egg so we can add it to the front of diff --git a/Misc/NEWS.d/next/Documentation/2021-07-12-11-39-20.bpo-44613.DIXNzc.rst b/Misc/NEWS.d/next/Documentation/2021-07-12-11-39-20.bpo-44613.DIXNzc.rst new file mode 100644 index 00000000..baf59107 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2021-07-12-11-39-20.bpo-44613.DIXNzc.rst @@ -0,0 +1 @@ +importlib.metadata is no longer provisional. diff --git a/README.rst b/README.rst deleted file mode 100644 index 9f8c8670..00000000 --- a/README.rst +++ /dev/null @@ -1,76 +0,0 @@ -.. image:: https://img.shields.io/pypi/v/importlib_metadata.svg - :target: `PyPI link`_ - -.. image:: https://img.shields.io/pypi/pyversions/importlib_metadata.svg - :target: `PyPI link`_ - -.. _PyPI link: https://pypi.org/project/importlib_metadata - -.. image:: https://github.com/python/importlib_metadata/workflows/tests/badge.svg - :target: https://github.com/python/importlib_metadata/actions?query=workflow%3A%22tests%22 - :alt: tests - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code style: Black - -.. image:: https://readthedocs.org/projects/importlib-metadata/badge/?version=latest - :target: https://importlib-metadata.readthedocs.io/en/latest/?badge=latest - -.. image:: https://img.shields.io/badge/skeleton-2021-informational - :target: https://blog.jaraco.com/skeleton - - -Library to access the metadata for a Python package. - -This package supplies third-party access to the functionality of -`importlib.metadata `_ -including improvements added to subsequent Python versions. - - -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_metadata - - stdlib - * - 4.4 - - 3.10 - * - 1.4 - - 3.8 - - -Usage -===== - -See the `online documentation `_ -for usage details. - -`Finder authors -`_ can -also add support for custom package installers. See the above documentation -for details. - - -Caveats -======= - -This project primarily supports third-party packages installed by PyPA -tools (or other conforming packages). It does not support: - -- Packages in the stdlib. -- Packages installed without metadata. - -Project details -=============== - - * Project home: https://github.com/python/importlib_metadata - * Report bugs at: https://github.com/python/importlib_metadata/issues - * Code hosting: https://github.com/python/importlib_metadata - * Documentation: https://importlib_metadata.readthedocs.io/ diff --git a/conftest.py b/conftest.py deleted file mode 100644 index ab6c8cae..00000000 --- a/conftest.py +++ /dev/null @@ -1,25 +0,0 @@ -import sys - - -collect_ignore = [ - # this module fails mypy tests because 'setup.py' matches './setup.py' - 'prepare/example/setup.py', -] - - -def pytest_configure(): - remove_importlib_metadata() - - -def remove_importlib_metadata(): - """ - Because pytest imports importlib_metadata, the coverage - reports are broken (#322). So work around the issue by - undoing the changes made by pytest's import of - importlib_metadata (if any). - """ - if sys.meta_path[-1].__class__.__name__ == 'MetadataPathFinder': - del sys.meta_path[-1] - for mod in list(sys.modules): - if mod.startswith('importlib_metadata'): - del sys.modules[mod] diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 02b389ba..00000000 --- a/docs/api.rst +++ /dev/null @@ -1,11 +0,0 @@ -============= -API Reference -============= - -``importlib_metadata`` module ------------------------------ - -.. automodule:: importlib_metadata - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index f619facc..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker'] - -master_doc = "index" - -link_files = { - '../CHANGES.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://www.python.org/dev/peps/pep-{pep_number:0>4}/', - ), - ], - ) -} - -# Be strict about any broken references: -nitpicky = True - -# Support intersphinx links -extensions += [ - 'sphinx.ext.intersphinx', -] -intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'importlib_resources': ( - 'https://importlib-resources.readthedocs.io/en/latest/', - None, - ), -} - -# Workaround for #316 -nitpick_ignore = [ - ('py:class', 'importlib_metadata.EntryPoints'), - ('py:class', 'importlib_metadata.SelectableGroups'), - ('py:class', 'importlib_metadata._meta._T'), -] diff --git a/docs/history.rst b/docs/history.rst deleted file mode 100644 index 8e217503..00000000 --- a/docs/history.rst +++ /dev/null @@ -1,8 +0,0 @@ -:tocdepth: 2 - -.. _changes: - -History -******* - -.. include:: ../CHANGES (links).rst diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 13e1c0a2..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,51 +0,0 @@ -Welcome to |project| documentation! -=================================== - -``importlib_metadata`` is a library which provides an API for accessing an -installed package's metadata (see :pep:`566`), such as its entry points or its top-level -name. This functionality intends to replace most uses of ``pkg_resources`` -`entry point API`_ and `metadata API`_. Along with :mod:`importlib.resources` in -Python 3.7 and newer (backported as :doc:`importlib_resources ` for older -versions of Python), this can eliminate the need to use the older and less -efficient ``pkg_resources`` package. - -``importlib_metadata`` supplies a backport of -:doc:`importlib.metadata ` as found in -Python 3.8 and later for earlier Python releases. Users of -Python 3.8 and beyond are encouraged to use the standard library module -when possible and fall back to ``importlib_metadata`` when necessary. -When imported on Python 3.8 and later, ``importlib_metadata`` replaces the -DistributionFinder behavior from the stdlib, but leaves the API in tact. -Developers looking for detailed API descriptions should refer to the Python -3.8 standard library documentation. - -The documentation here includes a general :ref:`usage ` guide. - - -.. toctree:: - :maxdepth: 1 - - using - api - history - - -Project details -=============== - - * Project home: https://github.com/python/importlib_metadata - * Report bugs at: https://github.com/python/importlib_metadata/issues - * Code hosting: https://github.com/python/importlib_metadata - * Documentation: https://importlib_metadata.readthedocs.io/ - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - - -.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points -.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api diff --git a/exercises.py b/exercises.py deleted file mode 100644 index bc8a44e9..00000000 --- a/exercises.py +++ /dev/null @@ -1,36 +0,0 @@ -from pytest_perf.deco import extras - - -@extras('perf') -def discovery_perf(): - "discovery" - import importlib_metadata # end warmup - - importlib_metadata.distribution('ipython') - - -def entry_points_perf(): - "entry_points()" - import importlib_metadata # end warmup - - importlib_metadata.entry_points() - - -@extras('perf') -def cached_distribution_perf(): - "cached distribution" - import importlib_metadata - - importlib_metadata.distribution('ipython') # end warmup - importlib_metadata.distribution('ipython') - - -@extras('perf') -def uncached_distribution_perf(): - "uncached distribution" - import importlib - import importlib_metadata - - # end warmup - importlib.invalidate_caches() - importlib_metadata.distribution('ipython') diff --git a/importlib_metadata/_compat.py b/importlib_metadata/_compat.py deleted file mode 100644 index 1947d449..00000000 --- a/importlib_metadata/_compat.py +++ /dev/null @@ -1,97 +0,0 @@ -import sys -import platform - - -__all__ = ['install', 'NullFinder', 'PyPy_repr', 'Protocol'] - - -try: - from typing import Protocol -except ImportError: # pragma: no cover - """ - pytest-mypy complains here because: - error: Incompatible import of "Protocol" (imported name has type - "typing_extensions._SpecialForm", local name has type "typing._SpecialForm") - """ - from typing_extensions import Protocol # type: ignore - - -def install(cls): - """ - Class decorator for installation on sys.meta_path. - - Adds the backport DistributionFinder to sys.meta_path and - attempts to disable the finder functionality of the stdlib - DistributionFinder. - """ - sys.meta_path.append(cls()) - disable_stdlib_finder() - return cls - - -def disable_stdlib_finder(): - """ - Give the backport primacy for discovering path-based distributions - by monkey-patching the stdlib O_O. - - See #91 for more background for rationale on this sketchy - behavior. - """ - - def matches(finder): - return getattr( - finder, '__module__', None - ) == '_frozen_importlib_external' and hasattr(finder, 'find_distributions') - - for finder in filter(matches, sys.meta_path): # pragma: nocover - del finder.find_distributions - - -class NullFinder: - """ - A "Finder" (aka "MetaClassFinder") that never finds any modules, - but may find distributions. - """ - - @staticmethod - def find_spec(*args, **kwargs): - return None - - # In Python 2, the import system requires finders - # to have a find_module() method, but this usage - # is deprecated in Python 3 in favor of find_spec(). - # For the purposes of this finder (i.e. being present - # on sys.meta_path but having no other import - # system functionality), the two methods are identical. - find_module = find_spec - - -class PyPy_repr: - """ - Override repr for EntryPoint objects on PyPy to avoid __iter__ access. - Ref #97, #102. - """ - - affected = hasattr(sys, 'pypy_version_info') - - def __compat_repr__(self): # pragma: nocover - def make_param(name): - value = getattr(self, name) - return f'{name}={value!r}' - - params = ', '.join(map(make_param, self._fields)) - return f'EntryPoint({params})' - - if affected: # pragma: nocover - __repr__ = __compat_repr__ - del affected - - -def pypy_partial(val): - """ - Adjust for variable stacklevel on partial under PyPy. - - Workaround for #327. - """ - is_pypy = platform.python_implementation() == 'PyPy' - return val + is_pypy diff --git a/importlib_metadata/py.typed b/importlib_metadata/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 976ba029..00000000 --- a/mypy.ini +++ /dev/null @@ -1,2 +0,0 @@ -[mypy] -ignore_missing_imports = True diff --git a/prepare/example/example/__init__.py b/prepare/example/example/__init__.py deleted file mode 100644 index ba73b743..00000000 --- a/prepare/example/example/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -def main(): - return 'example' diff --git a/prepare/example/setup.py b/prepare/example/setup.py deleted file mode 100644 index 479488a0..00000000 --- a/prepare/example/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -from setuptools import setup - -setup( - name='example', - version='21.12', - license='Apache Software License', - packages=['example'], - entry_points={ - 'console_scripts': ['example = example:main', 'Example=example:main'], - }, -) diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index b6ebc0be..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,20 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4.1"] -build-backend = "setuptools.build_meta" - -[tool.black] -skip-string-normalization = true - -[tool.setuptools_scm] - -[pytest.enabler.black] -addopts = "--black" - -[pytest.enabler.mypy] -addopts = "--mypy" - -[pytest.enabler.flake8] -addopts = "--flake8" - -[pytest.enabler.cov] -addopts = "--cov" diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 31b114fd..00000000 --- a/pytest.ini +++ /dev/null @@ -1,11 +0,0 @@ -[pytest] -norecursedirs=dist build .tox .eggs -addopts=--doctest-modules -doctest_optionflags=ALLOW_UNICODE ELLIPSIS -# workaround for warning pytest-dev/pytest#6178 -junit_family=xunit2 -filterwarnings= - # Suppress deprecation warning in flake8 - ignore:SelectableGroups dict interface is deprecated::flake8 - # Suppress deprecation warning in pypa/packaging#433 - ignore:The distutils package is deprecated::packaging.tags diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 98124e5c..00000000 --- a/setup.cfg +++ /dev/null @@ -1,67 +0,0 @@ -[metadata] -license_files = - LICENSE -name = importlib_metadata -author = Jason R. Coombs -author_email = jaraco@jaraco.com -description = Read metadata from Python packages -long_description = file:README.rst -url = https://github.com/python/importlib_metadata -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - -[options] -packages = find_namespace: -include_package_data = true -python_requires = >=3.6 -install_requires = - zipp>=0.5 - typing-extensions>=3.6.4; python_version < "3.8" - -[options.packages.find] -exclude = - build* - dist* - docs* - tests* - prepare* - -[options.extras_require] -testing = - # upstream - pytest >= 4.6 - pytest-checkdocs >= 2.4 - pytest-flake8 - # python_implementation: workaround for jaraco/skeleton#22 - # python_version: workaround for python/typed_ast#156 - pytest-black >= 0.3.7; python_implementation != "PyPy" and python_version < "3.10" - pytest-cov - # python_implementation: workaround for jaraco/skeleton#22 - # python_version: workaround for python/typed_ast#156 - pytest-mypy; python_implementation != "PyPy" and python_version < "3.10" - pytest-enabler >= 1.0.1 - - # local - importlib_resources>=1.3; python_version < "3.9" - packaging - pep517 - pyfakefs - flufl.flake8 - pytest-perf >= 0.9.2 - -docs = - # upstream - sphinx - jaraco.packaging >= 8.2 - rst.linker >= 1.9 - - # local - -perf = - ipython - -[options.entry_points] diff --git a/setup.py b/setup.py deleted file mode 100644 index bac24a43..00000000 --- a/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -import setuptools - -if __name__ == "__main__": - setuptools.setup() diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/data/__init__.py b/tests/data/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/py39compat.py b/tests/py39compat.py deleted file mode 100644 index 926dcad9..00000000 --- a/tests/py39compat.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - from test.support.os_helper import FS_NONASCII -except ImportError: - from test.support import FS_NONASCII # noqa diff --git a/tests/test_integration.py b/tests/test_integration.py deleted file mode 100644 index 00e9021a..00000000 --- a/tests/test_integration.py +++ /dev/null @@ -1,75 +0,0 @@ -import unittest -import packaging.requirements -import packaging.version - -from . import fixtures -from importlib_metadata import ( - Distribution, - MetadataPathFinder, - _compat, - distributions, - version, -) - - -class IntegrationTests(fixtures.DistInfoPkg, unittest.TestCase): - def test_package_spec_installed(self): - """ - Illustrate the recommended procedure to determine if - a specified version of a package is installed. - """ - - def is_installed(package_spec): - req = packaging.requirements.Requirement(package_spec) - return version(req.name) in req.specifier - - assert is_installed('distinfo-pkg==1.0') - assert is_installed('distinfo-pkg>=1.0,<2.0') - assert not is_installed('distinfo-pkg<1.0') - - -class FinderTests(fixtures.Fixtures, unittest.TestCase): - def test_finder_without_module(self): - class ModuleFreeFinder(fixtures.NullFinder): - """ - A finder without an __module__ attribute - """ - - def __getattribute__(self, name): - if name == '__module__': - raise AttributeError(name) - return super().__getattribute__(name) - - self.fixtures.enter_context(fixtures.install_finder(ModuleFreeFinder())) - _compat.disable_stdlib_finder() - - -class LocalProjectTests(fixtures.LocalPackage, unittest.TestCase): - def test_find_local(self): - dist = Distribution._local() - assert dist.metadata['Name'] == 'local-pkg' - assert dist.version == '2.0.1' - - -class DistSearch(unittest.TestCase): - def test_search_dist_dirs(self): - """ - Pip needs the _search_paths interface to locate - distribution metadata dirs. Protect it for PyPA - use-cases (only). Ref python/importlib_metadata#111. - """ - res = MetadataPathFinder._search_paths('any-name', []) - assert list(res) == [] - - def test_interleaved_discovery(self): - """ - When the search is cached, it is - possible for searches to be interleaved, so make sure - those use-cases are safe. - - Ref #293 - """ - dists = distributions() - next(dists) - version('importlib_metadata') - next(dists) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index cf96d1ad..00000000 --- a/tox.ini +++ /dev/null @@ -1,53 +0,0 @@ -[tox] -envlist = python -minversion = 3.2 -# https://github.com/jaraco/skeleton/issues/6 -tox_pip_extensions_ext_venv_update = true -toxworkdir={env:TOX_WORK_DIR:.tox} - - -[testenv] -deps = -commands = - pytest {posargs} -passenv = - HOME -usedevelop = True -extras = testing -setenv = - # workaround pypa/pip#9143 - PIP_USE_DEPRECATED=legacy-resolver - - -[testenv:docs] -extras = - docs - testing -changedir = docs -commands = - python -m sphinx -W --keep-going . {toxinidir}/build/html - -[testenv:diffcov] -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:release] -skip_install = True -deps = - build - twine>=3 - jaraco.develop>=7.1 -passenv = - 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