Metadata-Version: 2.4
Name: watchforge-sdk
Version: 0.1.7
Summary: WatchForge Python SDK
Author-email: WatchForge <support@watchforge.io>
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Dynamic: license-file

# WatchForge Python SDK (`watchforge-sdk`)

Errors, traces, structured logs, releases, and cron monitors for Python apps.

## Install

```bash
pip install watchforge-sdk
```

Pin a version (recommended for production):

```bash
pip install watchforge-sdk==0.1.7
```

---

## Setup by framework

Pick the section that matches your app.

### Plain Python

Use this for scripts, workers, CLIs, and any app without a web framework.

```python
from watchforge_sdk import register, capture_exception, capture_message
import logging

register(
    endpoint="https://PUBLIC_KEY@HOST/PROJECT_ID",  # your project DSN
    app_env="production",
    release="my-app@1.0.0",
    traces_sample_rate=1.0,
    enable_logs=True,
)

logging.info("app started")  # auto-captured when logging capture is on

try:
    risky()
except Exception as e:
    capture_exception(e)  # handled errors
    raise

capture_message("job finished", level="info")
```

Unhandled exceptions are captured automatically after `register()`.

---

### Django (including Django REST Framework)

Best path for Django / DRF: enable `DjangoHandler` so view/DRF exceptions, request context, SQL breadcrumbs, and request traces are wired for you.

**`settings.py` (or app startup):**

```python
from watchforge_sdk import register
from watchforge_sdk.handlers.django import DjangoHandler

register(
    endpoint="https://PUBLIC_KEY@HOST/PROJECT_ID",
    app_env="production",
    release="my-api@1.0.0",
    project_root=str(BASE_DIR),  # accurate stack file paths
    traces_sample_rate=1.0,
    enable_logs=True,
    integrations=[DjangoHandler()],
)
```

Optional: add middleware for request tracing (if not already installed by the handler):

```python
MIDDLEWARE = [
    "watchforge_sdk.handlers.django.WatchForgeMiddleware",
    # ... your middleware
]
```

**What you get automatically**

| Capture | Django / DRF |
| --- | --- |
| Unhandled view exceptions | Yes |
| DRF / database errors | Yes |
| Request URL, method, user | Yes |
| SQL / cache / Redis breadcrumbs | Yes (when hooks install) |
| Request traces | Yes (with middleware / `auto_trace`) |

You usually do **not** need manual `capture_exception` in views. For handled cases:

```python
from watchforge_sdk import capture_exception

try:
    do_payment()
except PaymentError as e:
    capture_exception(e)
```

---

### Flask

There is no Flask plugin yet. Register once, then hook request context + errors:

```python
from flask import Flask, request, g
from watchforge_sdk import register, Context, capture_exception

app = Flask(__name__)

register(
    endpoint="https://PUBLIC_KEY@HOST/PROJECT_ID",
    app_env="production",
    release="flask-api@1.0.0",
    traces_sample_rate=1.0,
    enable_logs=True,
)

@app.before_request
def watchforge_before_request():
    Context.set_request(
        method=request.method,
        url=request.url,
        headers=dict(request.headers),
        query_string=request.query_string.decode(errors="ignore"),
    )
    user = getattr(g, "user", None)
    if user is not None:
        Context.set_user(
            user_id=str(getattr(user, "id", "")),
            username=getattr(user, "username", None),
            email=getattr(user, "email", None),
        )

@app.errorhandler(Exception)
def watchforge_errorhandler(e):
    capture_exception(e, mechanism={"type": "flask", "handled": False})
    raise
```

---

### FastAPI

Same idea as Flask — register once, then capture in middleware:

```python
from fastapi import FastAPI, Request
from watchforge_sdk import register, Context, capture_exception

app = FastAPI()

register(
    endpoint="https://PUBLIC_KEY@HOST/PROJECT_ID",
    app_env="production",
    release="fastapi-api@1.0.0",
    traces_sample_rate=1.0,
    enable_logs=True,
)

@app.middleware("http")
async def watchforge_middleware(request: Request, call_next):
    Context.set_request(
        method=request.method,
        url=str(request.url),
        headers=dict(request.headers),
        query_string=str(request.query_params),
    )
    try:
        return await call_next(request)
    except Exception as e:
        capture_exception(e, mechanism={"type": "fastapi", "handled": False})
        raise
```

---

## Feature matrix

| Feature | Supported | Notes |
| --- | --- | --- |
| **Errors** | Yes | Auto unhandled + `capture_exception` / `capture_message` |
| **Traces** | Yes | Django integration + `start_transaction`; `traces_sample_rate` **0.0–1.0** |
| **Session Replay** | No | Browser SDK only |
| **Logs** | Yes | `enable_logs` + stdlib `logging` + `capture_log` |
| **Releases** | Yes | Set `release=` at `register()` |
| **Cron monitors** | Yes | `capture_checkin` (`in_progress` → `ok` / `error`) |

---

## Errors (all Python apps)

```python
from watchforge_sdk import capture_exception, capture_message

try:
    process()
except Exception as e:
    capture_exception(e)

capture_message("payment processed", level="info")
```

## Traces (manual)

Prefer Django integration for HTTP. For custom work:

```python
from watchforge_sdk import start_transaction, finish_transaction

txn = start_transaction("/api/orders", "Create Order", op="http.server")
span = txn.start_span("db.query", "INSERT orders")
span.finish()
finish_transaction("ok")
```

## Logs

```python
from watchforge_sdk import capture_log, flush_logs
import logging

logging.info("hello")  # auto when enable_logs + auto_capture_logging

capture_log("order created", level="info", attributes={"order_id": "123"})
flush_logs()
```

## Releases

Set once at register:

```python
register(..., release="my-api@1.2.3")
```

## Cron monitors

```python
from watchforge_sdk import capture_checkin

monitor_config = {
    "schedule": {"type": "crontab", "value": "0 * * * *"},
    "timezone": "UTC",
    "check_margin": 5,
    "max_runtime": 30,
}

check_in_id = capture_checkin(
    "nightly-job",
    status="in_progress",
    monitor_config=monitor_config,
)
# ... run job ...
capture_checkin("nightly-job", status="ok", check_in_id=check_in_id)
```

## Config options

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `endpoint` | str | required | Project DSN |
| `app_env` | str | `"production"` | Environment name |
| `release` | str | `None` | Version for Releases |
| `project_root` | str | cwd / Django `BASE_DIR` | Better stack paths + in-app frames |
| `enable_logs` | bool | `True` | Structured logs |
| `auto_capture_logging` | bool | `True` | Capture stdlib logging |
| `auto_capture_exceptions` | bool | `True` | Capture unhandled exceptions |
| `traces_sample_rate` | float | `1.0` | Trace sample rate **0.0–1.0** |
| `integrations` | list | `None` | e.g. `[DjangoHandler()]` |
| `debug` | bool | `False` | Print events before send |

## License

MIT
