Metadata-Version: 2.4
Name: labrador-sdk-gmi
Version: 0.1.0
Summary: Drop-in activity logging SDK for Python applications — ships logs to a Labrador server.
License: MIT
Keywords: activity,labrador,logging,observability
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Logging
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: requests>=2.28.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: responses>=0.25; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# Labrador SDK for Python

A lightweight, non-intrusive activity logging SDK. Initialize once, capture events anywhere — logs are buffered and shipped to your Labrador server in the background.

## Installation

```bash
# Local development
pip install -e ./labrador-sdk

# From PyPI (after publishing)
pip install labrador-sdk
```

## Quick Start

```python
import labrador

# Call once at application startup
labrador.init(
    base_url="http://localhost:8080",
    user_id="user-uuid-here",
)

# Capture events anywhere — non-blocking
labrador.capture("user uploaded a file")
labrador.capture("payment processed successfully")

# Block until all buffered events are shipped (optional)
labrador.flush()
```

## Standard Logging Integration

Route Python's built-in `logging` module into Labrador:

```python
import logging
import labrador

labrador.init("http://localhost:8080", user_id="service-worker")

# All WARNING+ records from your app will be forwarded to Labrador
logging.getLogger("myapp").addHandler(labrador.get_handler(level=logging.WARNING))
```

## API Reference

### `labrador.init(base_url, user_id, *, batch_size=20, flush_interval=5.0, max_retries=3, timeout=10.0)`

Initialise the SDK. Must be called before any other function. Safe to call again — any previous client is shut down first.

| Parameter | Type | Default | Description |
|---|---|---|---|
| `base_url` | `str` | — | Root URL of your Labrador server |
| `user_id` | `str` | — | Identifier attached to every log entry |
| `batch_size` | `int` | `20` | Maximum entries per HTTP request |
| `flush_interval` | `float` | `5.0` | Seconds between automatic background flushes |
| `max_retries` | `int` | `3` | Retry attempts on 5xx / timeout errors |
| `timeout` | `float` | `10.0` | HTTP request timeout in seconds |

### `labrador.capture(message: str) → None`

Push a log entry. **Non-blocking**. Safe to call from any thread. Silently no-ops if `init()` was not called.

### `labrador.flush(timeout: float = 5.0) → None`

Block until all buffered entries are shipped or `timeout` elapses.

### `labrador.shutdown(timeout: float = 5.0) → None`

Drain remaining entries, close the HTTP session, and stop the background worker. Called automatically via `atexit`.

### `labrador.get_handler(level: int = logging.WARNING) → logging.Handler`

Return a `logging.Handler` that forwards records to Labrador. Raises `RuntimeError` if `init()` was not called.

## How It Works

```
capture("msg")  →  LogBuffer (deque, maxlen=500)
                         ↓
BackgroundWorker (daemon thread, wakes every flush_interval)
                         ↓
              drain up to batch_size entries
                         ↓
HttpTransport  →  POST {base_url}/submit-logs
                         ↓
               202 ✓  |  5xx → retry with backoff  |  4xx → drop
```

- Buffer drops the **oldest** entry when full — `capture()` never blocks the caller.
- An `atexit` hook automatically flushes on clean process exit.
- The worker thread is a daemon — it exits silently if the process is killed.

## Server Contract

The SDK sends:

```
POST /submit-logs
Content-Type: application/json

{
  "logs": [
    {
      "raw_data": "user uploaded a file",
      "action_user_id": "user-uuid",
      "created_at": "2026-03-02T10:00:00+00:00"
    }
  ]
}
```

Expected response: `202 Accepted`.

## Running Tests

```bash
pip install -e ".[dev]"
pytest
```

## Dependencies

- **Runtime**: `requests >= 2.28.0`
- **Dev**: `pytest`, `pytest-mock`, `responses`
