StreamTree: declarative, typed composition for Streamlit.
Project description
StreamTree
Declarative, typed composition for Streamlit. StreamTree adds components, layout primitives, scoped session state, and test-friendly tree rendering while keeping Streamlit’s execution model and widgets. No JavaScript or separate frontend build is required.
Overview
| Traditional scripts | With StreamTree |
|---|---|
| Layout and widgets mixed in one long file | @component functions return an element tree |
Many ad hoc st.session_state keys |
state() and helpers scoped to the render path |
| Structure is hard to inspect or snapshot | streamtree.testing.render_to_tree() for tests and docs |
StreamTree is an architecture layer for Streamlit, not a React-style web framework.
Capabilities
- Components and elements —
@component,render/render_app, layouts (Page,Card,Grid,VStack,Form,Tabs,Sidebar,Routes,ErrorBoundary,Dialog,Popover, and more), and widget wrappers. - App shell (0.3+) —
Appwith a guardedst.set_page_config, plus optional sidebar and main composition viarender_app. - Theming (0.3+) —
Theme,ThemeRoot,theme(),theme_css(), andapp_context.provider(theme=...). - Background work (0.3+) —
streamtree.asyncio.submit/submit_manyandTaskHandlefor stdlib-thread jobs you poll across reruns;set_task_progress/TaskHandle.progress()(0.5+); 0.7+ cooperative cancel viaTaskHandle.cancel(),is_task_cancel_requested, andcomplete_cancelled(see module docstring instreamtree.asyncio). - Multipage helpers (0.5+) —
streamtree.helpers.pages(discover_pages,PageLink-friendly paths) for Streamlit’spages/layout; ships in the default install (the[pages]extra remains reserved for future pinned deps). - Forms (0.3+) — Pydantic-oriented helpers:
bind_str_fields/str_text_inputs, plusbind_numeric_fields/number_inputs(0.4+) forint/floatfields (optionalint | None/float | Noneuse model defaults orNonefor an empty number input). - CLI (0.4+) — Optional
streamtree[cli]:streamtree rundelegates to Streamlit;streamtree doctorprints versions;streamtree init(0.6+) scaffoldsapp.pyand optionalpages/(see examples/streamtree_run_demo.md). - State —
state,toggle_state,form_state,memo,cache. - Routing and context — Query-param routing (
streamtree.routing),ErrorBoundary,streamtree.formsutilities, andapp_context.provider/lookupfor shared values. - Optional auth (0.6+) —
pip install "streamtree[auth]":AuthGate+streamtree.auth.build_authenticatorforstreamlit-authenticator(see examples/auth_demo.py; treat config as trusted secrets). - Optional UI extras (0.6+) —
pip install "streamtree[ui]":ColoredHeader,VerticalSpaceLines(curatedstreamlit-extraswrappers). - Overlays (0.6+) —
Dialog/Popoverelements mapped tost.dialog/st.popover. On older Streamlit builds withoutst.dialog,Dialogshows a warning and renders its children inline on the page (not a modal);Popoverfalls back tost.expander. - Interop — Inside
@component, your function body runs during render; you may callst.*(columns, metrics, charts, third-party components) and still return an element tree, orfragment()when the subtree is fully imperative. - Quality — Pydantic v2 in the default install, typing-first APIs, and
render_to_treefor structural tests.
Optional extras: [tables], [charts] remain stubs; [ui] and [auth] pin streamlit-extras and streamlit-authenticator (0.6+). [cli] adds Typer and the streamtree console script (run, doctor, init). [asyncio] / [async], [pages], and [runner] remain metadata-oriented. See Dependency strategy. The streamtree.asyncio module, streamtree.helpers.runner, and streamtree.helpers.pages ship in the default package.
Requirements
Python 3.10+, with Streamlit ≥ 1.33 (for st.dialog / st.popover overlays and the current test matrix), Pydantic v2, and typing-extensions (see pyproject.toml). PageLink continues to require Streamlit’s multipage APIs from the 1.30+ line; the floor is 1.33 for the whole package as of 0.6.0+.
Installation
pip install streamtree==0.7.0
pip install "streamtree[cli]" # Typer + ``streamtree run`` / ``doctor`` / ``init``
pip install "streamtree[auth]" # streamlit-authenticator
pip install "streamtree[ui]" # streamlit-extras wrappers
From a clone, with dev dependencies:
git clone https://github.com/streamtree-dev/streamtree.git
cd streamtree
uv sync --extra dev
# or: pip install -e ".[dev]"
Quick start
from streamtree import component, render
from streamtree.elements import Button, Card, Page, Text
from streamtree.state import state
@component
def Counter():
count = state(0)
return Card(
Text(f"Count: {count()}"),
Button("Increment", on_click=lambda: count.increment(1)),
Button("Reset", on_click=lambda: count.set(0)),
)
if __name__ == "__main__":
render(Page(Counter()))
Run examples from the repository root:
streamlit run examples/counter.py
streamlit run examples/routed_app.py
streamlit run examples/app_shell.py
streamlit run examples/async_bg.py
streamlit run examples/model_form.py
streamlit run examples/numeric_nav_demo.py
streamlit run examples/pages_helpers_demo.py
streamlit run examples/overlay_demo.py
streamlit run examples/auth_demo.py
# With Typer installed (``pip install "streamtree[cli]"``):
streamtree run examples/counter.py
Using Streamlit inside components
The @component body runs on every rerun in the same process as streamlit. You can call st.columns, st.metric, plotting APIs, or components.v1 before returning elements. Use stable key= arguments on imperative widgets when Streamlit requires them. When a subtree is drawn entirely with st.*, return fragment().
import streamlit as st
from streamtree import component, fragment, render
from streamtree.elements import Button, Markdown, Page, TextInput, VStack
from streamtree.state import state
@component
def DashboardHeader():
band, meta = st.columns([3, 1])
with band:
st.title("Operations")
with meta:
st.metric("Queue depth", 12, delta=-2)
notes = state("", key="header_notes")
return VStack(
Markdown("**Notes** (StreamTree `TextInput`):"),
TextInput("Session notes", value=notes),
Button("Clear notes", on_click=lambda: notes.set("")),
)
@component
def MetricsStrip():
cols = st.columns(4)
for i, col in enumerate(cols):
with col:
st.metric(f"M{i + 1}", 100 + i * 7, delta=i - 1)
return fragment()
if __name__ == "__main__":
render(Page(DashboardHeader(), MetricsStrip()))
App shell, theme, and background tasks
App plus render_app() centralize page configuration. Combine ThemeRoot with provider(theme=Theme(...)) for CSS variables, and use streamtree.asyncio.submit for non-blocking work you observe via TaskHandle.status (and optionally TaskHandle.progress()) on reruns. From inside the worker, call streamtree.asyncio.set_task_progress with the same key you passed to submit.
from streamtree import asyncio, component, render_app
from streamtree.app import App
from streamtree.app_context import provider
from streamtree.elements import Page, Text, ThemeRoot, VStack
from streamtree.theme import Theme
@component
def Body():
handle = asyncio.submit(lambda: 7, key="demo_job")
return VStack(
ThemeRoot(),
Text(f"status={handle.status()} progress={handle.progress()!r} result={handle.result()}"),
)
if __name__ == "__main__":
with provider(theme=Theme(primary_color="#0068c9")):
render_app(App(page_title="Demo", body=Body()))
Patterns
Grid of components
from streamtree.elements import Grid
Grid(UserCard(user1), UserCard(user2), columns=2)
Bound text input
from streamtree.elements import TextInput
from streamtree.state import state
search = state("")
TextInput(label="Search", value=search)
Multipage discovery (0.5+)
Use streamtree.helpers.pages.discover_pages(__file__) to list scripts under Streamlit’s pages/ folder next to your entry script. Each PageEntry has label and page for PageLink. See examples/pages_helpers_demo.py.
Documentation
| Resource | Description |
|---|---|
| Plan | Vision, architecture, dependency timeline (incl. 0.6.0) |
| Roadmap | Phased delivery and release index |
| Phase 2 tail | Grooming after 0.6.0 (navigation, asyncio, forms) |
| Dependency strategy | Optional extras, default-install helpers (runner, pages), and CI typing notes |
| CHANGELOG | Release history (e.g. 0.7.0 asyncio orchestration; 0.6.0 init, AuthGate, overlays) |
Contributing
Install dev tools, then run lint, type check, and tests (mirrors CI on Python 3.10–3.13):
uv sync --extra dev
uv run ruff format .
uv run ruff check src tests
uv run ty check src
uv run pytest
Equivalent with pip: pip install -e ".[dev]", then ruff, ty check src, and pytest as above.
Releases
Before tagging v0.7.0 (or any v*.*.* release), confirm uv build succeeds, uv run pytest passes with coverage, and pyproject.toml, streamtree.__version__, tests/test_package_meta.py, and CHANGELOG.md all agree on the version.
Automated: Add a PYPI_API_TOKEN secret to the repository. When main is green, push a tag of the form v0.7.0. The release workflow runs lint, type check, pytest (including coverage), builds with uv build, and publishes to PyPI.
Manual: uv build (or python -m build), then upload dist/ with twine or uv publish. Keep pyproject.toml, streamtree.__version__, tests/test_package_meta.py, and CHANGELOG.md in sync when cutting a release.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file streamtree-0.7.0.tar.gz.
File metadata
- Download URL: streamtree-0.7.0.tar.gz
- Upload date:
- Size: 82.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
785cee44f3ee645c0c06f1e0028c032164a371cb5a544e00c7464cdc6980e709
|
|
| MD5 |
a9b407a9d53fb78df5c42661b96bf4c1
|
|
| BLAKE2b-256 |
9473a6062d965b777152f30e290c527e096afde91f578f8bd05075c785354820
|
File details
Details for the file streamtree-0.7.0-py3-none-any.whl.
File metadata
- Download URL: streamtree-0.7.0-py3-none-any.whl
- Upload date:
- Size: 41.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3104f42fbf3b34000eb7ff4954e4071fc0792c9ef5148dbc18967b3e4affbe15
|
|
| MD5 |
b943e7c6305d67d8384a86a44632824c
|
|
| BLAKE2b-256 |
c5b143f2042376a0a8f2e99e17e767145cd52029fb878866bc9416744a136197
|