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

spyoungtech / json-five / 10567855230

26 Aug 2024 09:53PM UTC coverage: 97.125% (-1.0%) from 98.133%
10567855230

push

github

web-flow
[pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0)
- [github.com/asottile/reorder-python-imports: v3.12.0 → v3.13.0](https://github.com/asottile/reorder-python-imports/compare/v3.12.0...v3.13.0)
- [github.com/psf/black: 23.10.1 → 24.8.0](https://github.com/psf/black/compare/23.10.1...24.8.0)
- [github.com/asottile/pyupgrade: v3.15.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.17.0)
- [github.com/pre-commit/mirrors-mypy: v1.6.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.1...v1.11.2)
- [github.com/pycqa/flake8: 6.1.0 → 7.1.1](https://github.com/pycqa/flake8/compare/6.1.0...7.1.1)

1216 of 1252 relevant lines covered (97.12%)

3.88 hits per line

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

96.63
/json5/model.py
1
from __future__ import annotations
4✔
2

3
import math
4✔
4
import typing
4✔
5
from collections import deque
4✔
6
from typing import Any
4✔
7
from typing import Literal
4✔
8
from typing import NamedTuple
4✔
9

10
from .tokenizer import JSON5Token
4✔
11

12
__all__ = [
4✔
13
    'Node',
14
    'JSONText',
15
    'Value',
16
    'Key',
17
    'JSONObject',
18
    'JSONArray',
19
    'KeyValuePair',
20
    'Identifier',
21
    'Number',
22
    'Integer',
23
    'Float',
24
    'Infinity',
25
    'NaN',
26
    'String',
27
    'DoubleQuotedString',
28
    'SingleQuotedString',
29
    'BooleanLiteral',
30
    'NullLiteral',
31
    'UnaryOp',
32
    'TrailingComma',
33
    'Comment',
34
    'LineComment',
35
    'BlockComment',
36
]
37

38

39
class KeyValuePair(NamedTuple):
4✔
40
    key: Key
4✔
41
    value: Value
4✔
42

43

44
def walk(root: Node) -> typing.Generator[Node, None, None]:
4✔
45
    todo = deque([root])
4✔
46
    while todo:
4✔
47
        node: Node = todo.popleft()
4✔
48
        todo.extend(iter_child_nodes(node))
4✔
49
        yield node
4✔
50

51

52
def iter_child_nodes(node: Node) -> typing.Generator[Node, None, None]:
4✔
53
    for attr, value in iter_fields(node):
4✔
54
        if isinstance(value, Node):
4✔
55
            yield value
4✔
56
        elif isinstance(value, list):
4✔
57
            for item in value:
4✔
58
                if isinstance(item, Node):
4✔
59
                    yield item
4✔
60

61

62
def iter_fields(node: Node) -> typing.Generator[tuple[str, Any], None, None]:
4✔
63
    for field_name in node._fields:
4✔
64
        try:
4✔
65
            value = getattr(node, field_name)
4✔
66
            yield field_name, value
4✔
67
        except AttributeError:
×
68
            pass
×
69

70

71
class Node:
4✔
72
    excluded_names = ['excluded_names', 'wsc_before', 'wsc_after', 'leading_wsc', 'tok', 'end_tok']
4✔
73

74
    def __init__(self, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None):
4✔
75
        # Whitespace/Comments before/after the node
76
        self.wsc_before: list[str | Comment] = []
4✔
77
        self.wsc_after: list[str | Comment] = []
4✔
78
        self._tok: JSON5Token | None = tok
4✔
79
        self._end_tok: JSON5Token | None = end_tok
4✔
80

81
    @property
4✔
82
    def col_offset(self) -> int | None:
4✔
83
        if self._tok is None:
4✔
84
            return None
×
85
        return self._tok.colno
4✔
86

87
    @property
4✔
88
    def end_col_offset(self) -> int | None:
4✔
89
        if self._end_tok is None:
4✔
90
            return None
×
91
        return self._end_tok.end_colno
4✔
92

93
    @property
4✔
94
    def lineno(self) -> int | None:
4✔
95
        if self._tok is None:
4✔
96
            return None
×
97
        return self._tok.lineno
4✔
98

99
    @property
4✔
100
    def end_lineno(self) -> int | None:
4✔
101
        if self._end_tok is None:
4✔
102
            return None
×
103
        r = self._end_tok.end_lineno
4✔
104
        return r
4✔
105

106
    def __repr__(self) -> str:
4✔
107
        rep = (
4✔
108
            f"{self.__class__.__name__}("
109
            + ", ".join(
110
                f"{key}={repr(value)}"
111
                for key, value in self.__dict__.items()
112
                if not key.startswith('_') and key not in self.excluded_names
113
            )
114
            + ")"
115
        )
116
        return rep
4✔
117

118
    @property
4✔
119
    def _fields(self) -> list[str]:
4✔
120
        fields = [item for item in list(self.__dict__) if not item.startswith('_') and item not in self.excluded_names]
4✔
121
        fields.extend(['wsc_before', 'wsc_after'])
4✔
122
        return fields
4✔
123

124

125
class JSONText(Node):
4✔
126
    def __init__(self, value: Value, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None):
4✔
127
        assert isinstance(value, Value)
4✔
128
        self.value: Value = value
4✔
129
        super().__init__(tok=tok, end_tok=tok)
4✔
130

131

132
class Value(Node):
4✔
133
    pass
4✔
134

135

136
class Key(Node):
4✔
137
    ...
4✔
138

139

140
class JSONObject(Value):
4✔
141
    def __init__(
4✔
142
        self,
143
        *key_value_pairs: KeyValuePair,
144
        trailing_comma: TrailingComma | None = None,
145
        leading_wsc: list[str | Comment] | None = None,
146
        tok: JSON5Token | None = None,
147
        end_tok: JSON5Token | None = None,
148
    ):
149
        keys: list[Key] = []
4✔
150
        values: list[Value] = []
4✔
151
        for key, value in key_value_pairs:
4✔
152
            assert isinstance(key, Key)
4✔
153
            assert isinstance(value, Value)
4✔
154
            keys.append(key)
4✔
155
            values.append(value)
4✔
156
        assert len(keys) == len(values)
4✔
157
        self.keys: list[Key] = keys
4✔
158
        self.values: list[Value] = values
4✔
159
        assert leading_wsc is None or all(isinstance(item, str) or isinstance(item, Comment) for item in leading_wsc)
4✔
160
        self.trailing_comma: TrailingComma | None = trailing_comma
4✔
161
        self.leading_wsc: list[str | Comment] = leading_wsc or []
4✔
162

163
        super().__init__(tok=tok, end_tok=end_tok)
4✔
164

165
    @property
4✔
166
    def key_value_pairs(self) -> list[KeyValuePair]:
4✔
167
        return list(KeyValuePair(key, value) for key, value in zip(self.keys, self.values))
4✔
168

169

170
class JSONArray(Value):
4✔
171
    def __init__(
4✔
172
        self,
173
        *values: Value,
174
        trailing_comma: TrailingComma | None = None,
175
        leading_wsc: list[str | Comment] | None = None,
176
        tok: JSON5Token | None = None,
177
        end_tok: JSON5Token | None = None,
178
    ):
179
        vals = list(values)
4✔
180
        for value in vals:
4✔
181
            assert isinstance(value, Value), f"Was expecting object with type Value. Got {type(value)}"
4✔
182
        assert leading_wsc is None or all(isinstance(item, str) or isinstance(item, Comment) for item in leading_wsc)
4✔
183
        self.values: list[Value] = vals
4✔
184
        self.trailing_comma: TrailingComma | None = trailing_comma
4✔
185
        self.leading_wsc: list[str | Comment] = leading_wsc or []
4✔
186

187
        super().__init__(tok=tok, end_tok=end_tok)
4✔
188

189

190
class Identifier(Key):
4✔
191
    def __init__(
4✔
192
        self, name: str, raw_value: str | None = None, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None
193
    ):
194
        assert isinstance(name, str)
4✔
195
        if raw_value is None:
4✔
196
            raw_value = name
4✔
197
        assert isinstance(raw_value, str)
4✔
198
        assert len(name) > 0
4✔
199
        self.name: str = name
4✔
200
        self.raw_value: str = raw_value
4✔
201

202
        super().__init__(tok=tok, end_tok=tok)
4✔
203

204
    def __hash__(self) -> int:
4✔
205
        return hash(self.name)
4✔
206

207
    def __eq__(self, other: Any) -> bool:
4✔
208
        return hash(self) == hash(other)
4✔
209

210

211
class Number(Value):
4✔
212
    ...
4✔
213

214

215
class Integer(Number):
4✔
216
    def __init__(
4✔
217
        self,
218
        raw_value: str,
219
        is_hex: bool = False,
220
        is_octal: bool = False,
221
        tok: JSON5Token | None = None,
222
        end_tok: JSON5Token | None = None,
223
    ):
224
        assert isinstance(raw_value, str)
4✔
225
        if is_hex and is_octal:
4✔
226
            raise ValueError("is_hex and is_octal are mutually exclusive")
4✔
227
        if is_hex:
4✔
228
            value = int(raw_value, 0)
4✔
229
        elif is_octal:
4✔
230
            if raw_value.startswith('0o'):
4✔
231
                value = int(raw_value, 8)
4✔
232
            else:
233
                value = int(raw_value.replace('0', '0o', 1), 8)
×
234
        else:
235
            value = int(raw_value)
4✔
236
        self.value: int = value
4✔
237
        self.raw_value: str = raw_value
4✔
238
        self.is_hex: bool = is_hex
4✔
239
        self.is_octal: bool = is_octal
4✔
240

241
        super().__init__(tok=tok, end_tok=end_tok or tok)
4✔
242

243

244
class Float(Number):
4✔
245
    def __init__(
4✔
246
        self,
247
        raw_value: str,
248
        exp_notation: str | None = None,
249
        tok: JSON5Token | None = None,
250
        end_tok: JSON5Token | None = None,
251
    ):
252
        value = float(raw_value)
4✔
253
        assert exp_notation is None or exp_notation in ('e', 'E')
4✔
254
        self.raw_value: str = raw_value
4✔
255
        self.exp_notation: str | None = exp_notation
4✔
256

257
        self.value: float = value
4✔
258
        super().__init__(tok=tok, end_tok=end_tok or tok)
4✔
259

260

261
class Infinity(Number):
4✔
262
    def __init__(self, negative: bool = False, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None):
4✔
263
        self.negative: bool = negative
4✔
264

265
        super().__init__(tok=tok, end_tok=tok)
4✔
266

267
    @property
4✔
268
    def value(self) -> float:
4✔
269
        return math.inf if not self.negative else -math.inf
4✔
270

271
    @property
4✔
272
    def const(self) -> Literal['Infinity', '-Infinity']:
4✔
273
        if self.negative:
4✔
274
            return '-Infinity'
4✔
275
        else:
276
            return 'Infinity'
4✔
277

278

279
class NaN(Number):
4✔
280
    def __init__(self, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None):
4✔
281
        super().__init__(tok=tok, end_tok=tok)
4✔
282

283
    @property
4✔
284
    def value(self) -> float:
4✔
285
        return math.nan
4✔
286

287
    @property
4✔
288
    def const(self) -> Literal['NaN']:
4✔
289
        return 'NaN'
4✔
290

291

292
class String(Value, Key):
4✔
293
    def __init__(
4✔
294
        self, characters: str, raw_value: str, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None
295
    ):
296
        assert isinstance(raw_value, str)
4✔
297
        assert isinstance(characters, str)
4✔
298
        self.characters: str = characters
4✔
299
        self.raw_value: str = raw_value
4✔
300

301
        super().__init__(tok=tok, end_tok=tok)
4✔
302

303

304
class DoubleQuotedString(String):
4✔
305
    ...
4✔
306

307

308
class SingleQuotedString(String):
4✔
309
    ...
4✔
310

311

312
class BooleanLiteral(Value):
4✔
313
    def __init__(self, value: bool, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None):
4✔
314
        assert value in (True, False)
4✔
315
        self.value: bool = value
4✔
316

317
        super().__init__(tok=tok, end_tok=tok)
4✔
318

319

320
class NullLiteral(Value):
4✔
321
    value = None
4✔
322

323
    def __init__(self, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None):
4✔
324
        super().__init__(tok=tok, end_tok=tok)
4✔
325

326

327
class UnaryOp(Value):
4✔
328
    def __init__(
4✔
329
        self, op: Literal['-', '+'], value: Number, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None
330
    ):
331
        assert op in ('-', '+')
4✔
332
        assert isinstance(value, Number)
4✔
333
        self.op: Literal['-', '+'] = op
4✔
334
        self.value: Number = value
4✔
335

336
        super().__init__(tok=tok, end_tok=end_tok)
4✔
337

338

339
class TrailingComma(Node):
4✔
340
    def __init__(self, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None):
4✔
341
        super().__init__(tok=tok, end_tok=tok)  # Trailing comma is always a single COMMA token
4✔
342

343

344
class Comment(Node):
4✔
345
    def __init__(self, value: str, tok: JSON5Token | None = None, end_tok: JSON5Token | None = None):
4✔
346
        assert isinstance(value, str), f"Expected str got {type(value)}"
4✔
347
        self.value: str = value
4✔
348
        super().__init__(tok=tok, end_tok=tok)  # Comments are always a single token
4✔
349

350

351
class LineComment(Comment):
4✔
352
    ...
4✔
353

354

355
class BlockComment(Comment):
4✔
356
    ...
4✔
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