Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert more record classes to dataclasses #12659

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Empty file.
50 changes: 18 additions & 32 deletions src/pip/_internal/index/package_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,44 +334,30 @@ class CandidatePreferences:
allow_all_prereleases: bool = False


@dataclass(frozen=True)
class BestCandidateResult:
"""A collection of candidates, returned by `PackageFinder.find_best_candidate`.

This class is only intended to be instantiated by CandidateEvaluator's
`compute_best_candidate()` method.
"""

def __init__(
self,
candidates: List[InstallationCandidate],
applicable_candidates: List[InstallationCandidate],
best_candidate: Optional[InstallationCandidate],
) -> None:
"""
:param candidates: A sequence of all available candidates found.
:param applicable_candidates: The applicable candidates.
:param best_candidate: The most preferred candidate found, or None
if no applicable candidates were found.
"""
assert set(applicable_candidates) <= set(candidates)

if best_candidate is None:
assert not applicable_candidates
else:
assert best_candidate in applicable_candidates

self._applicable_candidates = applicable_candidates
self._candidates = candidates
:param all_candidates: A sequence of all available candidates found.
:param applicable_candidates: The applicable candidates.
:param best_candidate: The most preferred candidate found, or None
if no applicable candidates were found.
"""

self.best_candidate = best_candidate
all_candidates: List[InstallationCandidate]
applicable_candidates: List[InstallationCandidate]
best_candidate: Optional[InstallationCandidate]

def iter_all(self) -> Iterable[InstallationCandidate]:
"""Iterate through all candidates."""
return iter(self._candidates)
def __post_init__(self) -> None:
assert set(self.applicable_candidates) <= set(self.all_candidates)

def iter_applicable(self) -> Iterable[InstallationCandidate]:
"""Iterate through the applicable candidates."""
return iter(self._applicable_candidates)
if self.best_candidate is None:
assert not self.applicable_candidates
else:
assert self.best_candidate in self.applicable_candidates


class CandidateEvaluator:
Expand Down Expand Up @@ -930,7 +916,7 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str:
"Could not find a version that satisfies the requirement %s "
"(from versions: %s)",
req,
_format_versions(best_candidate_result.iter_all()),
_format_versions(best_candidate_result.all_candidates),
)

raise DistributionNotFound(f"No matching distribution found for {req}")
Expand Down Expand Up @@ -964,15 +950,15 @@ def _should_install_candidate(
logger.debug(
"Using version %s (newest of versions: %s)",
best_candidate.version,
_format_versions(best_candidate_result.iter_applicable()),
_format_versions(best_candidate_result.applicable_candidates),
)
return best_candidate

# We have an existing version, and its the best version
logger.debug(
"Installed version (%s) is most up-to-date (past versions: %s)",
installed_version,
_format_versions(best_candidate_result.iter_applicable()),
_format_versions(best_candidate_result.applicable_candidates),
)
raise BestVersionAlreadyInstalled

Expand Down
24 changes: 11 additions & 13 deletions src/pip/_internal/operations/freeze.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import collections
import logging
import os
from dataclasses import dataclass, field
from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set

from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.packaging.version import InvalidVersion

from pip._internal.exceptions import BadCommand, InstallationError
Expand Down Expand Up @@ -220,19 +221,16 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
)


@dataclass(frozen=True)
class FrozenRequirement:
def __init__(
self,
name: str,
req: str,
editable: bool,
comments: Iterable[str] = (),
) -> None:
self.name = name
self.canonical_name = canonicalize_name(name)
self.req = req
self.editable = editable
self.comments = comments
name: str
req: str
editable: bool
comments: Iterable[str] = field(default_factory=tuple)

@property
def canonical_name(self) -> NormalizedName:
return canonicalize_name(self.name)

@classmethod
def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
Expand Down
70 changes: 29 additions & 41 deletions src/pip/_internal/req/req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import re
import shlex
import urllib.parse
from dataclasses import dataclass
from optparse import Values
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -84,49 +85,36 @@
logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class ParsedRequirement:
def __init__(
self,
requirement: str,
is_editable: bool,
comes_from: str,
constraint: bool,
options: Optional[Dict[str, Any]] = None,
line_source: Optional[str] = None,
) -> None:
self.requirement = requirement
self.is_editable = is_editable
self.comes_from = comes_from
self.options = options
self.constraint = constraint
self.line_source = line_source
requirement: str
is_editable: bool
comes_from: str
constraint: bool
options: Optional[Dict[str, Any]] = None
line_source: Optional[str] = None


@dataclass(frozen=True)
class ParsedLine:
def __init__(
self,
filename: str,
lineno: int,
args: str,
opts: Values,
constraint: bool,
) -> None:
self.filename = filename
self.lineno = lineno
self.opts = opts
self.constraint = constraint

if args:
self.is_requirement = True
self.is_editable = False
self.requirement = args
elif opts.editables:
self.is_requirement = True
self.is_editable = True
filename: str
lineno: int
args: str
opts: Values
constraint: bool

@property
def is_editable(self) -> bool:
return bool(self.opts.editables)

@property
def requirement(self) -> Optional[str]:
if self.args:
return self.args
elif self.is_editable:
# We don't support multiple -e on one line
self.requirement = opts.editables[0]
else:
self.is_requirement = False
return self.opts.editables[0]
return None


def parse_requirements(
Expand Down Expand Up @@ -179,7 +167,7 @@ def handle_requirement_line(
line.lineno,
)

assert line.is_requirement
assert line.requirement is not None

# get the options that apply to requirements
if line.is_editable:
Expand Down Expand Up @@ -301,7 +289,7 @@ def handle_line(
affect the finder.
"""

if line.is_requirement:
if line.requirement is not None:
parsed_req = handle_requirement_line(line, options)
return parsed_req
else:
Expand Down Expand Up @@ -335,7 +323,7 @@ def _parse_and_recurse(
self, filename: str, constraint: bool
) -> Generator[ParsedLine, None, None]:
for line in self._parse_file(filename, constraint):
if not line.is_requirement and (
if line.requirement is None and (
line.opts.requirements or line.opts.constraints
):
# parse a nested requirements file
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ def iter_index_candidate_infos() -> Iterator[IndexCandidateInfo]:
specifier=specifier,
hashes=hashes,
)
icans = list(result.iter_applicable())
icans = result.applicable_candidates

# PEP 592: Yanked releases are ignored unless the specifier
# explicitly pins a version (via '==' or '===') that can be
Expand Down
8 changes: 4 additions & 4 deletions tests/unit/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,13 +465,13 @@ def test_compute_best_candidate(self) -> None:
)
result = evaluator.compute_best_candidate(candidates)

assert result._candidates == candidates
assert result.all_candidates == candidates
expected_applicable = candidates[:2]
assert [str(c.version) for c in expected_applicable] == [
"1.10",
"1.11",
]
assert result._applicable_candidates == expected_applicable
assert result.applicable_candidates == expected_applicable

assert result.best_candidate is expected_applicable[1]

Expand All @@ -488,8 +488,8 @@ def test_compute_best_candidate__none_best(self) -> None:
)
result = evaluator.compute_best_candidate(candidates)

assert result._candidates == candidates
assert result._applicable_candidates == []
assert result.all_candidates == candidates
assert result.applicable_candidates == []
assert result.best_candidate is None

@pytest.mark.parametrize(
Expand Down