diff --git a/changelog/736.feature.rst b/changelog/736.feature.rst new file mode 100644 index 00000000..e6ce4e10 --- /dev/null +++ b/changelog/736.feature.rst @@ -0,0 +1 @@ +Eliminated dependency on Setuptools/pkg_resources and replaced with packaging and importlib_metadata. diff --git a/mypy.ini b/mypy.ini index 504e01fe..81394f10 100644 --- a/mypy.ini +++ b/mypy.ini @@ -22,6 +22,9 @@ strict_equality = True ; https://github.com/tartley/colorama/issues/206 ignore_missing_imports = True +[mypy-packaging] +ignore_missing_imports = True + [mypy-pkginfo] ; https://bugs.launchpad.net/pkginfo/+bug/1876591 ignore_missing_imports = True @@ -37,10 +40,6 @@ ignore_missing_imports = True [mypy-rfc3986] ignore_missing_imports = True -[mypy-setuptools] -; https://github.com/python/typeshed/issues/2171 -ignore_missing_imports = True - [mypy-tqdm] ; https://github.com/tqdm/tqdm/issues/260 ignore_missing_imports = True diff --git a/setup.cfg b/setup.cfg index f47de473..46f8fe2b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,7 @@ install_requires= readme_renderer >= 21.0 requests >= 2.20 requests-toolbelt >= 0.8.0, != 0.9.0 - setuptools >= 0.7.0 + packaging >= 16.2 tqdm >= 4.14 importlib_metadata >= 3.6 keyring >= 15.1 diff --git a/tests/fixtures/twine-3.3.0-py3.9.egg b/tests/fixtures/twine-3.3.0-py3.9.egg new file mode 100644 index 00000000..d0088a5e Binary files /dev/null and b/tests/fixtures/twine-3.3.0-py3.9.egg differ diff --git a/tests/test_package.py b/tests/test_package.py index b9ea4335..c6de1976 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -264,3 +264,8 @@ def test_malformed_from_file(monkeypatch): package_file.PackageFile.from_filename(filename, comment=None) assert "Invalid distribution file" in err.value.args[0] + + +def test_package_from_egg(): + filename = "tests/fixtures/twine-3.3.0-py3.9.egg" + package_file.PackageFile.from_filename(filename, comment=None) diff --git a/tests/test_repository.py b/tests/test_repository.py index 97639835..91c0d19a 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -89,7 +89,14 @@ def test_make_user_agent_string(default_repo): assert "User-Agent" in default_repo.session.headers user_agent = default_repo.session.headers["User-Agent"] - packages = ("twine/", "requests/", "requests-toolbelt/", "pkginfo/", "setuptools/") + packages = ( + "twine/", + "requests/", + "requests-toolbelt/", + "pkginfo/", + "importlib_metadata/", + "packaging/", + ) assert all(p in user_agent for p in packages) diff --git a/twine/_installed.py b/twine/_installed.py deleted file mode 100644 index d2dba83b..00000000 --- a/twine/_installed.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2013 Tres Seaver -# Copyright 2015 Ian Cordasco -# This code was originally licensed under the Python Software Foudation -# License by Tres Seaver. In order to facilitate finding the metadata of -# installed packages, part of the most current implementation of the -# pkginfo.Installed class is reproduced here with bug fixes from -# https://bugs.launchpad.net/pkginfo/+bug/1437570. -import glob -import os -import sys -import warnings -from typing import Optional - -import pkginfo - - -class Installed(pkginfo.Installed): - def read(self) -> Optional[str]: - opj = os.path.join - if self.package is not None: - package = self.package.__package__ - if package is None: - package = self.package.__name__ - egg_pattern = "%s*.egg-info" % package - dist_pattern = "%s*.dist-info" % package - file: Optional[str] = getattr(self.package, "__file__", None) - if file is not None: - candidates = [] - - def _add_candidate(where: str) -> None: - candidates.extend(glob.glob(where)) - - for entry in sys.path: - if file.startswith(entry): - _add_candidate(opj(entry, "METADATA")) # egg? - _add_candidate(opj(entry, "EGG-INFO")) # egg? - # dist-installed? - _add_candidate(opj(entry, egg_pattern)) - _add_candidate(opj(entry, dist_pattern)) - dir, name = os.path.split(self.package.__file__) - _add_candidate(opj(dir, egg_pattern)) - _add_candidate(opj(dir, dist_pattern)) - _add_candidate(opj(dir, "..", egg_pattern)) - _add_candidate(opj(dir, "..", dist_pattern)) - - for candidate in candidates: - if os.path.isdir(candidate): - path = opj(candidate, "PKG-INFO") - if not os.path.exists(path): - path = opj(candidate, "METADATA") - else: - path = candidate - if os.path.exists(path): - with open(path) as f: - return f.read() - - warnings.warn( - "No PKG-INFO or METADATA found for package: %s" % self.package_name - ) - return None diff --git a/twine/cli.py b/twine/cli.py index e0dc0fc1..cf3efdf2 100644 --- a/twine/cli.py +++ b/twine/cli.py @@ -14,27 +14,24 @@ import argparse from typing import Any, List, Tuple -import pkginfo -import requests -import requests_toolbelt -import setuptools -import tqdm from importlib_metadata import entry_points +from importlib_metadata import version import twine -from twine import _installed args = argparse.Namespace() def list_dependencies_and_versions() -> List[Tuple[str, str]]: - return [ - ("pkginfo", _installed.Installed(pkginfo).version), - ("requests", requests.__version__), - ("setuptools", setuptools.__version__), - ("requests-toolbelt", requests_toolbelt.__version__), - ("tqdm", tqdm.__version__), - ] + deps = ( + "importlib_metadata", + "packaging", + "pkginfo", + "requests", + "requests-toolbelt", + "tqdm", + ) + return [(dep, version(dep)) for dep in deps] # type: ignore[no-untyped-call] # python/importlib_metadata#288 # noqa: E501 def dep_versions() -> str: diff --git a/twine/package.py b/twine/package.py index 045ad81d..0b68bc9c 100644 --- a/twine/package.py +++ b/twine/package.py @@ -17,7 +17,8 @@ import subprocess from typing import Dict, NamedTuple, Optional, Sequence, Tuple, Union -import pkg_resources +import importlib_metadata +import packaging.utils import pkginfo from twine import exceptions @@ -58,7 +59,7 @@ def __init__( self.metadata = metadata self.python_version = python_version self.filetype = filetype - self.safe_name = pkg_resources.safe_name(metadata.name) + self.safe_name = packaging.utils.canonicalize_name(metadata.name) self.signed_filename = self.filename + ".asc" self.signed_basefilename = self.basefilename + ".asc" self.gpg_signature: Optional[Tuple[str, bytes]] = None @@ -99,8 +100,10 @@ def from_filename(cls, filename: str, comment: Optional[str]) -> "PackageFile": py_version: Optional[str] if dtype == "bdist_egg": - pkgd = pkg_resources.Distribution.from_filename(filename) - py_version = pkgd.py_version + (dist,) = importlib_metadata.Distribution.discover( # type: ignore[no-untyped-call] # python/importlib_metadata#288 # noqa: E501 + path=[filename] + ) + py_version = dist.metadata["Version"] elif dtype == "bdist_wheel": py_version = meta.py_version elif dtype == "bdist_wininst":