• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1:mod:`!contextvars` --- Context Variables
2=========================================
3
4.. module:: contextvars
5   :synopsis: Context Variables
6
7.. sectionauthor:: Yury Selivanov <yury@magic.io>
8
9--------------
10
11This module provides APIs to manage, store, and access context-local
12state.  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      .. versionadded:: 3.7.1
52
53   .. 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:: 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` if 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   Each thread has its own effective stack of :class:`!Context` objects.  The
148   :term:`current context` is the :class:`!Context` object at the top of the
149   current thread's stack.  All :class:`!Context` objects in the stacks are
150   considered to be *entered*.
151
152   *Entering* a context, which can be done by calling its :meth:`~Context.run`
153   method, makes the context the current context by pushing it onto the top of
154   the current thread's context stack.
155
156   *Exiting* from the current context, which can be done by returning from the
157   callback passed to the :meth:`~Context.run` method, restores the current
158   context to what it was before the context was entered by popping the context
159   off the top of the context stack.
160
161   Since each thread has its own context stack, :class:`ContextVar` objects
162   behave in a similar fashion to :func:`threading.local` when values are
163   assigned in different threads.
164
165   Attempting to enter an already entered context, including contexts entered in
166   other threads, raises a :exc:`RuntimeError`.
167
168   After exiting a context, it can later be re-entered (from any thread).
169
170   Any changes to :class:`ContextVar` values via the :meth:`ContextVar.set`
171   method are recorded in the current context.  The :meth:`ContextVar.get`
172   method returns the value associated with the current context.  Exiting a
173   context effectively reverts any changes made to context variables while the
174   context was entered (if needed, the values can be restored by re-entering the
175   context).
176
177   Context implements the :class:`collections.abc.Mapping` interface.
178
179   .. method:: run(callable, *args, **kwargs)
180
181      Enters the Context, executes ``callable(*args, **kwargs)``, then exits the
182      Context.  Returns *callable*'s return value, or propagates an exception if
183      one occurred.
184
185      Example:
186
187      .. testcode::
188
189         import contextvars
190
191         var = contextvars.ContextVar('var')
192         var.set('spam')
193         print(var.get())  # 'spam'
194
195         ctx = contextvars.copy_context()
196
197         def main():
198             # 'var' was set to 'spam' before
199             # calling 'copy_context()' and 'ctx.run(main)', so:
200             print(var.get())  # 'spam'
201             print(ctx[var])  # 'spam'
202
203             var.set('ham')
204
205             # Now, after setting 'var' to 'ham':
206             print(var.get())  # 'ham'
207             print(ctx[var])  # 'ham'
208
209         # Any changes that the 'main' function makes to 'var'
210         # will be contained in 'ctx'.
211         ctx.run(main)
212
213         # The 'main()' function was run in the 'ctx' context,
214         # so changes to 'var' are contained in it:
215         print(ctx[var])  # 'ham'
216
217         # However, outside of 'ctx', 'var' is still set to 'spam':
218         print(var.get())  # 'spam'
219
220      .. testoutput::
221         :hide:
222
223         spam
224         spam
225         spam
226         ham
227         ham
228         ham
229         spam
230
231   .. method:: copy()
232
233      Return a shallow copy of the context object.
234
235   .. describe:: var in context
236
237      Return ``True`` if the *context* has a value for *var* set;
238      return ``False`` otherwise.
239
240   .. describe:: context[var]
241
242      Return the value of the *var* :class:`ContextVar` variable.
243      If the variable is not set in the context object, a
244      :exc:`KeyError` is raised.
245
246   .. method:: get(var, [default])
247
248      Return the value for *var* if *var* has the value in the context
249      object.  Return *default* otherwise.  If *default* is not given,
250      return ``None``.
251
252   .. describe:: iter(context)
253
254      Return an iterator over the variables stored in the context
255      object.
256
257   .. describe:: len(proxy)
258
259      Return the number of variables set in the context object.
260
261   .. method:: keys()
262
263      Return a list of all variables in the context object.
264
265   .. method:: values()
266
267      Return a list of all variables' values in the context object.
268
269
270   .. method:: items()
271
272      Return a list of 2-tuples containing all variables and their
273      values in the context object.
274
275
276asyncio support
277---------------
278
279Context variables are natively supported in :mod:`asyncio` and are
280ready to be used without any extra configuration.  For example, here
281is a simple echo server, that uses a context variable to make the
282address of a remote client available in the Task that handles that
283client::
284
285    import asyncio
286    import contextvars
287
288    client_addr_var = contextvars.ContextVar('client_addr')
289
290    def render_goodbye():
291        # The address of the currently handled client can be accessed
292        # without passing it explicitly to this function.
293
294        client_addr = client_addr_var.get()
295        return f'Good bye, client @ {client_addr}\r\n'.encode()
296
297    async def handle_request(reader, writer):
298        addr = writer.transport.get_extra_info('socket').getpeername()
299        client_addr_var.set(addr)
300
301        # In any code that we call is now possible to get
302        # client's address by calling 'client_addr_var.get()'.
303
304        while True:
305            line = await reader.readline()
306            print(line)
307            if not line.strip():
308                break
309
310        writer.write(b'HTTP/1.1 200 OK\r\n')  # status line
311        writer.write(b'\r\n')  # headers
312        writer.write(render_goodbye())  # body
313        writer.close()
314
315    async def main():
316        srv = await asyncio.start_server(
317            handle_request, '127.0.0.1', 8081)
318
319        async with srv:
320            await srv.serve_forever()
321
322    asyncio.run(main())
323
324    # To test it you can use telnet or curl:
325    #     telnet 127.0.0.1 8081
326    #     curl 127.0.0.1:8081
327