Skip to content

Commit 5242ff5

Browse files
authored
Bump json to 3.14.2 (RustPython#6774)
1 parent e8dd582 commit 5242ff5

13 files changed

Lines changed: 340 additions & 123 deletions

Lib/json/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@
8686
'[2.0, 1.0]'
8787
8888
89-
Using json.tool from the shell to validate and pretty-print::
89+
Using json from the shell to validate and pretty-print::
9090
91-
$ echo '{"json":"obj"}' | python -m json.tool
91+
$ echo '{"json":"obj"}' | python -m json
9292
{
9393
"json": "obj"
9494
}
95-
$ echo '{ 1.2:3.4}' | python -m json.tool
95+
$ echo '{ 1.2:3.4}' | python -m json
9696
Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
9797
"""
9898
__version__ = '2.0.9'

Lib/json/__main__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Command-line tool to validate and pretty-print JSON
2+
3+
Usage::
4+
5+
$ echo '{"json":"obj"}' | python -m json
6+
{
7+
"json": "obj"
8+
}
9+
$ echo '{ 1.2:3.4}' | python -m json
10+
Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
11+
12+
"""
13+
import json.tool
14+
15+
16+
if __name__ == '__main__':
17+
try:
18+
json.tool.main()
19+
except BrokenPipeError as exc:
20+
raise SystemExit(exc.errno)

Lib/json/encoder.py

Lines changed: 67 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -295,37 +295,40 @@ def _iterencode_list(lst, _current_indent_level):
295295
else:
296296
newline_indent = None
297297
separator = _item_separator
298-
first = True
299-
for value in lst:
300-
if first:
301-
first = False
302-
else:
298+
for i, value in enumerate(lst):
299+
if i:
303300
buf = separator
304-
if isinstance(value, str):
305-
yield buf + _encoder(value)
306-
elif value is None:
307-
yield buf + 'null'
308-
elif value is True:
309-
yield buf + 'true'
310-
elif value is False:
311-
yield buf + 'false'
312-
elif isinstance(value, int):
313-
# Subclasses of int/float may override __repr__, but we still
314-
# want to encode them as integers/floats in JSON. One example
315-
# within the standard library is IntEnum.
316-
yield buf + _intstr(value)
317-
elif isinstance(value, float):
318-
# see comment above for int
319-
yield buf + _floatstr(value)
320-
else:
321-
yield buf
322-
if isinstance(value, (list, tuple)):
323-
chunks = _iterencode_list(value, _current_indent_level)
324-
elif isinstance(value, dict):
325-
chunks = _iterencode_dict(value, _current_indent_level)
301+
try:
302+
if isinstance(value, str):
303+
yield buf + _encoder(value)
304+
elif value is None:
305+
yield buf + 'null'
306+
elif value is True:
307+
yield buf + 'true'
308+
elif value is False:
309+
yield buf + 'false'
310+
elif isinstance(value, int):
311+
# Subclasses of int/float may override __repr__, but we still
312+
# want to encode them as integers/floats in JSON. One example
313+
# within the standard library is IntEnum.
314+
yield buf + _intstr(value)
315+
elif isinstance(value, float):
316+
# see comment above for int
317+
yield buf + _floatstr(value)
326318
else:
327-
chunks = _iterencode(value, _current_indent_level)
328-
yield from chunks
319+
yield buf
320+
if isinstance(value, (list, tuple)):
321+
chunks = _iterencode_list(value, _current_indent_level)
322+
elif isinstance(value, dict):
323+
chunks = _iterencode_dict(value, _current_indent_level)
324+
else:
325+
chunks = _iterencode(value, _current_indent_level)
326+
yield from chunks
327+
except GeneratorExit:
328+
raise
329+
except BaseException as exc:
330+
exc.add_note(f'when serializing {type(lst).__name__} item {i}')
331+
raise
329332
if newline_indent is not None:
330333
_current_indent_level -= 1
331334
yield '\n' + _indent * _current_indent_level
@@ -385,28 +388,34 @@ def _iterencode_dict(dct, _current_indent_level):
385388
yield item_separator
386389
yield _encoder(key)
387390
yield _key_separator
388-
if isinstance(value, str):
389-
yield _encoder(value)
390-
elif value is None:
391-
yield 'null'
392-
elif value is True:
393-
yield 'true'
394-
elif value is False:
395-
yield 'false'
396-
elif isinstance(value, int):
397-
# see comment for int/float in _make_iterencode
398-
yield _intstr(value)
399-
elif isinstance(value, float):
400-
# see comment for int/float in _make_iterencode
401-
yield _floatstr(value)
402-
else:
403-
if isinstance(value, (list, tuple)):
404-
chunks = _iterencode_list(value, _current_indent_level)
405-
elif isinstance(value, dict):
406-
chunks = _iterencode_dict(value, _current_indent_level)
391+
try:
392+
if isinstance(value, str):
393+
yield _encoder(value)
394+
elif value is None:
395+
yield 'null'
396+
elif value is True:
397+
yield 'true'
398+
elif value is False:
399+
yield 'false'
400+
elif isinstance(value, int):
401+
# see comment for int/float in _make_iterencode
402+
yield _intstr(value)
403+
elif isinstance(value, float):
404+
# see comment for int/float in _make_iterencode
405+
yield _floatstr(value)
407406
else:
408-
chunks = _iterencode(value, _current_indent_level)
409-
yield from chunks
407+
if isinstance(value, (list, tuple)):
408+
chunks = _iterencode_list(value, _current_indent_level)
409+
elif isinstance(value, dict):
410+
chunks = _iterencode_dict(value, _current_indent_level)
411+
else:
412+
chunks = _iterencode(value, _current_indent_level)
413+
yield from chunks
414+
except GeneratorExit:
415+
raise
416+
except BaseException as exc:
417+
exc.add_note(f'when serializing {type(dct).__name__} item {key!r}')
418+
raise
410419
if not first and newline_indent is not None:
411420
_current_indent_level -= 1
412421
yield '\n' + _indent * _current_indent_level
@@ -439,8 +448,14 @@ def _iterencode(o, _current_indent_level):
439448
if markerid in markers:
440449
raise ValueError("Circular reference detected")
441450
markers[markerid] = o
442-
o = _default(o)
443-
yield from _iterencode(o, _current_indent_level)
451+
newobj = _default(o)
452+
try:
453+
yield from _iterencode(newobj, _current_indent_level)
454+
except GeneratorExit:
455+
raise
456+
except BaseException as exc:
457+
exc.add_note(f'when serializing {type(o).__name__} object')
458+
raise
444459
if markers is not None:
445460
del markers[markerid]
446461
return _iterencode

Lib/json/tool.py

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,50 @@
1-
r"""Command-line tool to validate and pretty-print JSON
2-
3-
Usage::
4-
5-
$ echo '{"json":"obj"}' | python -m json.tool
6-
{
7-
"json": "obj"
8-
}
9-
$ echo '{ 1.2:3.4}' | python -m json.tool
10-
Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
1+
"""Command-line tool to validate and pretty-print JSON
112
3+
See `json.__main__` for a usage example (invocation as
4+
`python -m json.tool` is supported for backwards compatibility).
125
"""
136
import argparse
147
import json
8+
import re
159
import sys
10+
from _colorize import get_theme, can_colorize
11+
12+
13+
# The string we are colorizing is valid JSON,
14+
# so we can use a looser but simpler regex to match
15+
# the various parts, most notably strings and numbers,
16+
# where the regex given by the spec is much more complex.
17+
_color_pattern = re.compile(r'''
18+
(?P<key>"(\\.|[^"\\])*")(?=:) |
19+
(?P<string>"(\\.|[^"\\])*") |
20+
(?P<number>NaN|-?Infinity|[0-9\-+.Ee]+) |
21+
(?P<boolean>true|false) |
22+
(?P<null>null)
23+
''', re.VERBOSE)
24+
25+
_group_to_theme_color = {
26+
"key": "definition",
27+
"string": "string",
28+
"number": "number",
29+
"boolean": "keyword",
30+
"null": "keyword",
31+
}
32+
33+
34+
def _colorize_json(json_str, theme):
35+
def _replace_match_callback(match):
36+
for group, color in _group_to_theme_color.items():
37+
if m := match.group(group):
38+
return f"{theme[color]}{m}{theme.reset}"
39+
return match.group()
40+
41+
return re.sub(_color_pattern, _replace_match_callback, json_str)
1642

1743

1844
def main():
19-
prog = 'python -m json.tool'
2045
description = ('A simple command line interface for json module '
2146
'to validate and pretty-print JSON objects.')
22-
parser = argparse.ArgumentParser(prog=prog, description=description)
47+
parser = argparse.ArgumentParser(description=description, color=True)
2348
parser.add_argument('infile', nargs='?',
2449
help='a JSON file to be validated or pretty-printed',
2550
default='-')
@@ -75,9 +100,16 @@ def main():
75100
else:
76101
outfile = open(options.outfile, 'w', encoding='utf-8')
77102
with outfile:
78-
for obj in objs:
79-
json.dump(obj, outfile, **dump_args)
80-
outfile.write('\n')
103+
if can_colorize(file=outfile):
104+
t = get_theme(tty_file=outfile).syntax
105+
for obj in objs:
106+
json_str = json.dumps(obj, **dump_args)
107+
outfile.write(_colorize_json(json_str, t))
108+
outfile.write('\n')
109+
else:
110+
for obj in objs:
111+
json.dump(obj, outfile, **dump_args)
112+
outfile.write('\n')
81113
except ValueError as e:
82114
raise SystemExit(e)
83115

@@ -86,4 +118,4 @@ def main():
86118
try:
87119
main()
88120
except BrokenPipeError as exc:
89-
sys.exit(exc.errno)
121+
raise SystemExit(exc.errno)

Lib/test/test_json/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ def test_pyjson(self):
4141
'json.encoder')
4242

4343
class TestCTest(CTest):
44-
# TODO: RUSTPYTHON
45-
@unittest.expectedFailure
44+
@unittest.expectedFailure # TODO: RUSTPYTHON
4645
def test_cjson(self):
4746
self.assertEqual(self.json.scanner.make_scanner.__module__, '_json')
4847
self.assertEqual(self.json.decoder.scanstring.__module__, '_json')

Lib/test/test_json/test_decode.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ def test_float(self):
1818
self.assertIsInstance(rval, float)
1919
self.assertEqual(rval, 1.0)
2020

21-
# TODO: RUSTPYTHON
22-
@unittest.skip("TODO: RUSTPYTHON; called `Result::unwrap()` on an `Err` value: ParseFloatError { kind: Invalid }")
21+
@unittest.skip('TODO: RUSTPYTHON; called `Result::unwrap()` on an `Err` value: ParseFloatError { kind: Invalid }')
2322
def test_nonascii_digits_rejected(self):
2423
# JSON specifies only ascii digits, see gh-125687
2524
for num in ["1\uff10", "0.\uff10", "0e\uff10"]:
@@ -138,9 +137,6 @@ def test_limit_int(self):
138137
class TestPyDecode(TestDecode, PyTest): pass
139138

140139
class TestCDecode(TestDecode, CTest):
141-
def test_keys_reuse(self):
142-
return super().test_keys_reuse()
143-
144140
# TODO: RUSTPYTHON
145141
@unittest.expectedFailure
146142
def test_limit_int(self):

Lib/test/test_json/test_default.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import collections
2+
import unittest # XXX: RUSTPYTHON; importing to be able to skip tests
23
from test.test_json import PyTest, CTest
34

45

@@ -8,6 +9,26 @@ def test_default(self):
89
self.dumps(type, default=repr),
910
self.dumps(repr(type)))
1011

12+
# TODO: RUSTPYTHON
13+
@unittest.expectedFailure
14+
def test_bad_default(self):
15+
def default(obj):
16+
if obj is NotImplemented:
17+
raise ValueError
18+
if obj is ...:
19+
return NotImplemented
20+
if obj is type:
21+
return collections
22+
return [...]
23+
24+
with self.assertRaises(ValueError) as cm:
25+
self.dumps(type, default=default)
26+
self.assertEqual(cm.exception.__notes__,
27+
['when serializing ellipsis object',
28+
'when serializing list item 0',
29+
'when serializing module object',
30+
'when serializing type object'])
31+
1132
def test_ordereddict(self):
1233
od = collections.OrderedDict(a=1, b=2, c=3, d=4)
1334
od.move_to_end('b')

Lib/test/test_json/test_fail.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from test.test_json import PyTest, CTest
2-
31
import unittest # XXX: RUSTPYTHON; importing to be able to skip tests
42

3+
from test.test_json import PyTest, CTest
4+
55
# 2007-10-05
66
JSONDOCS = [
77
# https://json.org/JSON_checker/test/fail1.json
@@ -99,11 +99,32 @@ def test_non_string_keys_dict(self):
9999
'keys must be str, int, float, bool or None, not tuple'):
100100
self.dumps(data)
101101

102+
# TODO: RUSTPYTHON
103+
@unittest.expectedFailure
102104
def test_not_serializable(self):
103105
import sys
104106
with self.assertRaisesRegex(TypeError,
105-
'Object of type module is not JSON serializable'):
107+
'Object of type module is not JSON serializable') as cm:
106108
self.dumps(sys)
109+
self.assertNotHasAttr(cm.exception, '__notes__')
110+
111+
with self.assertRaises(TypeError) as cm:
112+
self.dumps([1, [2, 3, sys]])
113+
self.assertEqual(cm.exception.__notes__,
114+
['when serializing list item 2',
115+
'when serializing list item 1'])
116+
117+
with self.assertRaises(TypeError) as cm:
118+
self.dumps((1, (2, 3, sys)))
119+
self.assertEqual(cm.exception.__notes__,
120+
['when serializing tuple item 2',
121+
'when serializing tuple item 1'])
122+
123+
with self.assertRaises(TypeError) as cm:
124+
self.dumps({'a': {'b': sys}})
125+
self.assertEqual(cm.exception.__notes__,
126+
["when serializing dict item 'b'",
127+
"when serializing dict item 'a'"])
107128

108129
def test_truncated_input(self):
109130
test_cases = [

0 commit comments

Comments
 (0)