Skip to content

Commit c497061

Browse files
authored
Update json from 3.13.5 (#6007)
* Update `json` from 3.13.5 * Update `test_json` from 3.13.5
1 parent 8177525 commit c497061

19 files changed

+195
-102
lines changed

Lib/json/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
1+
r"""JSON (JavaScript Object Notation) <https://json.org> is a subset of
22
JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
33
interchange format.
44

Lib/json/decoder.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ def _from_serde(cls, msg, doc, line, col):
4141
pos += col
4242
return cls(msg, doc, pos)
4343

44-
4544
# Note that this exception is used from _json
4645
def __init__(self, msg, doc, pos):
4746
lineno = doc.count('\n', 0, pos) + 1
@@ -65,17 +64,18 @@ def __reduce__(self):
6564
}
6665

6766

67+
HEXDIGITS = re.compile(r'[0-9A-Fa-f]{4}', FLAGS)
6868
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
6969
BACKSLASH = {
7070
'"': '"', '\\': '\\', '/': '/',
7171
'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t',
7272
}
7373

74-
def _decode_uXXXX(s, pos):
75-
esc = s[pos + 1:pos + 5]
76-
if len(esc) == 4 and esc[1] not in 'xX':
74+
def _decode_uXXXX(s, pos, _m=HEXDIGITS.match):
75+
esc = _m(s, pos + 1)
76+
if esc is not None:
7777
try:
78-
return int(esc, 16)
78+
return int(esc.group(), 16)
7979
except ValueError:
8080
pass
8181
msg = "Invalid \\uXXXX escape"
@@ -215,10 +215,13 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
215215
break
216216
elif nextchar != ',':
217217
raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
218+
comma_idx = end - 1
218219
end = _w(s, end).end()
219220
nextchar = s[end:end + 1]
220221
end += 1
221222
if nextchar != '"':
223+
if nextchar == '}':
224+
raise JSONDecodeError("Illegal trailing comma before end of object", s, comma_idx)
222225
raise JSONDecodeError(
223226
"Expecting property name enclosed in double quotes", s, end - 1)
224227
if object_pairs_hook is not None:
@@ -255,19 +258,23 @@ def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
255258
break
256259
elif nextchar != ',':
257260
raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
261+
comma_idx = end - 1
258262
try:
259263
if s[end] in _ws:
260264
end += 1
261265
if s[end] in _ws:
262266
end = _w(s, end + 1).end()
267+
nextchar = s[end:end + 1]
263268
except IndexError:
264269
pass
270+
if nextchar == ']':
271+
raise JSONDecodeError("Illegal trailing comma before end of array", s, comma_idx)
265272

266273
return values, end
267274

268275

269276
class JSONDecoder(object):
270-
"""Simple JSON <http://json.org> decoder
277+
"""Simple JSON <https://json.org> decoder
271278
272279
Performs the following translations in decoding by default:
273280

Lib/json/encoder.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
for i in range(0x20):
3131
ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
3232
#ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
33+
del i
3334

3435
INFINITY = float('inf')
3536

@@ -71,7 +72,7 @@ def replace(match):
7172
c_encode_basestring_ascii or py_encode_basestring_ascii)
7273

7374
class JSONEncoder(object):
74-
"""Extensible JSON <http://json.org> encoder for Python data structures.
75+
"""Extensible JSON <https://json.org> encoder for Python data structures.
7576
7677
Supports the following objects and types by default:
7778
@@ -107,8 +108,8 @@ def __init__(self, *, skipkeys=False, ensure_ascii=True,
107108
"""Constructor for JSONEncoder, with sensible defaults.
108109
109110
If skipkeys is false, then it is a TypeError to attempt
110-
encoding of keys that are not str, int, float or None. If
111-
skipkeys is True, such items are simply skipped.
111+
encoding of keys that are not str, int, float, bool or None.
112+
If skipkeys is True, such items are simply skipped.
112113
113114
If ensure_ascii is true, the output is guaranteed to be str
114115
objects with all incoming non-ASCII characters escaped. If
@@ -173,7 +174,7 @@ def default(self, o):
173174
else:
174175
return list(iterable)
175176
# Let the base class default method raise the TypeError
176-
return JSONEncoder.default(self, o)
177+
return super().default(o)
177178
178179
"""
179180
raise TypeError(f'Object of type {o.__class__.__name__} '
@@ -243,15 +244,18 @@ def floatstr(o, allow_nan=self.allow_nan,
243244
return text
244245

245246

246-
if (_one_shot and c_make_encoder is not None
247-
and self.indent is None):
247+
if self.indent is None or isinstance(self.indent, str):
248+
indent = self.indent
249+
else:
250+
indent = ' ' * self.indent
251+
if _one_shot and c_make_encoder is not None:
248252
_iterencode = c_make_encoder(
249-
markers, self.default, _encoder, self.indent,
253+
markers, self.default, _encoder, indent,
250254
self.key_separator, self.item_separator, self.sort_keys,
251255
self.skipkeys, self.allow_nan)
252256
else:
253257
_iterencode = _make_iterencode(
254-
markers, self.default, _encoder, self.indent, floatstr,
258+
markers, self.default, _encoder, indent, floatstr,
255259
self.key_separator, self.item_separator, self.sort_keys,
256260
self.skipkeys, _one_shot)
257261
return _iterencode(o, 0)
@@ -271,9 +275,6 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
271275
_intstr=int.__repr__,
272276
):
273277

274-
if _indent is not None and not isinstance(_indent, str):
275-
_indent = ' ' * _indent
276-
277278
def _iterencode_list(lst, _current_indent_level):
278279
if not lst:
279280
yield '[]'
@@ -344,7 +345,6 @@ def _iterencode_dict(dct, _current_indent_level):
344345
_current_indent_level += 1
345346
newline_indent = '\n' + _indent * _current_indent_level
346347
item_separator = _item_separator + newline_indent
347-
yield newline_indent
348348
else:
349349
newline_indent = None
350350
item_separator = _item_separator
@@ -377,6 +377,8 @@ def _iterencode_dict(dct, _current_indent_level):
377377
f'not {key.__class__.__name__}')
378378
if first:
379379
first = False
380+
if newline_indent is not None:
381+
yield newline_indent
380382
else:
381383
yield item_separator
382384
yield _encoder(key)
@@ -403,7 +405,7 @@ def _iterencode_dict(dct, _current_indent_level):
403405
else:
404406
chunks = _iterencode(value, _current_indent_level)
405407
yield from chunks
406-
if newline_indent is not None:
408+
if not first and newline_indent is not None:
407409
_current_indent_level -= 1
408410
yield '\n' + _indent * _current_indent_level
409411
yield '}'

Lib/json/scanner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
__all__ = ['make_scanner']
1010

1111
NUMBER_RE = re.compile(
12-
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
12+
r'(-?(?:0|[1-9][0-9]*))(\.[0-9]+)?([eE][-+]?[0-9]+)?',
1313
(re.VERBOSE | re.MULTILINE | re.DOTALL))
1414

1515
def py_make_scanner(context):

Lib/json/tool.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import argparse
1414
import json
1515
import sys
16-
from pathlib import Path
1716

1817

1918
def main():
@@ -22,11 +21,9 @@ def main():
2221
'to validate and pretty-print JSON objects.')
2322
parser = argparse.ArgumentParser(prog=prog, description=description)
2423
parser.add_argument('infile', nargs='?',
25-
type=argparse.FileType(encoding="utf-8"),
2624
help='a JSON file to be validated or pretty-printed',
27-
default=sys.stdin)
25+
default='-')
2826
parser.add_argument('outfile', nargs='?',
29-
type=Path,
3027
help='write the output of infile to outfile',
3128
default=None)
3229
parser.add_argument('--sort-keys', action='store_true', default=False,
@@ -59,23 +56,30 @@ def main():
5956
dump_args['indent'] = None
6057
dump_args['separators'] = ',', ':'
6158

62-
with options.infile as infile:
59+
try:
60+
if options.infile == '-':
61+
infile = sys.stdin
62+
else:
63+
infile = open(options.infile, encoding='utf-8')
6364
try:
6465
if options.json_lines:
6566
objs = (json.loads(line) for line in infile)
6667
else:
6768
objs = (json.load(infile),)
69+
finally:
70+
if infile is not sys.stdin:
71+
infile.close()
6872

69-
if options.outfile is None:
70-
out = sys.stdout
71-
else:
72-
out = options.outfile.open('w', encoding='utf-8')
73-
with out as outfile:
74-
for obj in objs:
75-
json.dump(obj, outfile, **dump_args)
76-
outfile.write('\n')
77-
except ValueError as e:
78-
raise SystemExit(e)
73+
if options.outfile is None:
74+
outfile = sys.stdout
75+
else:
76+
outfile = open(options.outfile, 'w', encoding='utf-8')
77+
with outfile:
78+
for obj in objs:
79+
json.dump(obj, outfile, **dump_args)
80+
outfile.write('\n')
81+
except ValueError as e:
82+
raise SystemExit(e)
7983

8084

8185
if __name__ == '__main__':

Lib/test/test_json/test_decode.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,50 @@
22
from io import StringIO
33
from collections import OrderedDict
44
from test.test_json import PyTest, CTest
5+
from test import support
56

6-
import unittest
7+
import unittest # XXX: RUSTPYTHON; importing to be able to skip tests
78

89

910
class TestDecode:
1011
def test_decimal(self):
1112
rval = self.loads('1.1', parse_float=decimal.Decimal)
12-
self.assertTrue(isinstance(rval, decimal.Decimal))
13+
self.assertIsInstance(rval, decimal.Decimal)
1314
self.assertEqual(rval, decimal.Decimal('1.1'))
1415

1516
def test_float(self):
1617
rval = self.loads('1', parse_int=float)
17-
self.assertTrue(isinstance(rval, float))
18+
self.assertIsInstance(rval, float)
1819
self.assertEqual(rval, 1.0)
1920

21+
# TODO: RUSTPYTHON
22+
@unittest.skip("TODO: RUSTPYTHON; called `Result::unwrap()` on an `Err` value: ParseFloatError { kind: Invalid }")
23+
def test_nonascii_digits_rejected(self):
24+
# JSON specifies only ascii digits, see gh-125687
25+
for num in ["1\uff10", "0.\uff10", "0e\uff10"]:
26+
with self.assertRaises(self.JSONDecodeError):
27+
self.loads(num)
28+
29+
def test_bytes(self):
30+
self.assertEqual(self.loads(b"1"), 1)
31+
32+
def test_parse_constant(self):
33+
for constant, expected in [
34+
("Infinity", "INFINITY"),
35+
("-Infinity", "-INFINITY"),
36+
("NaN", "NAN"),
37+
]:
38+
self.assertEqual(
39+
self.loads(constant, parse_constant=str.upper), expected
40+
)
41+
42+
def test_constant_invalid_case(self):
43+
for constant in [
44+
"nan", "NAN", "naN", "infinity", "INFINITY", "inFiniTy"
45+
]:
46+
with self.assertRaises(self.JSONDecodeError):
47+
self.loads(constant)
48+
2049
def test_empty_objects(self):
2150
self.assertEqual(self.loads('{}'), {})
2251
self.assertEqual(self.loads('[]'), [])
@@ -89,17 +118,28 @@ def test_string_with_utf8_bom(self):
89118
self.json.load(StringIO(bom_json))
90119
self.assertIn('BOM', str(cm.exception))
91120
# make sure that the BOM is not detected in the middle of a string
92-
bom_in_str = '"{}"'.format(''.encode('utf-8-sig').decode('utf-8'))
121+
bom = ''.encode('utf-8-sig').decode('utf-8')
122+
bom_in_str = f'"{bom}"'
93123
self.assertEqual(self.loads(bom_in_str), '\ufeff')
94124
self.assertEqual(self.json.load(StringIO(bom_in_str)), '\ufeff')
95125

96126
def test_negative_index(self):
97127
d = self.json.JSONDecoder()
98128
self.assertRaises(ValueError, d.raw_decode, 'a'*42, -50000)
99129

130+
# TODO: RUSTPYTHON
131+
@unittest.expectedFailure
132+
def test_limit_int(self):
133+
maxdigits = 5000
134+
with support.adjust_int_max_str_digits(maxdigits):
135+
self.loads('1' * maxdigits)
136+
with self.assertRaises(ValueError):
137+
self.loads('1' * (maxdigits + 1))
138+
139+
100140
class TestPyDecode(TestDecode, PyTest): pass
101-
# TODO: RUSTPYTHON
102-
class TestCDecode(TestDecode, CTest): # pass
141+
142+
class TestCDecode(TestDecode, CTest):
103143
# TODO: RUSTPYTHON
104144
@unittest.expectedFailure
105145
def test_keys_reuse(self):

Lib/test/test_json/test_default.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import collections
12
from test.test_json import PyTest, CTest
23

34

@@ -7,6 +8,16 @@ def test_default(self):
78
self.dumps(type, default=repr),
89
self.dumps(repr(type)))
910

11+
def test_ordereddict(self):
12+
od = collections.OrderedDict(a=1, b=2, c=3, d=4)
13+
od.move_to_end('b')
14+
self.assertEqual(
15+
self.dumps(od),
16+
'{"a": 1, "c": 3, "d": 4, "b": 2}')
17+
self.assertEqual(
18+
self.dumps(od, sort_keys=True),
19+
'{"a": 1, "b": 2, "c": 3, "d": 4}')
20+
1021

1122
class TestPyDefault(TestDefault, PyTest): pass
1223
class TestCDefault(TestDefault, CTest): pass

Lib/test/test_json/test_dump.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ def test_dump_skipkeys(self):
2222
self.assertIn('valid_key', o)
2323
self.assertNotIn(b'invalid_key', o)
2424

25+
def test_dump_skipkeys_indent_empty(self):
26+
v = {b'invalid_key': False}
27+
self.assertEqual(self.json.dumps(v, skipkeys=True, indent=4), '{}')
28+
29+
def test_skipkeys_indent(self):
30+
v = {b'invalid_key': False, 'valid_key': True}
31+
self.assertEqual(self.json.dumps(v, skipkeys=True, indent=4), '{\n "valid_key": true\n}')
32+
2533
def test_encode_truefalse(self):
2634
self.assertEqual(self.dumps(
2735
{True: False, False: True}, sort_keys=True),

Lib/test/test_json/test_encode_basestring_ascii.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ def test_encode_basestring_ascii(self):
2323
for input_string, expect in CASES:
2424
result = self.json.encoder.encode_basestring_ascii(input_string)
2525
self.assertEqual(result, expect,
26-
'{0!r} != {1!r} for {2}({3!r})'.format(
27-
result, expect, fname, input_string))
26+
f'{result!r} != {expect!r} for {fname}({input_string!r})')
2827

2928
def test_ordered_dict(self):
3029
# See issue 6105

0 commit comments

Comments
 (0)