Skip to content

Commit

Permalink
Warn when legacy versions and specifiers are resolved
Browse files Browse the repository at this point in the history
Also warn in pip check.

...
  • Loading branch information
sbidoul committed Jun 26, 2023
1 parent b0a8317 commit b345823
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 0 deletions.
2 changes: 2 additions & 0 deletions news/12063.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Deprecate legacy version and version specifiers that don't conform to `PEP 440
<https://peps.python.org/pep-0440/>`_
2 changes: 2 additions & 0 deletions src/pip/_internal/commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pip._internal.operations.check import (
check_package_set,
create_package_set_from_installed,
warn_legacy_versions_and_specifiers,
)
from pip._internal.utils.misc import write_output

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

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

for project_name in missing:
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def run(self, options: Values, args: List[str]) -> int:
self.trace_basic_info(finder)

requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
requirement_set.warn_legacy_versions_and_specifiers()

downloaded: List[str] = []
for req in requirement_set.requirements.values():
Expand Down
3 changes: 3 additions & 0 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ def run(self, options: Values, args: List[str]) -> int:
json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)

if options.dry_run:
# In non dry-run mode, the legacy versions and specifiers check
# will be done as part of conflict detection.
requirement_set.warn_legacy_versions_and_specifiers()
would_install_items = sorted(
(r.metadata["name"], r.metadata["version"])
for r in requirement_set.requirements_to_install
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def run(self, options: Values, args: List[str]) -> int:
self.trace_basic_info(finder)

requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
requirement_set.warn_legacy_versions_and_specifiers()

reqs_to_build: List[InstallRequirement] = []
for req in requirement_set.requirements.values():
Expand Down
36 changes: 36 additions & 0 deletions src/pip/_internal/operations/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
from typing import Callable, Dict, List, NamedTuple, Optional, Set, Tuple

from pip._vendor.packaging.requirements import Requirement
from pip._vendor.packaging.specifiers import LegacySpecifier
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.packaging.version import LegacyVersion

from pip._internal.distributions import make_distribution_for_install_requirement
from pip._internal.metadata import get_default_environment
from pip._internal.metadata.base import DistributionVersion
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.deprecation import deprecated

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -57,6 +60,8 @@ def check_package_set(
package name and returns a boolean.
"""

warn_legacy_versions_and_specifiers(package_set)

missing = {}
conflicting = {}

Expand Down Expand Up @@ -147,3 +152,34 @@ def _create_whitelist(
break

return packages_affected


def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None:
for project_name, package_details in package_set.items():
if isinstance(package_details.version, LegacyVersion):
deprecated(
reason=(
f"{project_name} {package_details.version} "
f"has a non-standard version number."
),
replacement=(
f"to upgrade to a newer version of {project_name} "
f"or contact the author to suggest that they "
f"release a version with a conforming version number"
),
gone_in="23.3",
)
for dep in package_details.dependencies:
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
deprecated(
reason=(
f"{project_name} {package_details.version} "
f"has a non-standard dependency specifier {dep}."
),
replacement=(
f"to upgrade to a newer version of {project_name} "
f"or contact the author to suggest that they "
f"release a version with a conforming dependency specifiers"
),
gone_in="23.3",
)
35 changes: 35 additions & 0 deletions src/pip/_internal/req/req_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
from collections import OrderedDict
from typing import Dict, List

from pip._vendor.packaging.specifiers import LegacySpecifier
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import LegacyVersion

from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.deprecation import deprecated

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -80,3 +83,35 @@ def requirements_to_install(self) -> List[InstallRequirement]:
for install_req in self.all_requirements
if not install_req.constraint and not install_req.satisfied_by
]

def warn_legacy_versions_and_specifiers(self) -> None:
for req in self.requirements_to_install:
version = req.get_dist().version
if isinstance(version, LegacyVersion):
deprecated(
reason=(
f"pip has selected the non standard version {version} "
f"of {req}. In the future this version will be "
f"ignored as it isn't standard compliant."
),
replacement=(
"set or update constraints to select another version "
"or contact the package author to fix the version number"
),
gone_in="23.3",
)
for dep in req.get_dist().iter_dependencies():
if isinstance(dep.specifier, LegacySpecifier):
deprecated(
reason=(
f"pip has selected {req} {version} which has non "
f"standard dependency specifier {dep.specifier}. "
f"In the future this version of {req} will be "
f"ignored as it isn't standard compliant."
),
replacement=(
"set or update constraints to select another version "
"or contact the package author to fix the version number"
),
gone_in="23.3",
)

0 comments on commit b345823

Please sign in to comment.