Skip to content

Commit e7803f4

Browse files
authored
Merge branch 'main' into gh-102591-document-urlcleanup
2 parents ccbf2e8 + a2d3787 commit e7803f4

22 files changed

Lines changed: 1285 additions & 982 deletions

Doc/library/asyncio-queue.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ Queue
7171
Return an item if one is immediately available, else raise
7272
:exc:`QueueEmpty`.
7373

74+
Raises :exc:`QueueShutDown` if the queue has been shut down and is empty.
75+
7476
.. method:: join()
7577
:async:
7678

@@ -96,6 +98,8 @@ Queue
9698

9799
If no free slot is immediately available, raise :exc:`QueueFull`.
98100

101+
Raises :exc:`QueueShutDown` if the queue has been shut down.
102+
99103
.. method:: qsize()
100104

101105
Return the number of items in the queue.
@@ -188,8 +192,9 @@ Exceptions
188192

189193
.. exception:: QueueShutDown
190194

191-
Exception raised when :meth:`~Queue.put` or :meth:`~Queue.get` is
192-
called on a queue which has been shut down.
195+
Exception raised when :meth:`~Queue.put`, :meth:`~Queue.put_nowait`,
196+
:meth:`~Queue.get` or :meth:`~Queue.get_nowait` is called
197+
on a queue which has been shut down.
193198

194199
.. versionadded:: 3.13
195200

Doc/tutorial/introduction.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -532,10 +532,13 @@ This example introduces several new features.
532532
and ``!=`` (not equal to).
533533

534534
* The *body* of the loop is *indented*: indentation is Python's way of grouping
535-
statements. At the interactive prompt, you have to type a tab or space(s) for
536-
each indented line. In practice you will prepare more complicated input
537-
for Python with a text editor; all decent text editors have an auto-indent
538-
facility. When a compound statement is entered interactively, it must be
535+
statements. At the interactive prompt, the default REPL automatically
536+
indents continuation lines after compound statement headers like ``if`` or
537+
``while``. In the basic REPL (invoked with :envvar:`PYTHON_BASIC_REPL`)
538+
or in older Python versions, you have to type a tab or space(s) for each
539+
indented line manually. In practice you will prepare more complicated
540+
input for Python with a text editor; all decent text editors have an
541+
auto-indent facility. When a compound statement is entered interactively, it must be
539542
followed by a blank line to indicate completion (since the parser cannot
540543
guess when you have typed the last line). Note that each line within a basic
541544
block must be indented by the same amount.

Lib/_pyrepl/base_eventqueue.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,6 @@ def empty(self) -> bool:
5454
"""
5555
return not self.events
5656

57-
def flush_buf(self) -> bytearray:
58-
"""
59-
Flushes the buffer and returns its contents.
60-
"""
61-
old = self.buf
62-
self.buf = bytearray()
63-
return old
64-
6557
def insert(self, event: Event) -> None:
6658
"""
6759
Inserts an event into the queue.
@@ -87,7 +79,7 @@ def push(self, char: int | bytes) -> None:
8779
if isinstance(k, dict):
8880
self.keymap = k
8981
else:
90-
self.insert(Event('key', k, bytes(self.flush_buf())))
82+
self.insert(Event('key', k, self.buf.take_bytes())) # type: ignore[attr-defined]
9183
self.keymap = self.compiled_keymap
9284

9385
elif self.buf and self.buf[0] == 27: # escape
@@ -97,14 +89,14 @@ def push(self, char: int | bytes) -> None:
9789
trace('unrecognized escape sequence, propagating...')
9890
self.keymap = self.compiled_keymap
9991
self.insert(Event('key', '\033', b'\033'))
100-
for _c in self.flush_buf()[1:]:
92+
for _c in self.buf.take_bytes()[1:]: # type: ignore[attr-defined]
10193
self.push(_c)
10294

10395
else:
10496
try:
105-
decoded = bytes(self.buf).decode(self.encoding)
97+
decoded = self.buf.decode(self.encoding)
10698
except UnicodeError:
10799
return
108100
else:
109-
self.insert(Event('key', decoded, bytes(self.flush_buf())))
101+
self.insert(Event('key', decoded, self.buf.take_bytes())) # type: ignore[attr-defined]
110102
self.keymap = self.compiled_keymap

Lib/asyncio/streams.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@ def _stream_reader(self):
216216
def _replace_transport(self, transport):
217217
self._transport = transport
218218
self._over_ssl = transport.get_extra_info('sslcontext') is not None
219+
reader = self._stream_reader
220+
if reader is not None:
221+
reader._replace_transport(transport)
219222

220223
def connection_made(self, transport):
221224
if self._reject_connection:
@@ -473,6 +476,10 @@ def set_transport(self, transport):
473476
assert self._transport is None, 'Transport already set'
474477
self._transport = transport
475478

479+
def _replace_transport(self, transport):
480+
assert self._transport is not None, 'Transport not set'
481+
self._transport = transport
482+
476483
def _maybe_resume_transport(self):
477484
if self._paused and len(self._buffer) <= self._limit:
478485
self._paused = False

Lib/profiling/sampling/live_collector/collector.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@ def build_stats_list(self):
702702
def reset_stats(self):
703703
"""Reset all collected statistics."""
704704
self.result.clear()
705+
self.opcode_stats.clear()
705706
self.per_thread_data.clear()
706707
self.thread_ids.clear()
707708
self.view_mode = "ALL"

Lib/test/test_asyncio/test_streams.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,34 @@ async def run_test():
861861
self.loop.run_until_complete(run_test())
862862
self.assertEqual(messages, [])
863863

864+
def test_streamwriter_start_tls_updates_reader_transport(self):
865+
reader = asyncio.StreamReader(loop=self.loop)
866+
protocol = asyncio.StreamReaderProtocol(reader, loop=self.loop)
867+
old_transport = mock.Mock()
868+
old_transport.get_extra_info.return_value = None
869+
old_transport.is_closing.return_value = False
870+
protocol.connection_made(old_transport)
871+
872+
writer = asyncio.StreamWriter(old_transport, protocol, reader, self.loop)
873+
874+
ssl_context = mock.sentinel.ssl_context
875+
new_transport = mock.Mock()
876+
new_transport.get_extra_info.return_value = ssl_context
877+
self.loop.start_tls = mock.AsyncMock(return_value=new_transport)
878+
879+
self.loop.run_until_complete(writer.start_tls(ssl_context))
880+
881+
self.loop.start_tls.assert_awaited_once_with(
882+
old_transport, protocol, ssl_context,
883+
server_side=False, server_hostname=None,
884+
ssl_handshake_timeout=None,
885+
ssl_shutdown_timeout=None,
886+
)
887+
self.assertIs(writer.transport, new_transport)
888+
self.assertIs(protocol._transport, new_transport)
889+
self.assertIs(reader._transport, new_transport)
890+
self.assertTrue(protocol._over_ssl)
891+
864892
def test_streamreader_constructor_without_loop(self):
865893
with self.assertRaisesRegex(RuntimeError, 'no current event loop'):
866894
asyncio.StreamReader()

Lib/test/test_fstring.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,16 @@ def test_double_braces(self):
709709
["f'{ {{}} }'", # dict in a set
710710
])
711711

712+
def test_double_brace_ast_location_covers_both_source_braces(self):
713+
value = ast.parse('f"a{{"').body[0].value.values[0]
714+
self.assertIsInstance(value, ast.Constant)
715+
self.assertEqual(value.value, "a{")
716+
self.assertEqual(
717+
(value.lineno, value.col_offset, value.end_lineno,
718+
value.end_col_offset),
719+
(1, 2, 1, 5),
720+
)
721+
712722
def test_compile_time_concat(self):
713723
x = 'def'
714724
self.assertEqual('abc' f'## {x}ghi', 'abc## defghi')

Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def test_reset_stats(self):
9999
"cumulative_calls": 75,
100100
"total_rec_calls": 0,
101101
}
102+
self.collector.opcode_stats[("test.py", 1, "func")][100] = 5
102103

103104
# Reset
104105
self.collector.reset_stats()
@@ -107,6 +108,7 @@ def test_reset_stats(self):
107108
self.assertEqual(self.collector.successful_samples, 0)
108109
self.assertEqual(self.collector.failed_samples, 0)
109110
self.assertEqual(len(self.collector.result), 0)
111+
self.assertEqual(len(self.collector.opcode_stats), 0)
110112

111113
def test_increase_refresh_rate(self):
112114
"""Test increasing refresh rate (faster updates)."""

Lib/test/test_pyrepl/test_eventqueue.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,6 @@ def test_empty(self):
3838
eq.insert(Event("key", "a", b"a"))
3939
self.assertFalse(eq.empty())
4040

41-
def test_flush_buf(self):
42-
eq = self.make_eventqueue()
43-
eq.buf.extend(b"test")
44-
self.assertEqual(eq.flush_buf(), b"test")
45-
self.assertEqual(eq.buf, bytearray())
46-
4741
def test_insert(self):
4842
eq = self.make_eventqueue()
4943
event = Event("key", "a", b"a")
@@ -93,7 +87,7 @@ def test_push_with_keymap_in_keymap_and_escape(self, mock_keymap):
9387
eq.push(b"a")
9488
mock_keymap.compile_keymap.assert_called()
9589
self.assertTrue(eq.empty())
96-
eq.flush_buf()
90+
eq.buf.resize(0)
9791
eq.push(b"\033")
9892
self.assertEqual(eq.events[0].evt, "key")
9993
self.assertEqual(eq.events[0].data, "\033")

Lib/test/test_tokenize.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,13 @@ def test_number_starting_with_zero(self):
22362236
self.assertEqual(tokens, expected_tokens)
22372237

22382238
class CTokenizeTest(TestCase):
2239+
@staticmethod
2240+
def _get_tokens(source, *, extra_tokens=False):
2241+
return list(tokenize._generate_tokens_from_c_tokenizer(
2242+
StringIO(source).readline,
2243+
extra_tokens=extra_tokens,
2244+
))
2245+
22392246
def check_tokenize(self, s, expected):
22402247
# Format the tokens in s in a table format.
22412248
# The ENDMARKER and final NEWLINE are omitted.
@@ -2266,6 +2273,159 @@ def readline(encoding):
22662273
))
22672274
self.assertEqual(tokens, expected)
22682275

2276+
def test_extra_tokens_relaxes_lexer_errors(self):
2277+
cases = [
2278+
(
2279+
"2sin(x)",
2280+
("invalid decimal literal", (1, 1)),
2281+
[
2282+
(token.NUMBER, "2", (1, 0), (1, 1)),
2283+
(token.NAME, "sin", (1, 1), (1, 4)),
2284+
(token.OP, "(", (1, 4), (1, 5)),
2285+
(token.NAME, "x", (1, 5), (1, 6)),
2286+
(token.OP, ")", (1, 6), (1, 7)),
2287+
],
2288+
),
2289+
(
2290+
"01234",
2291+
(
2292+
"leading zeros in decimal integer literals are not permitted; "
2293+
"use an 0o prefix for octal integers",
2294+
(1, 1),
2295+
),
2296+
[(token.NUMBER, "01234", (1, 0), (1, 5))],
2297+
),
2298+
(
2299+
")",
2300+
("unmatched ')'", (1, 1)),
2301+
[(token.OP, ")", (1, 0), (1, 1))],
2302+
),
2303+
(
2304+
"(]",
2305+
(
2306+
"closing parenthesis ']' does not match opening parenthesis '('",
2307+
(1, 2),
2308+
),
2309+
[
2310+
(token.OP, "(", (1, 0), (1, 1)),
2311+
(token.OP, "]", (1, 1), (1, 2)),
2312+
],
2313+
),
2314+
(
2315+
"a☃b",
2316+
("invalid character '☃' (U+2603)", (1, 2)),
2317+
[(token.NAME, "a☃b", (1, 0), (1, 3))],
2318+
),
2319+
]
2320+
2321+
for source, error, expected in cases:
2322+
with self.subTest(source=source):
2323+
with self.assertRaises(tokenize.TokenError) as caught:
2324+
self._get_tokens(source)
2325+
self.assertEqual(caught.exception.args, error)
2326+
2327+
tokens = self._get_tokens(source, extra_tokens=True)
2328+
self.assertEqual(
2329+
[(tok.type, tok.string, tok.start, tok.end)
2330+
for tok in tokens[:-2]],
2331+
expected,
2332+
)
2333+
2334+
def test_extra_tokens_emits_comments_and_real_indent_locations(self):
2335+
source = "if x:\n # c\n y\nz\n"
2336+
kinds = {token.COMMENT, token.NL, token.INDENT, token.DEDENT}
2337+
2338+
parser_tokens = self._get_tokens(source)
2339+
self.assertEqual(
2340+
[(tok.type, tok.string, tok.start, tok.end)
2341+
for tok in parser_tokens if tok.type in kinds],
2342+
[
2343+
(token.INDENT, "", (3, -1), (3, -1)),
2344+
(token.DEDENT, "", (4, -1), (4, -1)),
2345+
],
2346+
)
2347+
2348+
tolerant_tokens = self._get_tokens(source, extra_tokens=True)
2349+
self.assertEqual(
2350+
[(tok.type, tok.string, tok.start, tok.end)
2351+
for tok in tolerant_tokens if tok.type in kinds],
2352+
[
2353+
(token.COMMENT, "# c", (2, 2), (2, 5)),
2354+
(token.NL, "\n", (2, 5), (2, 6)),
2355+
(token.INDENT, " ", (3, 0), (3, 2)),
2356+
(token.DEDENT, "", (4, 0), (4, 0)),
2357+
],
2358+
)
2359+
2360+
def test_printable_invalid_operator_streams_in_both_modes(self):
2361+
for extra_tokens in (False, True):
2362+
with self.subTest(extra_tokens=extra_tokens):
2363+
first = self._get_tokens(
2364+
"1 $ 2",
2365+
extra_tokens=extra_tokens,
2366+
)[1]
2367+
self.assertEqual(
2368+
(first.type, first.string, first.start, first.end),
2369+
(token.OP, "$", (1, 2), (1, 3)),
2370+
)
2371+
2372+
def test_degraded_fstring_format_spec(self):
2373+
tokens = self._get_tokens('f"{1:{2}{{3}}}"')
2374+
self.assertEqual(
2375+
[(tok.string, tok.start, tok.end)
2376+
for tok in tokens if tok.type == token.FSTRING_MIDDLE],
2377+
[
2378+
("{", (1, 8), (1, 9)),
2379+
("3", (1, 10), (1, 11)),
2380+
("}", (1, 12), (1, 13)),
2381+
],
2382+
)
2383+
2384+
tokens = self._get_tokens('f"{x:{y}}"')
2385+
self.assertEqual(
2386+
[(tok.string, tok.start, tok.end)
2387+
for tok in tokens if tok.type == token.FSTRING_MIDDLE],
2388+
[("", (1, 8), (1, 8))],
2389+
)
2390+
2391+
with self.assertRaises(tokenize.TokenError) as caught:
2392+
self._get_tokens('f"{1:{2}x}}y"')
2393+
self.assertEqual(
2394+
caught.exception.args,
2395+
("f-string: single '}' is not allowed", (1, 11)),
2396+
)
2397+
2398+
def test_escaped_fstring_brace_has_a_position_gap(self):
2399+
tokens = self._get_tokens('f"a{{"', extra_tokens=True)
2400+
self.assertEqual(
2401+
[(tok.type, tok.string, tok.start, tok.end)
2402+
for tok in tokens
2403+
if tok.type in {token.FSTRING_MIDDLE, token.FSTRING_END}],
2404+
[
2405+
(token.FSTRING_MIDDLE, "a{", (1, 2), (1, 4)),
2406+
(token.FSTRING_END, '"', (1, 5), (1, 6)),
2407+
],
2408+
)
2409+
2410+
def test_tolerant_incompatible_prefix_position_after_non_ascii(self):
2411+
with self.assertRaises(tokenize.TokenError) as caught:
2412+
self._get_tokens('bé )tf"2 ', extra_tokens=True)
2413+
self.assertEqual(
2414+
caught.exception.args,
2415+
("'f' and 't' prefixes are incompatible", (1, 6)),
2416+
)
2417+
2418+
def test_tolerant_fstring_closer_at_expression_entry_depth(self):
2419+
source = "f'1:{]]{}}r}''"
2420+
for extra_tokens, position in ((False, (1, 6)), (True, (1, 7))):
2421+
with self.subTest(extra_tokens=extra_tokens):
2422+
with self.assertRaises(tokenize.TokenError) as caught:
2423+
self._get_tokens(source, extra_tokens=extra_tokens)
2424+
self.assertEqual(
2425+
caught.exception.args,
2426+
("f-string: unmatched ']'", position),
2427+
)
2428+
22692429
def test_int(self):
22702430

22712431
self.check_tokenize('0xff <= 255', """\

0 commit comments

Comments
 (0)