Metadata-Version: 2.4
Name: hookwave
Version: 0.1.0
Summary: Official Python SDK for Hookwave — emit events into a Hookwave source. Batched, retried, fire-and-forget.
Project-URL: Homepage, https://docs.hookwave.dev
Project-URL: Documentation, https://docs.hookwave.dev
Project-URL: Repository, https://github.com/hookwave/sdk-python
Project-URL: Issues, https://github.com/hookwave/sdk-python/issues
Author: Hookwave
License: MIT
License-File: LICENSE
Keywords: events,hookwave,sdk,webhook,webhooks
Classifier: Development Status :: 4 - Beta
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 :: Software Development :: Libraries
Requires-Python: >=3.9
Requires-Dist: requests>=2.31
Provides-Extra: dev
Requires-Dist: pytest-mock>=3.12; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: responses>=0.25; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

# hookwave

Official Python SDK for [Hookwave](https://hookwave.dev) — emit events into a Hookwave source. Batched, retried, fire-and-forget.

```sh
pip install hookwave
```

## Quickstart

```python
import os
from hookwave import Hookwave

hw = Hookwave(source_key=os.environ["HOOKWAVE_SOURCE_KEY"])  # src_live_… or src_test_…

hw.emit("user.signed_up", {
    "user_id": "u_123",
    "email": "ruben@hookwave.dev",
    "plan": "pro",
})

# Before process exit (long-running workers, serverless handlers, etc.):
hw.shutdown()
```

Get a source key in the dashboard at [hookwave.dev/dashboard/sources](https://hookwave.dev/dashboard/sources), pick a source, click **Generate SDK key**.

## What the SDK does

- Buffers events and flushes in batches (default: every 1s or every 100 events).
- Retries with exponential backoff on network errors and 5xx responses.
- `idempotency_key` support so a retried batch doesn't double-fire.
- Background daemon thread for non-blocking emits.
- `atexit` auto-shutdown so short-lived scripts don't need explicit `shutdown()`.

Routing (destinations, templates, retry policy on the outbound side) lives in the Hookwave dashboard, **not** in the SDK. That's deliberate — you change a destination without redeploying code.

## API

### `Hookwave(...)`

```python
Hookwave(
    source_key="src_live_…",                    # required
    base_url="https://ingest.hookwave.dev",     # default
    flush_interval=1.0,                          # seconds, default
    max_batch_size=100,                          # events per batch
    max_retries=5,                               # per-batch
    request_timeout=30.0,                        # seconds
    on_error=None,                               # callable(exc, events)
    on_flush=None,                               # callable(count)
    on_before_emit=None,                         # callable(type, payload) -> payload | None
)
```

### `hw.emit(event_type, payload, options=None)`

Fire-and-forget. Returns `None`. Buffers the event for the next flush.

```python
from hookwave import EmitOptions

hw.emit(
    "order.created",
    {"order_id": "o_abc", "total": 4999},
    EmitOptions(
        idempotency_key="o_abc",
        occurred_at=datetime.utcnow(),
        metadata={"trace_id": "tr_xyz"},
        connection="ops-alerts",
        correlation_id="req_789",
    ),
)
```

### `hw.emit_sync(event_type, payload, options=None) -> EmitSyncResult`

Blocking variant — awaits delivery. Returns `EmitSyncResult(event_id, status)`. Raises on failure. Use sparingly.

```python
result = hw.emit_sync("payment.failed", {"order_id": "o_abc"})
```

### `hw.flush(timeout=None)`

Force the buffer to flush now. Blocks until empty or `timeout` seconds elapse.

### `hw.shutdown(timeout=30.0)`

Flush the buffer and stop the worker thread. Auto-called via `atexit` for short-lived scripts; call explicitly for deterministic behaviour in long-running or serverless workloads.

```python
import signal

def _on_sigterm(_signum, _frame):
    hw.shutdown()
    sys.exit(0)

signal.signal(signal.SIGTERM, _on_sigterm)
```

## Serverless usage

In AWS Lambda, Google Cloud Functions, etc., the runtime can freeze the process between invocations. Call `hw.shutdown()` at the end of your handler so the buffer flushes:

```python
def handler(event, _context):
    hw.emit("request.received", {"path": event["path"]})
    try:
        return do_work(event)
    finally:
        hw.shutdown()
```

Or use `emit_sync` so the call returns only after delivery succeeds.

## Security

- **Source keys are write-only.** They can only emit events into the source they're scoped to. If leaked, the blast radius is bounded by your per-source quota.
- Use `src_test_…` keys in development; events are tagged in the dashboard and excluded from billing.
- Never ship a source key in client-side code.

## Roadmap

- v0.2: AsyncIO variant (`hookwave.AsyncHookwave`), Django + Flask helpers.
- v0.3: Type-annotated event schemas via Pydantic.
- v1.0: Server-side schema registry + codegenerated event classes.

## License

MIT — see [LICENSE](./LICENSE).
