|
| 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 |
0 commit comments