• 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/backend/helm/utils/yaml.py
1
# Copyright 2022 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 abc import ABCMeta
×
UNCOV
7
from collections import defaultdict
×
UNCOV
8
from collections.abc import Callable, Iterable, Iterator, Mapping
×
UNCOV
9
from dataclasses import dataclass
×
UNCOV
10
from pathlib import PurePath
×
UNCOV
11
from typing import Any, Generic, TypeVar
×
12

UNCOV
13
from pants.engine.collection import Collection
×
UNCOV
14
from pants.util.frozendict import FrozenDict
×
15

16

UNCOV
17
@dataclass(unsafe_hash=True)
×
UNCOV
18
class YamlPath:
×
19
    """Simple implementation of YAML paths using `/` syntax and being the single slash the path to
20
    the root."""
21

UNCOV
22
    _elements: tuple[str, ...]
×
UNCOV
23
    _absolute: bool
×
24

UNCOV
25
    def __init__(self, elements: Iterable[str], *, absolute: bool) -> None:
×
UNCOV
26
        object.__setattr__(self, "_elements", tuple(elements))
×
UNCOV
27
        object.__setattr__(self, "_absolute", absolute)
×
28

UNCOV
29
        if len(self._elements) == 0 and not self._absolute:
×
30
            raise ValueError("Relative YAML paths with no elements are not allowed.")
×
31

UNCOV
32
    @classmethod
×
UNCOV
33
    def parse(cls, path: str) -> YamlPath:
×
34
        """Parses a YAML path."""
35

UNCOV
36
        is_absolute = path.startswith("/")
×
UNCOV
37
        return cls([elem for elem in path.split("/") if elem], absolute=is_absolute)
×
38

UNCOV
39
    @classmethod
×
UNCOV
40
    def root(cls) -> YamlPath:
×
41
        """Returns a YamlPath that represents the root element."""
42

UNCOV
43
        return cls([], absolute=True)
×
44

UNCOV
45
    @classmethod
×
UNCOV
46
    def index(cls, idx: int) -> YamlPath:
×
47
        """Returns a relative YamlPath for the index value provided."""
48

UNCOV
49
        return cls([str(idx)], absolute=False)
×
50

UNCOV
51
    @property
×
UNCOV
52
    def parent(self) -> YamlPath | None:
×
53
        """Returns the path to the parent element unless this path is already the root."""
54

UNCOV
55
        if not self.is_root:
×
UNCOV
56
            return YamlPath(self._elements[:-1], absolute=self._absolute)
×
UNCOV
57
        return None
×
58

UNCOV
59
    @property
×
UNCOV
60
    def current(self) -> str:
×
61
        """Returns the name of the current element referenced by this path.
62

63
        The root element will return the empty string.
64
        """
65

UNCOV
66
        if self.is_root:
×
UNCOV
67
            return ""
×
UNCOV
68
        return self._elements[len(self._elements) - 1]
×
69

UNCOV
70
    @property
×
UNCOV
71
    def is_absolute(self) -> bool:
×
72
        """Returns `True` if this is an absolute path."""
73

UNCOV
74
        return self._absolute
×
75

UNCOV
76
    @property
×
UNCOV
77
    def is_root(self) -> bool:
×
78
        """Returns `True` if this path represents the root element."""
79

UNCOV
80
        return len(self._elements) == 0 and self._absolute
×
81

UNCOV
82
    @property
×
UNCOV
83
    def is_index(self) -> bool:
×
84
        """Returns `True` if this path is referencing an indexed item inside an array."""
85

UNCOV
86
        try:
×
UNCOV
87
            int(self.current)
×
UNCOV
88
            return True
×
UNCOV
89
        except ValueError:
×
UNCOV
90
            return False
×
91

UNCOV
92
    def to_relative(self) -> YamlPath:
×
93
        """Transforms this YamlPath instance into a relative path."""
94

95
        if not self._absolute:
×
96
            return self
×
97
        return YamlPath(self._elements, absolute=False)
×
98

UNCOV
99
    def __truediv__(self, other: str | int | YamlPath) -> YamlPath:
×
UNCOV
100
        if isinstance(other, str):
×
UNCOV
101
            other_path = YamlPath.parse(other)
×
UNCOV
102
        elif isinstance(other, int):
×
103
            other_path = YamlPath.index(other)
×
104
        else:
UNCOV
105
            other_path = other
×
106

UNCOV
107
        if other_path._absolute:
×
108
            raise ValueError("Can not append an absolute path to another path.")
×
109

UNCOV
110
        return YamlPath(self._elements + other_path._elements, absolute=self._absolute)
×
111

UNCOV
112
    def __iter__(self):
×
113
        return iter(self._elements)
×
114

UNCOV
115
    def __str__(self) -> str:
×
116
        path = "/".join(self._elements)
×
117
        if self._absolute:
×
118
            path = f"/{path}"
×
119
        return path
×
120

121

UNCOV
122
@dataclass(frozen=True)
×
UNCOV
123
class YamlElement(metaclass=ABCMeta):
×
124
    """Abstract base class for elements read from YAML files.
125

126
    `element_path` represents the location inside the YAML file where this element is.
127
    """
128

UNCOV
129
    element_path: YamlPath
×
130

131

UNCOV
132
T = TypeVar("T")
×
UNCOV
133
R = TypeVar("R")
×
134

135

UNCOV
136
class MutableYamlIndex(Generic[T]):
×
137
    """Represents a mutable collection of items that is indexed by the following keys:
138

139
    - the relative path of the YAML file
140
    - the document index inside the YAML file
141
    - the YAML path of the item
142
    """
143

UNCOV
144
    _data: dict[PurePath, dict[int, dict[YamlPath, T]]]
×
145

UNCOV
146
    def __init__(self) -> None:
×
UNCOV
147
        self._data = defaultdict(dict)
×
148

UNCOV
149
    def insert(
×
150
        self, *, file_path: PurePath, yaml_path: YamlPath, item: T, document_index: int = 0
151
    ) -> None:
152
        """Inserts an item at the given position in the index."""
153

UNCOV
154
        doc_index = self._data[file_path].get(document_index, {})
×
UNCOV
155
        if not doc_index:
×
UNCOV
156
            self._data[file_path][document_index] = doc_index
×
157

UNCOV
158
        doc_index[yaml_path] = item
×
159

UNCOV
160
    def frozen(self) -> FrozenYamlIndex[T]:
×
161
        """Transforms this collection into a frozen (immutable) one."""
162

UNCOV
163
        return FrozenYamlIndex.create(self)
×
164

165

UNCOV
166
@dataclass(frozen=True)
×
UNCOV
167
class _YamlDocumentIndexNode(Generic[T]):
×
168
    """Helper node item for the `FrozenYamlIndex` type."""
169

UNCOV
170
    paths: FrozenDict[YamlPath, T]
×
171

UNCOV
172
    @classmethod
×
UNCOV
173
    def empty(cls: type[_YamlDocumentIndexNode[T]]) -> _YamlDocumentIndexNode[T]:
×
UNCOV
174
        return cls(paths=FrozenDict())
×
175

UNCOV
176
    def to_json_dict(self) -> dict[str, dict[str, str]]:
×
177
        items_dict: dict[str, str] = {}
×
178
        for path, item in self.paths.items():
×
179
            items_dict[str(path)] = str(item)
×
180
        return {"paths": items_dict}
×
181

182

UNCOV
183
@dataclass(frozen=True)
×
UNCOV
184
class FrozenYamlIndex(Generic[T]):
×
185
    """Represents a frozen collection of items that is indexed by the following keys:
186

187
    - the relative path of the YAML file
188
    - the document index inside the YAML file
189
    - the YAML path of the item
190
    """
191

UNCOV
192
    _data: FrozenDict[PurePath, Collection[_YamlDocumentIndexNode[T]]]
×
193

UNCOV
194
    @classmethod
×
UNCOV
195
    def create(cls, other: MutableYamlIndex[T]) -> FrozenYamlIndex[T]:
×
UNCOV
196
        data: dict[PurePath, Collection[_YamlDocumentIndexNode[T]]] = {}
×
UNCOV
197
        for file_path, doc_index in other._data.items():
×
UNCOV
198
            max_index = max(doc_index.keys())
×
UNCOV
199
            doc_list: list[_YamlDocumentIndexNode[T]] = [_YamlDocumentIndexNode.empty()] * (
×
200
                max_index + 1
201
            )
202

UNCOV
203
            for idx, item_map in doc_index.items():
×
UNCOV
204
                doc_list[idx] = _YamlDocumentIndexNode(paths=FrozenDict(item_map))
×
205

UNCOV
206
            data[file_path] = Collection(doc_list)
×
UNCOV
207
        return FrozenYamlIndex(_data=FrozenDict(data))
×
208

UNCOV
209
    @classmethod
×
UNCOV
210
    def empty(cls: type[FrozenYamlIndex[T]]) -> FrozenYamlIndex[T]:
×
211
        return FrozenYamlIndex[T](_data=FrozenDict())
×
212

UNCOV
213
    def transform_values(self, func: Callable[[T], R | None]) -> FrozenYamlIndex[R]:
×
214
        """Transforms the values of the given indexed collection into those that are returned from
215
        the received function.
216

217
        The items that map to `None` in the given function are not included in the result.
218

219
        This is a combination of the `map` and `filter` higher-order functions into one so
220
        both operations are performed in a single pass.
221
        """
222

223
        mutable_index: MutableYamlIndex[R] = MutableYamlIndex()
×
224
        for file_path, doc_index, yaml_path, item in self:
×
225
            new_item = func(item)
×
226
            if new_item is not None:
×
227
                mutable_index.insert(
×
228
                    file_path=file_path,
229
                    document_index=doc_index,
230
                    yaml_path=yaml_path,
231
                    item=new_item,
232
                )
233
        return mutable_index.frozen()
×
234

UNCOV
235
    def values(self) -> Iterator[T]:
×
236
        """Returns an iterator over the values of this index."""
UNCOV
237
        for _, _, _, item in self:
×
UNCOV
238
            yield item
×
239

UNCOV
240
    def to_json_dict(self) -> dict[str, Any]:
×
241
        """Transforms this collection into a JSON-like dictionary that can be dumped later."""
242

243
        result = {}
×
244
        for file_path, documents in self._data.items():
×
245
            result[str(file_path)] = [doc_idx.to_json_dict() for doc_idx in documents]
×
246
        return result
×
247

UNCOV
248
    def __iter__(self):
×
UNCOV
249
        for file_path, doc_indexes in self._data.items():
×
UNCOV
250
            for idx, doc_index in enumerate(doc_indexes):
×
UNCOV
251
                for yaml_path, item in doc_index.paths.items():
×
UNCOV
252
                    yield file_path, idx, yaml_path, item
×
253

254

UNCOV
255
def _to_snake_case(str: str) -> str:
×
256
    """Translates a camel-case or kebab-case identifier into a snake-case one."""
257

UNCOV
258
    base_string = str.replace("-", "_")
×
259

UNCOV
260
    result = ""
×
UNCOV
261
    idx = 0
×
UNCOV
262
    for c in base_string:
×
UNCOV
263
        char_to_add = c
×
UNCOV
264
        if char_to_add.isupper():
×
UNCOV
265
            char_to_add = c.lower()
×
UNCOV
266
            if idx > 0:
×
UNCOV
267
                result += "_"
×
UNCOV
268
        result += char_to_add
×
UNCOV
269
        idx += 1
×
270

UNCOV
271
    return result
×
272

273

UNCOV
274
def snake_case_attr_dict(d: Mapping[str, Any]) -> dict[str, Any]:
×
275
    """Transforms all keys in the given mapping to be snake-case."""
UNCOV
276
    return {_to_snake_case(name): value for name, value in d.items()}
×
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