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

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

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

0.0
/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

UNCOV
4
from __future__ import annotations
×
5

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

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

14

UNCOV
15
class InterpolationError(ValueError):
×
UNCOV
16
    @classmethod
×
UNCOV
17
    def attribute_error(cls, value: str | InterpolationValue, attribute: str) -> InterpolationError:
×
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

UNCOV
24
ErrorT = TypeVar("ErrorT", bound=InterpolationError)
×
25

26

UNCOV
27
class InterpolationValue(FrozenDict[str, str]):
×
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

UNCOV
31
    _attribute_error_type: ClassVar[type[InterpolationError]] = InterpolationError
×
32

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

38

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

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

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

UNCOV
54
    def format(
×
55
        self, text: str, *, source: TextSource, error_cls: type[ErrorT] | None = None
56
    ) -> str:
UNCOV
57
        stack = [text]
×
UNCOV
58
        try:
×
UNCOV
59
            while "{" in stack[-1] and "}" in stack[-1]:
×
UNCOV
60
                if len(stack) >= 5:
×
UNCOV
61
                    raise InterpolationError(
×
62
                        "The formatted placeholders recurse too deep.\n"
63
                        + " => ".join(map(repr, stack))
64
                    )
UNCOV
65
                stack.append(stack[-1].format(**self))
×
UNCOV
66
                if stack[-1] == stack[-2]:
×
67
                    break
×
UNCOV
68
            return stack[-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

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

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

UNCOV
110
        def __str__(self) -> str:
×
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