From 89abbd3cf19ae94cad595dd1fa32734529ed894c Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Sun, 16 Feb 2025 14:33:38 +0000 Subject: [PATCH 1/2] Experimental implementation of supporting [] disabling default extras --- src/pip/_internal/req/constructors.py | 9 ++++++++- src/pip/_internal/resolution/legacy/resolver.py | 14 ++++++++------ .../_internal/resolution/resolvelib/candidates.py | 14 ++++++++------ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index 56a964f3177..ce5d7af79bb 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -31,10 +31,13 @@ from pip._internal.utils.urls import path_to_url from pip._internal.vcs import is_url, vcs +EXPLICIT_EMPTY_EXTRAS = 'explicit-no-default-extras' + __all__ = [ "install_req_from_editable", "install_req_from_line", "parse_editable", + "EXPLICIT_EMPTY_EXTRAS" ] logger = logging.getLogger(__name__) @@ -48,7 +51,11 @@ def _strip_extras(path: str) -> Tuple[str, Optional[str]]: path_no_extras = m.group(1) extras = m.group(2) else: - path_no_extras = path + if '[]' in path: + extras = f'[{EXPLICIT_EMPTY_EXTRAS}]' + path_no_extras = path.replace('[]', '') + else: + path_no_extras = path return path_no_extras, extras diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index 1dd0d7041bb..a4f168a706b 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -38,6 +38,7 @@ InstallRequirement, check_invalid_constraint_type, ) +from pip._internal.req.constructors import EXPLICIT_EMPTY_EXTRAS from pip._internal.req.req_set import RequirementSet from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider from pip._internal.utils import compatibility_tags @@ -552,12 +553,13 @@ def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None: set(req_to_install.extras) - set(dist.iter_provided_extras()) ) for missing in missing_requested: - logger.warning( - "%s %s does not provide the extra '%s'", - dist.raw_name, - dist.version, - missing, - ) + if missing != EXPLICIT_EMPTY_EXTRAS: + logger.warning( + "%s %s does not provide the extra '%s'", + dist.raw_name, + dist.version, + missing, + ) available_requested = sorted( set(dist.iter_provided_extras()) & set(req_to_install.extras) diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 7f5a62b08e6..00a839bc61a 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -21,6 +21,7 @@ install_req_from_line, ) from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.constructors import EXPLICIT_EMPTY_EXTRAS from pip._internal.utils.direct_url_helpers import direct_url_from_link from pip._internal.utils.misc import normalize_version_info @@ -515,12 +516,13 @@ def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requiremen valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras()) invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras()) for extra in sorted(invalid_extras): - logger.warning( - "%s %s does not provide the extra '%s'", - self.base.name, - self.version, - extra, - ) + if extra != EXPLICIT_EMPTY_EXTRAS: + logger.warning( + "%s %s does not provide the extra '%s'", + self.base.name, + self.version, + extra, + ) for r in self.base.dist.iter_dependencies(valid_extras): yield from factory.make_requirements_from_spec( From fe2189caf7ebde2c5ded3c7d705f977d76030d0e Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Wed, 4 Jun 2025 14:50:18 +0100 Subject: [PATCH 2/2] Fix cases of package[...]==version --- src/pip/_internal/resolution/resolvelib/requirements.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/resolution/resolvelib/requirements.py b/src/pip/_internal/resolution/resolvelib/requirements.py index b04f41b2191..5e3b327e93b 100644 --- a/src/pip/_internal/resolution/resolvelib/requirements.py +++ b/src/pip/_internal/resolution/resolvelib/requirements.py @@ -3,7 +3,7 @@ from pip._vendor.packaging.specifiers import SpecifierSet from pip._vendor.packaging.utils import NormalizedName, canonicalize_name -from pip._internal.req.constructors import install_req_drop_extras +from pip._internal.req.constructors import install_req_drop_extras, EXPLICIT_EMPTY_EXTRAS from pip._internal.req.req_install import InstallRequirement from .base import Candidate, CandidateLookup, Requirement, format_name @@ -128,6 +128,8 @@ class SpecifierWithoutExtrasRequirement(SpecifierRequirement): def __init__(self, ireq: InstallRequirement) -> None: assert ireq.link is None, "This is a link, not a specifier" self._ireq = install_req_drop_extras(ireq) + if ireq.extras: + self._ireq.extras = {EXPLICIT_EMPTY_EXTRAS} self._equal_cache: Optional[str] = None self._hash: Optional[int] = None self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)