Skip to content

Commit 1a321fb

Browse files
Merge branch 'main' into marshal-read-errors
2 parents fecb4a0 + b0da7c7 commit 1a321fb

68 files changed

Lines changed: 2109 additions & 1407 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Doc/library/venv.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,12 @@ creation according to their needs, the :class:`EnvBuilder` class.
452452
On POSIX systems, if a specific executable ``python3.x`` was used,
453453
symlinks to ``python`` and ``python3`` will be created pointing to that
454454
executable, unless files with those names already exist.
455+
On POSIX systems, a broken symlink at a destination path is removed
456+
before the copy or symlink is created.
457+
458+
.. versionchanged:: next
459+
A broken symlink at a destination path is now removed and replaced.
460+
Previously it was left in place, or it made the copy fail.
455461

456462
.. method:: setup_scripts(context)
457463

Include/internal/pycore_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ int _PyCompile_EnterScope(struct _PyCompiler *c, identifier name, int scope_type
137137
void *key, int lineno, PyObject *private,
138138
_PyCompile_CodeUnitMetadata *umd);
139139
void _PyCompile_ExitScope(struct _PyCompiler *c);
140+
int _PyCompile_SetQualname(struct _PyCompiler *c);
140141
Py_ssize_t _PyCompile_AddConst(struct _PyCompiler *c, PyObject *o);
141142
_PyInstructionSequence *_PyCompile_InstrSequence(struct _PyCompiler *c);
142143
int _PyCompile_StartAnnotationSetup(struct _PyCompiler *c);

Lib/_py_abc.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ def _abc_caches_clear(cls):
9292
def __instancecheck__(cls, instance):
9393
"""Override for isinstance(instance, cls)."""
9494
# Inline the cache checking
95-
subclass = instance.__class__
95+
try:
96+
subclass = instance.__class__
97+
except AttributeError:
98+
# Fall back to the type when the instance has no __class__,
99+
# matching the behaviour of the built-in isinstance() (gh-153772).
100+
subclass = type(instance)
96101
if subclass in cls._abc_cache:
97102
return True
98103
subtype = type(instance)

Lib/asyncio/graph.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ def _build_graph_for_future(
5858
while coro is not None:
5959
if hasattr(coro, 'cr_await'):
6060
# A native coroutine or duck-type compatible iterator
61-
st.append(FrameCallGraphEntry(coro.cr_frame))
61+
if coro.cr_frame is not None:
62+
st.append(FrameCallGraphEntry(coro.cr_frame))
6263
coro = coro.cr_await
6364
elif hasattr(coro, 'ag_await'):
6465
# A native async generator or duck-type compatible iterator
@@ -273,4 +274,5 @@ def print_call_graph(
273274
limit: int | None = None,
274275
) -> None:
275276
"""Print the async call graph for the current task or the provided Future."""
276-
print(format_call_graph(future, depth=depth, limit=limit), file=file)
277+
# gh-156327: print_call_graph() must not report its own frame
278+
print(format_call_graph(future, depth=depth + 1, limit=limit), file=file)

Lib/asyncio/tasks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,9 +562,11 @@ def __init__(self, aws, timeout):
562562
self._timeout_handle = None
563563

564564
loop = events.get_event_loop()
565+
self._cur_task = current_task()
565566
todo = {ensure_future(aw, loop=loop) for aw in set(aws)}
566567
for f in todo:
567568
f.add_done_callback(self._handle_completion)
569+
futures.future_add_to_awaited_by(f, self._cur_task)
568570
if todo and timeout is not None:
569571
self._timeout_handle = (
570572
loop.call_later(timeout, self._handle_timeout)
@@ -595,13 +597,15 @@ def __next__(self):
595597
def _handle_timeout(self):
596598
for f in self._todo:
597599
f.remove_done_callback(self._handle_completion)
600+
futures.future_discard_from_awaited_by(f, self._cur_task)
598601
self._done.put_nowait(None) # Sentinel for _wait_for_one().
599602
self._todo.clear() # Can't do todo.remove(f) in the loop.
600603

601604
def _handle_completion(self, f):
602605
if not self._todo:
603606
return # _handle_timeout() was here first.
604607
self._todo.remove(f)
608+
futures.future_discard_from_awaited_by(f, self._cur_task)
605609
self._done.put_nowait(f)
606610
if not self._todo and self._timeout_handle is not None:
607611
self._timeout_handle.cancel()

Lib/configparser.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,8 @@ class RawConfigParser(MutableMapping):
618618
_OPT_TMPL = r"""
619619
(?P<option> # very permissive!
620620
(?:(?!{delim})\S)* # non-delimiter non-whitespace
621-
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
621+
(?:(?:(?!{delim})\s)+ # optionally more
622+
(?:(?!{delim})\S)+)*) # space-separated words
622623
\s*(?P<vi>{delim})\s* # any number of space/tab,
623624
# followed by any of the
624625
# allowed delimiters,
@@ -628,7 +629,8 @@ class RawConfigParser(MutableMapping):
628629
_OPT_NV_TMPL = r"""
629630
(?P<option> # very permissive!
630631
(?:(?!{delim})\S)* # non-delimiter non-whitespace
631-
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
632+
(?:(?:(?!{delim})\s)+ # optionally more
633+
(?:(?!{delim})\S)+)*) # space-separated words
632634
\s*(?: # any number of space/tab,
633635
(?P<vi>{delim})\s* # optionally followed by
634636
# any of the allowed

Lib/test/test_abc.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,25 @@ class C(str): pass
380380
self.assertIsSubclass(C, A)
381381
self.assertIsSubclass(C, (A,))
382382

383+
def test_instancecheck_no_class(self):
384+
# gh-153772: __instancecheck__ must fall back to type(instance)
385+
# when the instance has no __class__, matching isinstance().
386+
class NoClass:
387+
def __getattribute__(self, name):
388+
if name == "__class__":
389+
raise AttributeError(name)
390+
return super().__getattribute__(name)
391+
392+
class A(metaclass=abc_ABCMeta):
393+
pass
394+
395+
obj = NoClass()
396+
# Must return False rather than propagating the AttributeError.
397+
self.assertNotIsInstance(obj, A)
398+
# Registering the actual type makes the fallback report a match.
399+
A.register(NoClass)
400+
self.assertIsInstance(obj, A)
401+
383402
def test_registration_edge_cases(self):
384403
class A(metaclass=abc_ABCMeta):
385404
pass

Lib/test/test_asyncio/test_graph.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def walk(s):
4141
return ret
4242

4343
buf = io.StringIO()
44-
asyncio.print_call_graph(fut, file=buf, depth=depth+1)
44+
asyncio.print_call_graph(fut, file=buf, depth=depth)
4545

4646
stack = asyncio.capture_call_graph(fut, depth=depth)
4747
return walk(stack), buf.getvalue()
@@ -298,6 +298,68 @@ async def main(t1, t2):
298298
]
299299
])
300300

301+
async def test_stack_as_completed(self):
302+
# gh-156523: as_completed() must record the awaiting task
303+
stack_for_inner = None
304+
305+
async def inner():
306+
await asyncio.sleep(0)
307+
nonlocal stack_for_inner
308+
stack_for_inner = capture_test_stack()
309+
310+
async def main(t):
311+
for f in asyncio.as_completed([t]):
312+
await f
313+
314+
t = asyncio.create_task(inner(), name='inner')
315+
await main(t)
316+
self.assertFalse(t._asyncio_awaited_by)
317+
318+
self.assertEqual(stack_for_inner[0], [
319+
'T<inner>',
320+
['s capture_test_stack', 'a inner'],
321+
[
322+
['T<anon>',
323+
['a get', 'a _wait_for_one', 'a main',
324+
'a test_stack_as_completed'],
325+
[]
326+
]
327+
]
328+
])
329+
330+
async def test_stack_as_completed_timeout(self):
331+
# gh-156523: the awaiting task must be dropped when as_completed() times out
332+
stack_for_inner = None
333+
334+
async def inner():
335+
nonlocal stack_for_inner
336+
stack_for_inner = capture_test_stack()
337+
await asyncio.sleep(3600)
338+
339+
async def main(t):
340+
with self.assertRaises(TimeoutError):
341+
for f in asyncio.as_completed([t], timeout=0.01):
342+
await f
343+
344+
t = asyncio.create_task(inner(), name='inner')
345+
await main(t)
346+
self.assertFalse(t._asyncio_awaited_by)
347+
t.cancel()
348+
with self.assertRaises(asyncio.CancelledError):
349+
await t
350+
351+
self.assertEqual(stack_for_inner[0], [
352+
'T<inner>',
353+
['s capture_test_stack', 'a inner'],
354+
[
355+
['T<anon>',
356+
['a get', 'a _wait_for_one', 'a main',
357+
'a test_stack_as_completed_timeout'],
358+
[]
359+
]
360+
]
361+
])
362+
301363
async def test_stack_task(self):
302364

303365
stack_for_inner = None
@@ -422,6 +484,32 @@ def test_capture_call_graph_non_future(self):
422484
with self.assertRaises(TypeError):
423485
asyncio.capture_call_graph("not a future")
424486

487+
async def test_print_call_graph_innermost_frame(self):
488+
# gh-156327: print_call_graph() must not report its own frame
489+
buf = io.StringIO()
490+
lineno = sys._getframe().f_lineno + 1
491+
asyncio.print_call_graph(file=buf)
492+
first_frame = buf.getvalue().splitlines()[2]
493+
self.assertIn(f'File {__file__!r}, line {lineno},', first_frame)
494+
495+
async def test_call_graph_finished_task(self):
496+
# gh-156408: the call graph must not record a finished coroutine's None frame
497+
async def boom():
498+
raise ValueError
499+
500+
done = asyncio.create_task(asyncio.sleep(0), name='done')
501+
failed = asyncio.create_task(boom(), name='failed')
502+
cancelled = asyncio.create_task(asyncio.Event().wait(), name='cancelled')
503+
cancelled.cancel()
504+
await asyncio.gather(done, failed, cancelled, return_exceptions=True)
505+
506+
for task in (done, failed, cancelled):
507+
with self.subTest(task=task.get_name()):
508+
buf = io.StringIO()
509+
asyncio.print_call_graph(task, file=buf)
510+
self.assertEqual(asyncio.capture_call_graph(task).call_stack, ())
511+
self.assertIn(f"name={task.get_name()!r}", buf.getvalue())
512+
425513
async def test_capture_call_graph_no_current_task(self):
426514
results = []
427515

Lib/test/test_cmd_line_script.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
import os.path
1111
import py_compile
12+
import select
1213
import subprocess
1314
import io
1415

@@ -168,6 +169,24 @@ def test_stdin_loader(self):
168169
expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8")
169170
self.assertIn(expected, out)
170171

172+
@unittest.skipIf(sys.platform == "win32", "select() cannot wait for pipes")
173+
def test_stdin_syntax_error_does_not_read_ahead(self):
174+
process = spawn_python()
175+
try:
176+
process.stdin.write(b")\n")
177+
process.stdin.flush()
178+
output = b""
179+
while b"SyntaxError" not in output:
180+
ready, _, _ = select.select(
181+
[process.stdout], [], [], support.SHORT_TIMEOUT
182+
)
183+
self.assertTrue(ready, output)
184+
data = os.read(process.stdout.fileno(), 4096)
185+
self.assertTrue(data, output)
186+
output += data
187+
finally:
188+
kill_python(process)
189+
171190
@contextlib.contextmanager
172191
def interactive_python(self, separate_stderr=False):
173192
if separate_stderr:

Lib/test/test_configparser.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class CfgParserTestCaseClass:
4343
default_section = configparser.DEFAULTSECT
4444
interpolation = configparser._UNSET
4545

46-
def newconfig(self, defaults=None):
46+
def newconfig(self, defaults=None, **kwargs):
4747
arguments = dict(
4848
defaults=defaults,
4949
allow_no_value=self.allow_no_value,
@@ -56,6 +56,7 @@ def newconfig(self, defaults=None):
5656
default_section=self.default_section,
5757
interpolation=self.interpolation,
5858
)
59+
arguments.update(kwargs)
5960
instance = self.config_class(**arguments)
6061
return instance
6162

@@ -358,6 +359,32 @@ def test_basic(self):
358359
the larch {0[1]} 1
359360
""".format(self.delimiters)))
360361

362+
@support.subTests('data', [
363+
'foo bar=baz',
364+
'foo bar=baz',
365+
'foo=bar=baz',
366+
'foo = bar=baz',
367+
'foo\t \t=\t \tbar=baz',
368+
])
369+
def test_space_delimiter(self, data):
370+
# gh-156353: Space should be accepted as a delimiter
371+
cf = self.newconfig(delimiters=(' ', '='))
372+
cf.read_string(f"[all]\n{data}")
373+
self.assertEqual(cf.options('all'), ['foo'])
374+
self.assertEqual(cf.get('all', 'foo'), 'bar=baz')
375+
376+
@support.subTests('delimiter', ' =:;#x\t\0\N{RS}\N{CEDILLA}\N{CAT}')
377+
@support.subTests('space_before', ['', ' ', '\t', ' \t'])
378+
@support.subTests('space_after', ['', ' ', '\t', ' \t'])
379+
def test_any_delimiter(self, delimiter, space_before, space_after):
380+
cf = self.newconfig(
381+
delimiters=(delimiter,),
382+
inline_comment_prefixes=None,
383+
)
384+
cf.read_string(f"[all]\nfoo{space_before}{delimiter}{space_after}bar=baz")
385+
self.assertEqual(cf.options('all'), ['foo'])
386+
self.assertEqual(cf.get('all', 'foo'), 'bar=baz')
387+
361388
def test_basic_from_dict(self):
362389
config = {
363390
"Foo Bar": {
@@ -1991,8 +2018,8 @@ class ConvertersTestCase(BasicTestCase, unittest.TestCase):
19912018

19922019
config_class = configparser.ConfigParser
19932020

1994-
def newconfig(self, defaults=None):
1995-
instance = super().newconfig(defaults=defaults)
2021+
def newconfig(self, defaults=None, **kwargs):
2022+
instance = super().newconfig(defaults=defaults, **kwargs)
19962023
instance.converters['list'] = lambda v: [e.strip() for e in v.split()
19972024
if e.strip()]
19982025
return instance

0 commit comments

Comments
 (0)