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

pantsbuild / pants / 19250292619

11 Nov 2025 12:09AM UTC coverage: 77.865% (-2.4%) from 80.298%
19250292619

push

github

web-flow
flag non-runnable targets used with `code_quality_tool` (#22875)

2 of 5 new or added lines in 2 files covered. (40.0%)

1487 existing lines in 72 files now uncovered.

71448 of 91759 relevant lines covered (77.86%)

3.22 hits per line

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

68.12
/src/python/pants/util/value_interpolation.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
8✔
5

6
from collections.abc import Mapping
8✔
7
from dataclasses import dataclass
8✔
8
from typing import ClassVar, TypeVar, Union
8✔
9

10
from pants.engine.addresses import Address
8✔
11
from pants.util.frozendict import FrozenDict
8✔
12
from pants.util.strutil import softwrap
8✔
13

14

15
class InterpolationError(ValueError):
8✔
16
    @classmethod
8✔
17
    def attribute_error(cls, value: str | InterpolationValue, attribute: str) -> InterpolationError:
8✔
UNCOV
18
        msg = f"The placeholder {attribute!r} is unknown."
×
UNCOV
19
        if value and isinstance(value, InterpolationValue):
×
UNCOV
20
            msg += f" Try with one of: {', '.join(value.keys())}."
×
UNCOV
21
        return cls(msg)
×
22

23

24
ErrorT = TypeVar("ErrorT", bound=InterpolationError)
8✔
25

26

27
class InterpolationValue(FrozenDict[str, str]):
8✔
28
    """Dict class suitable for use as a format string context object, as it allows to use attribute
29
    access rather than item access."""
30

31
    _attribute_error_type: ClassVar[type[InterpolationError]] = InterpolationError
8✔
32

33
    def __getattr__(self, attribute: str) -> str:
8✔
34
        if attribute not in self:
1✔
35
            raise self._attribute_error_type.attribute_error(self, attribute)
1✔
36
        return self[attribute]
1✔
37

38

39
class InterpolationContext(FrozenDict[str, Union[str, InterpolationValue]]):
8✔
40
    @classmethod
8✔
41
    def from_dict(cls, data: Mapping[str, str | Mapping[str, str]]) -> InterpolationContext:
8✔
42
        return InterpolationContext({key: cls.create_value(value) for key, value in data.items()})
2✔
43

44
    @staticmethod
8✔
45
    def create_value(value: str | Mapping[str, str]) -> str | InterpolationValue:
8✔
46
        """Ensure that `value` satisfies the type `InterpolationValue`."""
47
        if isinstance(value, (str, InterpolationValue)):
2✔
48
            return value
2✔
49
        return InterpolationValue(value)
1✔
50

51
    def merge(self, other: Mapping[str, str | Mapping[str, str]]) -> InterpolationContext:
8✔
52
        return InterpolationContext.from_dict({**self, **other})
×
53

54
    def format(
8✔
55
        self, text: str, *, source: TextSource, error_cls: type[ErrorT] | None = None
56
    ) -> str:
57
        stack = [text]
1✔
58
        try:
1✔
59
            while "{" in stack[-1] and "}" in stack[-1]:
1✔
60
                if len(stack) >= 5:
1✔
UNCOV
61
                    raise InterpolationError(
×
62
                        "The formatted placeholders recurse too deep.\n"
63
                        + " => ".join(map(repr, stack))
64
                    )
65
                stack.append(stack[-1].format(**self))
1✔
66
                if stack[-1] == stack[-2]:
1✔
67
                    break
×
68
            return stack[-1]
1✔
UNCOV
69
        except (KeyError, InterpolationError) as e:
×
UNCOV
70
            default_error_cls = InterpolationError
×
UNCOV
71
            msg = f"Invalid value for the {source}: {text!r}.\n\n"
×
UNCOV
72
            if isinstance(e, InterpolationError):
×
UNCOV
73
                default_error_cls = type(e)
×
UNCOV
74
                msg += str(e)
×
75
            else:
76
                # KeyError
UNCOV
77
                msg += f"The placeholder {e} is unknown."
×
UNCOV
78
                if self:
×
UNCOV
79
                    msg += f" Try with one of: {', '.join(sorted(self.keys()))}."
×
80
                else:
81
                    msg += " "
×
82
                    msg += softwrap(
×
83
                        f"""
84
                        There are currently no known placeholders to use.
85

86
                        Check the documentation of the {source} to understand where you may need
87
                        to configure your placeholders.
88
                        """
89
                    )
UNCOV
90
            raise (error_cls or default_error_cls)(msg) from e
×
91

92
    @dataclass(frozen=True)
8✔
93
    class TextSource:
8✔
94
        address: Address | None = None
8✔
95
        target_alias: str | None = None
8✔
96
        field_alias: str | None = None
8✔
97
        options_scope: str | None = None
8✔
98

99
        def __post_init__(self):
8✔
100
            field_infos_is_none = (
1✔
101
                x is None for x in [self.address, self.target_alias, self.field_alias]
102
            )
103
            if self.options_scope is None:
1✔
104
                assert not any(field_infos_is_none), f"Missing target field details in {self!r}."
1✔
105
            else:
106
                assert all(field_infos_is_none), (
1✔
107
                    f"Must not refer to both configuration option and target field in {self!r}."
108
                )
109

110
        def __str__(self) -> str:
8✔
UNCOV
111
            if self.options_scope:
×
112
                return f"`{self.options_scope}` configuration option"
×
UNCOV
113
            return (
×
114
                f"`{self.field_alias}` field of the `{self.target_alias}` target at {self.address}"
115
            )
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