Skip to content

Commit 09e476a

Browse files
committed
Catch up with main
2 parents 53c06b0 + fbaa6c8 commit 09e476a

Some content is hidden

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

55 files changed

+1696
-1183
lines changed

Doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
'changes',
2929
'glossary_search',
3030
'grammar_snippet',
31+
'implementation_detail',
3132
'lexers',
3233
'misc_news',
3334
'pydoc_topics',

Doc/library/imaplib.rst

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
.. changes for IMAP4_SSL by Tino Lange <[email protected]>, March 2002
1111
.. changes for IMAP4_stream by Piers Lauder <[email protected]>,
1212
November 2002
13+
.. changes for IMAP4 IDLE by Forest <[email protected]>, August 2024
1314
1415
**Source code:** :source:`Lib/imaplib.py`
1516

@@ -187,7 +188,7 @@ However, the *password* argument to the ``LOGIN`` command is always quoted. If
187188
you want to avoid having an argument string quoted (eg: the *flags* argument to
188189
``STORE``) then enclose the string in parentheses (eg: ``r'(\Deleted)'``).
189190

190-
Each command returns a tuple: ``(type, [data, ...])`` where *type* is usually
191+
Most commands return a tuple: ``(type, [data, ...])`` where *type* is usually
191192
``'OK'`` or ``'NO'``, and *data* is either the text from the command response,
192193
or mandated results from the command. Each *data* is either a ``bytes``, or a
193194
tuple. If a tuple, then the first part is the header of the response, and the
@@ -307,6 +308,93 @@ An :class:`IMAP4` instance has the following methods:
307308
of the IMAP4 QUOTA extension defined in rfc2087.
308309

309310

311+
.. method:: IMAP4.idle(duration=None)
312+
313+
Return an :class:`!Idler`: an iterable context manager implementing the
314+
IMAP4 ``IDLE`` command as defined in :rfc:`2177`.
315+
316+
The returned object sends the ``IDLE`` command when activated by the
317+
:keyword:`with` statement, produces IMAP untagged responses via the
318+
:term:`iterator` protocol, and sends ``DONE`` upon context exit.
319+
320+
All untagged responses that arrive after sending the ``IDLE`` command
321+
(including any that arrive before the server acknowledges the command) will
322+
be available via iteration. Any leftover responses (those not iterated in
323+
the :keyword:`with` context) can be retrieved in the usual way after
324+
``IDLE`` ends, using :meth:`IMAP4.response`.
325+
326+
Responses are represented as ``(type, [data, ...])`` tuples, as described
327+
in :ref:`IMAP4 Objects <imap4-objects>`.
328+
329+
The *duration* argument sets a maximum duration (in seconds) to keep idling,
330+
after which any ongoing iteration will stop. It can be an :class:`int` or
331+
:class:`float`, or ``None`` for no time limit.
332+
Callers wishing to avoid inactivity timeouts on servers that impose them
333+
should keep this at most 29 minutes (1740 seconds).
334+
Requires a socket connection; *duration* must be ``None`` on
335+
:class:`IMAP4_stream` connections.
336+
337+
.. code-block:: pycon
338+
339+
>>> with M.idle(duration=29 * 60) as idler:
340+
... for typ, data in idler:
341+
... print(typ, data)
342+
...
343+
EXISTS [b'1']
344+
RECENT [b'1']
345+
346+
347+
.. method:: Idler.burst(interval=0.1)
348+
349+
Yield a burst of responses no more than *interval* seconds apart
350+
(expressed as an :class:`int` or :class:`float`).
351+
352+
This :term:`generator` is an alternative to iterating one response at a
353+
time, intended to aid in efficient batch processing. It retrieves the
354+
next response along with any immediately available subsequent responses.
355+
(For example, a rapid series of ``EXPUNGE`` responses after a bulk
356+
delete.)
357+
358+
Requires a socket connection; does not work on :class:`IMAP4_stream`
359+
connections.
360+
361+
.. code-block:: pycon
362+
363+
>>> with M.idle() as idler:
364+
... # get a response and any others following by < 0.1 seconds
365+
... batch = list(idler.burst())
366+
... print(f'processing {len(batch)} responses...')
367+
... print(batch)
368+
...
369+
processing 3 responses...
370+
[('EXPUNGE', [b'2']), ('EXPUNGE', [b'1']), ('RECENT', [b'0'])]
371+
372+
.. tip::
373+
374+
The ``IDLE`` context's maximum duration, as passed to
375+
:meth:`IMAP4.idle`, is respected when waiting for the first response
376+
in a burst. Therefore, an expired :class:`!Idler` will cause this
377+
generator to return immediately without producing anything. Callers
378+
should consider this if using it in a loop.
379+
380+
381+
.. note::
382+
383+
The iterator returned by :meth:`IMAP4.idle` is usable only within a
384+
:keyword:`with` statement. Before or after that context, unsolicited
385+
responses are collected internally whenever a command finishes, and can
386+
be retrieved with :meth:`IMAP4.response`.
387+
388+
.. note::
389+
390+
The :class:`!Idler` class name and structure are internal interfaces,
391+
subject to change. Calling code can rely on its context management,
392+
iteration, and public method to remain stable, but should not subclass,
393+
instantiate, compare, or otherwise directly reference the class.
394+
395+
.. versionadded:: next
396+
397+
310398
.. method:: IMAP4.list([directory[, pattern]])
311399

312400
List mailbox names in *directory* matching *pattern*. *directory* defaults to

Doc/library/itertools.rst

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -838,10 +838,10 @@ and :term:`generators <generator>` which incur interpreter overhead.
838838

839839
.. testcode::
840840

841-
from collections import deque
841+
from collections import Counter, deque
842842
from contextlib import suppress
843843
from functools import reduce
844-
from math import sumprod, isqrt
844+
from math import comb, prod, sumprod, isqrt
845845
from operator import itemgetter, getitem, mul, neg
846846

847847
def take(n, iterable):
@@ -1127,6 +1127,12 @@ The following recipes have a more mathematical flavor:
11271127
n -= n // prime
11281128
return n
11291129

1130+
def multinomial(*counts):
1131+
"Number of distinct arrangements of a multiset."
1132+
# Counter('abracadabra').values() -> 5 2 1 1 2
1133+
# multinomial(5, 2, 1, 1, 2) → 83160
1134+
return prod(map(comb, accumulate(counts), counts))
1135+
11301136

11311137
.. doctest::
11321138
:hide:
@@ -1730,6 +1736,12 @@ The following recipes have a more mathematical flavor:
17301736
>>> ''.join(it)
17311737
'DEF1'
17321738

1739+
>>> multinomial(5, 2, 1, 1, 2)
1740+
83160
1741+
>>> word = 'coffee'
1742+
>>> multinomial(*Counter(word).values()) == len(set(permutations(word)))
1743+
True
1744+
17331745

17341746
.. testcode::
17351747
:hide:

Doc/library/socket.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,11 +1668,6 @@ to sockets.
16681668
See the Unix manual page :manpage:`recv(2)` for the meaning of the optional argument
16691669
*flags*; it defaults to zero.
16701670

1671-
.. note::
1672-
1673-
For best match with hardware and network realities, the value of *bufsize*
1674-
should be a relatively small power of 2, for example, 4096.
1675-
16761671
.. versionchanged:: 3.5
16771672
If the system call is interrupted and the signal handler does not raise
16781673
an exception, the method now retries the system call instead of raising

Doc/library/socketserver.rst

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -499,11 +499,17 @@ This is the server side::
499499

500500
def handle(self):
501501
# self.request is the TCP socket connected to the client
502-
self.data = self.request.recv(1024).strip()
503-
print("Received from {}:".format(self.client_address[0]))
504-
print(self.data)
502+
pieces = [b'']
503+
total = 0
504+
while b'\n' not in pieces[-1] and total < 10_000:
505+
pieces.append(self.request.recv(2000))
506+
total += len(pieces[-1])
507+
self.data = b''.join(pieces)
508+
print(f"Received from {self.client_address[0]}:")
509+
print(self.data.decode("utf-8"))
505510
# just send back the same data, but upper-cased
506511
self.request.sendall(self.data.upper())
512+
# after we return, the socket will be closed.
507513

508514
if __name__ == "__main__":
509515
HOST, PORT = "localhost", 9999
@@ -520,20 +526,24 @@ objects that simplify communication by providing the standard file interface)::
520526
class MyTCPHandler(socketserver.StreamRequestHandler):
521527

522528
def handle(self):
523-
# self.rfile is a file-like object created by the handler;
524-
# we can now use e.g. readline() instead of raw recv() calls
525-
self.data = self.rfile.readline().strip()
526-
print("{} wrote:".format(self.client_address[0]))
527-
print(self.data)
529+
# self.rfile is a file-like object created by the handler.
530+
# We can now use e.g. readline() instead of raw recv() calls.
531+
# We limit ourselves to 10000 bytes to avoid abuse by the sender.
532+
self.data = self.rfile.readline(10000).rstrip()
533+
print(f"{self.client_address[0]} wrote:")
534+
print(self.data.decode("utf-8"))
528535
# Likewise, self.wfile is a file-like object used to write back
529536
# to the client
530537
self.wfile.write(self.data.upper())
531538

532539
The difference is that the ``readline()`` call in the second handler will call
533540
``recv()`` multiple times until it encounters a newline character, while the
534-
single ``recv()`` call in the first handler will just return what has been
535-
received so far from the client's ``sendall()`` call (typically all of it, but
536-
this is not guaranteed by the TCP protocol).
541+
the first handler had to use a ``recv()`` loop to accumulate data until a
542+
newline itself. If it had just used a single ``recv()`` without the loop it
543+
would just have returned what has been received so far from the client.
544+
TCP is stream based: data arrives in the order it was sent, but there no
545+
correlation between client ``send()`` or ``sendall()`` calls and the number
546+
of ``recv()`` calls on the server required to receive it.
537547

538548

539549
This is the client side::
@@ -548,13 +558,14 @@ This is the client side::
548558
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
549559
# Connect to server and send data
550560
sock.connect((HOST, PORT))
551-
sock.sendall(bytes(data + "\n", "utf-8"))
561+
sock.sendall(bytes(data, "utf-8"))
562+
sock.sendall(b"\n")
552563

553564
# Receive data from the server and shut down
554565
received = str(sock.recv(1024), "utf-8")
555566

556-
print("Sent: {}".format(data))
557-
print("Received: {}".format(received))
567+
print("Sent: ", data)
568+
print("Received:", received)
558569

559570

560571
The output of the example should look something like this:
@@ -599,7 +610,7 @@ This is the server side::
599610
def handle(self):
600611
data = self.request[0].strip()
601612
socket = self.request[1]
602-
print("{} wrote:".format(self.client_address[0]))
613+
print(f"{self.client_address[0]} wrote:")
603614
print(data)
604615
socket.sendto(data.upper(), self.client_address)
605616

@@ -624,8 +635,8 @@ This is the client side::
624635
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
625636
received = str(sock.recv(1024), "utf-8")
626637

627-
print("Sent: {}".format(data))
628-
print("Received: {}".format(received))
638+
print("Sent: ", data)
639+
print("Received:", received)
629640

630641
The output of the example should look exactly like for the TCP server example.
631642

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Support for marking up implementation details."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
from docutils import nodes
8+
from sphinx.locale import _ as sphinx_gettext
9+
from sphinx.util.docutils import SphinxDirective
10+
11+
if TYPE_CHECKING:
12+
from sphinx.application import Sphinx
13+
from sphinx.util.typing import ExtensionMetadata
14+
15+
16+
class ImplementationDetail(SphinxDirective):
17+
has_content = True
18+
final_argument_whitespace = True
19+
20+
# This text is copied to templates/dummy.html
21+
label_text = sphinx_gettext("CPython implementation detail:")
22+
23+
def run(self):
24+
self.assert_has_content()
25+
content_nodes = self.parse_content_to_nodes()
26+
27+
# insert our prefix at the start of the first paragraph
28+
first_node = content_nodes[0]
29+
first_node[:0] = [
30+
nodes.strong(self.label_text, self.label_text),
31+
nodes.Text(" "),
32+
]
33+
34+
# create a new compound container node
35+
cnode = nodes.compound("", *content_nodes, classes=["impl-detail"])
36+
self.set_source_info(cnode)
37+
return [cnode]
38+
39+
40+
def setup(app: Sphinx) -> ExtensionMetadata:
41+
app.add_directive("impl-detail", ImplementationDetail)
42+
43+
return {
44+
"version": "1.0",
45+
"parallel_read_safe": True,
46+
"parallel_write_safe": True,
47+
}

Doc/tools/extensions/pyspecific.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -65,31 +65,6 @@ def gh_issue_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
6565
return [refnode], []
6666

6767

68-
# Support for marking up implementation details
69-
70-
class ImplementationDetail(SphinxDirective):
71-
72-
has_content = True
73-
final_argument_whitespace = True
74-
75-
# This text is copied to templates/dummy.html
76-
label_text = sphinx_gettext('CPython implementation detail:')
77-
78-
def run(self):
79-
self.assert_has_content()
80-
pnode = nodes.compound(classes=['impl-detail'])
81-
content = self.content
82-
add_text = nodes.strong(self.label_text, self.label_text)
83-
self.state.nested_parse(content, self.content_offset, pnode)
84-
content = nodes.inline(pnode[0].rawsource, translatable=True)
85-
content.source = pnode[0].source
86-
content.line = pnode[0].line
87-
content += pnode[0].children
88-
pnode[0].replace_self(nodes.paragraph(
89-
'', '', add_text, nodes.Text(' '), content, translatable=False))
90-
return [pnode]
91-
92-
9368
class PyCoroutineMixin(object):
9469
def handle_signature(self, sig, signode):
9570
ret = super(PyCoroutineMixin, self).handle_signature(sig, signode)
@@ -219,7 +194,6 @@ def patch_pairindextypes(app, _env) -> None:
219194
def setup(app):
220195
app.add_role('issue', issue_role)
221196
app.add_role('gh', gh_issue_role)
222-
app.add_directive('impl-detail', ImplementationDetail)
223197
app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature)
224198
app.add_object_type('pdbcommand', 'pdbcmd', '%s (pdb command)', parse_pdb_command)
225199
app.add_object_type('monitoring-event', 'monitoring-event', '%s (monitoring event)', parse_monitoring_event)

Doc/tools/templates/dummy.html

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
This file is not an actual template, but used to add some
22
texts in extensions to sphinx.pot file.
33

4-
In extensions/pyspecific.py:
5-
6-
{% trans %}CPython implementation detail:{% endtrans %}
7-
{% trans %}Deprecated since version {deprecated}, will be removed in version {removed}{% endtrans %}
8-
{% trans %}Deprecated since version {deprecated}, removed in version {removed}{% endtrans %}
9-
104
In extensions/availability.py:
115

126
{% trans %}Availability{% endtrans %}
@@ -27,6 +21,15 @@
2721
{% trans %}Return value: New reference.{% endtrans %}
2822
{% trans %}Return value: Borrowed reference.{% endtrans %}
2923

24+
In extensions/implementation_detail.py:
25+
26+
{% trans %}CPython implementation detail:{% endtrans %}
27+
28+
In extensions/changes.py:
29+
30+
{% trans %}Deprecated since version {deprecated}, will be removed in version {removed}{% endtrans %}
31+
{% trans %}Deprecated since version {deprecated}, removed in version {removed}{% endtrans %}
32+
3033
In docsbuild-scripts, when rewriting indexsidebar.html with actual versions:
3134

3235
{% trans %}in development{% endtrans %}

Doc/using/configure.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,16 @@ also be used to improve performance.
618618
Enable computed gotos in evaluation loop (enabled by default on supported
619619
compilers).
620620

621+
.. option:: --with-tail-call-interp
622+
623+
Enable interpreters using tail calls in CPython. If enabled, enabling PGO
624+
(:option:`--enable-optimizations`) is highly recommended. This option specifically
625+
requires a C compiler with proper tail call support, and the
626+
`preserve_none <https://clang.llvm.org/docs/AttributeReference.html#preserve-none>`_
627+
calling convention. For example, Clang 19 and newer supports this feature.
628+
629+
.. versionadded:: next
630+
621631
.. option:: --without-mimalloc
622632

623633
Disable the fast :ref:`mimalloc <mimalloc>` allocator

0 commit comments

Comments
 (0)