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[dev] pytest, pytest-playwright Hacking on Weaverlet itself

Jupyter: Dash 4 has built-in Jupyter support, no extra needed. Construct your WeaverletApp normally and pass jupyter_mode='inline' (or 'tab' / 'external') to .app.run():

wapp = WeaverletApp(root_component=...)
wapp.app.run(jupyter_mode='inline')

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.1.tar.gz (27.2 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.1-py3-none-any.whl (21.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: weaverlet-0.3.1.tar.gz
  • Upload date:
  • Size: 27.2 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.1.tar.gz
Algorithm Hash digest
SHA256 cd88e81e1674de5c1fc75a5abe1d1adfb3cbacc7a6f0d52263e87956c549d1e9
MD5 4d761d70f22b4bea3db757da86f3b950
BLAKE2b-256 709e29087c56e51218f65544af98c2f86ce1a26f95de3b6ba0c45cc231bef90a

See more details on using hashes here.

File details

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

File metadata

  • Download URL: weaverlet-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 21.7 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0a484624860f44991d2597d756d6f5f5259b5251e285c55ee91ba60794e13fb7
MD5 0a95d21b42e1078ad1e361ab0ff77b80
BLAKE2b-256 037c33ce40e9107b6c0fad36409b58bd1d6ad0ac71b646c350a459f6ceadb9b0

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