Yury Selivanov | bd09335 | 2018-02-16 11:47:54 -0500 | [diff] [blame] | 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 | |
Tom Christie | e022bbc | 2018-04-27 23:35:13 +0100 | [diff] [blame] | 11 | This module provides APIs to manage, store, and access context-local |
Yury Selivanov | bd09335 | 2018-02-16 11:47:54 -0500 | [diff] [blame] | 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 | |
Andre Delfino | dcc997c | 2020-12-16 22:37:28 -0300 | [diff] [blame] | 29 | .. class:: ContextVar(name, [*, default]) |
Yury Selivanov | bd09335 | 2018-02-16 11:47:54 -0500 | [diff] [blame] | 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 | |
Yury Selivanov | 41cb0ba | 2018-06-28 13:20:29 -0400 | [diff] [blame] | 51 | .. versionadded:: 3.7.1 |
| 52 | |
Yury Selivanov | bd09335 | 2018-02-16 11:47:54 -0500 | [diff] [blame] | 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:: 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 | |
| 121 | Manual 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 Delfino | dcc997c | 2020-12-16 22:37:28 -0300 | [diff] [blame] | 149 | .. method:: run(callable, *args, **kwargs) |
Yury Selivanov | bd09335 | 2018-02-16 11:47:54 -0500 | [diff] [blame] | 150 | |
| 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 | |
| 233 | asyncio support |
| 234 | --------------- |
| 235 | |
| 236 | Context variables are natively supported in :mod:`asyncio` and are |
| 237 | ready to be used without any extra configuration. For example, here |
| 238 | is a simple echo server, that uses a context variable to make the |
| 239 | address of a remote client available in the Task that handles that |
| 240 | client:: |
| 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 Storchaka | bac2d5b | 2018-03-28 22:14:26 +0300 | [diff] [blame] | 258 | # In any code that we call is now possible to get |
Yury Selivanov | bd09335 | 2018-02-16 11:47:54 -0500 | [diff] [blame] | 259 | # 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 |