{"id":47704744,"url":"https://github.com/deftio/webwrench","last_synced_at":"2026-04-03T19:00:58.280Z","repository":{"id":347725242,"uuid":"1194230894","full_name":"deftio/webwrench","owner":"deftio","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-29T08:14:15.000Z","size":307,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-03T04:18:16.084Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/deftio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-28T04:30:42.000Z","updated_at":"2026-03-29T08:14:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/deftio/webwrench","commit_stats":null,"previous_names":["deftio/webwrench"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/deftio/webwrench","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2Fwebwrench","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2Fwebwrench/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2Fwebwrench/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2Fwebwrench/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/deftio","download_url":"https://codeload.github.com/deftio/webwrench/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2Fwebwrench/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31371632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T17:53:18.093Z","status":"ssl_error","status_checked_at":"2026-04-03T17:53:17.617Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-02T17:51:44.784Z","updated_at":"2026-04-03T19:00:58.273Z","avatar_url":"https://github.com/deftio.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# webwrench\n\n[![CI](https://github.com/deftio/webwrench/actions/workflows/ci.yml/badge.svg)](https://github.com/deftio/webwrench/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/webwrench.svg?style=flat-square)](https://pypi.org/project/webwrench/)\n[![Python](https://img.shields.io/pypi/pyversions/webwrench.svg?style=flat-square)](https://pypi.org/project/webwrench/)\n[![Downloads](https://img.shields.io/pypi/dm/webwrench.svg?style=flat-square)](https://pypi.org/project/webwrench/)\n[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](https://github.com/deftio/webwrench)\n[![License](https://img.shields.io/badge/License-BSD%202--Clause-blue.svg?style=flat-square)](https://opensource.org/licenses/BSD-2-Clause)\n\nA Python library for building interactive web dashboards and self-contained HTML reports. It uses [bitwrench.js](https://github.com/deftio/bitwrench) 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.\n\n- **Script mode** for quick dashboards, **app mode** with decorator-based routing for multi-page apps\n- **Static HTML export** -- same API produces a single self-contained file you can open offline or email\n- **Chart.js included** -- bar, line, pie, scatter, radar, and more without extra installs\n- **Callback-driven updates** -- define the UI once, update individual elements via `on_change`/`on_click`\n- **Zero runtime dependencies** -- `pip install webwrench` pulls nothing else in\n\n## Installation\n\n```bash\npip install webwrench\n```\n\nRequires Python 3.10+.\n\n## Quick Start\n\n### Script Mode\n\n```python\nimport webwrench as ww\n\nww.title(\"Hello World\")\nww.text(\"My first webwrench app\")\nww.serve()\n```\n\n### Interactive Dashboard\n\n```python\nimport webwrench as ww\n\ndata = [12, 19, 3, 5, 2, 3]\n\nww.title(\"Sales Dashboard\")\nchart = ww.chart(data, type='bar', labels=['Jan','Feb','Mar','Apr','May','Jun'])\nslider = ww.slider(\"Multiplier\", min=1, max=10, value=1)\n\n@slider.on_change\ndef update(value):\n    chart.update([d * value for d in data])\n\nww.serve()\n```\n\n### Static HTML Report\n\n```python\nimport webwrench as ww\n\nww.title(\"Quarterly Report\")\nww.chart([45, 67, 89, 34], type='line', labels=['Q1','Q2','Q3','Q4'])\nww.text(\"Revenue grew 48% year-over-year.\")\nww.export('quarterly-report.html')\n```\n\nThe exported file is self-contained -- open it in any browser, no server needed. Charts stay interactive (tooltips, hover, legend toggling).\n\n### Multi-Page App\n\n```python\nimport webwrench as ww\n\napp = ww.App()\n\n@app.page('/')\ndef home(ctx):\n    ctx.title(\"Dashboard\")\n    ctx.chart([10, 20, 30], type='bar', labels=['A', 'B', 'C'])\n\n@app.page('/settings')\ndef settings(ctx):\n    ctx.title(\"Settings\")\n    theme = ctx.select(\"Theme\", ['light', 'dark', 'ocean'])\n\n    @theme.on_change\n    def switch(value):\n        ctx.set_theme(value)\n\napp.serve(port=6502)\n```\n\n## API Reference\n\n### Display Elements\n\n```python\nww.title(text)                          # \u003ch1\u003e\nww.heading(text, level=2)               # \u003ch2\u003e..\u003ch6\u003e\nww.text(text)                           # \u003cp\u003e\nww.markdown(md_string)                  # Rendered markdown\nww.code(code_string, lang='python')     # Syntax-highlighted code block\nww.html(raw_html, raw=False)            # HTML content (escaped by default)\nww.image(src, alt='', width=None)       # Image\nww.divider()                            # \u003chr\u003e\nww.table(data, sortable=False, searchable=False, paginate=None)\nww.metric(label, value, delta=None, delta_color=None)\nww.json(data, collapsed=1)              # Collapsible JSON viewer\nww.progress(value=0, max_val=100)       # Progress bar\nww.toast(message, type='info', duration=3000)\n```\n\n### Input Widgets\n\nAll widgets return a handle with `.value` and `.on_change(callback)`.\n\n```python\nww.button(label, on_click=None)\nww.input(label, placeholder='', value='')\nww.textarea(label, rows=4)\nww.slider(label, min=0, max=100, value=50, step=1)\nww.select(label, options=['a','b','c'], value='a')\nww.checkbox(label, value=False)\nww.radio(label, options=['x','y','z'])\nww.file_upload(label, accept='.csv,.json')\nww.date_picker(label)\nww.color_picker(label, value='#3366cc')\nww.number(label, min=0, max=100, step=1, value=0)\n```\n\n### Charts\n\n```python\n# Simple chart\nww.chart([12, 19, 3], type='bar', labels=['A','B','C'])\n\n# Multi-dataset\nww.chart(datasets=[\n    {'label': 'Sales', 'data': [12, 19, 3], 'color': '#3366cc'},\n    {'label': 'Returns', 'data': [2, 3, 1], 'color': '#cc3333'}\n], type='line', labels=['Jan','Feb','Mar'])\n\n# From pandas DataFrame\nww.plot(df, x='date', y='revenue', type='line')\n\n# Supported types: bar, line, pie, doughnut, radar, polarArea, scatter, bubble\n```\n\n### Layout\n\n```python\nwith ww.columns(3) as cols:\n    with cols[0]: ww.text(\"Left\")\n    with cols[1]: ww.chart(data1)\n    with cols[2]: ww.chart(data2)\n\nwith ww.tabs(['Overview', 'Details']) as t:\n    with t[0]: ww.title(\"Overview\")\n    with t[1]: ww.table(detail_data)\n\nwith ww.accordion(\"Advanced Options\", open=False):\n    ww.slider(\"Threshold\", min=0, max=100, value=50)\n\nwith ww.card(title=\"Revenue\"):\n    ww.chart(revenue_data, type='line')\n\nwith ww.sidebar():\n    ww.nav([{'text': 'Home', 'href': '/'}])\n```\n\n### Theming\n\n```python\nww.theme('dark')                        # Built-in: light, dark, ocean, forest\nww.theme(primary='#006666')             # Custom palette\nww.toggle_theme()                       # Toggle light/dark\nww.css({'.my-card': {'border-radius': '12px'}})  # Custom CSS\n```\n\n### Export\n\n```python\nww.export('report.html')                # Self-contained HTML file\nww.export('report.html', minify=True)   # Minified output\nww.screenshot('dashboard.png')          # Screenshot via html2canvas\nww.download('data.csv', content=csv_string)  # Trigger browser download\n```\n\n## How It Works\n\n```\n Python (webwrench)                    Browser\n+------------------------+           +-------------------------+\n| Your Python script     |           | bitwrench.js (bundled)  |\n|   |                    |           |   +-- Chart.js           |\n|   v                    |  SSE --\u003e  |   +-- bwclient.js        |\n| webwrench server       | -------\u003e |                          |\n|  (asyncio, built-in)   | \u003c------- |                          |\n|                        |  POST \u003c-- |                          |\n+------------------------+           +-------------------------+\n```\n\n- **Frontend**: bitwrench.js handles DOM operations. webwrench generates TACO (Tag, Attributes, Content, Options) dicts that bitwrench renders.\n- **Backend**: Pure Python asyncio HTTP server. No Flask, no FastAPI, no external dependencies.\n- **Protocol**: bwserve -- SSE for server-to-client updates, POST for client-to-server actions.\n- **Updates**: Only the changed element is patched, not the whole page.\n\n## Development\n\n```bash\ngit clone https://github.com/deftio/webwrench.git\ncd webwrench\nuv sync --dev\n\n# Preflight — lint, security, tests (100% coverage), build, install verify\n./scripts/prerelease.sh\n```\n\n### Releasing\n\n**Direct from main** (quick):\n\n```bash\n./scripts/release.sh 0.2.0\n```\n\n**Two-phase** (for bigger releases):\n\n```bash\n./scripts/start-release.sh 0.2.0   # bump version + create release branch\n# ... develop, commit, iterate ...\n./scripts/prerelease.sh             # validate everything (safe, no side effects)\n./scripts/release.sh                # ship it: merge, tag, push, GH release\n```\n\n`release.sh` runs all prerelease checks, then tags, pushes, and creates a GitHub Release. The CI publish pipeline handles PyPI automatically.\n\n## License\n\nBSD-2-Clause. See [LICENSE.txt](LICENSE.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeftio%2Fwebwrench","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdeftio%2Fwebwrench","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeftio%2Fwebwrench/lists"}