Skip to content

Commit

Permalink
refinements
Browse files Browse the repository at this point in the history
  • Loading branch information
sanderr committed Jun 22, 2023
1 parent 937d8f0 commit 5f8f40e
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 28 deletions.
3 changes: 2 additions & 1 deletion src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,8 @@ def install_req_without(
req.specifier = SpecifierSet(prereleases=req.specifier.prereleases)
return InstallRequirement(
req=req,
comes_from=ireq.comes_from,
# TODO: document this!!!!
comes_from=ireq,
editable=ireq.editable,
link=ireq.link,
markers=ireq.markers,
Expand Down
15 changes: 13 additions & 2 deletions src/pip/_internal/resolution/resolvelib/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ def _prepare(self) -> BaseDistribution:
self._check_metadata_consistency(dist)
return dist

# TODO: add Explicit dependency on self to extra reqs can benefit from it?
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
requires = self.dist.iter_dependencies() if with_requires else ()
for r in requires:
Expand Down Expand Up @@ -428,9 +427,19 @@ def __init__(
self,
base: BaseCandidate,
extras: FrozenSet[str],
ireq: Optional[InstallRequirement] = None,
) -> None:
"""
:param ireq: the InstallRequirement that led to this candidate, if it
differs from the base's InstallRequirement. This will often be the
case in the sense that this candidate's requirement has the extras
while the base's does not. Unlike the InstallRequirement backed
candidates, this requirement is used solely for reporting purposes,
it does not do any leg work.
"""
self.base = base
self.extras = extras
self._ireq = ireq

def __str__(self) -> str:
name, rest = str(self.base).split(" ", 1)
Expand Down Expand Up @@ -504,7 +513,9 @@ def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requiremen

for r in self.base.dist.iter_dependencies(valid_extras):
yield from factory.make_requirements_from_spec(
str(r), self.base._ireq, valid_extras
str(r),
self._ireq if self._ireq is not None else self.base._ireq,
valid_extras,
)

def get_install_requirement(self) -> Optional[InstallRequirement]:
Expand Down
38 changes: 22 additions & 16 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,16 @@ def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None:
raise UnsupportedWheel(msg)

def _make_extras_candidate(
self, base: BaseCandidate, extras: FrozenSet[str]
self,
base: BaseCandidate,
extras: FrozenSet[str],
ireq: Optional[InstallRequirement] = None,
) -> ExtrasCandidate:
cache_key = (id(base), extras)
try:
candidate = self._extras_candidate_cache[cache_key]
except KeyError:
candidate = ExtrasCandidate(base, extras)
candidate = ExtrasCandidate(base, extras, ireq=ireq)
self._extras_candidate_cache[cache_key] = candidate
return candidate

Expand All @@ -161,7 +164,7 @@ def _make_candidate_from_dist(
self._installed_candidate_cache[dist.canonical_name] = base
if not extras:
return base
return self._make_extras_candidate(base, extras)
return self._make_extras_candidate(base, extras, ireq=template)

def _make_candidate_from_link(
self,
Expand Down Expand Up @@ -223,7 +226,7 @@ def _make_candidate_from_link(

if not extras:
return base
return self._make_extras_candidate(base, extras)
return self._make_extras_candidate(base, extras, ireq=template)

def _iter_found_candidates(
self,
Expand Down Expand Up @@ -389,16 +392,17 @@ def find_candidates(
# candidates from entries from extra-less identifier.
with contextlib.suppress(InvalidRequirement):
parsed_requirement = get_requirement(identifier)
explicit_candidates.update(
self._iter_explicit_candidates_from_base(
requirements.get(parsed_requirement.name, ()),
frozenset(parsed_requirement.extras),
),
)
for req in requirements.get(parsed_requirement.name, []):
_, ireq = req.get_candidate_lookup()
if ireq is not None:
ireqs.append(ireq)
if parsed_requirement.name != identifier:
explicit_candidates.update(
self._iter_explicit_candidates_from_base(
requirements.get(parsed_requirement.name, ()),
frozenset(parsed_requirement.extras),
),
)
for req in requirements.get(parsed_requirement.name, []):
_, ireq = req.get_candidate_lookup()
if ireq is not None:
ireqs.append(ireq)

# Add explicit candidates from constraints. We only do this if there are
# known ireqs, which represent requirements not already explicit. If
Expand Down Expand Up @@ -444,7 +448,6 @@ def find_candidates(
def _make_requirements_from_install_req(
self, ireq: InstallRequirement, requested_extras: Iterable[str]
) -> list[Requirement]:
# TODO: docstring
"""
Returns requirement objects associated with the given InstallRequirement. In
most cases this will be a single object but the following special cases exist:
Expand All @@ -454,7 +457,6 @@ def _make_requirements_from_install_req(
extra. This allows centralized constraint handling for the base,
resulting in fewer candidate rejections.
"""
# TODO: implement -> split in base req with constraint and extra req without
if not ireq.match_markers(requested_extras):
logger.info(
"Ignoring %s: markers '%s' don't match your environment",
Expand All @@ -466,6 +468,10 @@ def _make_requirements_from_install_req(
if ireq.extras and ireq.req.specifier:
return [
SpecifierRequirement(ireq, drop_extras=True),
# TODO: put this all the way at the back to have even fewer candidates?
# TODO: probably best to keep specifier as it makes the report
# slightly more readable -> should also update SpecReq constructor
# and req.constructors.install_req_without
SpecifierRequirement(ireq, drop_specifier=True),
]
else:
Expand Down
18 changes: 9 additions & 9 deletions src/pip/_internal/resolution/resolvelib/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def is_satisfied_by(self, candidate: Candidate) -> bool:
return candidate == self.candidate


# TODO: add some comments
class SpecifierRequirement(Requirement):
# TODO: document additional options
def __init__(
Expand All @@ -52,15 +51,17 @@ def __init__(
) -> None:
assert ireq.link is None, "This is a link, not a specifier"
self._drop_extras: bool = drop_extras
self._original_extras = frozenset(ireq.extras)
# TODO: name
self._original_req = ireq.req
self._ireq = install_req_without(
ireq, without_extras=self._drop_extras, without_specifier=drop_specifier
self._extras = frozenset(ireq.extras if not drop_extras else ())
self._ireq = (
ireq
if not drop_extras and not drop_specifier
else install_req_without(
ireq, without_extras=self._drop_extras, without_specifier=drop_specifier
)
)

def __str__(self) -> str:
return str(self._original_req)
return str(self._ireq)

def __repr__(self) -> str:
return "{class_name}({requirement!r})".format(
Expand All @@ -73,12 +74,11 @@ def project_name(self) -> NormalizedName:
assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
return canonicalize_name(self._ireq.req.name)

# TODO: make sure this can still be identified for error reporting purposes
@property
def name(self) -> str:
return format_name(
self.project_name,
self._original_extras if not self._drop_extras else frozenset(),
self._extras,
)

def format_for_error(self) -> str:
Expand Down

0 comments on commit 5f8f40e

Please sign in to comment.