A tiny prod-safe perf footer & toolbar for ASGI apps.
Project description
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
pip install pagebar # core only
pip install pagebar[sqlalchemy] # with SQL query counter
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.
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 bar adds, per request: each statement (truncated), bound parameters, and per-statement timing. 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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pagebar-0.1.0.tar.gz.
File metadata
- Download URL: pagebar-0.1.0.tar.gz
- Upload date:
- Size: 132.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5523c9ec275688be78ae6899ae9bd6bf875795e1162dc36663e53361d20d27f2
|
|
| MD5 |
7613bf480d8b848c69e6c09713223021
|
|
| BLAKE2b-256 |
2000f24aa88887909d05c22e3928145c068140926b55a98cbdb544a0311daa42
|
File details
Details for the file pagebar-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pagebar-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
687b55a0550551d4d571be1e9320b0f0614ddb9110d881da3f14cbcebad99993
|
|
| MD5 |
72facf098fdd50c40ff8fe4290682d57
|
|
| BLAKE2b-256 |
bf7a418b5ebf5cf578aeed22642adfc73c98c1d1a44c7ba5cc332a28d9420702
|