• 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

59.46
/src/python/pants/backend/python/lint/first_party_plugins.py
1
# Copyright 2025 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3
from dataclasses import dataclass
5✔
4
from typing import ClassVar, TypeVar
5✔
5

6
from pants.backend.python.target_types import (
5✔
7
    InterpreterConstraintsField,
8
    PythonRequirementsField,
9
    PythonResolveField,
10
)
11
from pants.backend.python.util_rules.pex_requirements import PexRequirements
5✔
12
from pants.backend.python.util_rules.python_sources import (
5✔
13
    PythonSourceFilesRequest,
14
    strip_python_sources,
15
)
16
from pants.engine.addresses import UnparsedAddressInputs
5✔
17
from pants.engine.fs import EMPTY_DIGEST, AddPrefix, Digest
5✔
18
from pants.engine.internals.graph import resolve_unparsed_address_inputs
5✔
19
from pants.engine.internals.graph import transitive_targets as transitive_targets_get
5✔
20
from pants.engine.intrinsics import add_prefix
5✔
21
from pants.engine.rules import implicitly
5✔
22
from pants.engine.target import TransitiveTargetsRequest
5✔
23
from pants.util.ordered_set import FrozenOrderedSet, OrderedSet
5✔
24

25

26
@dataclass(frozen=True)
5✔
27
class BaseFirstPartyPlugins:
5✔
28
    requirement_strings: FrozenOrderedSet[str]
5✔
29
    interpreter_constraints_and_resolve_fields: FrozenOrderedSet[
5✔
30
        tuple[InterpreterConstraintsField, PythonResolveField]
31
    ]
32
    sources_digest: Digest
5✔
33

34
    PREFIX: ClassVar[str] = "__plugins"
5✔
35

36
    def __bool__(self) -> bool:
5✔
NEW
37
        return self.sources_digest != EMPTY_DIGEST
×
38

39

40
FPP = TypeVar("FPP", bound=BaseFirstPartyPlugins)
5✔
41

42

43
async def resolve_first_party_plugins(
5✔
44
    source_plugins: UnparsedAddressInputs, fpp_type: type[FPP]
45
) -> FPP:
NEW
46
    if not source_plugins:
×
NEW
47
        return fpp_type(FrozenOrderedSet(), FrozenOrderedSet(), EMPTY_DIGEST)
×
48

NEW
49
    plugin_target_addresses = await resolve_unparsed_address_inputs(source_plugins, **implicitly())
×
NEW
50
    transitive_targets = await transitive_targets_get(
×
51
        TransitiveTargetsRequest(plugin_target_addresses), **implicitly()
52
    )
53

NEW
54
    requirements_fields: OrderedSet[PythonRequirementsField] = OrderedSet()
×
NEW
55
    interpreter_constraints_and_resolve_fields: OrderedSet[
×
56
        tuple[InterpreterConstraintsField, PythonResolveField]
57
    ] = OrderedSet()
NEW
58
    for tgt in transitive_targets.closure:
×
NEW
59
        if tgt.has_field(PythonRequirementsField):
×
NEW
60
            requirements_fields.add(tgt[PythonRequirementsField])
×
NEW
61
        if tgt.has_field(InterpreterConstraintsField):
×
NEW
62
            interpreter_constraints_and_resolve_fields.add(
×
63
                (tgt[InterpreterConstraintsField], tgt[PythonResolveField])
64
            )
65

66
    # NB: Flake8 and Pylint source plugins must be explicitly loaded via PYTHONPATH (i.e. PEX_EXTRA_SYS_PATH).
67
    # The value must point to the plugin's directory, rather than to a parent's directory, because
68
    # `flake8:local-plugins` values take a module name rather than a path to the module;
69
    # i.e. `plugin`, but not `path/to/plugin`.
70
    # (This means users must have specified the parent directory as a source root.)
NEW
71
    stripped_sources = await strip_python_sources(
×
72
        **implicitly(PythonSourceFilesRequest(transitive_targets.closure))
73
    )
NEW
74
    prefixed_sources = await add_prefix(
×
75
        AddPrefix(stripped_sources.stripped_source_files.snapshot.digest, fpp_type.PREFIX)
76
    )
77

NEW
78
    return fpp_type(
×
79
        requirement_strings=PexRequirements.req_strings_from_requirement_fields(
80
            requirements_fields,
81
        ),
82
        interpreter_constraints_and_resolve_fields=FrozenOrderedSet(
83
            interpreter_constraints_and_resolve_fields
84
        ),
85
        sources_digest=prefixed_sources,
86
    )
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