Metadata-Version: 2.4
Name: fastapi-request-context
Version: 1.0.0
Summary: FastAPI middleware for request ID tracking, correlation IDs, and extensible request context with logging integration
Project-URL: Homepage, https://github.com/ADR-007/fastapi-request-context
Project-URL: Repository, https://github.com/ADR-007/fastapi-request-context
Project-URL: Issues, https://github.com/ADR-007/fastapi-request-context/issues
Author-email: Adrian Dankiv <adr-007@ukr.net>
License: MIT
License-File: LICENSE
Keywords: correlation-id,fastapi,logging,middleware,request-context,request-id,tracing
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: fastapi>=0.100.0
Provides-Extra: all
Requires-Dist: context-logging>=0.7.0; extra == 'all'
Requires-Dist: python-json-logger>=2.0.0; extra == 'all'
Provides-Extra: build
Requires-Dist: uv>=0.9.0; extra == 'build'
Provides-Extra: context-logging
Requires-Dist: context-logging>=0.7.0; extra == 'context-logging'
Provides-Extra: json-formatter
Requires-Dist: python-json-logger>=2.0.0; extra == 'json-formatter'
Description-Content-Type: text/markdown

# fastapi-request-context

[![PyPI version](https://badge.fury.io/py/fastapi-request-context.svg)](https://badge.fury.io/py/fastapi-request-context)
[![Python versions](https://img.shields.io/pypi/pyversions/fastapi-request-context.svg)](https://pypi.org/project/fastapi-request-context/)
[![CI](https://github.com/ADR-007/fastapi-request-context/actions/workflows/ci.yaml/badge.svg)](https://github.com/ADR-007/fastapi-request-context/actions/workflows/ci.yaml)
[![codecov](https://codecov.io/gh/ADR-007/fastapi-request-context/branch/main/graph/badge.svg)](https://codecov.io/gh/ADR-007/fastapi-request-context)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

FastAPI middleware for request ID tracking, correlation IDs, and extensible request context with first-class logging integration.

## Features

- **Automatic request ID generation** - Every request gets a unique ID
- **Correlation ID support** - Accept from header or generate for distributed tracing
- **Response header injection** - Automatically add `X-Request-Id` and `X-Correlation-Id` to responses
- **Pluggable context backends** - Use `contextvars` (default) or `context-logging`
- **Custom context fields** - Extend with your own fields via StrEnum
- **Logging integration** - JSON and human-readable formatters with automatic context injection
- **Validation utilities** - Check that routes and dependencies are async
- **Zero configuration** - Works out of the box with sensible defaults
- **Type-safe** - Full type hints and mypy strict mode

## Installation

```bash
# Basic installation
pip install fastapi-request-context

# With context-logging support
pip install fastapi-request-context[context-logging]

# With JSON formatter support
pip install fastapi-request-context[json-formatter]

# All optional dependencies
pip install fastapi-request-context[all]
```

Using uv:
```bash
uv add fastapi-request-context
```

## Quick Start

```python
from fastapi import FastAPI
from fastapi_request_context import RequestContextMiddleware

# Create your app
app = FastAPI()

# Keep reference to raw app if needed (e.g., for TaskIQ, testing)
raw_app = app

# Wrap with middleware
app = RequestContextMiddleware(app)
```

Every request now has:
- Unique `request_id` (always generated)
- `correlation_id` (from `X-Correlation-Id` header or generated)
- Both added to response headers
- Context available in all log records (including access logs!)

## Usage

### Access Context Values

```python
from fastapi_request_context import get_context, get_full_context, StandardContextField

@app.get("/")
async def root():
    request_id = get_context(StandardContextField.REQUEST_ID)
    correlation_id = get_context(StandardContextField.CORRELATION_ID)
    
    # Or get everything
    all_context = get_full_context()
    
    return {"request_id": request_id}
```

### Custom Context Fields

```python
from enum import StrEnum
from fastapi_request_context import set_context, get_context

class MyContextField(StrEnum):
    USER_ID = "user_id"
    ORG_ID = "org_id"

async def get_current_user(token: str):
    user_id = decode_token(token)
    set_context(MyContextField.USER_ID, user_id)
    return user_id

@app.get("/me")
async def me(user_id: int = Depends(get_current_user)):
    # Context is available throughout the request
    return {"user_id": get_context(MyContextField.USER_ID)}
```

### Configuration

```python
from fastapi_request_context import RequestContextMiddleware, RequestContextConfig
from uuid import uuid4

config = RequestContextConfig(
    # Custom ID generators
    request_id_generator=lambda: str(uuid4()),
    correlation_id_generator=lambda: str(uuid4()),
    
    # Custom header names
    request_id_header="X-My-Request-Id",
    correlation_id_header="X-My-Correlation-Id",
    
    # Disable response headers
    add_response_headers=False,
    
    # Use context-logging adapter
    context_adapter="context_logging",
    
    # Only process HTTP (not WebSocket)
    scope_types={"http"},
)

app = RequestContextMiddleware(app, config=config)
```

### Logging Integration

#### JSON Formatter (Production)

```python
import logging
from fastapi_request_context.formatters import JsonContextFormatter

handler = logging.StreamHandler()
handler.setFormatter(JsonContextFormatter())
logging.basicConfig(handlers=[handler], level=logging.INFO)

# Logs automatically include context:
# {"message": "Processing", "level": "INFO", "request_id": "...", "user_id": 123}
```

#### Simple Formatter (Human-Readable)

```python
from fastapi_request_context import StandardContextField
from fastapi_request_context.formatters import SimpleContextFormatter

handler = logging.StreamHandler()
handler.setFormatter(SimpleContextFormatter(
    fmt="%(asctime)s %(levelname)s %(context)s %(message)s",
    shorten_fields={StandardContextField.REQUEST_ID},  # Show first 8 chars
    hidden_fields={StandardContextField.CORRELATION_ID},  # Hide completely
))
logging.basicConfig(handlers=[handler], level=logging.INFO)

# Output: 2025-01-15 10:30:00 INFO [request_id=3fa85f64 user_id=123] Processing
```

### Access Logs Integration

Context is automatically available in **all log records**, including Uvicorn access logs when using `context-logging` adapter:

```python
from fastapi_request_context import RequestContextMiddleware, RequestContextConfig

config = RequestContextConfig(context_adapter="context_logging")
app = RequestContextMiddleware(app, config=config)

# Now access logs will include request_id and correlation_id!
# Example: INFO 127.0.0.1:8000 - "GET / HTTP/1.1" 200 [request_id=abc123]
```

### Custom Context Adapter

```python
from fastapi_request_context.adapters import ContextAdapter

class RedisAdapter(ContextAdapter):
    def set_value(self, key: str, value: Any) -> None:
        redis.hset(self._request_key, key, value)
    
    def get_value(self, key: str) -> Any:
        return redis.hget(self._request_key, key)
    
    def get_all(self) -> dict[str, Any]:
        return redis.hgetall(self._request_key)
    
    def enter_context(self, initial_values: dict[str, Any]) -> None:
        self._request_key = f"request:{uuid4()}"
        redis.hmset(self._request_key, initial_values)
    
    def exit_context(self) -> None:
        redis.delete(self._request_key)

config = RequestContextConfig(context_adapter=RedisAdapter())
app = RequestContextMiddleware(app, config=config)
```

### Validation Utilities

Ensure all routes and dependencies are async (required for proper context propagation):

```python
from fastapi_request_context.validation import check_routes_and_dependencies_are_async

@app.on_event("startup")
async def validate():
    warnings = check_routes_and_dependencies_are_async(app)
    # Logs warnings for any sync routes/dependencies
    
    # Or raise an error
    check_routes_and_dependencies_are_async(app, raise_on_sync=True)
```

## API Reference

### Middleware

- `RequestContextMiddleware(app, config=None)` - Main middleware class

### Configuration

- `RequestContextConfig` - Configuration dataclass with all options

### Context Functions

- `set_context(key, value)` - Set a context value
- `get_context(key)` - Get a context value (returns None if not set)
- `get_full_context()` - Get all context values as a dict

### Fields

- `StandardContextField` - Built-in fields (REQUEST_ID, CORRELATION_ID)

### Adapters

- `ContextAdapter` - Protocol for custom adapters
- `ContextVarsAdapter` - Default adapter using Python's contextvars
- `ContextLoggingAdapter` - Adapter using context-logging library (enables access log integration)

### Formatters

- `JsonContextFormatter` - JSON formatter for structured logging
- `SimpleContextFormatter` - Human-readable formatter with inline context

### Validation

- `is_async(func)` - Check if a function is async
- `check_dependencies_are_async(deps)` - Check dependencies
- `check_routes_and_dependencies_are_async(app)` - Check entire app

## Why This Library?

### vs. Manual Implementation

| Feature | Manual | This Library |
|---------|--------|--------------|
| Request ID generation | DIY | ✅ Built-in |
| Correlation ID | DIY | ✅ Built-in |
| Response headers | DIY | ✅ Automatic |
| Context storage | DIY | ✅ Pluggable |
| Logging integration | DIY | ✅ Included |
| Type safety | Maybe | ✅ Full |
| Tests | Maybe | ✅ 100% coverage |

### vs. Other Libraries

- **Zero dependencies** beyond FastAPI (optional extras available)
- **Pluggable adapters** - not locked to one context library
- **Validation utilities** - catch sync code issues early
- **Production-ready formatters** - JSON and local dev support

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

```bash
# Clone the repo
git clone https://github.com/ADR-007/fastapi-request-context.git
cd fastapi-request-context

# Install dependencies
uv sync --all-extras

# Run tests
make test

# Run linting
make lint

# Fix linting issues
make lint-fix
```

## License

MIT License - see [LICENSE](LICENSE) for details.
