Skip to main content

Slim, server-side, component-driven framework on top of Plotly Dash.

Project description

Weaverlet

OOP for Plotly Dash — make your dashboards composable.

Vanilla Dash and Dash Pages give you primitives, not encapsulation: callbacks live globally, IDs are strings you manage by hand, and the same widget can't be dropped into two apps without surgery. Weaverlet packages layout + callbacks + state into reusable Python classes, with auto-unique IDs, typed inter-component signals, and a keep_mounted router that preserves WebGL state across navigation.

PyPI Python 3.12+ License: MIT


Weaverlet, developed by the Observatorio Metropolitano CentroGeo, turns a Plotly Dash app into a hierarchy of reusable Python classes. Each WeaverletComponent owns its own layout, callbacks, and identifiers; you compose them into a tree, hand the root to WeaverletApp, and ship.

It's aimed at:

  • Data scientists whose dashboard has outgrown a single file and needs structure.
  • Dash developers who want true component reuse instead of copy-paste between apps.
  • React-style developers looking for the same class-based component paradigm, server-side, in pure Python.

What Weaverlet adds on top of vanilla Dash

  • Class-based encapsulation — layout, callbacks, state, and IDs live together in one class. Instantiate it twice for two independent copies that don't step on each other.
  • Auto-unique IDs — the Identifier() descriptor generates a Dash-unique ID per instance. No more managing string IDs by hand or hitting DuplicateIdError when reusing a widget.
  • Signal-based eventsSignalComponent + SignalInput/SignalOutput/SignalTrigger replace ad-hoc dcc.Store + manual callback wiring as the canonical inter-component event bus.
  • State-preserving routing (new in 0.3)SimpleRouterComponent(keep_mounted=True) keeps every route mounted and toggles CSS, so WebGL canvases (dash-leaflet maps, dash-sylvereye graphs, Plotly WebGL) and React state (n_clicks, selections, scroll position) survive navigation.
  • Session-based auth routingAuthRouterComponent gates routes via flask.session out of the box.
  • Shared context — one dict propagates to every component in the DAG; no prop-drilling, no globals.

Weaverlet vs Dash Pages

Dash Pages (built into Dash 2.5+) and Weaverlet both solve "multi-page Dash" but at different levels of abstraction. Pages is a routing convention — drop a file in pages/, register it, get a route. Weaverlet is a component framework — write classes, compose them, navigate without losing state.

Capability Dash Pages Weaverlet
Built-in to Dash ✓ (Dash ≥ 2.5) One pip install
Routing source Filesystem (pages/*.py) Programmatic routes dict, computed at runtime
Path templating (/users/<id>) Via pathname / search parsing
Page-level encapsulation One module per page Reusable component classes; same class can power N routes
Cross-page widget reuse Imports + manual callback wiring Instantiate the same class anywhere; auto-unique IDs
Inter-component events dcc.Store + global callbacks SignalComponent typed events
State across navigation Unmount + remount on every route change keep_mounted=True preserves React + WebGL state
Auth gating DIY AuthRouterComponent + Flask sessions
Auto-discovered nav menu ✓ (dash.page_registry) Build it yourself

Reach for Dash Pages when your dashboard is content-heavy, each page is roughly independent, you want filesystem-driven routing, and you don't need to preserve state across navigation. It's the simpler tool and ships with Dash.

Reach for Weaverlet when you have shared widget logic across pages, when WebGL components (maps, network graphs, 3D plots) shouldn't reset on navigation, when you want typed inter-component events instead of dcc.Store plumbing, or when your dashboard has grown past the "one file per page" model and needs proper encapsulation.

The two aren't mutually exclusive — Weaverlet apps can coexist alongside a Pages-based site in the same Dash deployment if that fits your migration path.

Weaverlet vs Dash All-in-One Components

All-in-One Components (AIO, built into Dash 2+) and Weaverlet share a goal — bundle a Dash widget's layout and callbacks into one unit — but they operate at different scopes. AIO is a widget pattern: subclass html.Div, register pattern-matching callbacks once at module level, give each instance a UUID aio_id. Weaverlet is a whole-app framework: a tree of components with lifecycle hooks, routing, signals, and shared context.

Capability Dash AIO Weaverlet
Built-in to Dash ✓ (Dash ≥ 2.0) One pip install
ID style Pattern-matching dicts ({'component': ..., 'aio_id': uuid}) Flat strings via Identifier() descriptor
Callback registration Once at module level via MATCH / ALL Per instance, in register_callbacks(self, app)
Instance state on self Awkward (callbacks are module-level free functions) Native (callbacks close over self)
Composing hierarchies Limited — each AIO is typically a leaf DAG of nested components; parent/child wiring is automatic
Multipage routing Not provided (pair with Dash Pages or DIY) SimpleRouterComponent + AuthRouterComponent
Inter-component events Pattern-matching callbacks or DIY dcc.Store Typed SignalComponent events
State across navigation Up to surrounding router (none by default) keep_mounted=True preserves React + WebGL state
Shared context / config DIY Built-in dict propagated to every component
Auth gating DIY AuthRouterComponent + Flask sessions

Reach for AIO when you're building a single reusable widget — a fancy date picker, a search box, a chart-with-controls — to drop into existing Dash apps, especially when you want many instances and need to operate on all of them at once via pattern-matching. It's the lighter pattern for one widget.

Reach for Weaverlet when you're structuring a full application — multipage, auth, signal flows, shared context, state-preserving navigation. It's the framework for the whole dashboard.

They coexist nicely. Weaverlet components are regular Dash components under the hood, so you can use AIO widgets as leaves inside a WeaverletComponent.get_layout() without friction. The reverse (Weaverlet inside an AIO) is technically possible but usually awkward — Weaverlet wants to own the lifecycle of its own tree.

What's new in 0.3.0

Released April 2026 — full notes in CHANGELOG.md.

  • Dash 4.x support; Python ≥ 3.12; dependency pins relaxed (Flask / werkzeug now resolved transitively, no longer hard-pinned).
  • SimpleRouterComponent(keep_mounted=True, preserve_path="/...") for state preservation across route changes.
  • Top-level importsfrom weaverlet import WeaverletComponent, ... works directly; no need to drill into weaverlet.base / weaverlet.components.
  • Permissive get_layout signatures — components declare any subset of (pathname, hash, href, search, user, protected_route) and the router passes only what's declared.
  • DashProxy under the hood so dash_extensions.javascript.assign(), Serverside(...) return wrappers, and group= multi-output callbacks all work without extra wiring.
  • assets_folder auto-resolves to the user's main script directory — no manual asset path setup needed.
  • 60-test pytest suite; build via pyproject.toml + hatchling.
  • jupyter-dash moved to an optional extra (pip install weaverlet[jupyter]).

Install

pip install weaverlet

(or uv add weaverlet if you're on uv.)

Optional extras:

Extra Pulls in Use when
weaverlet[examples] dash-bootstrap-components, dash-mantine-components Running the bundled examples 11–14
weaverlet[jupyter] dash[jupyter] Constructing WeaverletApp(jupyter_mode=True)
weaverlet[dev] pytest, pytest-playwright Hacking on Weaverlet itself

Quick start

A single-page app that echoes user input:

from weaverlet import WeaverletComponent, WeaverletApp, Identifier
from dash_extensions.enrich import Input, Output
from dash import html, dcc


class EchoComponent(WeaverletComponent):
    text_input_id = Identifier()
    echo_div_id = Identifier()

    def get_layout(self):
        return html.Div([
            dcc.Input(id=self.text_input_id, type="text"),
            html.Div(id=self.echo_div_id),
        ])

    def register_callbacks(self, app):
        @app.callback(Output(self.echo_div_id, "children"),
                      Input(self.text_input_id, "value"))
        def echo(text_value):
            return text_value or ""


WeaverletApp(root_component=EchoComponent()).app.run()

A multipage app with WebGL-safe state preservation:

from weaverlet import (
    WeaverletComponent, WeaverletApp, SimpleRouterComponent,
)
from dash import html


class HomePage(WeaverletComponent):
    def get_layout(self):
        return html.Div("Home — your dash-leaflet map goes here.")


class AboutPage(WeaverletComponent):
    def get_layout(self):
        return html.Div("About — switch back to Home; the map's zoom survives.")


class NotFound(WeaverletComponent):
    def get_layout(self):
        return html.Div("404")


router = SimpleRouterComponent(
    routes={"/": HomePage(), "/about": AboutPage()},
    not_found_page_component=NotFound(),
    keep_mounted=True,        # keep all routes mounted; toggle CSS only
    preserve_path="/",        # which route owns the document flow (default: first)
)
WeaverletApp(root_component=router).app.run()

Aliasing — multiple paths pointing at the same component instance — works in both modes. Under keep_mounted=True, aliased paths automatically share a single wrapper, so the same Identifier-bearing layout never mounts twice.

Examples

The examples/ folder has 14 self-contained scripts, ordered by complexity:

# File Demonstrates
01 01_helloworld_app.py Minimal layout-only component
02 02_echo_app.py Layout + callbacks + Identifier
03 03_greeting_app.py Constructor-injected state
04 04_router_app.py Multipage with SimpleRouterComponent
05 05_auth_router_app.py Session-protected routes via AuthRouterComponent
06 06_redirect_app.py Programmatic redirect from a callback
07 07_signal_input.py Signals carrying a payload
08 08_signal_trigger.py Edge-only triggers
09 09_signal_chain_app.py Chained signal pipeline
10 10_div_signal_trigger_app.py Signal-driven <div> updates
11 11_dbc_app.py Single-page Weaverlet + Dash Bootstrap
12 12_dbc_multipage_app.py Multipage with DBC navbar
13 13_dbc_modal_app.py DBC modal triggered by signals
14 14_dmc_multipage_app.py Dash Mantine + AppShell + keep_mounted state preservation

Compatibility

Python ≥ 3.12
Dash ≥ 4.1.0, < 5.0.0
dash-extensions ≥ 1.0.0, < 3.0.0 (1.x and 2.x both supported)

For LLM coding assistants

Weaverlet ships a ReadMe.LLM.md following the ReadMe.LLM methodology — rules, context, 14 worked examples, API signatures, and patterns specifically formatted for LLMs like Claude, GPT, and Codex. To use it from another project, drop one of these URLs into your prompt or have your assistant fetch it:

There's also an llms.txt at the repo root following the llmstxt.org convention — a short index that tools like Cursor and Claude Code can auto-discover.

Contributing & support

  • Bug reports and feature requests: GitHub issues.
  • Tests: pip install -e ".[dev]" then pytest (60 tests, ~1 second).
  • Migrating from 0.2.x? See CHANGELOG.md — most code keeps working unchanged; the main gotcha is app.run_server()app.run() (Dash 4 removed the old name).

License

Weaverlet is open-source software licensed under the MIT license.

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

weaverlet-0.3.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

weaverlet-0.3.0-py3-none-any.whl (21.5 kB view details)

Uploaded Python 3

File details

Details for the file weaverlet-0.3.0.tar.gz.

File metadata

  • Download URL: weaverlet-0.3.0.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for weaverlet-0.3.0.tar.gz
Algorithm Hash digest
SHA256 5ba3a9cd192d1737ea6cf3091fcee5dc7e27c1558c99a6f48543d987c80ed59f
MD5 9e0eda1c01eba1b1a4c6f70cff65dc6f
BLAKE2b-256 27c64cce98f62b5bf6a7323f7bb45a81a19877623d6b5517345060e5f3f0ecd0

See more details on using hashes here.

File details

Details for the file weaverlet-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: weaverlet-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 21.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for weaverlet-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 69b0e10b4432dfaa55cd41d1776c637d9529dbabc81ec5f89348761a054745f6
MD5 d91441bb6e56e422cc85b3cdca95a67e
BLAKE2b-256 24842d0dd5788b770c4d71cc04b90e3eb018413c26defb565983433ccb1e0260

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page