2024-05-08 16:34:40 -03:00
|
|
|
:mod:`!contextvars` --- Context Variables
|
|
|
|
=========================================
|
2018-02-16 12:47:54 -04:00
|
|
|
|
|
|
|
.. module:: contextvars
|
|
|
|
:synopsis: Context Variables
|
|
|
|
|
|
|
|
.. sectionauthor:: Yury Selivanov <yury@magic.io>
|
|
|
|
|
|
|
|
--------------
|
|
|
|
|
2018-04-27 19:35:13 -03:00
|
|
|
This module provides APIs to manage, store, and access context-local
|
2018-02-16 12:47:54 -04:00
|
|
|
state. The :class:`~contextvars.ContextVar` class is used to declare
|
|
|
|
and work with *Context Variables*. The :func:`~contextvars.copy_context`
|
|
|
|
function and the :class:`~contextvars.Context` class should be used to
|
|
|
|
manage the current context in asynchronous frameworks.
|
|
|
|
|
|
|
|
Context managers that have state should use Context Variables
|
2024-08-30 08:34:09 -03:00
|
|
|
instead of :func:`threading.local` to prevent their state from
|
2018-02-16 12:47:54 -04:00
|
|
|
bleeding to other code unexpectedly, when used in concurrent code.
|
|
|
|
|
|
|
|
See also :pep:`567` for additional details.
|
|
|
|
|
|
|
|
.. versionadded:: 3.7
|
|
|
|
|
|
|
|
|
|
|
|
Context Variables
|
|
|
|
-----------------
|
|
|
|
|
2020-12-16 21:37:28 -04:00
|
|
|
.. class:: ContextVar(name, [*, default])
|
2018-02-16 12:47:54 -04:00
|
|
|
|
|
|
|
This class is used to declare a new Context Variable, e.g.::
|
|
|
|
|
|
|
|
var: ContextVar[int] = ContextVar('var', default=42)
|
|
|
|
|
|
|
|
The required *name* parameter is used for introspection and debug
|
|
|
|
purposes.
|
|
|
|
|
|
|
|
The optional keyword-only *default* parameter is returned by
|
|
|
|
:meth:`ContextVar.get` when no value for the variable is found
|
|
|
|
in the current context.
|
|
|
|
|
|
|
|
**Important:** Context Variables should be created at the top module
|
|
|
|
level and never in closures. :class:`Context` objects hold strong
|
|
|
|
references to context variables which prevents context variables
|
|
|
|
from being properly garbage collected.
|
|
|
|
|
|
|
|
.. attribute:: ContextVar.name
|
|
|
|
|
|
|
|
The name of the variable. This is a read-only property.
|
|
|
|
|
2018-06-28 14:20:29 -03:00
|
|
|
.. versionadded:: 3.7.1
|
|
|
|
|
2018-02-16 12:47:54 -04:00
|
|
|
.. method:: get([default])
|
|
|
|
|
|
|
|
Return a value for the context variable for the current context.
|
|
|
|
|
|
|
|
If there is no value for the variable in the current context,
|
|
|
|
the method will:
|
|
|
|
|
|
|
|
* return the value of the *default* argument of the method,
|
|
|
|
if provided; or
|
|
|
|
|
|
|
|
* return the default value for the context variable,
|
|
|
|
if it was created with one; or
|
|
|
|
|
|
|
|
* raise a :exc:`LookupError`.
|
|
|
|
|
|
|
|
.. method:: set(value)
|
|
|
|
|
|
|
|
Call to set a new value for the context variable in the current
|
|
|
|
context.
|
|
|
|
|
|
|
|
The required *value* argument is the new value for the context
|
|
|
|
variable.
|
|
|
|
|
|
|
|
Returns a :class:`~contextvars.Token` object that can be used
|
|
|
|
to restore the variable to its previous value via the
|
|
|
|
:meth:`ContextVar.reset` method.
|
|
|
|
|
|
|
|
.. method:: reset(token)
|
|
|
|
|
|
|
|
Reset the context variable to the value it had before the
|
|
|
|
:meth:`ContextVar.set` that created the *token* was used.
|
|
|
|
|
|
|
|
For example::
|
|
|
|
|
|
|
|
var = ContextVar('var')
|
|
|
|
|
|
|
|
token = var.set('new value')
|
|
|
|
# code that uses 'var'; var.get() returns 'new value'.
|
|
|
|
var.reset(token)
|
|
|
|
|
|
|
|
# After the reset call the var has no value again, so
|
|
|
|
# var.get() would raise a LookupError.
|
|
|
|
|
|
|
|
|
2021-11-12 05:47:55 -04:00
|
|
|
.. class:: Token
|
2018-02-16 12:47:54 -04:00
|
|
|
|
|
|
|
*Token* objects are returned by the :meth:`ContextVar.set` method.
|
|
|
|
They can be passed to the :meth:`ContextVar.reset` method to revert
|
|
|
|
the value of the variable to what it was before the corresponding
|
|
|
|
*set*.
|
|
|
|
|
|
|
|
.. attribute:: Token.var
|
|
|
|
|
|
|
|
A read-only property. Points to the :class:`ContextVar` object
|
|
|
|
that created the token.
|
|
|
|
|
|
|
|
.. attribute:: Token.old_value
|
|
|
|
|
|
|
|
A read-only property. Set to the value the variable had before
|
|
|
|
the :meth:`ContextVar.set` method call that created the token.
|
2022-10-28 18:13:48 -03:00
|
|
|
It points to :attr:`Token.MISSING` if the variable was not set
|
2018-02-16 12:47:54 -04:00
|
|
|
before the call.
|
|
|
|
|
|
|
|
.. attribute:: Token.MISSING
|
|
|
|
|
|
|
|
A marker object used by :attr:`Token.old_value`.
|
|
|
|
|
|
|
|
|
|
|
|
Manual Context Management
|
|
|
|
-------------------------
|
|
|
|
|
|
|
|
.. function:: copy_context()
|
|
|
|
|
|
|
|
Returns a copy of the current :class:`~contextvars.Context` object.
|
|
|
|
|
|
|
|
The following snippet gets a copy of the current context and prints
|
|
|
|
all variables and their values that are set in it::
|
|
|
|
|
|
|
|
ctx: Context = copy_context()
|
|
|
|
print(list(ctx.items()))
|
|
|
|
|
2024-01-10 09:01:18 -04:00
|
|
|
The function has an *O*\ (1) complexity, i.e. works equally fast for
|
2018-02-16 12:47:54 -04:00
|
|
|
contexts with a few context variables and for contexts that have
|
|
|
|
a lot of them.
|
|
|
|
|
|
|
|
|
|
|
|
.. class:: Context()
|
|
|
|
|
|
|
|
A mapping of :class:`ContextVars <ContextVar>` to their values.
|
|
|
|
|
|
|
|
``Context()`` creates an empty context with no values in it.
|
|
|
|
To get a copy of the current context use the
|
|
|
|
:func:`~contextvars.copy_context` function.
|
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
Each thread has its own effective stack of :class:`!Context` objects. The
|
|
|
|
:term:`current context` is the :class:`!Context` object at the top of the
|
|
|
|
current thread's stack. All :class:`!Context` objects in the stacks are
|
|
|
|
considered to be *entered*.
|
|
|
|
|
|
|
|
*Entering* a context, which can be done by calling its :meth:`~Context.run`
|
|
|
|
method, makes the context the current context by pushing it onto the top of
|
|
|
|
the current thread's context stack.
|
|
|
|
|
|
|
|
*Exiting* from the current context, which can be done by returning from the
|
|
|
|
callback passed to the :meth:`~Context.run` method, restores the current
|
|
|
|
context to what it was before the context was entered by popping the context
|
|
|
|
off the top of the context stack.
|
|
|
|
|
|
|
|
Since each thread has its own context stack, :class:`ContextVar` objects
|
|
|
|
behave in a similar fashion to :func:`threading.local` when values are
|
|
|
|
assigned in different threads.
|
|
|
|
|
|
|
|
Attempting to enter an already entered context, including contexts entered in
|
|
|
|
other threads, raises a :exc:`RuntimeError`.
|
|
|
|
|
|
|
|
After exiting a context, it can later be re-entered (from any thread).
|
|
|
|
|
|
|
|
Any changes to :class:`ContextVar` values via the :meth:`ContextVar.set`
|
|
|
|
method are recorded in the current context. The :meth:`ContextVar.get`
|
|
|
|
method returns the value associated with the current context. Exiting a
|
|
|
|
context effectively reverts any changes made to context variables while the
|
|
|
|
context was entered (if needed, the values can be restored by re-entering the
|
|
|
|
context).
|
2022-12-20 07:35:48 -04:00
|
|
|
|
2018-02-16 12:47:54 -04:00
|
|
|
Context implements the :class:`collections.abc.Mapping` interface.
|
|
|
|
|
2020-12-16 21:37:28 -04:00
|
|
|
.. method:: run(callable, *args, **kwargs)
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
Enters the Context, executes ``callable(*args, **kwargs)``, then exits the
|
|
|
|
Context. Returns *callable*'s return value, or propagates an exception if
|
|
|
|
one occurred.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
.. testcode::
|
|
|
|
|
|
|
|
import contextvars
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
var = contextvars.ContextVar('var')
|
|
|
|
var.set('spam')
|
|
|
|
print(var.get()) # 'spam'
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
ctx = contextvars.copy_context()
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
def main():
|
|
|
|
# 'var' was set to 'spam' before
|
|
|
|
# calling 'copy_context()' and 'ctx.run(main)', so:
|
|
|
|
print(var.get()) # 'spam'
|
|
|
|
print(ctx[var]) # 'spam'
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
var.set('ham')
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
# Now, after setting 'var' to 'ham':
|
|
|
|
print(var.get()) # 'ham'
|
|
|
|
print(ctx[var]) # 'ham'
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
# Any changes that the 'main' function makes to 'var'
|
|
|
|
# will be contained in 'ctx'.
|
|
|
|
ctx.run(main)
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
# The 'main()' function was run in the 'ctx' context,
|
|
|
|
# so changes to 'var' are contained in it:
|
|
|
|
print(ctx[var]) # 'ham'
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
# However, outside of 'ctx', 'var' is still set to 'spam':
|
|
|
|
print(var.get()) # 'spam'
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
.. testoutput::
|
|
|
|
:hide:
|
2018-02-16 12:47:54 -04:00
|
|
|
|
2024-10-09 20:44:03 -03:00
|
|
|
spam
|
|
|
|
spam
|
|
|
|
spam
|
|
|
|
ham
|
|
|
|
ham
|
|
|
|
ham
|
|
|
|
spam
|
2018-02-16 12:47:54 -04:00
|
|
|
|
|
|
|
.. method:: copy()
|
|
|
|
|
|
|
|
Return a shallow copy of the context object.
|
|
|
|
|
|
|
|
.. describe:: var in context
|
|
|
|
|
|
|
|
Return ``True`` if the *context* has a value for *var* set;
|
|
|
|
return ``False`` otherwise.
|
|
|
|
|
|
|
|
.. describe:: context[var]
|
|
|
|
|
|
|
|
Return the value of the *var* :class:`ContextVar` variable.
|
|
|
|
If the variable is not set in the context object, a
|
|
|
|
:exc:`KeyError` is raised.
|
|
|
|
|
|
|
|
.. method:: get(var, [default])
|
|
|
|
|
|
|
|
Return the value for *var* if *var* has the value in the context
|
|
|
|
object. Return *default* otherwise. If *default* is not given,
|
|
|
|
return ``None``.
|
|
|
|
|
|
|
|
.. describe:: iter(context)
|
|
|
|
|
|
|
|
Return an iterator over the variables stored in the context
|
|
|
|
object.
|
|
|
|
|
|
|
|
.. describe:: len(proxy)
|
|
|
|
|
|
|
|
Return the number of variables set in the context object.
|
|
|
|
|
|
|
|
.. method:: keys()
|
|
|
|
|
|
|
|
Return a list of all variables in the context object.
|
|
|
|
|
|
|
|
.. method:: values()
|
|
|
|
|
|
|
|
Return a list of all variables' values in the context object.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: items()
|
|
|
|
|
|
|
|
Return a list of 2-tuples containing all variables and their
|
|
|
|
values in the context object.
|
|
|
|
|
|
|
|
|
|
|
|
asyncio support
|
|
|
|
---------------
|
|
|
|
|
|
|
|
Context variables are natively supported in :mod:`asyncio` and are
|
|
|
|
ready to be used without any extra configuration. For example, here
|
|
|
|
is a simple echo server, that uses a context variable to make the
|
|
|
|
address of a remote client available in the Task that handles that
|
|
|
|
client::
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
import contextvars
|
|
|
|
|
|
|
|
client_addr_var = contextvars.ContextVar('client_addr')
|
|
|
|
|
|
|
|
def render_goodbye():
|
|
|
|
# The address of the currently handled client can be accessed
|
|
|
|
# without passing it explicitly to this function.
|
|
|
|
|
|
|
|
client_addr = client_addr_var.get()
|
2024-09-09 10:58:49 -03:00
|
|
|
return f'Good bye, client @ {client_addr}\r\n'.encode()
|
2018-02-16 12:47:54 -04:00
|
|
|
|
|
|
|
async def handle_request(reader, writer):
|
|
|
|
addr = writer.transport.get_extra_info('socket').getpeername()
|
|
|
|
client_addr_var.set(addr)
|
|
|
|
|
2018-03-28 16:14:26 -03:00
|
|
|
# In any code that we call is now possible to get
|
2018-02-16 12:47:54 -04:00
|
|
|
# client's address by calling 'client_addr_var.get()'.
|
|
|
|
|
|
|
|
while True:
|
|
|
|
line = await reader.readline()
|
|
|
|
print(line)
|
|
|
|
if not line.strip():
|
|
|
|
break
|
|
|
|
|
2024-09-09 10:58:49 -03:00
|
|
|
writer.write(b'HTTP/1.1 200 OK\r\n') # status line
|
|
|
|
writer.write(b'\r\n') # headers
|
|
|
|
writer.write(render_goodbye()) # body
|
2018-02-16 12:47:54 -04:00
|
|
|
writer.close()
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
srv = await asyncio.start_server(
|
|
|
|
handle_request, '127.0.0.1', 8081)
|
|
|
|
|
|
|
|
async with srv:
|
|
|
|
await srv.serve_forever()
|
|
|
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
2024-09-09 10:58:49 -03:00
|
|
|
# To test it you can use telnet or curl:
|
2018-02-16 12:47:54 -04:00
|
|
|
# telnet 127.0.0.1 8081
|
2024-09-09 10:58:49 -03:00
|
|
|
# curl 127.0.0.1:8081
|