• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 18987624565

31 Oct 2025 11:28PM UTC coverage: 80.299% (+0.02%) from 80.275%
18987624565

push

github

web-flow
Allow setting Python resolve interpreter_constraints as defaults for targets (#22676)

Closes #22574 

This PR is intended to solve a long-felt annoyance of mine when working
in repos with multiple Python resolves, which is having to configure
resolve interpreter constraints and source interpreter constraints
separately. It adds a new option,
`[python].default_to_resolve_interpreter_constraints`, which when set to
true, tells Pants to use the interpreter constraints of the resolve,
rather than the global interpreter constraints, if no interpreter
constraints are provided. If resolves are not enabled or no interpreter
constraints are set for the resolve, it still falls back to the global
default.

71 of 95 new or added lines in 17 files covered. (74.74%)

2 existing lines in 2 files now uncovered.

77993 of 97128 relevant lines covered (80.3%)

3.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.17
/src/python/pants/backend/python/util_rules/partition.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
7✔
5

6
import itertools
7✔
7
from collections import defaultdict
7✔
8
from collections.abc import Iterable, Mapping, Sequence
7✔
9
from typing import TypeVar
7✔
10

11
from pants.backend.python.subsystems.setup import PythonSetup
7✔
12
from pants.backend.python.target_types import InterpreterConstraintsField, PythonResolveField
7✔
13
from pants.backend.python.util_rules.interpreter_constraints import (
7✔
14
    FieldSetWithInterpreterConstraints,
15
    InterpreterConstraints,
16
)
17
from pants.engine.internals.graph import find_all_targets
7✔
18
from pants.engine.rules import implicitly
7✔
19
from pants.engine.target import FieldSet
7✔
20
from pants.util.ordered_set import OrderedSet
7✔
21

22
ResolveName = str
7✔
23

24

25
_FS = TypeVar("_FS", bound=FieldSetWithInterpreterConstraints)
7✔
26

27

28
def _partition_by_interpreter_constraints_and_resolve(
7✔
29
    field_sets: Sequence[_FS],
30
    python_setup: PythonSetup,
31
) -> Mapping[tuple[ResolveName, InterpreterConstraints], OrderedSet[_FS]]:
32
    resolve_and_interpreter_constraints_to_field_sets: Mapping[
×
33
        tuple[str, InterpreterConstraints], OrderedSet[_FS]
34
    ] = defaultdict(lambda: OrderedSet())
35
    for field_set in field_sets:
×
36
        resolve = field_set.resolve.normalized_value(python_setup)
×
NEW
37
        interpreter_constraints = InterpreterConstraints.create_from_field_sets(
×
38
            [field_set], python_setup
39
        )
40
        resolve_and_interpreter_constraints_to_field_sets[(resolve, interpreter_constraints)].add(
×
41
            field_set
42
        )
43

44
    return resolve_and_interpreter_constraints_to_field_sets
×
45

46

47
async def _find_all_unique_interpreter_constraints(
7✔
48
    python_setup: PythonSetup,
49
    field_set_type: type[FieldSet],
50
    *,
51
    extra_constraints_per_tgt: Iterable[InterpreterConstraintsField] = (),
52
) -> InterpreterConstraints:
53
    """Find all unique interpreter constraints used by given field set.
54

55
    This will find the constraints for each individual matching field set, and then OR across all
56
    unique constraints. Usually, Pants partitions when necessary so that conflicting interpreter
57
    constraints can be handled gracefully. But in some cases, like the `generate-lockfiles` goal,
58
    we need to combine those targets into a single value. This ORs, so that if you have a
59
    ==2.7 partition and ==3.6 partition, for example, we return ==2.7 OR ==3.6.
60

61
    Returns the global interpreter constraints if no relevant targets were matched.
62
    """
63
    all_tgts = await find_all_targets(**implicitly())
×
NEW
64
    extra_constraints_blank_resolves = [
×
65
        (ics_field, None) for ics_field in extra_constraints_per_tgt
66
    ]
UNCOV
67
    unique_constraints = {
×
68
        InterpreterConstraints.create_from_compatibility_fields(
69
            [
70
                (
71
                    tgt[InterpreterConstraintsField],
72
                    tgt[PythonResolveField] if tgt.has_field(PythonResolveField) else None,
73
                ),
74
                *extra_constraints_blank_resolves,
75
            ],
76
            python_setup,
77
        )
78
        for tgt in all_tgts
79
        if tgt.has_field(InterpreterConstraintsField) and field_set_type.is_applicable(tgt)
80
    }
81
    if not unique_constraints and extra_constraints_per_tgt:
×
82
        unique_constraints.add(
×
83
            InterpreterConstraints.create_from_compatibility_fields(
84
                extra_constraints_blank_resolves,
85
                python_setup,
86
            )
87
        )
88
    constraints = InterpreterConstraints(
×
89
        itertools.chain.from_iterable(ic for ic in unique_constraints if ic)
90
    )
91
    return constraints or InterpreterConstraints(python_setup.interpreter_constraints)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc