Skip to content

Commit

Permalink
fix: Handle the legacy specifiers
Browse files Browse the repository at this point in the history
Close #1719
  • Loading branch information
frostming committed Feb 18, 2023
1 parent 2c2bf3b commit 67dacbc
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 55 deletions.
1 change: 1 addition & 0 deletions news/1719.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Handle the legacy specifiers that is unable to parse with packaging>22.0.
23 changes: 2 additions & 21 deletions src/pdm/models/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from pdm.models.backends import BuildBackend, get_relative_path
from pdm.models.markers import Marker, get_marker, split_marker_extras
from pdm.models.setup import Setup
from pdm.models.specifiers import PySpecSet, get_specifier
from pdm.models.specifiers import PySpecSet, fix_legacy_specifier, get_specifier
from pdm.utils import (
PACKAGING_22,
add_ssh_scheme_to_git_uri,
Expand All @@ -38,8 +38,6 @@
)

if TYPE_CHECKING:
from typing import Match

from pdm._types import RequirementDict


Expand Down Expand Up @@ -443,31 +441,14 @@ def filter_requirements_with_extras(
return result


_legacy_specifier_re = re.compile(r"(==|!=|<=|>=|<|>)(\s*)([^,;\s)]*)")


def parse_as_pkg_requirement(line: str) -> PackageRequirement:
"""Parse a requirement line as packaging.requirement.Requirement"""

def fix_wildcard(match: Match[str]) -> str:
operator, _, version = match.groups()
if ".*" not in version or operator in ("==", "!="):
return match.group(0)
version = version.replace(".*", ".0")
if operator in ("<", "<="): # <4.* and <=4.* are equivalent to <4.0
operator = "<"
elif operator in (">", ">="): # >4.* and >=4.* are equivalent to >=4.0
operator = ">="
return f"{operator}{version}"

try:
return PackageRequirement(line)
except InvalidRequirement:
if not PACKAGING_22: # We can't do anything, reraise the error.
raise
# Since packaging 22.0, legacy specifiers like '>=4.*' are no longer
# supported. We try to normalize them to the new format.
new_line = _legacy_specifier_re.sub(fix_wildcard, line)
new_line = fix_legacy_specifier(line)
return PackageRequirement(new_line)


Expand Down
38 changes: 31 additions & 7 deletions src/pdm/models/specifiers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

import json
import re
from functools import lru_cache
from operator import attrgetter
from typing import Any, Iterable, cast
from typing import Any, Iterable, Match, cast

from packaging.specifiers import InvalidSpecifier, SpecifierSet

Expand All @@ -27,6 +28,29 @@ def get_specifier(version_str: SpecifierSet | str) -> SpecifierSet:
return SpecifierSet(version_str)


_legacy_specifier_re = re.compile(r"(==|!=|<=|>=|<|>)(\s*)([^,;\s)]*)")


@lru_cache()
def fix_legacy_specifier(specifier: str) -> str:
"""Since packaging 22.0, legacy specifiers like '>=4.*' are no longer
supported. We try to normalize them to the new format.
"""

def fix_wildcard(match: Match[str]) -> str:
operator, _, version = match.groups()
if ".*" not in version or operator in ("==", "!="):
return match.group(0)
version = version.replace(".*", ".0")
if operator in ("<", "<="): # <4.* and <=4.* are equivalent to <4.0
operator = "<"
elif operator in (">", ">="): # >4.* and >=4.* are equivalent to >=4.0
operator = ">="
return f"{operator}{version}"

return _legacy_specifier_re.sub(fix_wildcard, specifier)


def _normalize_op_specifier(op: str, version_str: str) -> tuple[str, Version]:
version = Version(version_str)
if version.is_wildcard:
Expand Down Expand Up @@ -58,17 +82,17 @@ class PySpecSet(SpecifierSet):
PY_MAX_MINOR_VERSION = _read_max_versions()
MAX_MAJOR_VERSION = max(PY_MAX_MINOR_VERSION)[:1].bump()

def __init__(self, version_str: str = "", analyze: bool = True) -> None:
if version_str == "*":
version_str = ""
def __init__(self, specifiers: str = "", analyze: bool = True) -> None:
if specifiers == "*":
specifiers = ""
try:
super().__init__(version_str)
super().__init__(fix_legacy_specifier(specifiers))
except InvalidSpecifier as e:
raise InvalidPyVersion(str(e)) from e
self._lower_bound = Version.MIN
self._upper_bound = Version.MAX
self._excludes: list[Version] = []
if version_str and analyze:
if specifiers and analyze:
self._analyze_specifiers()

def _analyze_specifiers(self) -> None:
Expand Down Expand Up @@ -379,7 +403,7 @@ class ImpossiblePySpecSet(PySpecSet):
"""

def __init__(self, version_str: str = "", analyze: bool = True) -> None:
super().__init__(version_str=version_str, analyze=False)
super().__init__(specifiers=version_str, analyze=False)
# Make sure the spec set is impossible
self._lower_bound = Version.MAX
self._upper_bound = Version.MIN
Expand Down
32 changes: 5 additions & 27 deletions tests/models/test_specifiers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest

from pdm.models.specifiers import PySpecSet
from pdm.utils import PACKAGING_22


@pytest.mark.parametrize(
Expand All @@ -20,26 +19,10 @@
(">=3.6,<3.8,!=3.8.*", ">=3.6,<3.8"),
(">=2.7,<3.2,!=3.0.*,!=3.1.*", ">=2.7,<3.0"),
("!=3.0.*,!=3.0.2", "!=3.0.*"),
pytest.param(
">=3.4.*",
">=3.4",
marks=pytest.mark.xfail(PACKAGING_22, reason="packaging 22.0 doesn't parse it"),
),
pytest.param(
">3.4.*",
">=3.5",
marks=pytest.mark.xfail(PACKAGING_22, reason="packaging 22.0 doesn't parse it"),
),
pytest.param(
"<=3.4.*",
"<3.4",
marks=pytest.mark.xfail(PACKAGING_22, reason="packaging 22.0 doesn't parse it"),
),
pytest.param(
"<3.4.*",
"<3.4",
marks=pytest.mark.xfail(PACKAGING_22, reason="packaging 22.0 doesn't parse it"),
),
(">=3.4.*", ">=3.4"),
(">3.4.*", ">=3.4"),
("<=3.4.*", "<3.4"),
("<3.4.*", "<3.4"),
("<3.10.0a6", "<3.10.0a6"),
("<3.10.2a3", "<3.10.2a3"),
],
Expand Down Expand Up @@ -108,12 +91,7 @@ def test_impossible_pyspec():
">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*",
">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
),
pytest.param(
# 11.* normalizes to 11.0
">=3.11.*",
">=3.11.0rc",
marks=pytest.mark.xfail(PACKAGING_22, reason="packaging 22.0 doesn't parse it"),
),
(">=3.11.*", ">=3.11.0rc"),
],
)
def test_pyspec_is_subset_superset(left, right):
Expand Down

0 comments on commit 67dacbc

Please sign in to comment.