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 be7a2e8
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 28 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

0 comments on commit be7a2e8

Please sign in to comment.