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

pantsbuild / pants / 20332790708

18 Dec 2025 09:48AM UTC coverage: 64.992% (-15.3%) from 80.295%
20332790708

Pull #22949

github

web-flow
Merge f730a56cd into 407284c67
Pull Request #22949: Add experimental uv resolver for Python lockfiles

54 of 97 new or added lines in 5 files covered. (55.67%)

8270 existing lines in 295 files now uncovered.

48990 of 75379 relevant lines covered (64.99%)

1.81 hits per line

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

57.33
/src/python/pants/backend/project_info/filter_targets.py
1
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
5✔
5

6
import re
5✔
7
from enum import Enum
5✔
8
from re import Pattern
5✔
9

10
from pants.base.deprecated import warn_or_error
5✔
11
from pants.engine.addresses import Addresses
5✔
12
from pants.engine.console import Console
5✔
13
from pants.engine.goal import Goal, GoalSubsystem, LineOriented
5✔
14
from pants.engine.rules import collect_rules, goal_rule
5✔
15
from pants.engine.target import RegisteredTargetTypes, Tags, Target, UnrecognizedTargetTypeException
5✔
16
from pants.option.option_types import EnumOption, StrListOption
5✔
17
from pants.util.docutil import bin_name
5✔
18
from pants.util.enums import match
5✔
19
from pants.util.filtering import TargetFilter, and_filters, create_filters
5✔
20
from pants.util.memo import memoized
5✔
21
from pants.util.strutil import help_text, softwrap
5✔
22

23

24
class TargetGranularity(Enum):
5✔
25
    all_targets = "all"
5✔
26
    file_targets = "file"
5✔
27
    build_targets = "BUILD"
5✔
28

29

30
class FilterSubsystem(LineOriented, GoalSubsystem):
5✔
31
    name = "filter"
5✔
32

33
    help = help_text(
5✔
34
        """
35
        Filter the input targets based on various criteria.
36

37
        Most of the filtering options below are comma-separated lists of filtering criteria, with
38
        an implied logical OR between them, so that a target passes the filter if it matches any of
39
        the criteria in the list.
40

41
        A '-' prefix inverts the sense of the entire comma-separated list, so that a target passes
42
        the filter only if it matches _none_ of the criteria in the list.
43

44
        Each of the filtering options may be specified multiple times, with an implied logical AND
45
        between them.
46
        """
47
    )
48

49
    target_type = StrListOption(
5✔
50
        metavar="[+-]type1,type2,...",
51
        help=softwrap(
52
            """
53
            Filter targets based each targets's target type, e.g. `resources` or `python_sources`.
54

55
            As with any target filter, a `-` prefix will negate matches for purposes of filtering;
56
            that is, the filter will include a target only if the target's target type fails to
57
            match all of the provided values.
58
            """
59
        ),
60
    )
61

62
    granularity = EnumOption(
5✔
63
        default=TargetGranularity.all_targets,
64
        help=softwrap(
65
            """
66
            Filter to rendering only targets declared in BUILD files, only file-level
67
            targets, or all targets.
68
            """
69
        ),
70
    )
71

72
    address_regex = StrListOption(
5✔
73
        metavar="[+-]regex1,regex2,...",
74
        help=softwrap(
75
            """
76
            Filter targets based on each target's address matching the provided regular expressions.
77

78
            The regular expressions are parsed by the Python `re` module. The syntax is documented
79
            at https://docs.python.org/3/library/re.html#regular-expression-syntax.
80

81
            As with any target filter, a `-` prefix will negate matches for purposes of filtering;
82
            that is, the filter will include a target only if the target's adddress fails to
83
            match all of the provided regular expressions.
84
            """
85
        ),
86
    )
87

88
    tag_regex = StrListOption(
5✔
89
        metavar="[+-]regex1,regex2,...",
90
        help=softwrap(
91
            """
92
            Filter targets based on whether any of each target's tags (in the target's `tags` field)
93
            matches the provided regular expressions.
94

95
            The regular expressions are parsed by the Python `re` module. The syntax is documented
96
            at https://docs.python.org/3/library/re.html#regular-expression-syntax.
97

98
            As with any target filter, a `-` prefix will negate matches for purposes of filtering;
99
            that is, the filter will include a target only if all of the target's tags fail to
100
            match all of the provided regular expressions.
101
            """
102
        ),
103
    )
104

105
    def target_type_filters(
5✔
106
        self, registered_target_types: RegisteredTargetTypes
107
    ) -> list[TargetFilter]:
UNCOV
108
        def outer_filter(target_alias: str) -> TargetFilter:
×
UNCOV
109
            if target_alias not in registered_target_types.aliases:
×
110
                raise UnrecognizedTargetTypeException(target_alias, registered_target_types)
×
111

UNCOV
112
            target_type = registered_target_types.aliases_to_types[target_alias]
×
UNCOV
113
            if target_type.deprecated_alias and target_alias == target_type.deprecated_alias:
×
114
                warn_deprecated_target_type(target_type)
×
115

UNCOV
116
            def inner_filter(tgt: Target) -> bool:
×
UNCOV
117
                return tgt.alias == target_alias or bool(
×
118
                    tgt.deprecated_alias and tgt.deprecated_alias == target_alias
119
                )
120

UNCOV
121
            return inner_filter
×
122

UNCOV
123
        return create_filters(self.target_type, outer_filter)
×
124

125
    def address_regex_filters(self) -> list[TargetFilter]:
5✔
UNCOV
126
        def outer_filter(address_regex: str) -> TargetFilter:
×
127
            regex = compile_regex(address_regex)
×
128
            return lambda tgt: bool(regex.search(tgt.address.spec))
×
129

UNCOV
130
        return create_filters(self.address_regex, outer_filter)
×
131

132
    def tag_regex_filters(self) -> list[TargetFilter]:
5✔
UNCOV
133
        def outer_filter(tag_regex: str) -> TargetFilter:
×
134
            regex = compile_regex(tag_regex)
×
135
            return lambda tgt: any(bool(regex.search(tag)) for tag in tgt.get(Tags).value or ())
×
136

UNCOV
137
        return create_filters(self.tag_regex, outer_filter)
×
138

139
    def granularity_filter(self) -> TargetFilter:
5✔
UNCOV
140
        return match(
×
141
            self.granularity,
142
            {
143
                TargetGranularity.all_targets: lambda _: True,
144
                TargetGranularity.file_targets: lambda tgt: tgt.address.is_file_target,
145
                TargetGranularity.build_targets: lambda tgt: not tgt.address.is_file_target,
146
            },
147
        )
148

149
    def all_filters(self, registered_target_types: RegisteredTargetTypes) -> TargetFilter:
5✔
UNCOV
150
        return and_filters(
×
151
            [
152
                *self.target_type_filters(registered_target_types),
153
                *self.address_regex_filters(),
154
                *self.tag_regex_filters(),
155
                self.granularity_filter(),
156
            ]
157
        )
158

159
    def is_specified(self) -> bool:
5✔
160
        """Return true if any of the options are set."""
UNCOV
161
        return bool(self.target_type or self.address_regex or self.tag_regex or self.granularity)
×
162

163

164
def compile_regex(regex: str) -> Pattern:
5✔
165
    try:
×
166
        return re.compile(regex)
×
167
    except re.error as e:
×
168
        raise re.error(f"Invalid regular expression {repr(regex)}: {e}")
×
169

170

171
# Memoized so the deprecation doesn't happen repeatedly.
172
@memoized
5✔
173
def warn_deprecated_target_type(tgt_type: type[Target]) -> None:
5✔
174
    assert tgt_type.deprecated_alias_removal_version is not None
×
175
    warn_or_error(
×
176
        removal_version=tgt_type.deprecated_alias_removal_version,
177
        entity=f"using `--filter-target-type={tgt_type.deprecated_alias}`",
178
        hint=f"Use `--filter-target-type={tgt_type.alias}` instead.",
179
    )
180

181

182
class FilterGoal(Goal):
5✔
183
    subsystem_cls = FilterSubsystem
5✔
184
    environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
5✔
185

186

187
@goal_rule
5✔
188
async def filter_targets(
5✔
189
    addresses: Addresses, filter_subsystem: FilterSubsystem, console: Console
190
) -> FilterGoal:
191
    # When removing, also remove the special casing in `help_info_extractor.py` to reclassify the
192
    # subsystem as not a goal with `pants_help`.
193
    warn_or_error(
×
194
        "3.0.0.dev0",
195
        "using `filter` as a goal",
196
        softwrap(
197
            f"""
198
            You can now specify `filter` arguments with any goal, e.g. `{bin_name()}
199
            --filter-target-type=python_test test ::`.
200

201
            This means that the `filter` goal is now identical to `list`. For example, rather than
202
            `{bin_name()} filter --target-type=python_test ::`, use
203
            `{bin_name()} --filter-target-type=python_test list ::`.
204

205
            Often, the `filter` goal was combined with `xargs` to build pipelines of commands. You
206
            can often now simplify those to a single command. Rather than `{bin_name()} filter
207
            --target-type=python_test filter :: | xargs {bin_name()} test`, simply use
208
            `{bin_name()} --filter-target-type=python_test test ::`.
209
            """
210
        ),
211
    )
212
    # `SpecsFilter` will have already filtered for us.
213
    with filter_subsystem.line_oriented(console) as print_stdout:
×
214
        for address in sorted(addresses):
×
215
            print_stdout(address.spec)
×
216
    return FilterGoal(exit_code=0)
×
217

218

219
def rules():
5✔
220
    return collect_rules()
2✔
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