Skip to content

Commit

Permalink
Merge branch 'main' into pythongh-71784
Browse files Browse the repository at this point in the history
  • Loading branch information
iritkatriel authored Oct 9, 2024
2 parents b6b3575 + 9940093 commit 17a9ebc
Show file tree
Hide file tree
Showing 30 changed files with 382 additions and 244 deletions.
20 changes: 13 additions & 7 deletions Doc/c-api/contextvars.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,24 @@ Context object management functions:
.. c:type:: PyContextEvent
Enumeration of possible context object watcher events:
- ``Py_CONTEXT_EVENT_ENTER``
- ``Py_CONTEXT_EVENT_EXIT``
- ``Py_CONTEXT_EVENT_ENTER``: A context has been entered, causing the
:term:`current context` to switch to it. The object passed to the watch
callback is the now-current :class:`contextvars.Context` object. Each
enter event will eventually have a corresponding exit event for the same
context object after any subsequently entered contexts have themselves been
exited.
- ``Py_CONTEXT_EVENT_EXIT``: A context is about to be exited, which will
cause the :term:`current context` to switch back to what it was before the
context was entered. The object passed to the watch callback is the
still-current :class:`contextvars.Context` object.
.. versionadded:: 3.14
.. c:type:: int (*PyContext_WatchCallback)(PyContextEvent event, PyContext* ctx)
Type of a context object watcher callback function.
If *event* is ``Py_CONTEXT_EVENT_ENTER``, then the callback is invoked
after *ctx* has been set as the current context for the current thread.
Otherwise, the callback is invoked before the deactivation of *ctx* as the current context
and the restoration of the previous contex object for the current thread.
Context object watcher callback function. The object passed to the callback
is event-specific; see :c:type:`PyContextEvent` for details.
If the callback returns with an exception set, it must return ``-1``; this
exception will be printed as an unraisable exception using
Expand Down
2 changes: 2 additions & 0 deletions Doc/c-api/unicode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1600,6 +1600,8 @@ object.
Discard the internal Unicode buffer and destroy the writer instance.
If *writer* is ``NULL``, no operation is performed.
.. c:function:: int PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch)
Write the single Unicode character *ch* into *writer*.
Expand Down
1 change: 1 addition & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

extensions = [
'audit_events',
'availability',
'c_annotations',
'glossary_search',
'lexers',
Expand Down
40 changes: 31 additions & 9 deletions Doc/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -265,19 +265,33 @@ Glossary
advanced mathematical feature. If you're not aware of a need for them,
it's almost certain you can safely ignore them.

context
This term has different meanings depending on where and how it is used.
Some common meanings:

* The temporary state or environment established by a :term:`context
manager` via a :keyword:`with` statement.
* The collection of key­value bindings associated with a particular
:class:`contextvars.Context` object and accessed via
:class:`~contextvars.ContextVar` objects. Also see :term:`context
variable`.
* A :class:`contextvars.Context` object. Also see :term:`current
context`.

context management protocol
The :meth:`~object.__enter__` and :meth:`~object.__exit__` methods called
by the :keyword:`with` statement. See :pep:`343`.

context manager
An object which controls the environment seen in a :keyword:`with`
statement by defining :meth:`~object.__enter__` and :meth:`~object.__exit__` methods.
See :pep:`343`.
An object which implements the :term:`context management protocol` and
controls the environment seen in a :keyword:`with` statement. See
:pep:`343`.

context variable
A variable which can have different values depending on its context.
This is similar to Thread-Local Storage in which each execution
thread may have a different value for a variable. However, with context
variables, there may be several contexts in one execution thread and the
main usage for context variables is to keep track of variables in
A variable whose value depends on which context is the :term:`current
context`. Values are accessed via :class:`contextvars.ContextVar`
objects. Context variables are primarily used to isolate state between
concurrent asynchronous tasks.
See :mod:`contextvars`.

contiguous
.. index:: C-contiguous, Fortran contiguous
Expand Down Expand Up @@ -311,6 +325,14 @@ Glossary
is used when necessary to distinguish this implementation from others
such as Jython or IronPython.

current context
The :term:`context` (:class:`contextvars.Context` object) that is
currently used by :class:`~contextvars.ContextVar` objects to access (get
or set) the values of :term:`context variables <context variable>`. Each
thread has its own current context. Frameworks for executing asynchronous
tasks (see :mod:`asyncio`) associate each task with a context which
becomes the current context whenever the task starts or resumes execution.

decorator
A function returning another function, usually applied as a function
transformation using the ``@wrapper`` syntax. Common examples for
Expand Down
98 changes: 68 additions & 30 deletions Doc/library/contextvars.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,51 +144,89 @@ Manual Context Management
To get a copy of the current context use the
:func:`~contextvars.copy_context` function.

Every thread will have a different top-level :class:`~contextvars.Context`
object. This means that a :class:`ContextVar` object behaves in a similar
fashion to :func:`threading.local` when values are assigned in different
threads.
Each thread has its own effective stack of :class:`!Context` objects. The
:term:`current context` is the :class:`!Context` object at the top of the
current thread's stack. All :class:`!Context` objects in the stacks are
considered to be *entered*.

*Entering* a context, which can be done by calling its :meth:`~Context.run`
method, makes the context the current context by pushing it onto the top of
the current thread's context stack.

*Exiting* from the current context, which can be done by returning from the
callback passed to the :meth:`~Context.run` method, restores the current
context to what it was before the context was entered by popping the context
off the top of the context stack.

Since each thread has its own context stack, :class:`ContextVar` objects
behave in a similar fashion to :func:`threading.local` when values are
assigned in different threads.

Attempting to enter an already entered context, including contexts entered in
other threads, raises a :exc:`RuntimeError`.

After exiting a context, it can later be re-entered (from any thread).

Any changes to :class:`ContextVar` values via the :meth:`ContextVar.set`
method are recorded in the current context. The :meth:`ContextVar.get`
method returns the value associated with the current context. Exiting a
context effectively reverts any changes made to context variables while the
context was entered (if needed, the values can be restored by re-entering the
context).

Context implements the :class:`collections.abc.Mapping` interface.

.. method:: run(callable, *args, **kwargs)

Execute ``callable(*args, **kwargs)`` code in the context object
the *run* method is called on. Return the result of the execution
or propagate an exception if one occurred.
Enters the Context, executes ``callable(*args, **kwargs)``, then exits the
Context. Returns *callable*'s return value, or propagates an exception if
one occurred.

Example:

.. testcode::

import contextvars

Any changes to any context variables that *callable* makes will
be contained in the context object::
var = contextvars.ContextVar('var')
var.set('spam')
print(var.get()) # 'spam'

var = ContextVar('var')
var.set('spam')
ctx = contextvars.copy_context()

def main():
# 'var' was set to 'spam' before
# calling 'copy_context()' and 'ctx.run(main)', so:
# var.get() == ctx[var] == 'spam'
def main():
# 'var' was set to 'spam' before
# calling 'copy_context()' and 'ctx.run(main)', so:
print(var.get()) # 'spam'
print(ctx[var]) # 'spam'

var.set('ham')
var.set('ham')

# Now, after setting 'var' to 'ham':
# var.get() == ctx[var] == 'ham'
# Now, after setting 'var' to 'ham':
print(var.get()) # 'ham'
print(ctx[var]) # 'ham'

ctx = copy_context()
# Any changes that the 'main' function makes to 'var'
# will be contained in 'ctx'.
ctx.run(main)

# Any changes that the 'main' function makes to 'var'
# will be contained in 'ctx'.
ctx.run(main)
# The 'main()' function was run in the 'ctx' context,
# so changes to 'var' are contained in it:
print(ctx[var]) # 'ham'

# The 'main()' function was run in the 'ctx' context,
# so changes to 'var' are contained in it:
# ctx[var] == 'ham'
# However, outside of 'ctx', 'var' is still set to 'spam':
print(var.get()) # 'spam'

# However, outside of 'ctx', 'var' is still set to 'spam':
# var.get() == 'spam'
.. testoutput::
:hide:

The method raises a :exc:`RuntimeError` when called on the same
context object from more than one OS thread, or when called
recursively.
spam
spam
spam
ham
ham
ham
spam

.. method:: copy()

Expand Down
125 changes: 125 additions & 0 deletions Doc/tools/extensions/availability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""Support for documenting platform availability"""

from __future__ import annotations

from typing import TYPE_CHECKING

from docutils import nodes
from sphinx import addnodes
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective

if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata

logger = logging.getLogger("availability")

# known platform, libc, and threading implementations
_PLATFORMS = frozenset({
"AIX",
"Android",
"BSD",
"DragonFlyBSD",
"Emscripten",
"FreeBSD",
"GNU/kFreeBSD",
"iOS",
"Linux",
"macOS",
"NetBSD",
"OpenBSD",
"POSIX",
"Solaris",
"Unix",
"VxWorks",
"WASI",
"Windows",
})
_LIBC = frozenset({
"BSD libc",
"glibc",
"musl",
})
_THREADING = frozenset({
# POSIX platforms with pthreads
"pthreads",
})
KNOWN_PLATFORMS = _PLATFORMS | _LIBC | _THREADING


class Availability(SphinxDirective):
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True

def run(self) -> list[nodes.container]:
title = "Availability"
refnode = addnodes.pending_xref(
title,
nodes.inline(title, title, classes=["xref", "std", "std-ref"]),
refdoc=self.env.docname,
refdomain="std",
refexplicit=True,
reftarget="availability",
reftype="ref",
refwarn=True,
)
sep = nodes.Text(": ")
parsed, msgs = self.state.inline_text(self.arguments[0], self.lineno)
pnode = nodes.paragraph(title, "", refnode, sep, *parsed, *msgs)
self.set_source_info(pnode)
cnode = nodes.container("", pnode, classes=["availability"])
self.set_source_info(cnode)
if self.content:
self.state.nested_parse(self.content, self.content_offset, cnode)
self.parse_platforms()

return [cnode]

def parse_platforms(self) -> dict[str, str | bool]:
"""Parse platform information from arguments
Arguments is a comma-separated string of platforms. A platform may
be prefixed with "not " to indicate that a feature is not available.
Example::
.. availability:: Windows, Linux >= 4.2, not WASI
Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not
parsed into separate tokens.
"""
platforms = {}
for arg in self.arguments[0].rstrip(".").split(","):
arg = arg.strip()
platform, _, version = arg.partition(" >= ")
if platform.startswith("not "):
version = False
platform = platform.removeprefix("not ")
elif not version:
version = True
platforms[platform] = version

if unknown := set(platforms).difference(KNOWN_PLATFORMS):
logger.warning(
"Unknown platform%s or syntax '%s' in '.. availability:: %s', "
"see %s:KNOWN_PLATFORMS for a set of known platforms.",
"s" if len(platforms) != 1 else "",
" ".join(sorted(unknown)),
self.arguments[0],
__file__,
)

return platforms


def setup(app: Sphinx) -> ExtensionMetadata:
app.add_directive("availability", Availability)

return {
"version": "1.0",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
Loading

0 comments on commit 17a9ebc

Please sign in to comment.