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

Update types using fresh dependencies #1927

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ repos:
# Keep exclude in sync with mypy own excludes
exclude: ^tests/test_data/
additional_dependencies:
- click==8.0.1
- pep517==0.10.0
- toml==0.10.2
- pip==20.3.4
- build==0.9.0
- click==8.1.6
- pip==23.2
- build==0.10.0
Comment on lines +31 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't got these requirements pinned in pyproject.toml - Should we do this, or instead remove the boundary here?

- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
hooks:
Expand Down
2 changes: 1 addition & 1 deletion piptools/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from piptools.scripts import compile, sync


@click.group()
@click.group
def cli() -> None:
pass

Expand Down
15 changes: 8 additions & 7 deletions piptools/_compat/pip_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import optparse
from dataclasses import dataclass
from typing import TYPE_CHECKING, Iterable, Iterator, Set, cast
from typing import Iterable, Iterator, Set, cast

import pip
from pip._internal.cache import WheelCache
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import BaseDistribution
from pip._internal.metadata.importlib import Distribution as _ImportLibDist
from pip._internal.metadata.pkg_resources import Distribution as _PkgResourcesDist
from pip._internal.models.direct_url import DirectUrl
from pip._internal.network.session import PipSession
Expand All @@ -23,8 +24,6 @@
# importlib.metadata, so this compat layer allows for a consistent access
# pattern. In pip 22.1, importlib.metadata became the default on Python 3.11
# (and later), but is overridable. `select_backend` returns what's being used.
if TYPE_CHECKING:
from pip._internal.metadata.importlib import Distribution as _ImportLibDist


@dataclass(frozen=True)
Expand All @@ -40,8 +39,10 @@ def from_pip_distribution(cls, dist: BaseDistribution) -> Distribution:
# instead of specializing by type.
if isinstance(dist, _PkgResourcesDist):
return cls._from_pkg_resources(dist)
else:
elif isinstance(dist, _ImportLibDist):
return cls._from_importlib(dist)
else:
raise NotImplementedError

@classmethod
def _from_pkg_resources(cls, dist: _PkgResourcesDist) -> Distribution:
Expand Down Expand Up @@ -78,15 +79,15 @@ def parse_requirements(


def create_wheel_cache(cache_dir: str, format_control: str | None = None) -> WheelCache:
kwargs: dict[str, str | None] = {"cache_dir": cache_dir}
kwargs: dict[str, str] = {"cache_dir": cache_dir}
if PIP_VERSION[:2] <= (23, 0):
kwargs["format_control"] = format_control
kwargs["format_control"] = format_control # type: ignore
return WheelCache(**kwargs)


def get_dev_pkgs() -> set[str]:
if PIP_VERSION[:2] <= (23, 1):
from pip._internal.commands.freeze import DEV_PKGS
from pip._internal.commands.freeze import DEV_PKGS # type: ignore

return cast(Set[str], DEV_PKGS)

Expand Down
14 changes: 7 additions & 7 deletions piptools/repositories/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import optparse
from contextlib import contextmanager
from typing import Iterator, Mapping, cast
from typing import Iterator, Mapping

from pip._internal.commands.install import InstallCommand
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.candidate import InstallationCandidate
from pip._internal.network.session import PipSession
from pip._internal.req import InstallRequirement
from pip._internal.utils.hashes import FAVORITE_HASH
Expand All @@ -18,17 +17,18 @@


def ireq_satisfied_by_existing_pin(
ireq: InstallRequirement, existing_pin: InstallationCandidate
ireq: InstallRequirement, existing_pin: InstallRequirement
) -> bool:
"""
Return True if the given InstallationRequirement is satisfied by the
previously encountered version pin.
"""
assert ireq.req is not None
assert existing_pin.req is not None
version = next(iter(existing_pin.req.specifier)).version
result = ireq.req.specifier.contains(
return ireq.req.specifier.contains(
version, prereleases=existing_pin.req.specifier.prereleases
)
return cast(bool, result)


class LocalRequirementsRepository(BaseRepository):
Expand All @@ -44,7 +44,7 @@ class LocalRequirementsRepository(BaseRepository):

def __init__(
self,
existing_pins: Mapping[str, InstallationCandidate],
existing_pins: Mapping[str, InstallRequirement],
proxied_repository: PyPIRepository,
reuse_hashes: bool = True,
):
Expand Down Expand Up @@ -74,7 +74,7 @@ def clear_caches(self) -> None:

def find_best_match(
self, ireq: InstallRequirement, prereleases: bool | None = None
) -> InstallationCandidate:
) -> InstallRequirement:
key = key_from_ireq(ireq)
existing_pin = self.existing_pins.get(key)
if existing_pin and ireq_satisfied_by_existing_pin(ireq, existing_pin):
Expand Down
28 changes: 18 additions & 10 deletions piptools/repositories/pypi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
from contextlib import contextmanager
from shutil import rmtree
from typing import Any, BinaryIO, ContextManager, Iterator, NamedTuple
from typing import Any, BinaryIO, ContextManager, Iterable, Iterator, NamedTuple

from click import progressbar
from pip._internal.cache import WheelCache
Expand All @@ -21,6 +21,7 @@
from pip._internal.network.session import PipSession
from pip._internal.operations.build.build_tracker import get_build_tracker
from pip._internal.req import InstallRequirement, RequirementSet
from pip._internal.resolution.legacy.resolver import Resolver
from pip._internal.utils.hashes import FAVORITE_HASH
from pip._internal.utils.logging import indent_log, setup_logging
from pip._internal.utils.misc import normalize_path
Expand Down Expand Up @@ -64,7 +65,9 @@ def __init__(self, pip_args: list[str], cache_dir: str):
# Use pip's parser for pip.conf management and defaults.
# General options (find_links, index_url, extra_index_url, trusted_host,
# and pre) are deferred to pip.
self._command: InstallCommand = create_command("install")
command = create_command("install")
assert isinstance(command, InstallCommand)
self._command = command

options, _ = self.command.parse_args(pip_args)
if options.cache_dir:
Expand Down Expand Up @@ -136,6 +139,7 @@ def find_best_match(
if ireq.editable or is_url_requirement(ireq):
return ireq # return itself as the best match

assert ireq.name is not None
all_candidates = self.find_all_candidates(ireq.name)
candidates_by_version = lookup_table(all_candidates, key=candidate_version)
matching_versions = ireq.specifier.filter(
Expand All @@ -155,6 +159,7 @@ def find_best_match(
best_candidate = best_candidate_result.best_candidate

# Turn the candidate into a pinned InstallRequirement
assert best_candidate is not None
return make_install_requirement(
best_candidate.name,
best_candidate.version,
Expand All @@ -166,11 +171,11 @@ def resolve_reqs(
download_dir: str | None,
ireq: InstallRequirement,
wheel_cache: WheelCache,
) -> set[InstallationCandidate]:
) -> set[InstallRequirement]:
with get_build_tracker() as build_tracker, TempDirectory(
kind="resolver"
) as temp_dir, indent_log():
preparer_kwargs = {
preparer_kwargs: dict[str, Any] = {
"temp_build_dir": temp_dir,
"options": self.options,
"session": self.session,
Expand Down Expand Up @@ -199,6 +204,7 @@ def resolve_reqs(
force_reinstall=False,
upgrade_strategy="to-satisfy-only",
)
assert isinstance(resolver, Resolver)
results = resolver._resolve_one(reqset, ireq)
if not ireq.prepared:
# If still not prepared, e.g. a constraint, do enough to assign
Expand Down Expand Up @@ -323,6 +329,7 @@ def get_hashes(self, ireq: InstallRequirement) -> set[str]:
if not is_pinned_requirement(ireq):
raise TypeError(f"Expected pinned requirement, got {ireq}")

assert ireq.name is not None
log.debug(ireq.name)

with log.indentation():
Expand Down Expand Up @@ -385,6 +392,7 @@ def _get_matching_candidates(
# We need to get all of the candidates that match our current version
# pin, these will represent all of the files that could possibly
# satisfy this constraint.
assert ireq.name is not None
all_candidates = self.find_all_candidates(ireq.name)
candidates_by_version = lookup_table(all_candidates, key=candidate_version)
matching_versions = list(
Expand Down Expand Up @@ -431,20 +439,20 @@ def allow_all_wheels(self) -> Iterator[None]:
the previous non-patched calls will interfere.
"""

def _wheel_supported(self: Wheel, tags: list[Tag]) -> bool:
def _wheel_supported(self: Wheel, tags: Iterable[Tag]) -> bool:
# Ignore current platform. Support everything.
return True

def _wheel_support_index_min(self: Wheel, tags: list[Tag]) -> int:
def _wheel_support_index_min(self: Wheel, tags: Iterable[Tag]) -> int:
# All wheels are equal priority for sorting.
return 0

original_wheel_supported = Wheel.supported
original_support_index_min = Wheel.support_index_min
original_cache = self._available_candidates_cache

Wheel.supported = _wheel_supported
Wheel.support_index_min = _wheel_support_index_min
Wheel.supported = _wheel_supported # type: ignore[method-assign]
Wheel.support_index_min = _wheel_support_index_min # type: ignore[method-assign]
self._available_candidates_cache = {}

# If we don't clear this cache then it can contain results from an
Expand All @@ -454,8 +462,8 @@ def _wheel_support_index_min(self: Wheel, tags: list[Tag]) -> int:
try:
yield
finally:
Wheel.supported = original_wheel_supported
Wheel.support_index_min = original_support_index_min
Wheel.supported = original_wheel_supported # type: ignore[method-assign]
Wheel.support_index_min = original_support_index_min # type: ignore[method-assign]
self._available_candidates_cache = original_cache


Expand Down
31 changes: 21 additions & 10 deletions piptools/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def combine_install_requirements(
req.specifier &= ireq.req.specifier

constraint &= ireq.constraint
extras |= ireq.extras
extras |= set(ireq.extras)
if req is not None:
req.extras = set(extras)

Expand Down Expand Up @@ -141,11 +141,16 @@ def combine_install_requirements(
extras=extras,
**link_attrs,
)
combined_ireq._source_ireqs = source_ireqs
combined_ireq._source_ireqs = source_ireqs # type: ignore[attr-defined]

return combined_ireq


def key_from_req_summary(req_summary: RequirementSummary) -> str:
assert req_summary.req is not None
return key_from_req(req_summary.req)


class BaseResolver(metaclass=ABCMeta):
repository: BaseRepository
unsafe_constraints: set[InstallRequirement]
Expand Down Expand Up @@ -358,11 +363,11 @@ def _resolve_one_round(self) -> tuple[bool, set[InstallRequirement]]:
log.debug("")
log.debug("New dependencies found in this round:")
with log.indentation():
for new_dependency in sorted(diff, key=key_from_ireq):
for new_dependency in sorted(diff, key=key_from_req_summary):
log.debug(f"adding {new_dependency}")
log.debug("Removed dependencies in this round:")
with log.indentation():
for removed_dependency in sorted(removed, key=key_from_ireq):
for removed_dependency in sorted(removed, key=key_from_req_summary):
log.debug(f"removing {removed_dependency}")

# Store the last round's results in the their_constraints
Expand Down Expand Up @@ -409,7 +414,7 @@ def get_best_match(self, ireq: InstallRequirement) -> InstallRequirement:
)
best_match.comes_from = ireq.comes_from
if hasattr(ireq, "_source_ireqs"):
best_match._source_ireqs = ireq._source_ireqs
best_match._source_ireqs = ireq._source_ireqs # type: ignore[attr-defined]
return best_match

def _iter_dependencies(
Expand Down Expand Up @@ -564,7 +569,7 @@ def resolve(self, max_rounds: int = 10) -> set[InstallRequirement]:
globally_managed=True,
)

preparer_kwargs = {
preparer_kwargs: dict[str, Any] = {
"temp_build_dir": temp_dir,
"options": self.options,
"session": self.session,
Expand All @@ -586,6 +591,7 @@ def resolve(self, max_rounds: int = 10) -> set[InstallRequirement]:
use_pep517=self.options.use_pep517,
upgrade_strategy="to-satisfy-only",
)
assert isinstance(resolver, Resolver)

self.command.trace_basic_info(self.finder)

Expand Down Expand Up @@ -702,7 +708,9 @@ def _get_install_requirements(
for extras_candidate in extras_candidates:
project_name = canonicalize_name(extras_candidate.project_name)
ireq = result_ireqs[project_name]
ireq.extras |= extras_candidate.extras
ireq.extras = set(ireq.extras) | extras_candidate.extras

assert ireq.req is not None
ireq.req.extras |= extras_candidate.extras

return set(result_ireqs.values())
Expand Down Expand Up @@ -759,6 +767,7 @@ def _get_install_requirement_from_candidate(

# Canonicalize name
assert ireq.name is not None
assert pinned_ireq.req is not None
pinned_ireq.req.name = canonicalize_name(ireq.name)

# Pin requirement to a resolved version
Expand All @@ -768,16 +777,18 @@ def _get_install_requirement_from_candidate(

# Save reverse dependencies for annotation
ireq_key = key_from_ireq(ireq)
pinned_ireq._required_by = reverse_dependencies.get(ireq_key, set())
required_by = reverse_dependencies.get(ireq_key, set())
pinned_ireq._required_by = required_by # type: ignore[attr-defined]

# Save sources for annotation
constraint_ireq = self._constraints_map.get(ireq_key)
if constraint_ireq is not None:
if hasattr(constraint_ireq, "_source_ireqs"):
# If the constraint is combined (has _source_ireqs), use those
pinned_ireq._source_ireqs = constraint_ireq._source_ireqs
source_ireqs = constraint_ireq._source_ireqs
else:
# Otherwise (the constraint is not combined) it is the source
pinned_ireq._source_ireqs = [constraint_ireq]
source_ireqs = [constraint_ireq]
pinned_ireq._source_ireqs = source_ireqs # type: ignore[attr-defined]

return pinned_ireq
11 changes: 5 additions & 6 deletions piptools/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,10 @@ def diff_key_from_ireq(ireq: InstallRequirement) -> str:
if the contents at the URL have changed but the version has not.
"""
if is_url_requirement(ireq):
if getattr(ireq.req, "name", None) and ireq.link.has_hash:
return str(
direct_url_as_pep440_direct_reference(
direct_url_from_link(ireq.link), ireq.req.name
)
req_name = getattr(ireq.req, "name", None)
if req_name is not None and ireq.link is not None and ireq.link.has_hash:
return direct_url_as_pep440_direct_reference(
direct_url_from_link(ireq.link), req_name
)
# TODO: Also support VCS and editable installs.
return str(ireq.link)
Expand Down Expand Up @@ -189,7 +188,7 @@ def diff(

def sync(
to_install: Iterable[InstallRequirement],
to_uninstall: Iterable[InstallRequirement],
to_uninstall: Iterable[str],
dry_run: bool = False,
install_flags: list[str] | None = None,
ask: bool = False,
Expand Down
Loading