Metadata-Version: 2.4
Name: aiotrace
Version: 0.1.0
Summary: OpenTelemetry-native async context propagation for asyncio
Author: aiotrace contributors
License: MIT
License-File: LICENSE
Keywords: asyncio,contextvars,observability,opentelemetry,tracing
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: System :: Monitoring
Requires-Dist: opentelemetry-api>=1.20.0
Provides-Extra: dev
Requires-Dist: coverage>=7.0; extra == 'dev'
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'dev'
Requires-Dist: opentelemetry-test-utils>=0.40b0; extra == 'dev'
Requires-Dist: pyright>=1.1; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Provides-Extra: sdk
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'sdk'
Provides-Extra: test
Requires-Dist: coverage>=7.0; extra == 'test'
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'test'
Requires-Dist: opentelemetry-test-utils>=0.40b0; extra == 'test'
Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
Requires-Dist: pytest>=7.0; extra == 'test'
Description-Content-Type: text/markdown

# aiotrace – Async context propagation for OpenTelemetry

[![PyPI](https://img.shields.io/pypi/v/aiotrace)](https://pypi.org/project/aiotrace/)
[![Python Versions](https://img.shields.io/pypi/pyversions/aiotrace)](https://pypi.org/project/aiotrace/)
[![License](https://img.shields.io/pypi/l/aiotrace)](https://github.com/Kubenew/aiotrace/blob/main/LICENSE)
[![GitHub](https://img.shields.io/github/stars/Kubenew/aiotrace?style=social)](https://github.com/Kubenew/aiotrace)
[![Tests](https://img.shields.io/github/actions/workflow/status/Kubenew/aiotrace/ci.yml?label=tests)](https://github.com/Kubenew/aiotrace/actions)

Fixes OpenTelemetry context propagation across asyncio boundaries:
`create_task`, `Queue`, `Lock`, `Event`, `Semaphore`, `TaskGroup`, and
`run_in_executor` (thread pool).

## Problem

OpenTelemetry stores the current span in a `contextvars.ContextVar`.
When asyncio tasks are created or resumed, Python copies/shallow-copies
these contextvars.  On Python < 3.9.17 / 3.10.7 / 3.11.1, `Task.__step`
does **not** restore the task's own context, causing:

* Spans created in child tasks appearing under the wrong parent
* Context leaking between producer/consumer queues
* Lost context when using `run_in_executor` (threads)

## Quick Start

```python
import asyncio
from opentelemetry import trace
from aiotrace import install

install()

tracer = trace.get_tracer(__name__)

async def child():
    with tracer.start_as_current_span("child"):
        pass

async def main():
    with tracer.start_as_current_span("parent"):
        await asyncio.create_task(child())

asyncio.run(main())
```

## Manual Usage (no monkey-patching)

```python
from aiotrace import create_task_with_context, PropagatingQueue

# Use explicit wrapper instead of monkey-patch
task = create_task_with_context(some_coro())

# Explicit propagating queue
queue = PropagatingQueue()
await queue.put(item)
item = await queue.get()
```

## Installation

```bash
pip install aiotrace
```

## What Gets Patched

| Primitive | Replacement | Description |
|-----------|-------------|-------------|
| `asyncio.create_task` | Wrapped | Captures OTEL context at task creation, restores inside child |
| `asyncio.Queue` | `PropagatingQueue` | Re-attaches context after `get()`/`put()` resume |
| `asyncio.Lock` | `PropagatingLock` | Re-attaches context after `acquire()` |
| `asyncio.Event` | `PropagatingEvent` | Re-attaches context after `wait()` |
| `asyncio.Semaphore` | `PropagatingSemaphore` | Re-attaches context after `acquire()` |
| `asyncio.Condition` | `PropagatingCondition` | Re-attaches context after `wait()` |
| `asyncio.TaskGroup` (3.11+) | `PropagatingTaskGroup` | Captures context at `create_task()` |

## API

### `install(patch_queue=True, patch_locks=True)`

Monkey-patches asyncio primitives. Safe to call multiple times (idempotent).

### `uninstall()`

Restores original asyncio primitives.

### `PropagatingQueue(maxsize=0)`

Drop-in replacement for `asyncio.Queue` with context propagation.

### `PropagatingLock`, `PropagatingEvent`, `PropagatingSemaphore`, `PropagatingCondition`

Drop-in replacements for synchronization primitives.

### `create_task_with_context(coro, *, name=None, otel_ctx=None)`

Create a task with explicit OTEL context propagation.

### `run_in_executor_with_context(executor, func, *args)`

Schedule a function in a thread pool with OTEL context.

## Requirements

* Python 3.8–3.12
* `opentelemetry-api >= 1.20.0`

## License

MIT
