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
Click it to expand the full panel:
Install
pip install pagebar # core only
pip install pagebar[sqlalchemy] # with SQL query counter
pip install pagebar[flask] # Flask (WSGI) integration
Use
Starlette / FastAPI
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(...):
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.
Flask (WSGI)
Flask isn't ASGI, so there's no middleware — the pagebar[flask] extra ships a small extension that wires the same data collection into Flask's request lifecycle:
from flask import Flask
from pagebar.flask import Pagebar
app = Flask(__name__)
Pagebar(app, package="my-app", unsafe=app.debug)
Same knobs as PagebarMiddleware. pagebar_html is registered as a Jinja global, so the template usage below works unchanged. enabled/unsafe callables receive the Flask request (the ASGI versions get the connection scope).
Template
In your base template, anywhere inside <body>:
{{ 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
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 panel gains a SQL summary with a SQL details button. That opens a full-screen modal with two sections: a grouped N+1 report (each unique statement with its repeat count and total time, repeats flagged in red — the panel also shows how many queries were redundant), and the full list of every query in order with bound parameters and per-statement timing. The modal text is selectable, and a Copy report button 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
A red UNSAFE badge in the pill makes the mode obvious.
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:
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>:
{{ 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.
Metadata
Release files for pagebar 0.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pagebar-0.1.6.tar.gz | 12.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pagebar-0.1.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.1 kB
Release files / pagebar-0.1.6.tar.gz
| Download URL | pagebar-0.1.6.tar.gz |
|---|---|
| Size | 12.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4f88a79ce8b52e514485839f4e1b2fc68cba16fd40df436b30ba4e90d3f9f312
|
|
BLAKE2b-256 checksum How to use checksums |
394318069b5a4acd962dead49dc5eba4be2d52cb8ec9b6a8e7af75482bf25a65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / pagebar-0.1.6-py3-none-any.whl
| Download URL | pagebar-0.1.6-py3-none-any.whl |
|---|---|
| Size | 13.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e9e86ea9f4de390d315ac98c852041a60a6e462953a49a80386d1f49db2bbaa1
|
|
BLAKE2b-256 checksum How to use checksums |
c70ad66e194ae837e1b877b3da3d70315c4739bc1eae9803df96afb177fe2f66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|