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

[internal] Remove unused InterpreterConstraints.partition_by_major_minor_versions #12515

Merged
merged 1 commit into from
Aug 7, 2021
Merged
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
5 changes: 2 additions & 3 deletions src/python/pants/backend/python/lint/bandit/subsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,8 @@ async def setup_bandit_lockfile(
# While Bandit will run in partitions, we need a single lockfile that works with every
# partition.
#
# This ORs all unique interpreter constraints. When paired with
# `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that every
# possible Python interpreter used will be covered.
# This ORs all unique interpreter constraints. The net effect is that every possible Python
# interpreter used will be covered.
all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")]))
unique_constraints = {
InterpreterConstraints.create_from_targets([tgt], python_setup)
Expand Down
5 changes: 2 additions & 3 deletions src/python/pants/backend/python/lint/flake8/subsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,8 @@ async def setup_flake8_lockfile(
# While Flake8 will run in partitions, we need a single lockfile that works with every
# partition.
#
# This ORs all unique interpreter constraints. When paired with
# `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that every
# possible Python interpreter used will be covered.
# This ORs all unique interpreter constraints. The net effect is that every possible Python
# interpreter used will be covered.
all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")]))
unique_constraints = {
InterpreterConstraints.create_from_targets([tgt], python_setup)
Expand Down
5 changes: 2 additions & 3 deletions src/python/pants/backend/python/lint/pylint/subsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,8 @@ async def setup_pylint_lockfile(
#
# This first computes the constraints for each individual target, including its direct
# dependencies (which will AND across each target in the closure). Then, it ORs all unique
# resulting interpreter constraints. When paired with
# `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that
# every possible Python interpreter used will be covered.
# resulting interpreter constraints. The net effect is that every possible Python interpreter
# used will be covered.
all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")]))
relevant_targets = tuple(tgt for tgt in all_build_targets if PylintFieldSet.is_applicable(tgt))
direct_deps_per_target = await MultiGet(
Expand Down
5 changes: 2 additions & 3 deletions src/python/pants/backend/python/subsystems/ipython.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,8 @@ async def setup_ipython_lockfile(
# `./pants repl py2::` and then `./pants repl py3::`. Still, even with those subsets possible,
# we need a single lockfile that works with all possible Python interpreters in use.
#
# This ORs all unique interpreter constraints. When paired with
# `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that
# every possible Python interpreter used will be covered.
# This ORs all unique interpreter constraints. The net effect is that every possible Python
# interpreter used will be covered.
all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")]))
unique_constraints = {
InterpreterConstraints.create_from_compatibility_fields(
Expand Down
5 changes: 2 additions & 3 deletions src/python/pants/backend/python/subsystems/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,8 @@ async def setup_pytest_lockfile(
#
# This first computes the constraints for each individual `python_tests` target
# (which will AND across each target in the closure). Then, it ORs all unique resulting
# interpreter constraints. When paired with
# `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that
# every possible Python interpreter used will be covered.
# interpreter constraints. The net effect is that every possible Python interpreter used will
# be covered.
all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")]))
transitive_targets_per_test = await MultiGet(
Get(TransitiveTargets, TransitiveTargetsRequest([tgt.address]))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,70 +238,6 @@ def requires_python38_or_newer(self, interpreter_universe: Iterable[str]) -> boo
allowed_versions=py38_and_later, prior_version="3.7"
)

def partition_by_major_minor_versions(
self, interpreter_universe: Iterable[str]
) -> tuple[InterpreterConstraints, ...]:
"""Create a distinct InterpreterConstraints value for each CPython major-minor version that
is compatible with the original constraints."""
if any(req.project_name != "CPython" for req in self):
raise AssertionError(
"This function only works with CPython interpreter constraints for now."
)

def all_valid_patch_versions(major_minor: str) -> list[int]:
return [
p
for p in range(0, _EXPECTED_LAST_PATCH_VERSION + 1)
for req in self
if req.specifier.contains(f"{major_minor}.{p}") # type: ignore[attr-defined]
]

result = []

def maybe_add_version(major_minor: str) -> None:
major, minor = _major_minor_to_int(major_minor)
next_major_minor = f"{major}.{minor + 1}"

valid_patch_versions = all_valid_patch_versions(major_minor)
if not valid_patch_versions:
return

if len(valid_patch_versions) == 1:
result.append(
InterpreterConstraints([f"=={major_minor}.{valid_patch_versions[0]}"])
)
return

skipped_patch_versions = _not_in_contiguous_range(valid_patch_versions)
first_patch_supported = valid_patch_versions[0] == 0
last_patch_supported = valid_patch_versions[-1] == _EXPECTED_LAST_PATCH_VERSION
if not skipped_patch_versions and first_patch_supported and last_patch_supported:
constraint = f"=={major_minor}.*"
else:
min_constraint = (
f">={major_minor}"
if first_patch_supported
else f">={major_minor}.{valid_patch_versions[0]}"
)
max_constraint = (
f"<{next_major_minor}"
if last_patch_supported
else f"<={major_minor}.{valid_patch_versions[-1]}"
)
if skipped_patch_versions:
skipped_constraints = ",".join(
f"!={major_minor}.{p}" for p in skipped_patch_versions
)
constraint = f"{min_constraint},{max_constraint},{skipped_constraints}"
else:
constraint = f"{min_constraint},{max_constraint}"

result.append(InterpreterConstraints([constraint]))

for major_minor in sorted(interpreter_universe, key=_major_minor_to_int):
maybe_add_version(major_minor)
return tuple(result)

def __str__(self) -> str:
return " OR ".join(str(constraint) for constraint in self)

Expand All @@ -311,9 +247,3 @@ def debug_hint(self) -> str:

def _major_minor_to_int(major_minor: str) -> tuple[int, int]:
return tuple(int(x) for x in major_minor.split(".", maxsplit=1)) # type: ignore[return-value]


def _not_in_contiguous_range(nums: list[int]) -> list[int]:
# Expects list to already be sorted and have 1+ elements.
expected = {i for i in range(nums[0], nums[-1])}
return sorted(expected - set(nums))
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
from pkg_resources import Requirement

from pants.backend.python.target_types import InterpreterConstraintsField
from pants.backend.python.util_rules.interpreter_constraints import (
InterpreterConstraints,
_not_in_contiguous_range,
)
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
from pants.build_graph.address import Address
from pants.engine.target import FieldSet
from pants.python.python_setup import PythonSetup
Expand Down Expand Up @@ -278,64 +275,3 @@ def test_group_field_sets_by_constraints_with_unsorted_inputs() -> None:
Address("src/python/c_dir/path.py", target_name="test"), "==3.6.*"
),
)


@pytest.mark.parametrize(
"nums,expected",
(
([0, 1, 2], []),
([0, 2], [1]),
([0, 1, 2, 4], [3]),
([0, 1, 4], [2, 3]),
([1, 4], [2, 3]),
([1, 2, 3, 4], []),
),
)
def test_not_in_contiguous_range(nums, expected) -> None:
assert _not_in_contiguous_range(nums) == expected


@pytest.mark.parametrize(
"constraints,expected",
(
# Input constraints already are for a single interpreter version.
(["==3.7.5"], [["==3.7.5"]]),
(["==3.7.*"], [["==3.7.*"]]),
([">=3.7,<3.8"], [["==3.7.*"]]),
([">=3.7.5,<3.7.7"], [[">=3.7.5,<=3.7.6"]]),
([">=3.7.5,<3.8"], [[">=3.7.5,<3.8"]]),
([">=3.7.5,<3.8,!=3.7.7"], [[">=3.7.5,<3.8,!=3.7.7"]]),
([">=3.7.5,<3.7.12,!=3.7.7,!=3.7.8"], [[">=3.7.5,<=3.7.11,!=3.7.7,!=3.7.8"]]),
# Input constraints are for multiple interpreters, but only a single constraint.
([">=3.7,<3.9"], [["==3.7.*"], ["==3.8.*"]]),
([">=3.7.5,<3.9.2,!=3.8.0"], [[">=3.7.5,<3.8"], [">=3.8.1,<3.9"], [">=3.9,<=3.9.1"]]),
# Input constraints have multiple elements, which get ORed, but each is for a single
# interpreter version.
(["==3.7.5", "==3.8.6"], [["==3.7.5"], ["==3.8.6"]]),
(["==3.7.*", "==3.8.*"], [["==3.7.*"], ["==3.8.*"]]),
([">=3.7.5,<3.8", ">=3.9.5,<3.10"], [[">=3.7.5,<3.8"], [">=3.9.5,<3.10"]]),
(["==2.7.*", ">=3.6,<3.8"], [["==2.7.*"], ["==3.6.*"], ["==3.7.*"]]),
([">=2.7.4,<2.7.7", "==2.7.5"], [[">=2.7.4,<=2.7.6"]]),
# Input constraints have multiple elements, and those elements are for multiple
# interpreters.
([">=3.7,<3.9", "==3.8.6"], [["==3.7.*"], ["==3.8.*"]]),
([">=3.7,<3.9", ">=3.8,<=3.9.1"], [["==3.7.*"], ["==3.8.*"], [">=3.9.0,<=3.9.1"]]),
# Unsatisfiable input constraints.
(["==2.7.*,==3.6.*"], []),
(["==2.7.*,==3.6.*", "==3.7.*"], [["==3.7.*"]]),
),
)
def test_partition_by_major_minor_version(
constraints: list[str], expected: list[list[str]]
) -> None:
ics = InterpreterConstraints(constraints)
universe = ("2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11")
assert ics.partition_by_major_minor_versions(universe) == tuple(
InterpreterConstraints(x) for x in expected
)
assert ics.partition_by_major_minor_versions(reversed(universe)) == tuple(
InterpreterConstraints(x) for x in expected
)
assert ics.partition_by_major_minor_versions(sorted(universe)) == tuple(
InterpreterConstraints(x) for x in expected
)