blob: 9c7ea2bdb7b68bfc5ed5ffd17a7d8c4b2d4b26f8 [file] [log] [blame]
Yury Selivanovbd093352018-02-16 11:47:54 -05001:mod:`contextvars` --- Context Variables
2========================================
3
4.. module:: contextvars
5 :synopsis: Context Variables
6
7.. sectionauthor:: Yury Selivanov <yury@magic.io>
8
9--------------
10
Tom Christiee022bbc2018-04-27 23:35:13 +010011This module provides APIs to manage, store, and access context-local
Yury Selivanovbd093352018-02-16 11:47:54 -050012state. The :class:`~contextvars.ContextVar` class is used to declare
13and work with *Context Variables*. The :func:`~contextvars.copy_context`
14function and the :class:`~contextvars.Context` class should be used to
15manage the current context in asynchronous frameworks.
16
17Context managers that have state should use Context Variables
18instead of :func:`threading.local()` to prevent their state from
19bleeding to other code unexpectedly, when used in concurrent code.
20
21See also :pep:`567` for additional details.
22
23.. versionadded:: 3.7
24
25
26Context 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
119Manual 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
231asyncio support
232---------------
233
234Context variables are natively supported in :mod:`asyncio` and are
235ready to be used without any extra configuration. For example, here
236is a simple echo server, that uses a context variable to make the
237address of a remote client available in the Task that handles that
238client::
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
Serhiy Storchakabac2d5b2018-03-28 22:14:26 +0300256 # In any code that we call is now possible to get
Yury Selivanovbd093352018-02-16 11:47:54 -0500257 # 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