# Python Contextvars Module

The `contextvars` module provides APIs to manage, store, and access context-local state. The `ContextVar` class is used to declare and work with *Context Variables*.

This module was introduced in Python 3.7 and is designed to work correctly with asynchronous frameworks like `asyncio`, where multiple tasks might run concurrently in the same thread. Unlike `threading.local()`, which is thread-specific, `contextvars` are task-specific in an async environment.

## Importing the Module

```python
import contextvars
```

## Context Variables

### Declaration

Context variables should be declared at the top level of a module.

```python
import contextvars

# Declare a context variable with a default value
request_id = contextvars.ContextVar('request_id', default=None)
```

### Getting and Setting Values

*   `get()`: Returns the current value for the context variable. If no value has been set, returns the default value or raises `LookupError`.
*   `set(value)`: Sets a new value for the context variable in the current context. Returns a `Token` object.

```python
# Set a value
token = request_id.set('12345')

# Get the value
print(request_id.get()) # Output: 12345
```

### Resetting Values

You can restore the variable to its previous value using the `Token` object returned by `set()`.

```python
# Reset to previous value
request_id.reset(token)

print(request_id.get()) # Output: None (default)
```

## Usage with Asyncio

Context variables are natively supported in `asyncio`. When a new task is created, it inherits the context of its creator (a shallow copy). Changes in the new task do not affect the creator's context.

```python
import asyncio
import contextvars

client_addr = contextvars.ContextVar('client_addr')

def render_goodbye():
    # The address in the currently running Context
    # is accessible directly
    return f'Good bye, client @ {client_addr.get()}'

async def handle_request(addr):
    client_addr.set(addr)
    
    # sleep to simulate IO
    await asyncio.sleep(1)
    
    print(render_goodbye())

async def main():
    await asyncio.gather(
        handle_request('127.0.0.1'),
        handle_request('192.168.0.1'),
    )

# asyncio.run(main())
```

## Manual Context Management

You can manually manage contexts using the `Context` class and `copy_context()`.

```python
import contextvars

var = contextvars.ContextVar('var')
var.set('spam')

def main():
    var.set('ham')

ctx = contextvars.copy_context()
ctx.run(main)

print(var.get()) # Output: spam
print(ctx[var])  # Output: ham
```

[[programming/python/python]]