Metadata-Version: 2.4
Name: notiformer
Version: 1.0.0
Summary: Official Python SDK for Notiformer — human-in-the-loop approval gates and push notifications for AI agents.
Author-email: Notiformer <hello@notiformer.com>
License: MIT
Project-URL: Homepage, https://notiformer.com
Project-URL: Documentation, https://notiformer.com/docs
Project-URL: Repository, https://github.com/notiformer/notiformer-python
Project-URL: Issues, https://github.com/notiformer/notiformer-python/issues
Keywords: notiformer,human-in-the-loop,ai-agents,approval,notifications,llm
Classifier: Development Status :: 4 - Beta
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.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 :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# notiformer (Python)

Official Python SDK for [Notiformer](https://notiformer.com) — approval
gates, multi-option decisions, real-time push notifications, and feature
gates for AI agents. Mirrors the [Node.js SDK](https://www.npmjs.com/package/notiformer)
1:1: same methods, same config, same REST API underneath.

## Install

```bash
pip install notiformer
```

Requires Python 3.8+.

## Get started

**1. Create a free account at [app.notiformer.com](https://app.notiformer.com)** — no
credit card required for the Dev plan. Verify your email before creating a
project or using the API.

**2. Create a project and copy your API key** (`ntf_live_...`).

**3. Quick start:**

```python
from notiformer import Notiformer

n = Notiformer("ntf_live_...")

# 🛑 Pause and wait for Approve / Deny — always set fallback or handle the raise
try:
    result = n.ask(
        "Deploy v2 to production?",
        context="Build #442 · 3 services affected",
        timeout=300,
        fallback="deny",  # omit and a timeout raises NotiformerError
    )
    if result["approved"]:
        deploy()
    else:
        print("Timed out — auto-denied" if result["timed_out"] else "Denied")
except NotiformerError as err:
    if err.code == "timeout":
        print("No response. Respond via the app, Telegram, or Slack.")

# 🔔 Fire-and-forget alert
n.event("agents", "task_complete")
```

Use `n = Notiformer("ntf_live_test")` to try the SDK without a real key —
calls are skipped locally and safe defaults are returned (a one-time setup
notice is printed).

## Config

```python
Notiformer(
    api_key,
    *,
    silent=False,          # skip all calls locally, return safe defaults — good for local/dev/test
    throw_on_error=True,   # False: event()/gate() failures return a default instead of raising
    on_error=None,         # callback: fn(NotiformerError) -> None, called on every failure
)
```

> **`ask()`/`select()` always raise `NotiformerError(code="timeout")`** when
> nobody responds and no `fallback` was set — this is unconditional and
> ignores `throw_on_error=False`. Set a `fallback`, or wrap the call in
> `try`/`except` and handle it explicitly.

## API

### `n.event(channel, event, *, description=None, icon=None, tags=None, value=None, notify=True, recipients=None)`

Fire-and-forget. Returns `{"id", "createdAt", "rateLimited"?, "usageMicroUsd"?}`,
or `None` if the placeholder key or `silent` is used, or the call failed
and `throw_on_error=False`.

### `n.ask(message, *, timeout=300, fallback=None, context=None, details=None)`

Approval gate. Posts the request, then polls every 2s until it resolves.
Returns `{"approved", "timed_out", "responded_at"}`.

### `n.select(message, options, *, timeout=300, fallback=None, context=None, details=None)`

Like `ask()`, but the user picks one of 2–6 custom options instead of
Approve/Deny. Each option: `{"value", "label", "isDestructive"?}` — use the
`select_option()` helper:

```python
from notiformer import select_option
select_option("stop", "🛑 Stop the pipeline", is_destructive=True)
```

Returns `{"selected", "timed_out", "responded_at"}`.

### `n.gate(key, *, fallback=False, cache_ttl=0)`

Boolean feature-gate check. **Never raises** — always falls back on any
error. `cache_ttl` (seconds) enables a local in-memory cache on this client
instance only; there's no server-side caching.

### `n.gate_details(key, *, fallback=False, cache_ttl=0)`

Same as `gate()`, returns `{"key", "enabled", "cached"}`.

### `n.clear_gate_cache(key=None)`

Clears the local gate cache for one key, or all of them if omitted.

## Errors

API and timeout failures raise `notiformer.NotiformerError`:

```python
from notiformer import NotiformerError

try:
    n.ask("Deploy?", timeout=60)  # no fallback → raises on timeout
except NotiformerError as e:
    print(e.code)  # "timeout", "invalid_api_key", "rate_limited", "cap_reached", ...
    raise
```

`.code` is one of: `invalid_api_key`, `card_required`, `card_locked`,
`cap_reached`, `feature_not_available`, `validation`, `rate_limited`,
`network`, `internal`, `timeout`. On `cap_reached`, `.cycle_resets_at`,
`.manage_url`, and `.upgrade_url` may also be set.

Request-shape mistakes (missing `message`, wrong number of `options`, a
`fallback` that doesn't match any option, etc.) raise a plain `ValueError`
instead — these are always raised, regardless of `throw_on_error`.

## Links

- [Documentation](https://notiformer.com/docs)
- [Dashboard](https://app.notiformer.com)
- [Node.js SDK](https://www.npmjs.com/package/notiformer)
- Support: hello@notiformer.com

## License

MIT
