Skip to main content

webwrench

CI PyPI Python License

A Python library for building interactive web dashboards and self-contained HTML reports. It uses bitwrench.js for rendering and the bwserve protocol (SSE down, POST back) for live updates. No JavaScript required on your end, no build tools, no runtime dependencies.

  • Script mode for quick dashboards, app mode with decorator-based routing for multi-page apps
  • Static HTML export -- same API produces a single self-contained file you can open offline or email
  • Chart.js included -- bar, line, pie, scatter, radar, and more without extra installs
  • Callback-driven updates -- define the UI once, update individual elements via on_change/on_click
  • Zero runtime dependencies -- pip install webwrench pulls nothing else in

Installation

pip install webwrench

Requires Python 3.10+.

Quick Start

Script Mode

import webwrench as ww

ww.title("Hello World")
ww.text("My first webwrench app")
ww.serve()

Interactive Dashboard

import webwrench as ww

data = [12, 19, 3, 5, 2, 3]

ww.title("Sales Dashboard")
chart = ww.chart(data, type='bar', labels=['Jan','Feb','Mar','Apr','May','Jun'])
slider = ww.slider("Multiplier", min=1, max=10, value=1)

@slider.on_change
def update(value):
    chart.update([d * value for d in data])

ww.serve()

Static HTML Report

import webwrench as ww

ww.title("Quarterly Report")
ww.chart([45, 67, 89, 34], type='line', labels=['Q1','Q2','Q3','Q4'])
ww.text("Revenue grew 48% year-over-year.")
ww.export('quarterly-report.html')

The exported file is self-contained -- open it in any browser, no server needed. Charts stay interactive (tooltips, hover, legend toggling).

Multi-Page App

import webwrench as ww

app = ww.App()

@app.page('/')
def home(ctx):
    ctx.title("Dashboard")
    ctx.chart([10, 20, 30], type='bar', labels=['A', 'B', 'C'])

@app.page('/settings')
def settings(ctx):
    ctx.title("Settings")
    theme = ctx.select("Theme", ['light', 'dark', 'ocean'])

    @theme.on_change
    def switch(value):
        ctx.set_theme(value)

app.serve(port=6502)

API Reference

Display Elements

ww.title(text)                          # <h1>
ww.heading(text, level=2)               # <h2>..<h6>
ww.text(text)                           # <p>
ww.markdown(md_string)                  # Rendered markdown
ww.code(code_string, lang='python')     # Syntax-highlighted code block
ww.html(raw_html, raw=False)            # HTML content (escaped by default)
ww.image(src, alt='', width=None)       # Image
ww.divider()                            # <hr>
ww.table(data, sortable=False, searchable=False, paginate=None)
ww.metric(label, value, delta=None, delta_color=None)
ww.json(data, collapsed=1)              # Collapsible JSON viewer
ww.progress(value=0, max_val=100)       # Progress bar
ww.toast(message, type='info', duration=3000)

Input Widgets

All widgets return a handle with .value and .on_change(callback).

ww.button(label, on_click=None)
ww.input(label, placeholder='', value='')
ww.textarea(label, rows=4)
ww.slider(label, min=0, max=100, value=50, step=1)
ww.select(label, options=['a','b','c'], value='a')
ww.checkbox(label, value=False)
ww.radio(label, options=['x','y','z'])
ww.file_upload(label, accept='.csv,.json')
ww.date_picker(label)
ww.color_picker(label, value='#3366cc')
ww.number(label, min=0, max=100, step=1, value=0)

Charts

# Simple chart
ww.chart([12, 19, 3], type='bar', labels=['A','B','C'])

# Multi-dataset
ww.chart(datasets=[
    {'label': 'Sales', 'data': [12, 19, 3], 'color': '#3366cc'},
    {'label': 'Returns', 'data': [2, 3, 1], 'color': '#cc3333'}
], type='line', labels=['Jan','Feb','Mar'])

# From pandas DataFrame
ww.plot(df, x='date', y='revenue', type='line')

# Supported types: bar, line, pie, doughnut, radar, polarArea, scatter, bubble

Layout

with ww.columns(3) as cols:
    with cols[0]: ww.text("Left")
    with cols[1]: ww.chart(data1)
    with cols[2]: ww.chart(data2)

with ww.tabs(['Overview', 'Details']) as t:
    with t[0]: ww.title("Overview")
    with t[1]: ww.table(detail_data)

with ww.accordion("Advanced Options", open=False):
    ww.slider("Threshold", min=0, max=100, value=50)

with ww.card(title="Revenue"):
    ww.chart(revenue_data, type='line')

with ww.sidebar():
    ww.nav([{'text': 'Home', 'href': '/'}])

Theming

ww.theme('dark')                        # Built-in: light, dark, ocean, forest
ww.theme(primary='#006666')             # Custom palette
ww.toggle_theme()                       # Toggle light/dark
ww.css({'.my-card': {'border-radius': '12px'}})  # Custom CSS

Export

ww.export('report.html')                # Self-contained HTML file
ww.export('report.html', minify=True)   # Minified output
ww.screenshot('dashboard.png')          # Screenshot via html2canvas
ww.download('data.csv', content=csv_string)  # Trigger browser download

How It Works

 Python (webwrench)                    Browser
+------------------------+           +-------------------------+
| Your Python script     |           | bitwrench.js (bundled)  |
|   |                    |           |   +-- Chart.js           |
|   v                    |  SSE -->  |   +-- bwclient.js        |
| webwrench server       | -------> |                          |
|  (asyncio, built-in)   | <------- |                          |
|                        |  POST <-- |                          |
+------------------------+           +-------------------------+
  • Frontend: bitwrench.js handles DOM operations. webwrench generates TACO (Tag, Attributes, Content, Options) dicts that bitwrench renders.
  • Backend: Pure Python asyncio HTTP server. No Flask, no FastAPI, no external dependencies.
  • Protocol: bwserve -- SSE for server-to-client updates, POST for client-to-server actions.
  • Updates: Only the changed element is patched, not the whole page.

Development

git clone https://github.com/deftio/webwrench.git
cd webwrench
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage (100% required)
pytest --cov=webwrench --cov-report=term-missing --cov-fail-under=100

# Lint + security scan
ruff check webwrench/
bandit -r webwrench/ -c pyproject.toml

Releasing

Direct from main (quick):

./scripts/release.sh 0.2.0

This bumps the version, runs lint + tests + build, tags, and pushes.

Two-phase (for bigger releases):

# Phase 1: bump version + create release branch
./scripts/start-release.sh 0.2.0

# ... develop, commit, iterate ...

# Phase 2: validate, test, build, squash-merge to main, tag, push
./scripts/release.sh

After either flow, create a GitHub Release to publish to PyPI:

gh release create v0.2.0 --title "webwrench v0.2.0" --generate-notes

License

BSD-2-Clause. See LICENSE.txt.

Release files for webwrench 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for webwrench 0.1.2
File Size Uploaded
webwrench-0.1.2.tar.gz 251.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for webwrench 0.1.2
File Interpreter ABI Platform
webwrench-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 333.3 kB

Release files / webwrench-0.1.2.tar.gz

Download URL webwrench-0.1.2.tar.gz
Size 251.4 kB
Tags Source
SHA-256 checksum
How to use checksums
27782af7ce79b4df0dc12064df47d988878bf89db3cea1fc8a82b64e3fc6d1cd
BLAKE2b-256 checksum
How to use checksums
bdd73ab7f9774cd9bc6efead1bd6d5455eef5624a2f1c7730ab5ed249ac48c0a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 29, 2026.

Transparency log

Release files / webwrench-0.1.2-py3-none-any.whl

Download URL webwrench-0.1.2-py3-none-any.whl
Size 82.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c652928ecdd8e3e9d6fc52be6fdd8797a8c35c504e1bb17e32f4209596553823
BLAKE2b-256 checksum
How to use checksums
4a0b299464667acd18b61ccfeff8ef88a2c13b1f8d5e044ae75058ebb07d9a93
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

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