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

pantsbuild / pants / 18517631058

15 Oct 2025 04:18AM UTC coverage: 69.207% (-11.1%) from 80.267%
18517631058

Pull #22745

github

web-flow
Merge 642a76ca1 into 99919310e
Pull Request #22745: [windows] Add windows support in the stdio crate.

53815 of 77759 relevant lines covered (69.21%)

2.42 hits per line

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

42.03
/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
5✔
5

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

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

14

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

23

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

26

27
class InterpolationValue(FrozenDict[str, str]):
5✔
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
5✔
32

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

38

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

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

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

54
    def format(
5✔
55
        self, text: str, *, source: TextSource, error_cls: type[ErrorT] | None = None
56
    ) -> str:
57
        stack = [text]
×
58
        try:
×
59
            while "{" in stack[-1] and "}" in stack[-1]:
×
60
                if len(stack) >= 5:
×
61
                    raise InterpolationError(
×
62
                        "The formatted placeholders recurse too deep.\n"
63
                        + " => ".join(map(repr, stack))
64
                    )
65
                stack.append(stack[-1].format(**self))
×
66
                if stack[-1] == stack[-2]:
×
67
                    break
×
68
            return stack[-1]
×
69
        except (KeyError, InterpolationError) as e:
×
70
            default_error_cls = InterpolationError
×
71
            msg = f"Invalid value for the {source}: {text!r}.\n\n"
×
72
            if isinstance(e, InterpolationError):
×
73
                default_error_cls = type(e)
×
74
                msg += str(e)
×
75
            else:
76
                # KeyError
77
                msg += f"The placeholder {e} is unknown."
×
78
                if self:
×
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
                    )
90
            raise (error_cls or default_error_cls)(msg) from e
×
91

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

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

110
        def __str__(self) -> str:
5✔
111
            if self.options_scope:
×
112
                return f"`{self.options_scope}` configuration option"
×
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