Skip to content

Commit bd09335

Browse files
authored
bpo-32436: Add docs for contextvars (#5685)
1 parent b65cb16 commit bd09335

File tree

4 files changed

+300
-0
lines changed

4 files changed

+300
-0
lines changed

Doc/library/contextvars.rst

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
:mod:`contextvars` --- Context Variables
2+
========================================
3+
4+
.. module:: contextvars
5+
:synopsis: Context Variables
6+
7+
.. sectionauthor:: Yury Selivanov <[email protected]>
8+
9+
--------------
10+
11+
This module provides APIs to manage, store, and access non-local
12+
state. The :class:`~contextvars.ContextVar` class is used to declare
13+
and work with *Context Variables*. The :func:`~contextvars.copy_context`
14+
function and the :class:`~contextvars.Context` class should be used to
15+
manage the current context in asynchronous frameworks.
16+
17+
Context managers that have state should use Context Variables
18+
instead of :func:`threading.local()` to prevent their state from
19+
bleeding to other code unexpectedly, when used in concurrent code.
20+
21+
See also :pep:`567` for additional details.
22+
23+
.. versionadded:: 3.7
24+
25+
26+
Context Variables
27+
-----------------
28+
29+
.. class:: ContextVar(name, [\*, default])
30+
31+
This class is used to declare a new Context Variable, e.g.::
32+
33+
var: ContextVar[int] = ContextVar('var', default=42)
34+
35+
The required *name* parameter is used for introspection and debug
36+
purposes.
37+
38+
The optional keyword-only *default* parameter is returned by
39+
:meth:`ContextVar.get` when no value for the variable is found
40+
in the current context.
41+
42+
**Important:** Context Variables should be created at the top module
43+
level and never in closures. :class:`Context` objects hold strong
44+
references to context variables which prevents context variables
45+
from being properly garbage collected.
46+
47+
.. attribute:: ContextVar.name
48+
49+
The name of the variable. This is a read-only property.
50+
51+
.. method:: get([default])
52+
53+
Return a value for the context variable for the current context.
54+
55+
If there is no value for the variable in the current context,
56+
the method will:
57+
58+
* return the value of the *default* argument of the method,
59+
if provided; or
60+
61+
* return the default value for the context variable,
62+
if it was created with one; or
63+
64+
* raise a :exc:`LookupError`.
65+
66+
.. method:: set(value)
67+
68+
Call to set a new value for the context variable in the current
69+
context.
70+
71+
The required *value* argument is the new value for the context
72+
variable.
73+
74+
Returns a :class:`~contextvars.Token` object that can be used
75+
to restore the variable to its previous value via the
76+
:meth:`ContextVar.reset` method.
77+
78+
.. method:: reset(token)
79+
80+
Reset the context variable to the value it had before the
81+
:meth:`ContextVar.set` that created the *token* was used.
82+
83+
For example::
84+
85+
var = ContextVar('var')
86+
87+
token = var.set('new value')
88+
# code that uses 'var'; var.get() returns 'new value'.
89+
var.reset(token)
90+
91+
# After the reset call the var has no value again, so
92+
# var.get() would raise a LookupError.
93+
94+
95+
.. class:: contextvars.Token
96+
97+
*Token* objects are returned by the :meth:`ContextVar.set` method.
98+
They can be passed to the :meth:`ContextVar.reset` method to revert
99+
the value of the variable to what it was before the corresponding
100+
*set*.
101+
102+
.. attribute:: Token.var
103+
104+
A read-only property. Points to the :class:`ContextVar` object
105+
that created the token.
106+
107+
.. attribute:: Token.old_value
108+
109+
A read-only property. Set to the value the variable had before
110+
the :meth:`ContextVar.set` method call that created the token.
111+
It points to :attr:`Token.MISSING` is the variable was not set
112+
before the call.
113+
114+
.. attribute:: Token.MISSING
115+
116+
A marker object used by :attr:`Token.old_value`.
117+
118+
119+
Manual Context Management
120+
-------------------------
121+
122+
.. function:: copy_context()
123+
124+
Returns a copy of the current :class:`~contextvars.Context` object.
125+
126+
The following snippet gets a copy of the current context and prints
127+
all variables and their values that are set in it::
128+
129+
ctx: Context = copy_context()
130+
print(list(ctx.items()))
131+
132+
The function has an O(1) complexity, i.e. works equally fast for
133+
contexts with a few context variables and for contexts that have
134+
a lot of them.
135+
136+
137+
.. class:: Context()
138+
139+
A mapping of :class:`ContextVars <ContextVar>` to their values.
140+
141+
``Context()`` creates an empty context with no values in it.
142+
To get a copy of the current context use the
143+
:func:`~contextvars.copy_context` function.
144+
145+
Context implements the :class:`collections.abc.Mapping` interface.
146+
147+
.. method:: run(callable, \*args, \*\*kwargs)
148+
149+
Execute ``callable(*args, **kwargs)`` code in the context object
150+
the *run* method is called on. Return the result of the execution
151+
or propagate an exception if one occurred.
152+
153+
Any changes to any context variables that *callable* makes will
154+
be contained in the context object::
155+
156+
var = ContextVar('var')
157+
var.set('spam')
158+
159+
def main():
160+
# 'var' was set to 'spam' before
161+
# calling 'copy_context()' and 'ctx.run(main)', so:
162+
# var.get() == ctx[var] == 'spam'
163+
164+
var.set('ham')
165+
166+
# Now, after setting 'var' to 'ham':
167+
# var.get() == ctx[var] == 'ham'
168+
169+
ctx = copy_context()
170+
171+
# Any changes that the 'main' function makes to 'var'
172+
# will be contained in 'ctx'.
173+
ctx.run(main)
174+
175+
# The 'main()' function was run in the 'ctx' context,
176+
# so changes to 'var' are contained in it:
177+
# ctx[var] == 'ham'
178+
179+
# However, outside of 'ctx', 'var' is still set to 'spam':
180+
# var.get() == 'spam'
181+
182+
The method raises a :exc:`RuntimeError` when called on the same
183+
context object from more than one OS thread, or when called
184+
recursively.
185+
186+
.. method:: copy()
187+
188+
Return a shallow copy of the context object.
189+
190+
.. describe:: var in context
191+
192+
Return ``True`` if the *context* has a value for *var* set;
193+
return ``False`` otherwise.
194+
195+
.. describe:: context[var]
196+
197+
Return the value of the *var* :class:`ContextVar` variable.
198+
If the variable is not set in the context object, a
199+
:exc:`KeyError` is raised.
200+
201+
.. method:: get(var, [default])
202+
203+
Return the value for *var* if *var* has the value in the context
204+
object. Return *default* otherwise. If *default* is not given,
205+
return ``None``.
206+
207+
.. describe:: iter(context)
208+
209+
Return an iterator over the variables stored in the context
210+
object.
211+
212+
.. describe:: len(proxy)
213+
214+
Return the number of variables set in the context object.
215+
216+
.. method:: keys()
217+
218+
Return a list of all variables in the context object.
219+
220+
.. method:: values()
221+
222+
Return a list of all variables' values in the context object.
223+
224+
225+
.. method:: items()
226+
227+
Return a list of 2-tuples containing all variables and their
228+
values in the context object.
229+
230+
231+
asyncio support
232+
---------------
233+
234+
Context variables are natively supported in :mod:`asyncio` and are
235+
ready to be used without any extra configuration. For example, here
236+
is a simple echo server, that uses a context variable to make the
237+
address of a remote client available in the Task that handles that
238+
client::
239+
240+
import asyncio
241+
import contextvars
242+
243+
client_addr_var = contextvars.ContextVar('client_addr')
244+
245+
def render_goodbye():
246+
# The address of the currently handled client can be accessed
247+
# without passing it explicitly to this function.
248+
249+
client_addr = client_addr_var.get()
250+
return f'Good bye, client @ {client_addr}\n'.encode()
251+
252+
async def handle_request(reader, writer):
253+
addr = writer.transport.get_extra_info('socket').getpeername()
254+
client_addr_var.set(addr)
255+
256+
# In any code that we call is is now possible to get
257+
# client's address by calling 'client_addr_var.get()'.
258+
259+
while True:
260+
line = await reader.readline()
261+
print(line)
262+
if not line.strip():
263+
break
264+
writer.write(line)
265+
266+
writer.write(render_goodbye())
267+
writer.close()
268+
269+
async def main():
270+
srv = await asyncio.start_server(
271+
handle_request, '127.0.0.1', 8081)
272+
273+
async with srv:
274+
await srv.serve_forever()
275+
276+
asyncio.run(main())
277+
278+
# To test it you can use telnet:
279+
# telnet 127.0.0.1 8081

Doc/library/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ the `Python Package Index <https://pypi.python.org/pypi>`_.
5555
crypto.rst
5656
allos.rst
5757
concurrency.rst
58+
contextvars.rst
5859
ipc.rst
5960
netdata.rst
6061
markup.rst

Doc/whatsnew/3.7.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,25 @@ For example::
340340
PEP written and implemented by Eric V. Smith
341341

342342

343+
PEP 567: Context Variables
344+
--------------------------
345+
346+
Adds a new module :mod:`contextvars`, that provides APIs to manage,
347+
store, and access non-local state.
348+
349+
Context variables are natively supported in :mod:`asyncio` and are
350+
ready to be used without any extra configuration.
351+
352+
The :mod:`decimal` module was updated to use *contextvars* to store
353+
the current decimal context. This allows decimal operations to work
354+
with the correct context in async/await code.
355+
356+
.. seealso::
357+
358+
:pep:`567` -- Context Variables
359+
PEP written and implemented by Yury Selivanov
360+
361+
343362
New Development Mode: -X dev
344363
----------------------------
345364

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add documentation for the contextvars module (PEP 567).

0 commit comments

Comments
 (0)