Reference¶
Public surface¶
Two names, both importable from pagebar:
Plus pagebar.flask.Pagebar if you installed the [flask] extra.
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 — the fixed field set is the security model, so adding a field is a security review, not a feature.
The SQL counter needs the [sqlalchemy] extra. Without SQLAlchemy installed, the listener registration is a silent no-op and the count stays at 0.
Knobs¶
PagebarMiddleware(
app,
package="", # 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=0, # >0 → log a WARNING when SQL count exceeds this
)
All keyword-only. pagebar.flask.Pagebar(app, ...) takes the same set, with enabled / unsafe callables receiving the Flask request instead of the ASGI scope.
PagebarMiddleware.bound(**kwargs)¶
Returns a thin subclass with the kwargs baked into __init__, for frameworks that instantiate middleware as cls(app). See Usage.
pagebar_html(*, nonce="")¶
Renders the footer for the current request. Returns HTML as str — mark it safe in your template. nonce is threaded into the inline <style> and <script> for CSP.
Unsafe mode¶
In dev, you usually want to see the SQL text. Flip unsafe=True and the panel gains a SQL summary — total, unique, and how many queries were redundant — plus a SQL details button:

A red UNSAFE badge in the pill makes the mode obvious even when the panel is closed:

The SQL modal¶
SQL details opens a full-screen modal. Up top, the grouped N+1 report: one row per unique statement with its repeat count and total time, repeats flagged in red.

Below it, All queries — collapsed by default — lists every statement in execution order with its bound parameters and per-statement timing. Statements are never truncated.

The modal text is selectable, and Copy report puts a self-contained N+1 summary on the clipboard — request line, totals, and each unique statement with its call count — ready to paste into a bug report:
pagebar SQL report — GET /widgets
21 queries, 2 unique, 19 redundant (possible N+1)
20× 0.0ms SELECT name FROM widget WHERE id = ?
1× 0.1ms SELECT id FROM widget
Turning it on¶
import os
PagebarMiddleware(app, package="my-app", unsafe=bool(os.getenv("DEBUG")))
# or framework-driven
PagebarMiddleware(app, package="my-app", unsafe=app.debug)
unsafe comes from deploy config only
It 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, and there is deliberately no runtime backchannel to flip it.
How it works¶
Two halves, decoupled by ContextVars. The middleware can't hand data to the template helper directly — they sit at opposite ends of the request — so per-request state (start time, query log, method, path, version, unsafe flag) goes through module-level context variables. PagebarMiddleware.__call__ writes them at request start; pagebar_html() reads them when the template renders.
ASGI tasks get a fresh context per request. Flask's WSGI worker threads don't, so the extension resets the state in teardown_request — that reset is load-bearing: without it one request's data could surface on the next request served by the same thread.