Skip to content

Commit ec36e22

Browse files
authored
Merge branch 'main' into 3.14-empty-force-color-no-color
2 parents c7b5419 + 67d804b commit ec36e22

File tree

75 files changed

+16331
-17992
lines changed

Some content is hidden

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

75 files changed

+16331
-17992
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
'lexers',
31+
'pydoc_topics',
3132
'pyspecific',
3233
'sphinx.ext.coverage',
3334
'sphinx.ext.doctest',
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Pending removal in Python 3.18
2+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3+
4+
* Deprecated private functions (:gh:`128863`):
5+
6+
* :c:func:`!_PyBytes_Join`: use :c:func:`PyBytes_Join`.
7+
* :c:func:`!_PyDict_GetItemStringWithError`: use :c:func:`PyDict_GetItemStringRef`.
8+
* :c:func:`!_PyDict_Pop()`: :c:func:`PyDict_Pop`.
9+
* :c:func:`!_PyThreadState_UncheckedGet`: use :c:func:`PyThreadState_GetUnchecked`.
10+
* :c:func:`!_PyUnicode_AsString`: use :c:func:`PyUnicode_AsUTF8`.
11+
* :c:func:`!_Py_HashPointer`: use :c:func:`Py_HashPointer`.
12+
* :c:func:`!_Py_fopen_obj`: use :c:func:`Py_fopen`.
13+
14+
The `pythoncapi-compat project
15+
<https://github.com/python/pythoncapi-compat/>`__ can be used to get these
16+
new public functions on Python 3.13 and older.

Doc/library/asyncio-graph.rst

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
.. currentmodule:: asyncio
2+
3+
4+
.. _asyncio-graph:
5+
6+
========================
7+
Call Graph Introspection
8+
========================
9+
10+
**Source code:** :source:`Lib/asyncio/graph.py`
11+
12+
-------------------------------------
13+
14+
asyncio has powerful runtime call graph introspection utilities
15+
to trace the entire call graph of a running *coroutine* or *task*, or
16+
a suspended *future*. These utilities and the underlying machinery
17+
can be used from within a Python program or by external profilers
18+
and debuggers.
19+
20+
.. versionadded:: next
21+
22+
23+
.. function:: print_call_graph(future=None, /, *, file=None, depth=1, limit=None)
24+
25+
Print the async call graph for the current task or the provided
26+
:class:`Task` or :class:`Future`.
27+
28+
This function prints entries starting from the top frame and going
29+
down towards the invocation point.
30+
31+
The function receives an optional *future* argument.
32+
If not passed, the current running task will be used.
33+
34+
If the function is called on *the current task*, the optional
35+
keyword-only *depth* argument can be used to skip the specified
36+
number of frames from top of the stack.
37+
38+
If the optional keyword-only *limit* argument is provided, each call stack
39+
in the resulting graph is truncated to include at most ``abs(limit)``
40+
entries. If *limit* is positive, the entries left are the closest to
41+
the invocation point. If *limit* is negative, the topmost entries are
42+
left. If *limit* is omitted or ``None``, all entries are present.
43+
If *limit* is ``0``, the call stack is not printed at all, only
44+
"awaited by" information is printed.
45+
46+
If *file* is omitted or ``None``, the function will print
47+
to :data:`sys.stdout`.
48+
49+
**Example:**
50+
51+
The following Python code:
52+
53+
.. code-block:: python
54+
55+
import asyncio
56+
57+
async def test():
58+
asyncio.print_call_graph()
59+
60+
async def main():
61+
async with asyncio.TaskGroup() as g:
62+
g.create_task(test())
63+
64+
asyncio.run(main())
65+
66+
will print::
67+
68+
* Task(name='Task-2', id=0x1039f0fe0)
69+
+ Call stack:
70+
| File 't2.py', line 4, in async test()
71+
+ Awaited by:
72+
* Task(name='Task-1', id=0x103a5e060)
73+
+ Call stack:
74+
| File 'taskgroups.py', line 107, in async TaskGroup.__aexit__()
75+
| File 't2.py', line 7, in async main()
76+
77+
.. function:: format_call_graph(future=None, /, *, depth=1, limit=None)
78+
79+
Like :func:`print_call_graph`, but returns a string.
80+
If *future* is ``None`` and there's no current task,
81+
the function returns an empty string.
82+
83+
84+
.. function:: capture_call_graph(future=None, /, *, depth=1, limit=None)
85+
86+
Capture the async call graph for the current task or the provided
87+
:class:`Task` or :class:`Future`.
88+
89+
The function receives an optional *future* argument.
90+
If not passed, the current running task will be used. If there's no
91+
current task, the function returns ``None``.
92+
93+
If the function is called on *the current task*, the optional
94+
keyword-only *depth* argument can be used to skip the specified
95+
number of frames from top of the stack.
96+
97+
Returns a ``FutureCallGraph`` data class object:
98+
99+
* ``FutureCallGraph(future, call_stack, awaited_by)``
100+
101+
Where *future* is a reference to a :class:`Future` or
102+
a :class:`Task` (or their subclasses.)
103+
104+
``call_stack`` is a tuple of ``FrameCallGraphEntry`` objects.
105+
106+
``awaited_by`` is a tuple of ``FutureCallGraph`` objects.
107+
108+
* ``FrameCallGraphEntry(frame)``
109+
110+
Where *frame* is a frame object of a regular Python function
111+
in the call stack.
112+
113+
114+
Low level utility functions
115+
===========================
116+
117+
To introspect an async call graph asyncio requires cooperation from
118+
control flow structures, such as :func:`shield` or :class:`TaskGroup`.
119+
Any time an intermediate :class:`Future` object with low-level APIs like
120+
:meth:`Future.add_done_callback() <asyncio.Future.add_done_callback>` is
121+
involved, the following two functions should be used to inform asyncio
122+
about how exactly such intermediate future objects are connected with
123+
the tasks they wrap or control.
124+
125+
126+
.. function:: future_add_to_awaited_by(future, waiter, /)
127+
128+
Record that *future* is awaited on by *waiter*.
129+
130+
Both *future* and *waiter* must be instances of
131+
:class:`Future` or :class:`Task` or their subclasses,
132+
otherwise the call would have no effect.
133+
134+
A call to ``future_add_to_awaited_by()`` must be followed by an
135+
eventual call to the :func:`future_discard_from_awaited_by` function
136+
with the same arguments.
137+
138+
139+
.. function:: future_discard_from_awaited_by(future, waiter, /)
140+
141+
Record that *future* is no longer awaited on by *waiter*.
142+
143+
Both *future* and *waiter* must be instances of
144+
:class:`Future` or :class:`Task` or their subclasses, otherwise
145+
the call would have no effect.

Doc/library/asyncio.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ You can experiment with an ``asyncio`` concurrent context in the :term:`REPL`:
9999
asyncio-subprocess.rst
100100
asyncio-queue.rst
101101
asyncio-exceptions.rst
102+
asyncio-graph.rst
102103

103104
.. toctree::
104105
:caption: Low-level APIs

Doc/library/inspect.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
150150
| | f_locals | local namespace seen by |
151151
| | | this frame |
152152
+-----------------+-------------------+---------------------------+
153+
| | f_generator | returns the generator or |
154+
| | | coroutine object that |
155+
| | | owns this frame, or |
156+
| | | ``None`` if the frame is |
157+
| | | of a regular function |
158+
+-----------------+-------------------+---------------------------+
153159
| | f_trace | tracing function for this |
154160
| | | frame, or ``None`` |
155161
+-----------------+-------------------+---------------------------+
@@ -310,6 +316,10 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
310316

311317
Add ``__builtins__`` attribute to functions.
312318

319+
.. versionchanged:: next
320+
321+
Add ``f_generator`` attribute to frames.
322+
313323
.. function:: getmembers(object[, predicate])
314324

315325
Return all the members of an object in a list of ``(name, value)``

Doc/tools/extensions/pydoc_topics.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
"""Support for building "topic help" for pydoc."""
2+
3+
from __future__ import annotations
4+
5+
from time import asctime
6+
from typing import TYPE_CHECKING
7+
8+
from sphinx.builders.text import TextBuilder
9+
from sphinx.util import logging
10+
from sphinx.util.display import status_iterator
11+
from sphinx.util.docutils import new_document
12+
from sphinx.writers.text import TextTranslator
13+
14+
if TYPE_CHECKING:
15+
from collections.abc import Sequence, Set
16+
17+
from sphinx.application import Sphinx
18+
from sphinx.util.typing import ExtensionMetadata
19+
20+
logger = logging.getLogger(__name__)
21+
22+
_PYDOC_TOPIC_LABELS: Sequence[str] = sorted({
23+
"assert",
24+
"assignment",
25+
"assignment-expressions",
26+
"async",
27+
"atom-identifiers",
28+
"atom-literals",
29+
"attribute-access",
30+
"attribute-references",
31+
"augassign",
32+
"await",
33+
"binary",
34+
"bitwise",
35+
"bltin-code-objects",
36+
"bltin-ellipsis-object",
37+
"bltin-null-object",
38+
"bltin-type-objects",
39+
"booleans",
40+
"break",
41+
"callable-types",
42+
"calls",
43+
"class",
44+
"comparisons",
45+
"compound",
46+
"context-managers",
47+
"continue",
48+
"conversions",
49+
"customization",
50+
"debugger",
51+
"del",
52+
"dict",
53+
"dynamic-features",
54+
"else",
55+
"exceptions",
56+
"execmodel",
57+
"exprlists",
58+
"floating",
59+
"for",
60+
"formatstrings",
61+
"function",
62+
"global",
63+
"id-classes",
64+
"identifiers",
65+
"if",
66+
"imaginary",
67+
"import",
68+
"in",
69+
"integers",
70+
"lambda",
71+
"lists",
72+
"naming",
73+
"nonlocal",
74+
"numbers",
75+
"numeric-types",
76+
"objects",
77+
"operator-summary",
78+
"pass",
79+
"power",
80+
"raise",
81+
"return",
82+
"sequence-types",
83+
"shifting",
84+
"slicings",
85+
"specialattrs",
86+
"specialnames",
87+
"string-methods",
88+
"strings",
89+
"subscriptions",
90+
"truth",
91+
"try",
92+
"types",
93+
"typesfunctions",
94+
"typesmapping",
95+
"typesmethods",
96+
"typesmodules",
97+
"typesseq",
98+
"typesseq-mutable",
99+
"unary",
100+
"while",
101+
"with",
102+
"yield",
103+
})
104+
105+
106+
class PydocTopicsBuilder(TextBuilder):
107+
name = "pydoc-topics"
108+
109+
def init(self) -> None:
110+
super().init()
111+
self.topics: dict[str, str] = {}
112+
113+
def get_outdated_docs(self) -> str:
114+
# Return a string describing what an update build will build.
115+
return "all pydoc topics"
116+
117+
def write_documents(self, _docnames: Set[str]) -> None:
118+
env = self.env
119+
120+
labels: dict[str, tuple[str, str, str]]
121+
labels = env.domains.standard_domain.labels
122+
123+
# docname -> list of (topic_label, label_id) pairs
124+
doc_labels: dict[str, list[tuple[str, str]]] = {}
125+
for topic_label in _PYDOC_TOPIC_LABELS:
126+
try:
127+
docname, label_id, _section_name = labels[topic_label]
128+
except KeyError:
129+
logger.warning("label %r not in documentation", topic_label)
130+
continue
131+
doc_labels.setdefault(docname, []).append((topic_label, label_id))
132+
133+
for docname, label_ids in status_iterator(
134+
doc_labels.items(),
135+
"building topics... ",
136+
length=len(doc_labels),
137+
stringify_func=_display_labels,
138+
):
139+
doctree = env.get_and_resolve_doctree(docname, builder=self)
140+
doc_ids = doctree.ids
141+
for topic_label, label_id in label_ids:
142+
document = new_document("<section node>")
143+
document.append(doc_ids[label_id])
144+
visitor = TextTranslator(document, builder=self)
145+
document.walkabout(visitor)
146+
self.topics[topic_label] = visitor.body
147+
148+
def finish(self) -> None:
149+
topics_repr = "\n".join(
150+
f" '{topic}': {_repr(self.topics[topic])},"
151+
for topic in sorted(self.topics)
152+
)
153+
topics = f"""\
154+
# Autogenerated by Sphinx on {asctime()}
155+
# as part of the release process.
156+
157+
topics = {{
158+
{topics_repr}
159+
}}
160+
"""
161+
self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8")
162+
163+
164+
def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str:
165+
_docname, label_ids = item
166+
labels = [name for name, _id in label_ids]
167+
if len(labels) > 4:
168+
return f"{labels[0]}, {labels[1]}, ..., {labels[-2]}, {labels[-1]}"
169+
return ", ".join(labels)
170+
171+
172+
def _repr(text: str, /) -> str:
173+
"""Return a triple-single-quoted representation of text."""
174+
if "'''" not in text:
175+
return f"r'''{text}'''"
176+
text = text.replace("\\", "\\\\").replace("'''", r"\'\'\'")
177+
return f"'''{text}'''"
178+
179+
180+
def setup(app: Sphinx) -> ExtensionMetadata:
181+
app.add_builder(PydocTopicsBuilder)
182+
183+
return {
184+
"version": "1.0",
185+
"parallel_read_safe": True,
186+
"parallel_write_safe": True,
187+
}

0 commit comments

Comments
 (0)