From 30b11308288ad31f0b7afa1d5a9c1934f1cb84e0 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 14 Aug 2020 18:34:14 -0700 Subject: [PATCH 01/22] Move tests and docs directories outside of source directory These directories were previously included in the wheel package. Now, they are not. This had resulted in the files being unnecessarily downloaded and installed to virtual environments when using pip. However, these files go unused production installations. These files are only necessary for the development of importlib_metadata itself, so they now live at the root of the project. This was more noticeable when pip dependencies are vendored or bundled, as it adds unnecessary bloat to these bundles. The directories are still included in the sdist, so packagers and library users can run the tests locally if they choose, they just wont be installed to the site-packages directory. --- {importlib_metadata/docs => docs}/__init__.py | 0 .../docs => docs}/changelog.rst | 0 {importlib_metadata/docs => docs}/conf.py | 0 {importlib_metadata/docs => docs}/index.rst | 0 {importlib_metadata/docs => docs}/using.rst | 0 setup.cfg | 17 +---------------- .../tests => tests}/__init__.py | 0 .../tests => tests}/data/__init__.py | 0 .../data/example-21.12-py3-none-any.whl | Bin .../data/example-21.12-py3.6.egg | Bin .../tests => tests}/fixtures.py | 2 +- .../tests => tests}/test_api.py | 2 +- .../tests => tests}/test_integration.py | 2 +- .../tests => tests}/test_main.py | 2 +- .../tests => tests}/test_zip.py | 4 ++-- tox.ini | 2 +- 16 files changed, 8 insertions(+), 23 deletions(-) rename {importlib_metadata/docs => docs}/__init__.py (100%) rename {importlib_metadata/docs => docs}/changelog.rst (100%) rename {importlib_metadata/docs => docs}/conf.py (100%) rename {importlib_metadata/docs => docs}/index.rst (100%) rename {importlib_metadata/docs => docs}/using.rst (100%) rename {importlib_metadata/tests => tests}/__init__.py (100%) rename {importlib_metadata/tests => tests}/data/__init__.py (100%) rename {importlib_metadata/tests => tests}/data/example-21.12-py3-none-any.whl (100%) rename {importlib_metadata/tests => tests}/data/example-21.12-py3.6.egg (100%) rename {importlib_metadata/tests => tests}/fixtures.py (98%) rename {importlib_metadata/tests => tests}/test_api.py (99%) rename {importlib_metadata/tests => tests}/test_integration.py (97%) rename {importlib_metadata/tests => tests}/test_main.py (99%) rename {importlib_metadata/tests => tests}/test_zip.py (97%) diff --git a/importlib_metadata/docs/__init__.py b/docs/__init__.py similarity index 100% rename from importlib_metadata/docs/__init__.py rename to docs/__init__.py diff --git a/importlib_metadata/docs/changelog.rst b/docs/changelog.rst similarity index 100% rename from importlib_metadata/docs/changelog.rst rename to docs/changelog.rst diff --git a/importlib_metadata/docs/conf.py b/docs/conf.py similarity index 100% rename from importlib_metadata/docs/conf.py rename to docs/conf.py diff --git a/importlib_metadata/docs/index.rst b/docs/index.rst similarity index 100% rename from importlib_metadata/docs/index.rst rename to docs/index.rst diff --git a/importlib_metadata/docs/using.rst b/docs/using.rst similarity index 100% rename from importlib_metadata/docs/using.rst rename to docs/using.rst diff --git a/setup.cfg b/setup.cfg index eee6caf2..d993f5cd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,16 +22,7 @@ install_requires = pathlib2; python_version < '3' contextlib2; python_version < '3' configparser>=3.5; python_version < '3' -packages = find: - -[options.package_data] -* = *.zip, *.file, *.txt, *.toml -importlib_metadata = - docs/* - docs/_static/* -importlib_metadata.tests.data = - *.egg - *.whl +packages = importlib_metadata [mypy] ignore_missing_imports = True @@ -42,12 +33,6 @@ ignore_missing_imports = True # that package.__spec__ is not None. strict_optional = False -[mypy-importlib_metadata.docs.*] -ignore_errors: True - -[mypy-importlib_metadata.tests.*] -ignore_errors: True - [wheel] universal=1 diff --git a/importlib_metadata/tests/__init__.py b/tests/__init__.py similarity index 100% rename from importlib_metadata/tests/__init__.py rename to tests/__init__.py diff --git a/importlib_metadata/tests/data/__init__.py b/tests/data/__init__.py similarity index 100% rename from importlib_metadata/tests/data/__init__.py rename to tests/data/__init__.py diff --git a/importlib_metadata/tests/data/example-21.12-py3-none-any.whl b/tests/data/example-21.12-py3-none-any.whl similarity index 100% rename from importlib_metadata/tests/data/example-21.12-py3-none-any.whl rename to tests/data/example-21.12-py3-none-any.whl diff --git a/importlib_metadata/tests/data/example-21.12-py3.6.egg b/tests/data/example-21.12-py3.6.egg similarity index 100% rename from importlib_metadata/tests/data/example-21.12-py3.6.egg rename to tests/data/example-21.12-py3.6.egg diff --git a/importlib_metadata/tests/fixtures.py b/tests/fixtures.py similarity index 98% rename from importlib_metadata/tests/fixtures.py rename to tests/fixtures.py index 20982fa1..506d20ff 100644 --- a/importlib_metadata/tests/fixtures.py +++ b/tests/fixtures.py @@ -7,7 +7,7 @@ import textwrap import test.support -from .._compat import pathlib, contextlib +from importlib_metadata._compat import pathlib, contextlib __metaclass__ = type diff --git a/importlib_metadata/tests/test_api.py b/tests/test_api.py similarity index 99% rename from importlib_metadata/tests/test_api.py rename to tests/test_api.py index eb0ff53b..9ef1e733 100644 --- a/importlib_metadata/tests/test_api.py +++ b/tests/test_api.py @@ -3,7 +3,7 @@ import unittest from . import fixtures -from .. import ( +from importlib_metadata import ( Distribution, PackageNotFoundError, distribution, entry_points, files, metadata, requires, version, ) diff --git a/importlib_metadata/tests/test_integration.py b/tests/test_integration.py similarity index 97% rename from importlib_metadata/tests/test_integration.py rename to tests/test_integration.py index cbb940bd..377574c4 100644 --- a/importlib_metadata/tests/test_integration.py +++ b/tests/test_integration.py @@ -7,7 +7,7 @@ import packaging.version from . import fixtures -from .. import ( +from importlib_metadata import ( Distribution, _compat, version, diff --git a/importlib_metadata/tests/test_main.py b/tests/test_main.py similarity index 99% rename from importlib_metadata/tests/test_main.py rename to tests/test_main.py index 4ffdd5d6..847750bc 100644 --- a/importlib_metadata/tests/test_main.py +++ b/tests/test_main.py @@ -11,7 +11,7 @@ import pyfakefs.fake_filesystem_unittest as ffs from . import fixtures -from .. import ( +from importlib_metadata import ( Distribution, EntryPoint, MetadataPathFinder, PackageNotFoundError, distributions, entry_points, metadata, version, diff --git a/importlib_metadata/tests/test_zip.py b/tests/test_zip.py similarity index 97% rename from importlib_metadata/tests/test_zip.py rename to tests/test_zip.py index 4aae933d..5cebcd02 100644 --- a/importlib_metadata/tests/test_zip.py +++ b/tests/test_zip.py @@ -1,7 +1,7 @@ import sys import unittest -from .. import ( +from importlib_metadata import ( distribution, entry_points, files, PackageNotFoundError, version, distributions, ) @@ -20,7 +20,7 @@ class TestZip(unittest.TestCase): - root = 'importlib_metadata.tests.data' + root = 'tests.data' def _fixture_on_path(self, filename): pkg_file = resources.files(self.root).joinpath(filename) diff --git a/tox.ini b/tox.ini index b2775cd1..1f0e9757 100644 --- a/tox.ini +++ b/tox.ini @@ -55,7 +55,7 @@ extras = [testenv:docs] basepython = python3 commands = - sphinx-build importlib_metadata/docs build/sphinx/html + sphinx-build docs build/sphinx/html extras = docs From 4daebe860ef9abdd02198c9be85c306fe6da5b56 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 19 Oct 2020 16:23:20 -0400 Subject: [PATCH 02/22] FS_NONASCII moves to test.support.os_helper in Python 3.10, so add a compatibility shim to support both sides. --- tests/fixtures.py | 5 +++-- tests/py39compat.py | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 tests/py39compat.py diff --git a/tests/fixtures.py b/tests/fixtures.py index 506d20ff..f206f365 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -5,7 +5,8 @@ import shutil import tempfile import textwrap -import test.support + +from .py39compat import FS_NONASCII from importlib_metadata._compat import pathlib, contextlib @@ -218,7 +219,7 @@ def build_files(file_defs, prefix=pathlib.Path()): class FileBuilder: def unicode_filename(self): - return test.support.FS_NONASCII or \ + return FS_NONASCII or \ self.skip("File system does not support non-ascii.") diff --git a/tests/py39compat.py b/tests/py39compat.py new file mode 100644 index 00000000..a175d4c3 --- /dev/null +++ b/tests/py39compat.py @@ -0,0 +1,4 @@ +try: + from test.support.os_helpers import FS_NONASCII +except ImportError: + from test.support import FS_NONASCII # noqa From c484c781d360eb944abb46c05af8d88a2bfcb634 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Oct 2020 09:33:29 -0400 Subject: [PATCH 03/22] Replace GitLab CI with Github workflow --- .github/workflows/main.yml | 42 ++++++++++++++++++++++++++++++++ .gitlab-ci.yml | 50 -------------------------------------- 2 files changed, 42 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .gitlab-ci.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8c5c232c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,42 @@ +name: Automated Tests + +on: [push, pull_request] + +jobs: + test: + strategy: + matrix: + python: [3.6, 3.8, 3.9] + 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 + + 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/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index a8d1bd28..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,50 +0,0 @@ -image: quay.io/python-devs/ci-image - -stages: - - test - - qa - - docs - - codecov - - deploy - -qa: - script: - - tox -e qa - -tests: - script: - - tox -e py27,py35,py36,py37,py38 - -coverage: - script: - - tox -e py27-cov,py35-cov,py36-cov,py37-cov,py38-cov - artifacts: - paths: - - coverage.xml - -benchmark: - script: - - tox -e perf - -diffcov: - script: - - tox -e py27-diffcov,py35-diffcov,py36-diffcov,py37-diffcov,py38-diffcov - -docs: - script: - - tox -e docs - -codecov: - stage: codecov - dependencies: - - coverage - script: - - codecov - when: on_success - -release: - stage: deploy - only: - - /^v\d+\.\d+(\.\d+)?([abc]\d*)?$/ - script: - - tox -e release From 137026d0964c42d6922192d52060f1a454159802 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Oct 2020 09:40:26 -0400 Subject: [PATCH 04/22] Update URLs. --- README.rst | 8 ++++---- docs/index.rst | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 2bdd4b8a..6829603b 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ tools (or other conforming packages). It does not support: Project details =============== - * Project home: https://gitlab.com/python-devs/importlib_metadata - * Report bugs at: https://gitlab.com/python-devs/importlib_metadata/issues - * Code hosting: https://gitlab.com/python-devs/importlib_metadata.git - * Documentation: http://importlib_metadata.readthedocs.io/ + * 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/docs/index.rst b/docs/index.rst index 530197cf..b1fe02e2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,10 +32,10 @@ The documentation here includes a general :ref:`usage ` guide. Project details =============== - * Project home: https://gitlab.com/python-devs/importlib_metadata - * Report bugs at: https://gitlab.com/python-devs/importlib_metadata/issues - * Code hosting: https://gitlab.com/python-devs/importlib_metadata.git - * Documentation: http://importlib_metadata.readthedocs.io/ + * 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 From 5b73f25b7b895354d92a6ec8073a373ff225b13c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Oct 2020 09:49:18 -0400 Subject: [PATCH 05/22] Add Python versions, specify specific env for tests, add jobs. --- .github/workflows/main.yml | 84 +++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c5c232c..172a52c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,7 @@ jobs: test: strategy: matrix: - python: [3.6, 3.8, 3.9] + python: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: @@ -20,6 +20,88 @@ jobs: python -m pip install tox - name: Run tests run: tox + env: + TOXENV: python + + qa: + 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: Run checks + run: tox + env: + TOXENV: qa + + coverage: + 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: Evaluate coverage + run: tox + env: + TOXENV: cov + + benchmark: + 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: Run benchmarks + run: tox + env: + TOXENV: perf + + diffcov: + 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: Evaluate coverage + run: tox + env: + TOXENV: diffcov + + docs: + 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: Build docs + run: tox + env: + TOXENV: docs release: needs: test From 747f6833eb1409e01399a56cfa7d3ce384c444c9 Mon Sep 17 00:00:00 2001 From: Julian Berman Date: Sat, 24 Oct 2020 12:54:01 -0400 Subject: [PATCH 06/22] Clarify that this has been added to the stdlib. --- README.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 6829603b..61437464 100644 --- a/README.rst +++ b/README.rst @@ -2,8 +2,13 @@ ``importlib_metadata`` ========================= -``importlib_metadata`` is a library to access the metadata for a Python -package. It is intended to be ported to Python 3.8. +``importlib_metadata`` is a library to access the metadata for a +Python package. + +It has been `added to the Python standard library +`_ as of +Python 3.8, but exists here to support backported usage in earlier +versions. Usage From 7c2164b5857d3ff70171898c66e8fff56647fd61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sat, 24 Oct 2020 22:36:26 +0100 Subject: [PATCH 07/22] ci: fix diffcov MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe LaĆ­ns --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 172a52c7..b56320fd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -75,6 +75,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v2 with: From 8fb96b0d97294e2e331f8862e7805c3275d61690 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Mon, 26 Oct 2020 04:11:37 +0800 Subject: [PATCH 08/22] Fix dot handling in dist-info directory name When searching for a matching dist-info directory, try to escape dots in the name segment. This makes functions like distribution() able to accept PEP 503 normalized names to locate a distribution's dist-info directory if the distribution's name contains dots. --- importlib_metadata/__init__.py | 2 +- tests/fixtures.py | 15 +++++++++++++++ tests/test_api.py | 4 ++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 7031323d..9155ee05 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -473,7 +473,7 @@ def search(self, name): for child in self.children(): n_low = child.lower() if (n_low in name.exact_matches - or n_low.startswith(name.prefix) + or n_low.replace('.', '_').startswith(name.prefix) and n_low.endswith(name.suffixes) # legacy case: or self.is_egg(name) and n_low == 'egg-info'): diff --git a/tests/fixtures.py b/tests/fixtures.py index f206f365..d6961409 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -104,6 +104,21 @@ def setUp(self): build_files(DistInfoPkg.files, self.site_dir) +class DistInfoPkgWithDot(OnSysPath, SiteDir): + files = { + "pkg.dot-1.0.0.dist-info": { + "METADATA": """ + Name: pkg.dot + Version: 1.0.0 + """, + }, + } + + def setUp(self): + super(DistInfoPkgWithDot, self).setUp() + build_files(DistInfoPkgWithDot.files, self.site_dir) + + class DistInfoPkgOffPath(SiteDir): def setUp(self): super(DistInfoPkgOffPath, self).setUp() diff --git a/tests/test_api.py b/tests/test_api.py index 9ef1e733..3f406ee2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,6 +22,7 @@ class APITests( fixtures.EggInfoPkg, fixtures.DistInfoPkg, + fixtures.DistInfoPkgWithDot, fixtures.EggInfoFile, unittest.TestCase): @@ -41,6 +42,9 @@ def test_for_name_does_not_exist(self): with self.assertRaises(PackageNotFoundError): distribution('does-not-exist') + def test_for_name_containing_dot(self): + assert distribution('pkg-dot').metadata['Name'] == 'pkg.dot' + def test_for_top_level(self): self.assertEqual( distribution('egginfo-pkg').read_text('top_level.txt').strip(), From 216397d6767a28af99b80dd7d7e8e1f77c19a1ec Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 22 Nov 2020 03:17:35 -0500 Subject: [PATCH 09/22] Reword backport philosophy to capture intention on subsequent Python releases. Fixes #254. --- README.rst | 9 +++++---- docs/index.rst | 8 +++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 61437464..5655d9ab 100644 --- a/README.rst +++ b/README.rst @@ -5,10 +5,11 @@ ``importlib_metadata`` is a library to access the metadata for a Python package. -It has been `added to the Python standard library -`_ as of -Python 3.8, but exists here to support backported usage in earlier -versions. +As of Python 3.8, this functionality has been added to the +`Python standard library +`_. +This package supplies backports of that functionality including +improvements added to subsequent Python versions. Usage diff --git a/docs/index.rst b/docs/index.rst index b1fe02e2..57332f5e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,9 +10,11 @@ Python 3.7 and newer (backported as :doc:`importlib_resources ` module for Python 2.7, and 3.4 through 3.7. Users of -Python 3.8 and beyond are encouraged to use the standard library module. +``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 From d027af68f05084c2cabc29164e4546110e116eeb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 22 Nov 2020 04:38:37 -0500 Subject: [PATCH 10/22] Update changelog. --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index b7e93b5d..39653574 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,13 @@ importlib_metadata NEWS ========================= +v2.1.0 +====== + +* #253: When querying for package metadata, the lookup + now honors + `package normalization rules `_. + v2.0.0 ====== From dbdef6ba3576f55cac49451364854e09511ad33b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 22 Nov 2020 05:05:35 -0500 Subject: [PATCH 11/22] Capture the target expectation of normalized names. --- importlib_metadata/__init__.py | 11 +++++++++-- tests/fixtures.py | 2 +- tests/test_api.py | 7 +++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 9155ee05..fc4cf1f8 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -473,7 +473,7 @@ def search(self, name): for child in self.children(): n_low = child.lower() if (n_low in name.exact_matches - or n_low.replace('.', '_').startswith(name.prefix) + or n_low.startswith(name.prefix) and n_low.endswith(name.suffixes) # legacy case: or self.is_egg(name) and n_low == 'egg-info'): @@ -494,12 +494,19 @@ def __init__(self, name): self.name = name if name is None: return - self.normalized = name.lower().replace('-', '_') + self.normalized = self.normalize(name) self.prefix = self.normalized + '-' self.exact_matches = [ self.normalized + suffix for suffix in self.suffixes] self.versionless_egg_name = self.normalized + '.egg' + @staticmethod + def normalize(name): + """ + PEP 503 normalization plus dashes as underscores. + """ + return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') + @install class MetadataPathFinder(NullFinder, DistributionFinder): diff --git a/tests/fixtures.py b/tests/fixtures.py index d6961409..2ed9bd39 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -106,7 +106,7 @@ def setUp(self): class DistInfoPkgWithDot(OnSysPath, SiteDir): files = { - "pkg.dot-1.0.0.dist-info": { + "pkg_dot-1.0.0.dist-info": { "METADATA": """ Name: pkg.dot Version: 1.0.0 diff --git a/tests/test_api.py b/tests/test_api.py index 3f406ee2..a62e1b88 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -42,8 +42,11 @@ def test_for_name_does_not_exist(self): with self.assertRaises(PackageNotFoundError): distribution('does-not-exist') - def test_for_name_containing_dot(self): - assert distribution('pkg-dot').metadata['Name'] == 'pkg.dot' + def test_name_normalization(self): + names = 'pkg.dot', 'pkg_dot', 'pkg-dot', 'pkg..dot', 'Pkg.Dot' + for name in names: + with self.subTest(name): + assert distribution(name).metadata['Name'] == 'pkg.dot' def test_for_top_level(self): self.assertEqual( From e7d59299d5250c286b038a1131840d76d2007e95 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 22 Nov 2020 05:10:21 -0500 Subject: [PATCH 12/22] Add test to capture legacy expectation where distinfo might appear with a dot in the name. --- tests/fixtures.py | 15 +++++++++++++++ tests/test_api.py | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/tests/fixtures.py b/tests/fixtures.py index 2ed9bd39..0d834c65 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -119,6 +119,21 @@ def setUp(self): build_files(DistInfoPkgWithDot.files, self.site_dir) +class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir): + files = { + "pkg.dot-1.0.0.dist-info": { + "METADATA": """ + Name: pkg.dot + Version: 1.0.0 + """, + }, + } + + def setUp(self): + super(DistInfoPkgWithDotLegacy, self).setUp() + build_files(DistInfoPkgWithDotLegacy.files, self.site_dir) + + class DistInfoPkgOffPath(SiteDir): def setUp(self): super(DistInfoPkgOffPath, self).setUp() diff --git a/tests/test_api.py b/tests/test_api.py index a62e1b88..ca6f11ba 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -163,6 +163,14 @@ def test_more_complex_deps_requires_text(self): assert deps == expected +class LegacyDots(fixtures.DistInfoPkgWithDotLegacy, unittest.TestCase): + def test_name_normalization(self): + names = 'pkg.dot', 'pkg_dot', 'pkg-dot', 'pkg..dot', 'Pkg.Dot' + for name in names: + with self.subTest(name): + assert distribution(name).metadata['Name'] == 'pkg.dot' + + class OffSysPathTests(fixtures.DistInfoPkgOffPath, unittest.TestCase): def test_find_distributions_specified_path(self): dists = Distribution.discover(path=[str(self.site_dir)]) From 1028cb9b9e3f1307cc77a2c7ae48e3049d0839b1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 22 Nov 2020 05:15:36 -0500 Subject: [PATCH 13/22] Support legacy filenames with an unnormalized dot. --- importlib_metadata/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index fc4cf1f8..d5cbc2d0 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -473,7 +473,7 @@ def search(self, name): for child in self.children(): n_low = child.lower() if (n_low in name.exact_matches - or n_low.startswith(name.prefix) + or n_low.replace('.', '_').startswith(name.prefix) and n_low.endswith(name.suffixes) # legacy case: or self.is_egg(name) and n_low == 'egg-info'): From b072a4649dc5a9c4edfa74e9133f7fdff8d2b4b4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 22 Nov 2020 05:25:35 -0500 Subject: [PATCH 14/22] Restore Python 2 compatibility. --- setup.cfg | 1 + tests/test_api.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d993f5cd..fa10c8d3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,6 +41,7 @@ testing = importlib_resources>=1.3; python_version < "3.9" packaging pep517 + unittest2; python_version < "3" docs = sphinx rst.linker diff --git a/tests/test_api.py b/tests/test_api.py index ca6f11ba..efa97996 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,6 +1,10 @@ import re import textwrap -import unittest + +try: + import unittest2 as unittest +except ImportError: + import unittest from . import fixtures from importlib_metadata import ( From 4335defc4efcd827ba300917e74e8e449eda4d50 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 30 Nov 2020 21:46:05 -0500 Subject: [PATCH 15/22] Add test capturing expectation where versionless metadata exists but hasn't been updated to the new normalization technique. Ref #261. --- tests/fixtures.py | 6 ++++++ tests/test_api.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/fixtures.py b/tests/fixtures.py index 0d834c65..71e17275 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -127,6 +127,12 @@ class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir): Version: 1.0.0 """, }, + "pkg.lot.egg-info": { + "METADATA": """ + Name: pkg.lot + Version: 1.0.0 + """, + }, } def setUp(self): diff --git a/tests/test_api.py b/tests/test_api.py index efa97996..3bec91b6 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -174,6 +174,12 @@ def test_name_normalization(self): with self.subTest(name): assert distribution(name).metadata['Name'] == 'pkg.dot' + def test_name_normalization_versionless_egg_info(self): + names = 'pkg.lot', 'pkg_lot', 'pkg-lot', 'pkg..lot', 'Pkg.Lot' + for name in names: + with self.subTest(name): + assert distribution(name).metadata['Name'] == 'pkg.lot' + class OffSysPathTests(fixtures.DistInfoPkgOffPath, unittest.TestCase): def test_find_distributions_specified_path(self): From 313535ae43536aa7907bb3e38fc165a166d2076c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 30 Nov 2020 22:09:16 -0500 Subject: [PATCH 16/22] Extract method for matching a name in a prepared search. --- importlib_metadata/__init__.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index d5cbc2d0..6f0778fb 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -470,14 +470,11 @@ def is_egg(self, search): and base.endswith('.egg')) def search(self, name): - for child in self.children(): - n_low = child.lower() - if (n_low in name.exact_matches - or n_low.replace('.', '_').startswith(name.prefix) - and n_low.endswith(name.suffixes) - # legacy case: - or self.is_egg(name) and n_low == 'egg-info'): - yield self.joinpath(child) + return ( + self.joinpath(child) + for child in self.children() + if name.matches(child, self) + ) class Prepared: @@ -507,6 +504,16 @@ def normalize(name): """ return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') + def matches(self, name, path): + n_low = name.lower() + return ( + n_low in self.exact_matches + or n_low.replace('.', '_').startswith(self.prefix) + and n_low.endswith(self.suffixes) + # legacy case: + or path.is_egg(self) and n_low == 'egg-info' + ) + @install class MetadataPathFinder(NullFinder, DistributionFinder): From fea6e754a1d60c558ef6a8158e73550befb812e9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 30 Nov 2020 22:27:34 -0500 Subject: [PATCH 17/22] Move is_egg into prepared. --- importlib_metadata/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 6f0778fb..ce330aaf 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -462,18 +462,11 @@ def zip_children(self): for child in names ) - def is_egg(self, search): - base = self.base - return ( - base == search.versionless_egg_name - or base.startswith(search.prefix) - and base.endswith('.egg')) - def search(self, name): return ( self.joinpath(child) for child in self.children() - if name.matches(child, self) + if name.matches(child, self.base) ) @@ -504,16 +497,22 @@ def normalize(name): """ return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') - def matches(self, name, path): + def matches(self, name, base): n_low = name.lower() return ( n_low in self.exact_matches or n_low.replace('.', '_').startswith(self.prefix) and n_low.endswith(self.suffixes) # legacy case: - or path.is_egg(self) and n_low == 'egg-info' + or self.is_egg(base) and n_low == 'egg-info' ) + def is_egg(self, base): + return ( + base == self.versionless_egg_name + or base.startswith(self.prefix) + and base.endswith('.egg')) + @install class MetadataPathFinder(NullFinder, DistributionFinder): From 595eb5f6671a40e304ad35791689f4a9feec42bb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 30 Nov 2020 22:47:40 -0500 Subject: [PATCH 18/22] Compare the name against self.normalized. Fixes #261 but also will cause 'lib' to match 'lib_foo'. --- importlib_metadata/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index ce330aaf..a70a48e5 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -497,14 +497,16 @@ def normalize(name): """ return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') - def matches(self, name, base): - n_low = name.lower() + def matches(self, cand, base): + low = cand.lower() + pre, ext = os.path.splitext(low) + name, sep, rest = pre.partition('-') return ( - n_low in self.exact_matches - or n_low.replace('.', '_').startswith(self.prefix) - and n_low.endswith(self.suffixes) + low in self.exact_matches + or name.replace('.', '_').startswith(self.normalized) + and ext in self.suffixes # legacy case: - or self.is_egg(base) and n_low == 'egg-info' + or self.is_egg(base) and low == 'egg-info' ) def is_egg(self, base): From 7bdbb572462cb6626eec5efca5a9899dc5fa6c8b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 1 Dec 2020 19:19:22 -0500 Subject: [PATCH 19/22] Add test to ensure that a prefix isn't matched --- tests/test_api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index 3bec91b6..f36da1c5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -52,6 +52,13 @@ def test_name_normalization(self): with self.subTest(name): assert distribution(name).metadata['Name'] == 'pkg.dot' + def test_prefix_not_matched(self): + prefixes = 'p', 'pkg', 'pkg.' + for prefix in prefixes: + with self.subTest(prefix): + with self.assertRaises(PackageNotFoundError): + distribution(prefix) + def test_for_top_level(self): self.assertEqual( distribution('egginfo-pkg').read_text('top_level.txt').strip(), From 4098b519652fd0cedc2849929b5e8e64005c92ae Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 1 Dec 2020 19:25:56 -0500 Subject: [PATCH 20/22] Perform exact match on Prepared.normalized, and then add a separate check for an empty self.normalized instead of relying on a degenerate result from startswith. --- importlib_metadata/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index a70a48e5..adf57a87 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -474,7 +474,7 @@ class Prepared: """ A prepared search for metadata on a possibly-named package. """ - normalized = '' + normalized = None prefix = '' suffixes = '.dist-info', '.egg-info' exact_matches = [''][:0] @@ -503,8 +503,10 @@ def matches(self, cand, base): name, sep, rest = pre.partition('-') return ( low in self.exact_matches - or name.replace('.', '_').startswith(self.normalized) - and ext in self.suffixes + or ext in self.suffixes and ( + not self.normalized or + name.replace('.', '_') == self.normalized + ) # legacy case: or self.is_egg(base) and low == 'egg-info' ) From 6036a37a728e8732b1d4b93860e1825a803a22c7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 1 Dec 2020 19:54:37 -0500 Subject: [PATCH 21/22] Avoid relying on new-style normalization for legacy eggs. --- importlib_metadata/__init__.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index adf57a87..e296a2c7 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -475,20 +475,16 @@ class Prepared: A prepared search for metadata on a possibly-named package. """ normalized = None - prefix = '' suffixes = '.dist-info', '.egg-info' exact_matches = [''][:0] - versionless_egg_name = '' def __init__(self, name): self.name = name if name is None: return self.normalized = self.normalize(name) - self.prefix = self.normalized + '-' self.exact_matches = [ self.normalized + suffix for suffix in self.suffixes] - self.versionless_egg_name = self.normalized + '.egg' @staticmethod def normalize(name): @@ -497,6 +493,14 @@ def normalize(name): """ return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') + @staticmethod + def legacy_normalize(name): + """ + Normalize the package name as found in the convention in + older packaging tools versions and specs. + """ + return name.lower().replace('-', '_') + def matches(self, cand, base): low = cand.lower() pre, ext = os.path.splitext(low) @@ -512,9 +516,12 @@ def matches(self, cand, base): ) def is_egg(self, base): + normalized = self.legacy_normalize(self.name or '') + prefix = normalized + '-' if normalized else '' + versionless_egg_name = normalized + '.egg' if self.name else '' return ( - base == self.versionless_egg_name - or base.startswith(self.prefix) + base == versionless_egg_name + or base.startswith(prefix) and base.endswith('.egg')) From 4cb3bd016f4e18f6fb9e564041a7d288a9a5ccf6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 1 Dec 2020 20:40:21 -0500 Subject: [PATCH 22/22] Update changelog. Ref #261. --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 39653574..532ec971 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,13 @@ importlib_metadata NEWS ========================= +v2.1.1 +====== + +* #261: Restored compatibility for package discovery for + metadata without version in the name and for legacy + eggs. + v2.1.0 ======