blob: 14ac47f4c9eb164050749ff5b7431b720c11145d [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
Andre Delfinodcc997c2020-12-16 22:37:28 -030029.. class:: ContextVar(name, [*, default])
Yury Selivanovbd093352018-02-16 11:47:54 -050030
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
Yury Selivanov41cb0ba2018-06-28 13:20:29 -040051 .. versionadded:: 3.7.1
52
Yury Selivanovbd093352018-02-16 11:47:54 -050053 .. method:: get([default])
54
55 Return a value for the context variable for the current context.
56
57 If there is no value for the variable in the current context,
58 the method will:
59
60 * return the value of the *default* argument of the method,
61 if provided; or
62
63 * return the default value for the context variable,
64 if it was created with one; or
65
66 * raise a :exc:`LookupError`.
67
68 .. method:: set(value)
69
70 Call to set a new value for the context variable in the current
71 context.
72
73 The required *value* argument is the new value for the context
74 variable.
75
76 Returns a :class:`~contextvars.Token` object that can be used
77 to restore the variable to its previous value via the
78 :meth:`ContextVar.reset` method.
79
80 .. method:: reset(token)
81
82 Reset the context variable to the value it had before the
83 :meth:`ContextVar.set` that created the *token* was used.
84
85 For example::
86
87 var = ContextVar('var')
88
89 token = var.set('new value')
90 # code that uses 'var'; var.get() returns 'new value'.
91 var.reset(token)
92
93 # After the reset call the var has no value again, so
94 # var.get() would raise a LookupError.
95
96
97.. class:: contextvars.Token
98
99 *Token* objects are returned by the :meth:`ContextVar.set` method.
100 They can be passed to the :meth:`ContextVar.reset` method to revert
101 the value of the variable to what it was before the corresponding
102 *set*.
103
104 .. attribute:: Token.var
105
106 A read-only property. Points to the :class:`ContextVar` object
107 that created the token.
108
109 .. attribute:: Token.old_value
110
111 A read-only property. Set to the value the variable had before
112 the :meth:`ContextVar.set` method call that created the token.
113 It points to :attr:`Token.MISSING` is the variable was not set
114 before the call.
115
116 .. attribute:: Token.MISSING
117
118 A marker object used by :attr:`Token.old_value`.
119
120
121Manual Context Management
122-------------------------
123
124.. function:: copy_context()
125
126 Returns a copy of the current :class:`~contextvars.Context` object.
127
128 The following snippet gets a copy of the current context and prints
129 all variables and their values that are set in it::
130
131 ctx: Context = copy_context()
132 print(list(ctx.items()))
133
134 The function has an O(1) complexity, i.e. works equally fast for
135 contexts with a few context variables and for contexts that have
136 a lot of them.
137
138
139.. class:: Context()
140
141 A mapping of :class:`ContextVars <ContextVar>` to their values.
142
143 ``Context()`` creates an empty context with no values in it.
144 To get a copy of the current context use the
145 :func:`~contextvars.copy_context` function.
146
147 Context implements the :class:`collections.abc.Mapping` interface.
148
Andre Delfinodcc997c2020-12-16 22:37:28 -0300149 .. method:: run(callable, *args, **kwargs)
Yury Selivanovbd093352018-02-16 11:47:54 -0500150
151 Execute ``callable(*args, **kwargs)`` code in the context object
152 the *run* method is called on. Return the result of the execution
153 or propagate an exception if one occurred.
154
155 Any changes to any context variables that *callable* makes will
156 be contained in the context object::
157
158 var = ContextVar('var')
159 var.set('spam')
160
161 def main():
162 # 'var' was set to 'spam' before
163 # calling 'copy_context()' and 'ctx.run(main)', so:
164 # var.get() == ctx[var] == 'spam'
165
166 var.set('ham')
167
168 # Now, after setting 'var' to 'ham':
169 # var.get() == ctx[var] == 'ham'
170
171 ctx = copy_context()
172
173 # Any changes that the 'main' function makes to 'var'
174 # will be contained in 'ctx'.
175 ctx.run(main)
176
177 # The 'main()' function was run in the 'ctx' context,
178 # so changes to 'var' are contained in it:
179 # ctx[var] == 'ham'
180
181 # However, outside of 'ctx', 'var' is still set to 'spam':
182 # var.get() == 'spam'
183
184 The method raises a :exc:`RuntimeError` when called on the same
185 context object from more than one OS thread, or when called
186 recursively.
187
188 .. method:: copy()
189
190 Return a shallow copy of the context object.
191
192 .. describe:: var in context
193
194 Return ``True`` if the *context* has a value for *var* set;
195 return ``False`` otherwise.
196
197 .. describe:: context[var]
198
199 Return the value of the *var* :class:`ContextVar` variable.
200 If the variable is not set in the context object, a
201 :exc:`KeyError` is raised.
202
203 .. method:: get(var, [default])
204
205 Return the value for *var* if *var* has the value in the context
206 object. Return *default* otherwise. If *default* is not given,
207 return ``None``.
208
209 .. describe:: iter(context)
210
211 Return an iterator over the variables stored in the context
212 object.
213
214 .. describe:: len(proxy)
215
216 Return the number of variables set in the context object.
217
218 .. method:: keys()
219
220 Return a list of all variables in the context object.
221
222 .. method:: values()
223
224 Return a list of all variables' values in the context object.
225
226
227 .. method:: items()
228
229 Return a list of 2-tuples containing all variables and their
230 values in the context object.
231
232
233asyncio support
234---------------
235
236Context variables are natively supported in :mod:`asyncio` and are
237ready to be used without any extra configuration. For example, here
238is a simple echo server, that uses a context variable to make the
239address of a remote client available in the Task that handles that
240client::
241
242 import asyncio
243 import contextvars
244
245 client_addr_var = contextvars.ContextVar('client_addr')
246
247 def render_goodbye():
248 # The address of the currently handled client can be accessed
249 # without passing it explicitly to this function.
250
251 client_addr = client_addr_var.get()
252 return f'Good bye, client @ {client_addr}\n'.encode()
253
254 async def handle_request(reader, writer):
255 addr = writer.transport.get_extra_info('socket').getpeername()
256 client_addr_var.set(addr)
257
Serhiy Storchakabac2d5b2018-03-28 22:14:26 +0300258 # In any code that we call is now possible to get
Yury Selivanovbd093352018-02-16 11:47:54 -0500259 # client's address by calling 'client_addr_var.get()'.
260
261 while True:
262 line = await reader.readline()
263 print(line)
264 if not line.strip():
265 break
266 writer.write(line)
267
268 writer.write(render_goodbye())
269 writer.close()
270
271 async def main():
272 srv = await asyncio.start_server(
273 handle_request, '127.0.0.1', 8081)
274
275 async with srv:
276 await srv.serve_forever()
277
278 asyncio.run(main())
279
280 # To test it you can use telnet:
281 # telnet 127.0.0.1 8081