Skip to main content

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

pagebar's footer pill

Click it to expand the full panel:

The expanded pagebar 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

The expanded panel in unsafe mode, with the SQL summary and details button

The unsafe-mode SQL modal: grouped N+1 report above a collapsible full-query list

A red UNSAFE badge in the pill makes the mode obvious.

The pill in unsafe mode, with a red UNSAFE badge

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)

Source distribution for pagebar 0.1.6
File Size Uploaded
pagebar-0.1.6.tar.gz 12.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pagebar 0.1.6
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

0.1.6 This release

2 release files

0.1.5

2 release files

0.1.4

1 release file

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page