Skip to content

Commit 77be1bd

Browse files
authored
Merge branch 'main' into port-marshal-to-pep757/127936
2 parents 95d89a7 + 65ae3d5 commit 77be1bd

File tree

25 files changed

+489
-163
lines changed

25 files changed

+489
-163
lines changed

Doc/library/json.rst

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -258,64 +258,89 @@ Basic Usage
258258
the original one. That is, ``loads(dumps(x)) != x`` if x has non-string
259259
keys.
260260

261-
.. function:: load(fp, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
261+
.. function:: load(fp, *, cls=None, object_hook=None, parse_float=None, \
262+
parse_int=None, parse_constant=None, \
263+
object_pairs_hook=None, **kw)
262264
263-
Deserialize *fp* (a ``.read()``-supporting :term:`text file` or
264-
:term:`binary file` containing a JSON document) to a Python object using
265-
this :ref:`conversion table <json-to-py-table>`.
265+
Deserialize *fp* to a Python object
266+
using the :ref:`JSON-to-Python conversion table <json-to-py-table>`.
266267

267-
*object_hook* is an optional function that will be called with the result of
268-
any object literal decoded (a :class:`dict`). The return value of
269-
*object_hook* will be used instead of the :class:`dict`. This feature can
270-
be used to implement custom decoders (e.g. `JSON-RPC
271-
<https://www.jsonrpc.org>`_ class hinting).
268+
:param fp:
269+
A ``.read()``-supporting :term:`text file` or :term:`binary file`
270+
containing the JSON document to be deserialized.
271+
:type fp: :term:`file-like object`
272272

273-
*object_pairs_hook* is an optional function that will be called with the
274-
result of any object literal decoded with an ordered list of pairs. The
275-
return value of *object_pairs_hook* will be used instead of the
276-
:class:`dict`. This feature can be used to implement custom decoders. If
277-
*object_hook* is also defined, the *object_pairs_hook* takes priority.
273+
:param cls:
274+
If set, a custom JSON decoder.
275+
Additional keyword arguments to :func:`!load`
276+
will be passed to the constructor of *cls*.
277+
If ``None`` (the default), :class:`!JSONDecoder` is used.
278+
:type cls: a :class:`JSONDecoder` subclass
279+
280+
:param object_hook:
281+
If set, a function that is called with the result of
282+
any object literal decoded (a :class:`dict`).
283+
The return value of this function will be used
284+
instead of the :class:`dict`.
285+
This feature can be used to implement custom decoders,
286+
for example `JSON-RPC <https://www.jsonrpc.org>`_ class hinting.
287+
Default ``None``.
288+
:type object_hook: :term:`callable` | None
289+
290+
:param object_pairs_hook:
291+
If set, a function that is called with the result of
292+
any object literal decoded with an ordered list of pairs.
293+
The return value of this function will be used
294+
instead of the :class:`dict`.
295+
This feature can be used to implement custom decoders.
296+
If *object_hook* is also set, *object_pairs_hook* takes priority.
297+
Default ``None``.
298+
:type object_pairs_hook: :term:`callable` | None
299+
300+
:param parse_float:
301+
If set, a function that is called with
302+
the string of every JSON float to be decoded.
303+
If ``None`` (the default), it is equivalent to ``float(num_str)``.
304+
This can be used to parse JSON floats into custom datatypes,
305+
for example :class:`decimal.Decimal`.
306+
:type parse_float: :term:`callable` | None
307+
308+
:param parse_int:
309+
If set, a function that is called with
310+
the string of every JSON int to be decoded.
311+
If ``None`` (the default), it is equivalent to ``int(num_str)``.
312+
This can be used to parse JSON integers into custom datatypes,
313+
for example :class:`float`.
314+
:type parse_int: :term:`callable` | None
315+
316+
:param parse_constant:
317+
If set, a function that is called with one of the following strings:
318+
``'-Infinity'``, ``'Infinity'``, or ``'NaN'``.
319+
This can be used to raise an exception
320+
if invalid JSON numbers are encountered.
321+
Default ``None``.
322+
:type parse_constant: :term:`callable` | None
323+
324+
:raises JSONDecodeError:
325+
When the data being deserialized is not a valid JSON document.
278326

279327
.. versionchanged:: 3.1
280-
Added support for *object_pairs_hook*.
281328

282-
*parse_float* is an optional function that will be called with the string of
283-
every JSON float to be decoded. By default, this is equivalent to
284-
``float(num_str)``. This can be used to use another datatype or parser for
285-
JSON floats (e.g. :class:`decimal.Decimal`).
329+
* Added the optional *object_pairs_hook* parameter.
330+
* *parse_constant* doesn't get called on 'null', 'true', 'false' anymore.
286331

287-
*parse_int* is an optional function that will be called with the string of
288-
every JSON int to be decoded. By default, this is equivalent to
289-
``int(num_str)``. This can be used to use another datatype or parser for
290-
JSON integers (e.g. :class:`float`).
332+
.. versionchanged:: 3.6
333+
334+
* All optional parameters are now :ref:`keyword-only <keyword-only_parameter>`.
335+
* *fp* can now be a :term:`binary file`.
336+
The input encoding should be UTF-8, UTF-16 or UTF-32.
291337

292338
.. versionchanged:: 3.11
293339
The default *parse_int* of :func:`int` now limits the maximum length of
294340
the integer string via the interpreter's :ref:`integer string
295341
conversion length limitation <int_max_str_digits>` to help avoid denial
296342
of service attacks.
297343

298-
*parse_constant* is an optional function that will be called with one of the
299-
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This can be
300-
used to raise an exception if invalid JSON numbers are encountered.
301-
302-
.. versionchanged:: 3.1
303-
*parse_constant* doesn't get called on 'null', 'true', 'false' anymore.
304-
305-
To use a custom :class:`JSONDecoder` subclass, specify it with the ``cls``
306-
kwarg; otherwise :class:`JSONDecoder` is used. Additional keyword arguments
307-
will be passed to the constructor of the class.
308-
309-
If the data being deserialized is not a valid JSON document, a
310-
:exc:`JSONDecodeError` will be raised.
311-
312-
.. versionchanged:: 3.6
313-
All optional parameters are now :ref:`keyword-only <keyword-only_parameter>`.
314-
315-
.. versionchanged:: 3.6
316-
*fp* can now be a :term:`binary file`. The input encoding should be
317-
UTF-8, UTF-16 or UTF-32.
318-
319344
.. function:: loads(s, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
320345

321346
Deserialize *s* (a :class:`str`, :class:`bytes` or :class:`bytearray`

Lib/asyncio/base_events.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,12 @@ def create_task(self, coro, *, name=None, context=None):
477477

478478
task.set_name(name)
479479

480-
return task
480+
try:
481+
return task
482+
finally:
483+
# gh-128552: prevent a refcycle of
484+
# task.exception().__traceback__->BaseEventLoop.create_task->task
485+
del task
481486

482487
def set_task_factory(self, factory):
483488
"""Set a task factory that will be used by loop.create_task().

Lib/asyncio/taskgroups.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,12 @@ def create_task(self, coro, *, name=None, context=None):
205205
else:
206206
self._tasks.add(task)
207207
task.add_done_callback(self._on_task_done)
208-
return task
208+
try:
209+
return task
210+
finally:
211+
# gh-128552: prevent a refcycle of
212+
# task.exception().__traceback__->TaskGroup.create_task->task
213+
del task
209214

210215
# Since Python 3.8 Tasks propagate all exceptions correctly,
211216
# except for KeyboardInterrupt and SystemExit which are

Lib/test/test_asyncio/test_taskgroups.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Adapted with permission from the EdgeDB project;
22
# license: PSFL.
33

4+
import weakref
45
import sys
56
import gc
67
import asyncio
@@ -38,7 +39,25 @@ def no_other_refs():
3839
return [coro]
3940

4041

41-
class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
42+
def set_gc_state(enabled):
43+
was_enabled = gc.isenabled()
44+
if enabled:
45+
gc.enable()
46+
else:
47+
gc.disable()
48+
return was_enabled
49+
50+
51+
@contextlib.contextmanager
52+
def disable_gc():
53+
was_enabled = set_gc_state(enabled=False)
54+
try:
55+
yield
56+
finally:
57+
set_gc_state(enabled=was_enabled)
58+
59+
60+
class BaseTestTaskGroup:
4261

4362
async def test_taskgroup_01(self):
4463

@@ -832,15 +851,15 @@ async def test_taskgroup_without_parent_task(self):
832851
with self.assertRaisesRegex(RuntimeError, "has not been entered"):
833852
tg.create_task(coro)
834853

835-
def test_coro_closed_when_tg_closed(self):
854+
async def test_coro_closed_when_tg_closed(self):
836855
async def run_coro_after_tg_closes():
837856
async with taskgroups.TaskGroup() as tg:
838857
pass
839858
coro = asyncio.sleep(0)
840859
with self.assertRaisesRegex(RuntimeError, "is finished"):
841860
tg.create_task(coro)
842-
loop = asyncio.get_event_loop()
843-
loop.run_until_complete(run_coro_after_tg_closes())
861+
862+
await run_coro_after_tg_closes()
844863

845864
async def test_cancelling_level_preserved(self):
846865
async def raise_after(t, e):
@@ -965,6 +984,30 @@ async def coro_fn():
965984
self.assertIsInstance(exc, _Done)
966985
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
967986

987+
988+
async def test_exception_refcycles_parent_task_wr(self):
989+
"""Test that TaskGroup deletes self._parent_task and create_task() deletes task"""
990+
tg = asyncio.TaskGroup()
991+
exc = None
992+
993+
class _Done(Exception):
994+
pass
995+
996+
async def coro_fn():
997+
async with tg:
998+
raise _Done
999+
1000+
with disable_gc():
1001+
try:
1002+
async with asyncio.TaskGroup() as tg2:
1003+
task_wr = weakref.ref(tg2.create_task(coro_fn()))
1004+
except* _Done as excs:
1005+
exc = excs.exceptions[0].exceptions[0]
1006+
1007+
self.assertIsNone(task_wr())
1008+
self.assertIsInstance(exc, _Done)
1009+
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
1010+
9681011
async def test_exception_refcycles_propagate_cancellation_error(self):
9691012
"""Test that TaskGroup deletes propagate_cancellation_error"""
9701013
tg = asyncio.TaskGroup()
@@ -998,5 +1041,16 @@ class MyKeyboardInterrupt(KeyboardInterrupt):
9981041
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
9991042

10001043

1044+
class TestTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
1045+
loop_factory = asyncio.EventLoop
1046+
1047+
class TestEagerTaskTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
1048+
@staticmethod
1049+
def loop_factory():
1050+
loop = asyncio.EventLoop()
1051+
loop.set_task_factory(asyncio.eager_task_factory)
1052+
return loop
1053+
1054+
10011055
if __name__ == "__main__":
10021056
unittest.main()

Lib/test/test_capi/test_file.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
_testcapi = import_helper.import_module('_testcapi')
77

8+
NULL = None
9+
810

911
class CAPIFileTest(unittest.TestCase):
1012
def test_py_fopen(self):
@@ -25,15 +27,22 @@ def test_py_fopen(self):
2527
os_helper.TESTFN,
2628
os.fsencode(os_helper.TESTFN),
2729
]
28-
# TESTFN_UNDECODABLE cannot be used to create a file on macOS/WASI.
30+
if os_helper.TESTFN_UNDECODABLE is not None:
31+
filenames.append(os_helper.TESTFN_UNDECODABLE)
32+
filenames.append(os.fsdecode(os_helper.TESTFN_UNDECODABLE))
2933
if os_helper.TESTFN_UNENCODABLE is not None:
3034
filenames.append(os_helper.TESTFN_UNENCODABLE)
3135
for filename in filenames:
3236
with self.subTest(filename=filename):
3337
try:
3438
with open(filename, "wb") as fp:
3539
fp.write(source)
36-
40+
except OSError:
41+
# TESTFN_UNDECODABLE cannot be used to create a file
42+
# on macOS/WASI.
43+
filename = None
44+
continue
45+
try:
3746
data = _testcapi.py_fopen(filename, "rb")
3847
self.assertEqual(data, source[:256])
3948
finally:
@@ -47,7 +56,14 @@ def test_py_fopen(self):
4756

4857
# non-ASCII mode failing with "Invalid argument"
4958
with self.assertRaises(OSError):
50-
_testcapi.py_fopen(__file__, "\xe9")
59+
_testcapi.py_fopen(__file__, b"\xc2\x80")
60+
with self.assertRaises(OSError):
61+
# \x98 is invalid in cp1250, cp1251, cp1257
62+
# \x9d is invalid in cp1252-cp1255, cp1258
63+
_testcapi.py_fopen(__file__, b"\xc2\x98\xc2\x9d")
64+
# UnicodeDecodeError can come from the audit hook code
65+
with self.assertRaises((UnicodeDecodeError, OSError)):
66+
_testcapi.py_fopen(__file__, b"\x98\x9d")
5167

5268
# invalid filename type
5369
for invalid_type in (123, object()):
@@ -60,7 +76,8 @@ def test_py_fopen(self):
6076
# On Windows, the file mode is limited to 10 characters
6177
_testcapi.py_fopen(__file__, "rt+, ccs=UTF-8")
6278

63-
# CRASHES py_fopen(__file__, None)
79+
# CRASHES _testcapi.py_fopen(NULL, 'rb')
80+
# CRASHES _testcapi.py_fopen(__file__, NULL)
6481

6582

6683
if __name__ == "__main__":

Lib/test/test_capi/test_opt.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import contextlib
2+
import itertools
23
import sys
34
import textwrap
45
import unittest
@@ -1511,6 +1512,49 @@ def test_jit_error_pops(self):
15111512
with self.assertRaises(TypeError):
15121513
{item for item in items}
15131514

1515+
def test_power_type_depends_on_input_values(self):
1516+
template = textwrap.dedent("""
1517+
import _testinternalcapi
1518+
1519+
L, R, X, Y = {l}, {r}, {x}, {y}
1520+
1521+
def check(actual: complex, expected: complex) -> None:
1522+
assert actual == expected, (actual, expected)
1523+
assert type(actual) is type(expected), (actual, expected)
1524+
1525+
def f(l: complex, r: complex) -> None:
1526+
expected_local_local = pow(l, r) + pow(l, r)
1527+
expected_const_local = pow(L, r) + pow(L, r)
1528+
expected_local_const = pow(l, R) + pow(l, R)
1529+
expected_const_const = pow(L, R) + pow(L, R)
1530+
for _ in range(_testinternalcapi.TIER2_THRESHOLD):
1531+
# Narrow types:
1532+
l + l, r + r
1533+
# The powers produce results, and the addition is unguarded:
1534+
check(l ** r + l ** r, expected_local_local)
1535+
check(L ** r + L ** r, expected_const_local)
1536+
check(l ** R + l ** R, expected_local_const)
1537+
check(L ** R + L ** R, expected_const_const)
1538+
1539+
# JIT for one pair of values...
1540+
f(L, R)
1541+
# ...then run with another:
1542+
f(X, Y)
1543+
""")
1544+
interesting = [
1545+
(1, 1), # int ** int -> int
1546+
(1, -1), # int ** int -> float
1547+
(1.0, 1), # float ** int -> float
1548+
(1, 1.0), # int ** float -> float
1549+
(-1, 0.5), # int ** float -> complex
1550+
(1.0, 1.0), # float ** float -> float
1551+
(-1.0, 0.5), # float ** float -> complex
1552+
]
1553+
for (l, r), (x, y) in itertools.product(interesting, repeat=2):
1554+
s = template.format(l=l, r=r, x=x, y=y)
1555+
with self.subTest(l=l, r=r, x=x, y=y):
1556+
script_helper.assert_python_ok("-c", s)
1557+
15141558

15151559
def global_identity(x):
15161560
return x

0 commit comments

Comments
 (0)