• 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

92.31
/src/python/pants/backend/python/lint/flake8/subsystem.py
1
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
4✔
5

6
from dataclasses import dataclass
4✔
7

8
from pants.backend.python.lint.first_party_plugins import (
4✔
9
    BaseFirstPartyPlugins,
10
    resolve_first_party_plugins,
11
)
12
from pants.backend.python.lint.flake8.skip_field import SkipFlake8Field
4✔
13
from pants.backend.python.subsystems.python_tool_base import PythonToolBase
4✔
14
from pants.backend.python.target_types import (
4✔
15
    ConsoleScript,
16
    InterpreterConstraintsField,
17
    PythonResolveField,
18
    PythonSourceField,
19
)
20
from pants.backend.python.util_rules import python_sources
4✔
21
from pants.core.goals.resolves import ExportableTool
4✔
22
from pants.core.util_rules.config_files import ConfigFilesRequest
4✔
23
from pants.engine.addresses import UnparsedAddressInputs
4✔
24
from pants.engine.rules import collect_rules, rule
4✔
25
from pants.engine.target import FieldSet, Target
4✔
26
from pants.engine.unions import UnionRule
4✔
27
from pants.option.option_types import (
4✔
28
    ArgsListOption,
29
    BoolOption,
30
    FileListOption,
31
    FileOption,
32
    SkipOption,
33
    TargetListOption,
34
)
35
from pants.util.docutil import doc_url
4✔
36
from pants.util.logging import LogLevel
4✔
37
from pants.util.strutil import softwrap
4✔
38

39

40
@dataclass(frozen=True)
4✔
41
class Flake8FieldSet(FieldSet):
4✔
42
    required_fields = (PythonSourceField,)
4✔
43

44
    source: PythonSourceField
4✔
45
    interpreter_constraints: InterpreterConstraintsField
4✔
46
    resolve: PythonResolveField
4✔
47

48
    @classmethod
4✔
49
    def opt_out(cls, tgt: Target) -> bool:
4✔
50
        return tgt.get(SkipFlake8Field).value
×
51

52

53
class Flake8(PythonToolBase):
4✔
54
    options_scope = "flake8"
4✔
55
    name = "Flake8"
4✔
56
    help_short = "The Flake8 Python linter (https://flake8.pycqa.org/)."
4✔
57

58
    default_main = ConsoleScript("flake8")
4✔
59
    default_requirements = ["flake8>=5.0.4,<7"]
4✔
60

61
    default_lockfile_resource = ("pants.backend.python.lint.flake8", "flake8.lock")
4✔
62

63
    skip = SkipOption("lint")
4✔
64
    args = ArgsListOption(example="--ignore E123,W456 --enable-extensions H111")
4✔
65
    config = FileOption(
4✔
66
        default=None,
67
        advanced=True,
68
        help=lambda cls: softwrap(
69
            f"""
70
            Path to an INI config file understood by Flake8
71
            (https://flake8.pycqa.org/en/latest/user/configuration.html).
72

73
            Setting this option will disable `[{cls.options_scope}].config_discovery`. Use
74
            this option if the config is located in a non-standard location.
75
            """
76
        ),
77
    )
78
    extra_files = FileListOption(
4✔
79
        default=None,
80
        advanced=True,
81
        help=softwrap(
82
            """Paths to extra files to include in the sandbox. This can be useful for Flake8 plugins,
83
            like including config files for the `flake8-bandit` plugin."""
84
        ),
85
    )
86
    config_discovery = BoolOption(
4✔
87
        default=True,
88
        advanced=True,
89
        help=lambda cls: softwrap(
90
            f"""
91
            If true, Pants will include any relevant config files during
92
            runs (`.flake8`, `flake8`, `setup.cfg`, and `tox.ini`).
93

94
            Use `[{cls.options_scope}].config` instead if your config is in a
95
            non-standard location.
96
            """
97
        ),
98
    )
99
    _source_plugins = TargetListOption(
4✔
100
        advanced=True,
101
        help=softwrap(
102
            f"""
103
            An optional list of `python_sources` target addresses to load first-party plugins.
104

105
            You must set the plugin's parent directory as a source root. For
106
            example, if your plugin is at `build-support/flake8/custom_plugin.py`, add
107
            `'build-support/flake8'` to `[source].root_patterns` in `pants.toml`. This is
108
            necessary for Pants to know how to tell Flake8 to discover your plugin. See
109
            {doc_url("docs/using-pants/key-concepts/source-roots")}
110

111
            You must also set `[flake8:local-plugins]` in your Flake8 config file.
112

113
            For example:
114

115
                [flake8:local-plugins]
116
                extension =
117
                CUSTOMCODE = custom_plugin:MyChecker
118

119
            While your plugin's code can depend on other first-party code and third-party
120
            requirements, all first-party dependencies of the plugin must live in the same
121
            directory or a subdirectory.
122

123
            To instead load third-party plugins, add them to a custom resolve alongside
124
            flake8 itself, as described in {doc_url("docs/python/overview/lockfiles#lockfiles-for-tools")}.
125
            """
126
        ),
127
    )
128

129
    @property
4✔
130
    def config_request(self) -> ConfigFilesRequest:
4✔
131
        # See https://flake8.pycqa.org/en/latest/user/configuration.html#configuration-locations
132
        # for how Flake8 discovers config files.
133
        return ConfigFilesRequest(
×
134
            specified=self.config,
135
            specified_option_name=f"[{self.options_scope}].config",
136
            discovery=self.config_discovery,
137
            check_existence=["flake8", ".flake8"],
138
            check_content={"setup.cfg": b"[flake8]", "tox.ini": b"[flake8]"},
139
        )
140

141
    @property
4✔
142
    def source_plugins(self) -> UnparsedAddressInputs:
4✔
143
        return UnparsedAddressInputs(
×
144
            self._source_plugins,
145
            owning_address=None,
146
            description_of_origin=f"the option `[{self.options_scope}].source_plugins`",
147
        )
148

149

150
# --------------------------------------------------------------------------------------
151
# First-party plugins
152
# --------------------------------------------------------------------------------------
153

154

155
class Flake8FirstPartyPlugins(BaseFirstPartyPlugins):
4✔
156
    pass
4✔
157

158

159
@rule(desc="Prepare [flake8].source_plugins", level=LogLevel.DEBUG)
4✔
160
async def flake8_first_party_plugins(flake8: Flake8) -> Flake8FirstPartyPlugins:
4✔
NEW
161
    return await resolve_first_party_plugins(flake8.source_plugins, Flake8FirstPartyPlugins)
×
162

163

164
def rules():
4✔
165
    return (
4✔
166
        *collect_rules(),
167
        *python_sources.rules(),
168
        UnionRule(ExportableTool, Flake8),
169
    )
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

© 2026 Coveralls, Inc