Skip to content

Commit a45a74f

Browse files
committed
Warn when legacy versions and specifiers are resolved
Also warn in pip check. ...
1 parent b0a8317 commit a45a74f

File tree

7 files changed

+84
-0
lines changed

7 files changed

+84
-0
lines changed

news/12063.removal.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Deprecate legacy version and version specifiers that don't conform to `PEP 440
2+
<https://peps.python.org/pep-0440/>`_

src/pip/_internal/commands/check.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pip._internal.operations.check import (
88
check_package_set,
99
create_package_set_from_installed,
10+
warn_legacy_versions_and_specifiers,
1011
)
1112
from pip._internal.utils.misc import write_output
1213

@@ -21,6 +22,7 @@ class CheckCommand(Command):
2122

2223
def run(self, options: Values, args: List[str]) -> int:
2324
package_set, parsing_probs = create_package_set_from_installed()
25+
warn_legacy_versions_and_specifiers(package_set)
2426
missing, conflicting = check_package_set(package_set)
2527

2628
for project_name in missing:

src/pip/_internal/commands/download.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ def run(self, options: Values, args: List[str]) -> int:
130130
self.trace_basic_info(finder)
131131

132132
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
133+
requirement_set.warn_legacy_versions_and_specifiers()
133134

134135
downloaded: List[str] = []
135136
for req in requirement_set.requirements.values():

src/pip/_internal/commands/install.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,9 @@ def run(self, options: Values, args: List[str]) -> int:
387387
json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
388388

389389
if options.dry_run:
390+
# In non dry-run mode, the legacy versions and specifiers check
391+
# will be done as part of conflict detection.
392+
requirement_set.warn_legacy_versions_and_specifiers()
390393
would_install_items = sorted(
391394
(r.metadata["name"], r.metadata["version"])
392395
for r in requirement_set.requirements_to_install

src/pip/_internal/commands/wheel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ def run(self, options: Values, args: List[str]) -> int:
145145
self.trace_basic_info(finder)
146146

147147
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
148+
requirement_set.warn_legacy_versions_and_specifiers()
148149

149150
reqs_to_build: List[InstallRequirement] = []
150151
for req in requirement_set.requirements.values():

src/pip/_internal/operations/check.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
from typing import Callable, Dict, List, NamedTuple, Optional, Set, Tuple
66

77
from pip._vendor.packaging.requirements import Requirement
8+
from pip._vendor.packaging.specifiers import LegacySpecifier
89
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
10+
from pip._vendor.packaging.version import LegacyVersion
911

1012
from pip._internal.distributions import make_distribution_for_install_requirement
1113
from pip._internal.metadata import get_default_environment
1214
from pip._internal.metadata.base import DistributionVersion
1315
from pip._internal.req.req_install import InstallRequirement
16+
from pip._internal.utils.deprecation import deprecated
1417

1518
logger = logging.getLogger(__name__)
1619

@@ -57,6 +60,8 @@ def check_package_set(
5760
package name and returns a boolean.
5861
"""
5962

63+
warn_legacy_versions_and_specifiers(package_set)
64+
6065
missing = {}
6166
conflicting = {}
6267

@@ -147,3 +152,36 @@ def _create_whitelist(
147152
break
148153

149154
return packages_affected
155+
156+
157+
def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None:
158+
for project_name, package_details in package_set.items():
159+
if isinstance(package_details.version, LegacyVersion):
160+
deprecated(
161+
reason=(
162+
f"{project_name} {package_details.version} "
163+
f"has a non-standard version number."
164+
),
165+
replacement=(
166+
f"to upgrade to a newer version of {project_name} "
167+
f"or contact the author to suggest that they "
168+
f"release a version with a conforming version number"
169+
),
170+
issue=12063,
171+
gone_in="23.3",
172+
)
173+
for dep in package_details.dependencies:
174+
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
175+
deprecated(
176+
reason=(
177+
f"{project_name} {package_details.version} "
178+
f"has a non-standard dependency specifier {dep}."
179+
),
180+
replacement=(
181+
f"to upgrade to a newer version of {project_name} "
182+
f"or contact the author to suggest that they "
183+
f"release a version with a conforming dependency specifiers"
184+
),
185+
issue=12063,
186+
gone_in="23.3",
187+
)

src/pip/_internal/req/req_set.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
from collections import OrderedDict
33
from typing import Dict, List
44

5+
from pip._vendor.packaging.specifiers import LegacySpecifier
56
from pip._vendor.packaging.utils import canonicalize_name
7+
from pip._vendor.packaging.version import LegacyVersion
68

79
from pip._internal.req.req_install import InstallRequirement
10+
from pip._internal.utils.deprecation import deprecated
811

912
logger = logging.getLogger(__name__)
1013

@@ -80,3 +83,37 @@ def requirements_to_install(self) -> List[InstallRequirement]:
8083
for install_req in self.all_requirements
8184
if not install_req.constraint and not install_req.satisfied_by
8285
]
86+
87+
def warn_legacy_versions_and_specifiers(self) -> None:
88+
for req in self.requirements_to_install:
89+
version = req.get_dist().version
90+
if isinstance(version, LegacyVersion):
91+
deprecated(
92+
reason=(
93+
f"pip has selected the non standard version {version} "
94+
f"of {req}. In the future this version will be "
95+
f"ignored as it isn't standard compliant."
96+
),
97+
replacement=(
98+
"set or update constraints to select another version "
99+
"or contact the package author to fix the version number"
100+
),
101+
issue=12063,
102+
gone_in="23.3",
103+
)
104+
for dep in req.get_dist().iter_dependencies():
105+
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
106+
deprecated(
107+
reason=(
108+
f"pip has selected {req} {version} which has non "
109+
f"standard dependency specifier {dep}. "
110+
f"In the future this version of {req} will be "
111+
f"ignored as it isn't standard compliant."
112+
),
113+
replacement=(
114+
"set or update constraints to select another version "
115+
"or contact the package author to fix the version number"
116+
),
117+
issue=12063,
118+
gone_in="23.3",
119+
)

0 commit comments

Comments
 (0)