Skip to content

Commit

Permalink
Remove deprecated dependency pkg_resources
Browse files Browse the repository at this point in the history
PipRequirement relied on pkg_resources which is deprecated. This commit removes it in favor of using packaging.Requirement directly.

This change allows us to completely remove the setuptools dependency from the requirements files.

Signed-off-by: Ian Rocha <[email protected]>
  • Loading branch information
Ian Rocha authored and brunoapimentel committed Sep 4, 2024
1 parent 6b4de55 commit f21f732
Show file tree
Hide file tree
Showing 6 changed files with 16 additions and 45 deletions.
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ pip-compile:
"apk add git && \
pip3 install pip-tools && \
pip-compile \
--allow-unsafe \
--generate-hashes \
--output-file=requirements.txt \
pyproject.toml && \
Expand Down
38 changes: 11 additions & 27 deletions cachi2/core/package_managers/pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
Literal,
Optional,
Pattern,
Sequence,
Union,
cast,
no_type_check,
Expand All @@ -38,11 +37,10 @@
if TYPE_CHECKING:
from typing_extensions import TypeGuard

import pkg_resources
import pypi_simple
import requests
from packaging.utils import canonicalize_version
from pkg_resources import Requirement, RequirementParseError
from packaging.requirements import InvalidRequirement, Requirement
from packaging.utils import canonicalize_name, canonicalize_version

from cachi2.core.checksum import ChecksumInfo, must_match_any_checksum
from cachi2.core.config import get_config
Expand Down Expand Up @@ -321,7 +319,7 @@ def _get_pip_metadata(package_dir: RootedPath) -> tuple[str, Optional[str]]:
package_subpath = package_dir.subpath_from_root

resolved_path = Path(repo_name).joinpath(package_subpath)
normalized_path = pkg_resources.safe_name(str(resolved_path))
normalized_path = canonicalize_name(str(resolved_path).replace("/", "-"))
name = normalized_path.strip("-.")
except UnsupportedFeature:
raise PackageRejected(
Expand Down Expand Up @@ -362,7 +360,7 @@ def _any_to_version(obj: Any) -> str:
else:
version = str(version)

return pkg_resources.safe_version(version)
return canonicalize_version(version, strip_trailing_zero=False)


def _get_top_level_attr(
Expand Down Expand Up @@ -1310,32 +1308,18 @@ def from_line(cls, line: str, options: list[str]) -> Optional["PipRequirement"]:
requirement.kind = "pypi"

try:
parsed: Sequence[Requirement] = list(pkg_resources.parse_requirements(to_be_parsed))
except (
RequirementParseError,
pkg_resources.extern.packaging.requirements.InvalidRequirement,
) as exc:
req = Requirement(to_be_parsed)
except InvalidRequirement as exc:
# see https://github.com/pypa/setuptools/pull/2137
raise UnexpectedFormat(f"Unable to parse the requirement {to_be_parsed!r}: {exc}")

if not parsed:
return None
# parse_requirements is able to process a multi-line string, thus returning multiple
# parsed requirements. However, since it cannot handle the additional syntax from a
# requirements file, we parse each line individually. The conditional below should
# never be reached, but is left here to aid diagnosis in case this assumption is
# not correct.
if len(parsed) > 1:
raise RuntimeError(f"Didn't expect to find multiple requirements in: {line!r}")
req: Requirement = parsed[0]

hashes, options = cls._split_hashes_from_options(options)

requirement.download_line = to_be_parsed
requirement.options = options
requirement.package = req.project_name
requirement.package = canonicalize_name(req.name)
requirement.raw_package = req.name
requirement.version_specs = req.specs
requirement.version_specs = [(spec.operator, spec.version) for spec in req.specifier]
requirement.extras = req.extras
requirement.environment_marker = str(req.marker) if req.marker else None
requirement.hashes = hashes
Expand Down Expand Up @@ -1397,13 +1381,13 @@ def _assess_direct_access_requirement(line: str) -> tuple[Optional[str], bool]:
def _adjust_direct_access_requirement(
line: str, direct_ref_pattern: Pattern[str]
) -> tuple[str, dict[str, str]]:
"""Modify the requirement line so it can be parsed by pkg_resources and extract qualifiers.
"""Modify the requirement line so it can be parsed and extract qualifiers.
:param str line: a direct access requirement line
:param str direct_ref_pattern: a Regex used to determine if a requirement
specifies a package name
:return: two-item tuple where the first item is a modified direct access requirement
line that can be parsed by pkg_resources, and the second item is a dict of the
line that can be parsed, and the second item is a dict of the
qualifiers extracted from the direct access URL
"""
package_name = None
Expand Down Expand Up @@ -1443,7 +1427,7 @@ def _adjust_direct_access_requirement(
requirement_parts = [package_name.strip(), "@", url.strip()]
if environment_marker:
# Although a space before the semicolon is not needed by pip, it is needed when
# using pkg_resources later on.
# using packaging later on.
requirement_parts.append(";")
requirement_parts.append(environment_marker.strip())
return " ".join(requirement_parts), qualifiers
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ dependencies = [
"pyyaml",
"requests",
"semver",
"setuptools",
"tomli",
"typer",
"createrepo-c",
Expand Down
6 changes: 0 additions & 6 deletions requirements-extras.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1079,9 +1079,3 @@ yarl==1.9.4 \
--hash=sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749 \
--hash=sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec
# via aiohttp

# The following packages are considered to be unsafe in a requirements file:
setuptools==70.2.0 \
--hash=sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05 \
--hash=sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1
# via cachi2 (pyproject.toml)
8 changes: 1 addition & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements.txt pyproject.toml
# pip-compile --generate-hashes --output-file=requirements.txt pyproject.toml
#
aiohappyeyeballs==2.3.4 \
--hash=sha256:40a16ceffcf1fc9e142fd488123b2e218abc4188cf12ac20c67200e1579baa42 \
Expand Down Expand Up @@ -736,9 +736,3 @@ yarl==1.9.4 \
--hash=sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749 \
--hash=sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec
# via aiohttp

# The following packages are considered to be unsafe in a requirements file:
setuptools==70.2.0 \
--hash=sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05 \
--hash=sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1
# via cachi2 (pyproject.toml)
7 changes: 4 additions & 3 deletions tests/unit/package_managers/test_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -2205,10 +2205,11 @@ def test_parsing_of_invalid_cases(

def test_corner_cases_when_parsing_single_line(self) -> None:
"""Test scenarios in PipRequirement that cannot be triggered via PipRequirementsFile."""
# Empty lines are ignored
assert pip.PipRequirement.from_line(" ", []) is None
# Empty lines are NOT ignored
with pytest.raises(UnexpectedFormat, match="Unable to parse the requirement"):
assert pip.PipRequirement.from_line(" ", []) is None

with pytest.raises(RuntimeError, match="Didn't expect to find multiple requirements in:"):
with pytest.raises(UnexpectedFormat, match="Unable to parse the requirement"):
pip.PipRequirement.from_line("aiowsgi==0.7 \nasn1crypto==1.3.0", [])

def test_replace_requirements(self, rooted_tmp_path: RootedPath) -> None:
Expand Down

0 comments on commit f21f732

Please sign in to comment.