Metadata-Version: 2.4
Name: northbytes-status
Version: 1.0.1
Summary: Report the things Fly cannot see from outside the container. Two lines per project.
License-Expression: MIT
Project-URL: Homepage, https://github.com/northbytes/status
Project-URL: Source, https://github.com/northbytes/status/tree/main/packages/status-python
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# northbytes-status

The Python twin of `@northbytes/status`. Same contract, same envelope, same
rails, so the collector cannot tell which language a project is written in — and
neither can you, reading the document at 3am.

Built for `lotw` (Flask + SQLite on a volume) and `forged-backend`.

```bash
pip install northbytes-status
```

```python
from northbytes_status import status_bp, Check

app.register_blueprint(status_bp({
    "project": "lotw",
    "checks": {
        # lotw already HAS this function — it is what /healthz calls. A check is
        # just a callable that raises if the thing is broken, so it needs no
        # rewriting to become one.
        "db": Check(lambda: db.session.execute(text("select 1")), critical=True),
    },
    "metrics": {"listings": lambda: Listing.query.count()},
}))
```

Then flip one field on the target in the portal's `status.config.ts`:

```ts
{ project: "lotw", flyApp: "lotw", url: "https://lotw.uk",
  volume: "lotw_data", agent: "/_status" },
```

## And a real Fly health check, free

```toml
[[http_service.checks]]
  grace_period = "10s"
  interval     = "30s"
  method       = "get"
  path         = "/_status?mode=public"
  timeout      = "5s"
```

`?mode=public` returns `{"ok": true}` and nothing else — no token, no version, no
metrics. `ok` reflects **critical checks only**: a degraded cache is not "down",
and a machine must never leave rotation because a business metric timed out.

## Rails

- **Checks run concurrently against one deadline** — 2s each, 5s for the
  document. Running them one at a time would make five checks a ten-second
  response without any single check misbehaving.
- **Business metrics cached separately**, 60s by default. A failed metric keeps
  its last value: a gap reads as zero on a chart, and zero is a number somebody
  will act on.
- **Authenticated, never public** except `?mode=public`. `STATUS_TOKEN`,
  compared with `hmac.compare_digest`. No token set means the full document is
  refused, not served.
- **Failure is never fatal.** A raising check is a recorded failure, not a 500.

## Not Flask

| | |
|---|---|
| Flask / Quart | `app.register_blueprint(status_bp(cfg))` |
| Any WSGI app | `app = status_wsgi(cfg)(app)` |
| No inbound HTTP | `start_status_push(cfg)` — same document, arrow reversed |

## One known ceiling

Python cannot kill a thread, so a permanently hung check permanently consumes
one of eight pool workers. Eight simultaneously wedged checks would starve the
pool and every check would then report a timeout — loudly and correctly, but
without detail. Raise `max_workers` if a project ever grows more than a handful
of checks.
