Metadata-Version: 2.4
Name: pagebar
Version: 0.1.0
Summary: A tiny prod-safe perf footer & toolbar for ASGI apps.
Author-email: Stéfane Fermigier <sf@fermigier.com>
License: MIT
License-File: LICENSE
Keywords: asgi,footer,monitoring,performance,sql
Classifier: Framework :: AsyncIO
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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 :: Internet :: WWW/HTTP :: WSGI :: Middleware
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Provides-Extra: sqlalchemy
Requires-Dist: sqlalchemy>=2.0; extra == 'sqlalchemy'
Description-Content-Type: text/markdown

# pagebar

A tiny prod-safe perf footer for ASGI apps. One line at the bottom of every page, click to expand. No SQL text, no env vars, no stack traces — nothing that could leak.

```
v2026.6.19 · 22ms · 4 SQL · GET /editeurs · 200
```

## Install

```sh
pip install pagebar              # core only
pip install pagebar[sqlalchemy]  # with SQL query counter
```

## Use

### Starlette / FastAPI

```python
from pagebar import PagebarMiddleware, pagebar_html
from starlette.applications import Starlette
from starlette.middleware import Middleware

app = Starlette(
    middleware=[Middleware(PagebarMiddleware, package="my-app")],
    routes=[...],
)
```

### Litestar (and other `mw_cls(app)`-style frameworks)

Litestar instantiates middleware as `cls(app)` with no further kwargs, so config has to be baked in. Use `PagebarMiddleware.bound(...)`:

```python
from litestar import Litestar
from pagebar import PagebarMiddleware

app = Litestar(
    middleware=[
        PagebarMiddleware.bound(package="my-app", unsafe=False),
    ],
    route_handlers=[...],
)
```

`.bound(**kwargs)` returns a thin subclass with the kwargs baked into `__init__`. Same fields as the regular constructor.

### Template

In your base template, anywhere inside `<body>`:

```html
{{ pagebar_html() | safe }}
</body>
```

That's it. One name for the middleware, one for the helper.

## What's on screen

| Field   | Source                                                            |
|---------|-------------------------------------------------------------------|
| Version | `importlib.metadata.version(package)` — or explicit `version=`    |
| Time    | `time.perf_counter()` delta                                       |
| SQL     | SQLAlchemy `before_cursor_execute` listener (if installed)        |
| Request | method + path                                                     |
| Memory  | `resource.getrusage().ru_maxrss` — RSS in MiB                     |
| Uptime  | `time.monotonic()` since worker import                            |
| Threads | `threading.active_count()`                                        |
| GC      | `gc.get_count()` — gen0/gen1/gen2 pending                         |
| Python  | `sys.version_info`                                                |
| PID     | `os.getpid()`                                                     |

No SQL text. No params. No env. No traces. That's the whole surface.

## Knobs

```python
PagebarMiddleware(
    app,
    package="my-app",     # distribution name (PyPI / pyproject.toml)
    version="",           # escape hatch: bypass importlib.metadata lookup
    enabled=True,         # bool or callable(scope) -> bool
    unsafe=False,         # bool or callable(scope) -> bool — see below
    query_budget=20,      # >0 → log a WARNING when SQL count exceeds this
)
```

## Unsafe mode

In dev, you usually *want* to see the SQL text. Flip `unsafe=True` and the bar adds, per request: each statement (truncated), bound parameters, and per-statement timing. A red `UNSAFE` badge in the pill makes the mode obvious.

```python
import os
PagebarMiddleware(app, package="my-app", unsafe=bool(os.getenv("DEBUG")))
# or framework-driven
PagebarMiddleware(app, package="my-app", unsafe=app.debug)
```

`unsafe` defaults to `False` and **only** takes its value from constructor wiring — no URL parameter, no header, no cookie. The host's deploy config is the authority.

`enabled` lets you hide the bar from JSON endpoints, healthchecks, or admin routes:

```python
def show(scope):
    return not scope["path"].startswith(("/api/", "/health"))

Middleware(PagebarMiddleware, package="my-app", enabled=show)
```

Bots are skipped automatically (`User-Agent` matching `bot|crawler|spider|googlebot|bingbot`).

## CSP

`pagebar_html()` accepts a `nonce` keyword that's applied to the inline `<style>` and `<script>`:

```html
{{ pagebar_html(nonce=csp_nonce) | safe }}
```

Otherwise the host needs `'unsafe-inline'` for both directives. No external assets, no third-party requests.

## Why not the Django/Flask/Litestar debug toolbars?

They reveal SQL text, environment, settings, request bodies, stack traces — fine in dev, unacceptable in production. pagebar surfaces a fixed, public-safe set of fields. The shape is the security model.

## Non-goals

No panels system, no plugins, no per-framework adapters, no APM, no time series, no SQL EXPLAIN, no profiler. Pure ASGI middleware + one helper function in one file.

## License

MIT.
