streamtree 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamtree-0.3.0/CHANGELOG.md +65 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/LICENSE +1 -1
- streamtree-0.3.0/PKG-INFO +246 -0
- streamtree-0.3.0/README.md +204 -0
- streamtree-0.2.0/docs/STREAMTREE_DEPENDENCY_STRATEGY.md → streamtree-0.3.0/docs/DEPENDENCY_STRATEGY.md +44 -42
- streamtree-0.2.0/docs/STREAMTREE_PLAN.md → streamtree-0.3.0/docs/PLAN.md +16 -14
- streamtree-0.2.0/docs/STREAMTREE_ROADMAP.md → streamtree-0.3.0/docs/ROADMAP.md +32 -24
- streamtree-0.3.0/examples/app_shell.py +34 -0
- streamtree-0.3.0/examples/async_bg.py +24 -0
- streamtree-0.3.0/examples/model_form.py +62 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/examples/routed_app.py +4 -1
- {streamtree-0.2.0 → streamtree-0.3.0}/pyproject.toml +7 -5
- streamtree-0.3.0/src/streamtree/__init__.py +47 -0
- streamtree-0.3.0/src/streamtree/app.py +53 -0
- streamtree-0.3.0/src/streamtree/asyncio/__init__.py +147 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/core/__init__.py +2 -1
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/core/component.py +17 -2
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/elements/__init__.py +3 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/elements/layout.py +9 -2
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/forms.py +48 -1
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/renderers/streamlit.py +47 -4
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/routing.py +14 -7
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/testing/__init__.py +15 -1
- streamtree-0.3.0/src/streamtree/theme.py +104 -0
- streamtree-0.3.0/src/streamtree.egg-info/PKG-INFO +246 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree.egg-info/SOURCES.txt +13 -3
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree.egg-info/requires.txt +2 -0
- streamtree-0.3.0/tests/test_app.py +93 -0
- streamtree-0.3.0/tests/test_asyncio_pkg.py +203 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_component_and_testing.py +18 -0
- streamtree-0.3.0/tests/test_forms_builder.py +61 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_layout_routes.py +5 -0
- streamtree-0.3.0/tests/test_package_meta.py +22 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_render_streamlit_mocked.py +52 -2
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_routing.py +22 -10
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_testing_all_nodes.py +2 -0
- streamtree-0.3.0/tests/test_theme.py +94 -0
- streamtree-0.2.0/CHANGELOG.md +0 -37
- streamtree-0.2.0/PKG-INFO +0 -235
- streamtree-0.2.0/README.md +0 -194
- streamtree-0.2.0/src/streamtree/__init__.py +0 -22
- streamtree-0.2.0/src/streamtree.egg-info/PKG-INFO +0 -235
- streamtree-0.2.0/tests/test_package_meta.py +0 -9
- {streamtree-0.2.0 → streamtree-0.3.0}/MANIFEST.in +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/examples/counter.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/setup.cfg +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/app_context.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/core/context.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/core/element.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/elements/widgets.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/py.typed +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/renderers/__init__.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/state/__init__.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree.egg-info/dependency_links.txt +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree.egg-info/top_level.txt +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_app_context.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_context.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_element.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_forms.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_state.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_streamlit_app.py +0 -0
- {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_tree.py +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.0] — 2026-05-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Packaging:** optional extra **`[async]`** as a TOML-quoted alias of the empty **`[asyncio]`** stub extra (matches install examples in the plan and dependency strategy).
|
|
13
|
+
- **`streamtree.app.App`** shell with optional sidebar composition, **`apply_page_config`**, **`app_root_element`**, and **`render_app()`** (one-time `st.set_page_config` guard).
|
|
14
|
+
- **`streamtree.theme`**: **`Theme`** (Pydantic), **`theme()`** / **`theme_css()`**, and **`ThemeRoot`** element for CSS injection via `app_context.provider(theme=...)`.
|
|
15
|
+
- **`streamtree.asyncio`**: **`submit()`** / **`TaskHandle`** for daemon-thread background work with session-scoped poll keys (stdlib-only; optional `[asyncio]` / `[async]` stub extras unchanged as meta).
|
|
16
|
+
- **`streamtree.forms`**: **`bind_str_fields`** and **`str_text_inputs`** for declarative `TextInput` grids from Pydantic string fields.
|
|
17
|
+
- Examples **`examples/app_shell.py`**, **`examples/async_bg.py`**, **`examples/model_form.py`**.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Routing:** `sync_route` / `set_route` now store the active route in `st.session_state` under `streamtree.routing.active.<param>` (one slot per query-param name) instead of a single global `streamtree.routing.active` key. Apps that read that private key must migrate.
|
|
22
|
+
- **`ErrorBoundary.on_error`** is typed as ``Callable[[Exception], None]``, matching the renderer (only `Exception` subclasses are passed through).
|
|
23
|
+
- **`Routes`:** duplicate route names are rejected at construction.
|
|
24
|
+
- **`@component`:** returning ``None`` or a non-``Element`` value raises a clear ``TypeError`` (including in ``render_to_tree(..., expand_components=True)``).
|
|
25
|
+
- **`streamtree.asyncio`:** task status dict updates are serialized with a lock; module and ``submit`` docstrings document rerun-polling semantics.
|
|
26
|
+
- **`Theme`:** ``primary_color`` is restricted to ``#RGB`` / ``#RRGGBB`` hex; ``font_stack`` rejects ``<``, ``>``, and backticks; ``custom_css`` rejects ``<script`` and ``expression(`` substrings (full ``custom_css`` remains trusted CSS).
|
|
27
|
+
- **`HStack`:** non-empty ``gap`` inserts gutter columns between children (CSS ``min-width`` on a spacer).
|
|
28
|
+
|
|
29
|
+
### Documentation
|
|
30
|
+
|
|
31
|
+
- README refresh (badges, absolute GitHub links for design docs and workflows); install examples pin **0.3.0**.
|
|
32
|
+
- Design docs live at `docs/PLAN.md`, `docs/ROADMAP.md`, and `docs/DEPENDENCY_STRATEGY.md` with updated cross-links.
|
|
33
|
+
- **StreamTree** is used as the product name in metadata and user-facing defaults (the PyPI / import name remains **`streamtree`**).
|
|
34
|
+
|
|
35
|
+
## [0.2.0] — 2026-05-12
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **Pydantic** and **typing-extensions** as core dependencies; stub optional extras (`tables`, `charts`, `ui`, `auth`, `asyncio`, `cli`, `all`) per dependency strategy.
|
|
40
|
+
- **`streamtree.app_context`**: `provider()` / `lookup()` / `current_bag()` for rerun-scoped DI-style values.
|
|
41
|
+
- **`streamtree.routing`**: `sync_route` / `set_route` for query-param + session alignment.
|
|
42
|
+
- **`Routes`** layout element (with Streamlit renderer) for one-of-many pages keyed by the active route.
|
|
43
|
+
- **`ErrorBoundary`** element with Streamlit renderer fallback and optional `on_error` callback.
|
|
44
|
+
- **`streamtree.forms`**: `str_field_names`, `model_validate_json`, `format_validation_errors` for Pydantic-first forms.
|
|
45
|
+
- Example **`examples/routed_app.py`**: multi-page navigation + JSON profile validation.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- Stricter validation for routing params and route names, app-context keys, `Routes` defaults/query keys, and `Annotated[...]` handling in `str_field_names`; `current_bag()` returns a shallow copy.
|
|
50
|
+
- Pytest coverage targets `src/streamtree` by path to avoid import-order coverage warnings.
|
|
51
|
+
|
|
52
|
+
## [0.1.0] — 2026-05-11
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- Initial public API: `@component`, `render`, virtual elements (`Page`, layouts, widgets).
|
|
57
|
+
- Session helpers: `state`, `toggle_state`, `form_state`, `session_state`, `memo`, `cache`.
|
|
58
|
+
- Streamlit renderer mapping the virtual tree to `st.*` calls (including `Form` and submit flows).
|
|
59
|
+
- `streamtree.testing.render_to_tree` for structure-focused tests without a live Streamlit session.
|
|
60
|
+
- Runnable example: `examples/counter.py`.
|
|
61
|
+
- Design docs under `docs/` (plan, roadmap, dependency strategy).
|
|
62
|
+
|
|
63
|
+
[0.3.0]: https://github.com/streamtree-dev/streamtree/releases/tag/v0.3.0
|
|
64
|
+
[0.2.0]: https://github.com/streamtree-dev/streamtree/releases/tag/v0.2.0
|
|
65
|
+
[0.1.0]: https://github.com/streamtree-dev/streamtree/releases/tag/v0.1.0
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: streamtree
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: StreamTree: declarative, typed composition for Streamlit.
|
|
5
|
+
Author: StreamTree contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/streamtree-dev/streamtree
|
|
8
|
+
Project-URL: Documentation, https://github.com/streamtree-dev/streamtree#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/streamtree-dev/streamtree
|
|
10
|
+
Project-URL: Issues, https://github.com/streamtree-dev/streamtree/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/streamtree-dev/streamtree/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: streamlit,ui,components,declarative
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pydantic>=2.4.0
|
|
24
|
+
Requires-Dist: streamlit>=1.28.0
|
|
25
|
+
Requires-Dist: typing-extensions>=4.8.0
|
|
26
|
+
Provides-Extra: tables
|
|
27
|
+
Provides-Extra: charts
|
|
28
|
+
Provides-Extra: ui
|
|
29
|
+
Provides-Extra: auth
|
|
30
|
+
Provides-Extra: asyncio
|
|
31
|
+
Provides-Extra: async
|
|
32
|
+
Provides-Extra: cli
|
|
33
|
+
Provides-Extra: all
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-xdist>=3.5.0; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.2.0; extra == "dev"
|
|
40
|
+
Requires-Dist: ty>=0.0.30; extra == "dev"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# StreamTree
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/streamtree/)
|
|
46
|
+
[](https://pypi.org/project/streamtree/)
|
|
47
|
+
[](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE)
|
|
48
|
+
[](https://github.com/eddiethedean/streamtree/actions/workflows/ci.yml?query=branch%3Amain)
|
|
49
|
+
[](https://docs.astral.sh/ruff/)
|
|
50
|
+
|
|
51
|
+
Declarative, typed composition for [Streamlit](https://streamlit.io/). 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.
|
|
52
|
+
|
|
53
|
+
## Overview
|
|
54
|
+
|
|
55
|
+
| Traditional scripts | With StreamTree |
|
|
56
|
+
|---------------------|-----------------|
|
|
57
|
+
| Layout and widgets mixed in one long file | `@component` functions return an **element tree** |
|
|
58
|
+
| Many ad hoc `st.session_state` keys | `state()` and helpers scoped to the render path |
|
|
59
|
+
| Structure is hard to inspect or snapshot | `streamtree.testing.render_to_tree()` for tests and docs |
|
|
60
|
+
|
|
61
|
+
StreamTree is an **architecture layer** for Streamlit, not a React-style web framework.
|
|
62
|
+
|
|
63
|
+
## Capabilities
|
|
64
|
+
|
|
65
|
+
- **Components and elements** — `@component`, `render` / `render_app`, layouts (`Page`, `Card`, `Grid`, `VStack`, `Form`, `Tabs`, `Sidebar`, `Routes`, `ErrorBoundary`, and more), and widget wrappers.
|
|
66
|
+
- **App shell (0.3+)** — `App` with a guarded `st.set_page_config`, plus optional sidebar and main composition via `render_app`.
|
|
67
|
+
- **Theming (0.3+)** — `Theme`, `ThemeRoot`, `theme()`, `theme_css()`, and `app_context.provider(theme=...)`.
|
|
68
|
+
- **Background work (0.3+)** — `streamtree.asyncio.submit` and `TaskHandle` for stdlib-thread jobs you poll across reruns.
|
|
69
|
+
- **Forms (0.3+)** — Pydantic-oriented helpers such as `bind_str_fields` and `str_text_inputs` for string fields.
|
|
70
|
+
- **State** — `state`, `toggle_state`, `form_state`, `memo`, `cache`.
|
|
71
|
+
- **Routing and context** — Query-param routing (`streamtree.routing`), `ErrorBoundary`, `streamtree.forms` utilities, and `app_context.provider` / `lookup` for shared values.
|
|
72
|
+
- **Interop** — Inside `@component`, your function body runs during render; you may call `st.*` (columns, metrics, charts, third-party components) and still return an element tree, or `fragment()` when the subtree is fully imperative.
|
|
73
|
+
- **Quality** — Pydantic v2 in the default install, typing-first APIs, and `render_to_tree` for structural tests.
|
|
74
|
+
|
|
75
|
+
Stub optional extras (`tables`, `charts`, `ui`, `auth`, `asyncio`, `async`, `cli`) are reserved for future wrappers; see [Dependency strategy](https://github.com/streamtree-dev/streamtree/blob/main/docs/DEPENDENCY_STRATEGY.md). The `streamtree.asyncio` module ships in the default package.
|
|
76
|
+
|
|
77
|
+
## Requirements
|
|
78
|
+
|
|
79
|
+
Python **3.10+**, with **Streamlit ≥ 1.28**, **Pydantic v2**, and **typing-extensions** (see `pyproject.toml`).
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install streamtree==0.3.0
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
From a clone, with dev dependencies:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git clone https://github.com/streamtree-dev/streamtree.git
|
|
91
|
+
cd streamtree
|
|
92
|
+
uv sync --extra dev
|
|
93
|
+
# or: pip install -e ".[dev]"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Quick start
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from streamtree import component, render
|
|
100
|
+
from streamtree.elements import Button, Card, Page, Text
|
|
101
|
+
from streamtree.state import state
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@component
|
|
105
|
+
def Counter():
|
|
106
|
+
count = state(0)
|
|
107
|
+
return Card(
|
|
108
|
+
Text(f"Count: {count()}"),
|
|
109
|
+
Button("Increment", on_click=lambda: count.increment(1)),
|
|
110
|
+
Button("Reset", on_click=lambda: count.set(0)),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
render(Page(Counter()))
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Run examples from the repository root:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
streamlit run examples/counter.py
|
|
122
|
+
streamlit run examples/routed_app.py
|
|
123
|
+
streamlit run examples/app_shell.py
|
|
124
|
+
streamlit run examples/async_bg.py
|
|
125
|
+
streamlit run examples/model_form.py
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Using Streamlit inside components
|
|
129
|
+
|
|
130
|
+
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()`.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
import streamlit as st
|
|
134
|
+
|
|
135
|
+
from streamtree import component, fragment, render
|
|
136
|
+
from streamtree.elements import Button, Markdown, Page, TextInput, VStack
|
|
137
|
+
from streamtree.state import state
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@component
|
|
141
|
+
def DashboardHeader():
|
|
142
|
+
band, meta = st.columns([3, 1])
|
|
143
|
+
with band:
|
|
144
|
+
st.title("Operations")
|
|
145
|
+
with meta:
|
|
146
|
+
st.metric("Queue depth", 12, delta=-2)
|
|
147
|
+
|
|
148
|
+
notes = state("", key="header_notes")
|
|
149
|
+
return VStack(
|
|
150
|
+
Markdown("**Notes** (StreamTree `TextInput`):"),
|
|
151
|
+
TextInput("Session notes", value=notes),
|
|
152
|
+
Button("Clear notes", on_click=lambda: notes.set("")),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@component
|
|
157
|
+
def MetricsStrip():
|
|
158
|
+
cols = st.columns(4)
|
|
159
|
+
for i, col in enumerate(cols):
|
|
160
|
+
with col:
|
|
161
|
+
st.metric(f"M{i + 1}", 100 + i * 7, delta=i - 1)
|
|
162
|
+
return fragment()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
render(Page(DashboardHeader(), MetricsStrip()))
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## App shell, theme, and background tasks
|
|
170
|
+
|
|
171
|
+
`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` on reruns.
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from streamtree import asyncio, component, render_app
|
|
175
|
+
from streamtree.app import App
|
|
176
|
+
from streamtree.app_context import provider
|
|
177
|
+
from streamtree.elements import Page, Text, ThemeRoot, VStack
|
|
178
|
+
from streamtree.theme import Theme
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@component
|
|
182
|
+
def Body():
|
|
183
|
+
handle = asyncio.submit(lambda: 7, key="demo_job")
|
|
184
|
+
return VStack(
|
|
185
|
+
ThemeRoot(),
|
|
186
|
+
Text(f"status={handle.status()} result={handle.result()}"),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
with provider(theme=Theme(primary_color="#0068c9")):
|
|
192
|
+
render_app(App(page_title="Demo", body=Body()))
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Patterns
|
|
196
|
+
|
|
197
|
+
**Grid of components**
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from streamtree.elements import Grid
|
|
201
|
+
|
|
202
|
+
Grid(UserCard(user1), UserCard(user2), columns=2)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Bound text input**
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from streamtree.elements import TextInput
|
|
209
|
+
from streamtree.state import state
|
|
210
|
+
|
|
211
|
+
search = state("")
|
|
212
|
+
TextInput(label="Search", value=search)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Documentation
|
|
216
|
+
|
|
217
|
+
| Resource | Description |
|
|
218
|
+
|----------|-------------|
|
|
219
|
+
| [Plan](https://github.com/streamtree-dev/streamtree/blob/main/docs/PLAN.md) | Vision, architecture, risks |
|
|
220
|
+
| [Roadmap](https://github.com/streamtree-dev/streamtree/blob/main/docs/ROADMAP.md) | Phased delivery |
|
|
221
|
+
| [Dependency strategy](https://github.com/streamtree-dev/streamtree/blob/main/docs/DEPENDENCY_STRATEGY.md) | Dependencies and optional extras |
|
|
222
|
+
| [CHANGELOG](https://github.com/streamtree-dev/streamtree/blob/main/CHANGELOG.md) | Release history |
|
|
223
|
+
|
|
224
|
+
## Contributing
|
|
225
|
+
|
|
226
|
+
Install dev tools, then run lint, type check, and tests (mirrors CI on Python 3.10–3.12):
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
uv sync --extra dev
|
|
230
|
+
uv run ruff format .
|
|
231
|
+
uv run ruff check src tests
|
|
232
|
+
uv run ty check src
|
|
233
|
+
uv run pytest
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Equivalent with **pip**: `pip install -e ".[dev]"`, then `ruff`, `ty check src`, and `pytest` as above.
|
|
237
|
+
|
|
238
|
+
## Releases
|
|
239
|
+
|
|
240
|
+
**Automated:** Add a **`PYPI_API_TOKEN`** secret to the repository. When `main` is green, push a tag of the form **`v0.3.0`**. The [release workflow](https://github.com/streamtree-dev/streamtree/blob/main/.github/workflows/release.yml) runs lint, type check, pytest (including coverage), builds with `uv build`, and publishes to PyPI.
|
|
241
|
+
|
|
242
|
+
**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.
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT. See [LICENSE](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE).
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# StreamTree
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/streamtree/)
|
|
4
|
+
[](https://pypi.org/project/streamtree/)
|
|
5
|
+
[](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/eddiethedean/streamtree/actions/workflows/ci.yml?query=branch%3Amain)
|
|
7
|
+
[](https://docs.astral.sh/ruff/)
|
|
8
|
+
|
|
9
|
+
Declarative, typed composition for [Streamlit](https://streamlit.io/). 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.
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
| Traditional scripts | With StreamTree |
|
|
14
|
+
|---------------------|-----------------|
|
|
15
|
+
| Layout and widgets mixed in one long file | `@component` functions return an **element tree** |
|
|
16
|
+
| Many ad hoc `st.session_state` keys | `state()` and helpers scoped to the render path |
|
|
17
|
+
| Structure is hard to inspect or snapshot | `streamtree.testing.render_to_tree()` for tests and docs |
|
|
18
|
+
|
|
19
|
+
StreamTree is an **architecture layer** for Streamlit, not a React-style web framework.
|
|
20
|
+
|
|
21
|
+
## Capabilities
|
|
22
|
+
|
|
23
|
+
- **Components and elements** — `@component`, `render` / `render_app`, layouts (`Page`, `Card`, `Grid`, `VStack`, `Form`, `Tabs`, `Sidebar`, `Routes`, `ErrorBoundary`, and more), and widget wrappers.
|
|
24
|
+
- **App shell (0.3+)** — `App` with a guarded `st.set_page_config`, plus optional sidebar and main composition via `render_app`.
|
|
25
|
+
- **Theming (0.3+)** — `Theme`, `ThemeRoot`, `theme()`, `theme_css()`, and `app_context.provider(theme=...)`.
|
|
26
|
+
- **Background work (0.3+)** — `streamtree.asyncio.submit` and `TaskHandle` for stdlib-thread jobs you poll across reruns.
|
|
27
|
+
- **Forms (0.3+)** — Pydantic-oriented helpers such as `bind_str_fields` and `str_text_inputs` for string fields.
|
|
28
|
+
- **State** — `state`, `toggle_state`, `form_state`, `memo`, `cache`.
|
|
29
|
+
- **Routing and context** — Query-param routing (`streamtree.routing`), `ErrorBoundary`, `streamtree.forms` utilities, and `app_context.provider` / `lookup` for shared values.
|
|
30
|
+
- **Interop** — Inside `@component`, your function body runs during render; you may call `st.*` (columns, metrics, charts, third-party components) and still return an element tree, or `fragment()` when the subtree is fully imperative.
|
|
31
|
+
- **Quality** — Pydantic v2 in the default install, typing-first APIs, and `render_to_tree` for structural tests.
|
|
32
|
+
|
|
33
|
+
Stub optional extras (`tables`, `charts`, `ui`, `auth`, `asyncio`, `async`, `cli`) are reserved for future wrappers; see [Dependency strategy](https://github.com/streamtree-dev/streamtree/blob/main/docs/DEPENDENCY_STRATEGY.md). The `streamtree.asyncio` module ships in the default package.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
Python **3.10+**, with **Streamlit ≥ 1.28**, **Pydantic v2**, and **typing-extensions** (see `pyproject.toml`).
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install streamtree==0.3.0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
From a clone, with dev dependencies:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/streamtree-dev/streamtree.git
|
|
49
|
+
cd streamtree
|
|
50
|
+
uv sync --extra dev
|
|
51
|
+
# or: pip install -e ".[dev]"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from streamtree import component, render
|
|
58
|
+
from streamtree.elements import Button, Card, Page, Text
|
|
59
|
+
from streamtree.state import state
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@component
|
|
63
|
+
def Counter():
|
|
64
|
+
count = state(0)
|
|
65
|
+
return Card(
|
|
66
|
+
Text(f"Count: {count()}"),
|
|
67
|
+
Button("Increment", on_click=lambda: count.increment(1)),
|
|
68
|
+
Button("Reset", on_click=lambda: count.set(0)),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
render(Page(Counter()))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Run examples from the repository root:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
streamlit run examples/counter.py
|
|
80
|
+
streamlit run examples/routed_app.py
|
|
81
|
+
streamlit run examples/app_shell.py
|
|
82
|
+
streamlit run examples/async_bg.py
|
|
83
|
+
streamlit run examples/model_form.py
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Using Streamlit inside components
|
|
87
|
+
|
|
88
|
+
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()`.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import streamlit as st
|
|
92
|
+
|
|
93
|
+
from streamtree import component, fragment, render
|
|
94
|
+
from streamtree.elements import Button, Markdown, Page, TextInput, VStack
|
|
95
|
+
from streamtree.state import state
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@component
|
|
99
|
+
def DashboardHeader():
|
|
100
|
+
band, meta = st.columns([3, 1])
|
|
101
|
+
with band:
|
|
102
|
+
st.title("Operations")
|
|
103
|
+
with meta:
|
|
104
|
+
st.metric("Queue depth", 12, delta=-2)
|
|
105
|
+
|
|
106
|
+
notes = state("", key="header_notes")
|
|
107
|
+
return VStack(
|
|
108
|
+
Markdown("**Notes** (StreamTree `TextInput`):"),
|
|
109
|
+
TextInput("Session notes", value=notes),
|
|
110
|
+
Button("Clear notes", on_click=lambda: notes.set("")),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@component
|
|
115
|
+
def MetricsStrip():
|
|
116
|
+
cols = st.columns(4)
|
|
117
|
+
for i, col in enumerate(cols):
|
|
118
|
+
with col:
|
|
119
|
+
st.metric(f"M{i + 1}", 100 + i * 7, delta=i - 1)
|
|
120
|
+
return fragment()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
render(Page(DashboardHeader(), MetricsStrip()))
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## App shell, theme, and background tasks
|
|
128
|
+
|
|
129
|
+
`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` on reruns.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from streamtree import asyncio, component, render_app
|
|
133
|
+
from streamtree.app import App
|
|
134
|
+
from streamtree.app_context import provider
|
|
135
|
+
from streamtree.elements import Page, Text, ThemeRoot, VStack
|
|
136
|
+
from streamtree.theme import Theme
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@component
|
|
140
|
+
def Body():
|
|
141
|
+
handle = asyncio.submit(lambda: 7, key="demo_job")
|
|
142
|
+
return VStack(
|
|
143
|
+
ThemeRoot(),
|
|
144
|
+
Text(f"status={handle.status()} result={handle.result()}"),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
with provider(theme=Theme(primary_color="#0068c9")):
|
|
150
|
+
render_app(App(page_title="Demo", body=Body()))
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Patterns
|
|
154
|
+
|
|
155
|
+
**Grid of components**
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from streamtree.elements import Grid
|
|
159
|
+
|
|
160
|
+
Grid(UserCard(user1), UserCard(user2), columns=2)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Bound text input**
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from streamtree.elements import TextInput
|
|
167
|
+
from streamtree.state import state
|
|
168
|
+
|
|
169
|
+
search = state("")
|
|
170
|
+
TextInput(label="Search", value=search)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Documentation
|
|
174
|
+
|
|
175
|
+
| Resource | Description |
|
|
176
|
+
|----------|-------------|
|
|
177
|
+
| [Plan](https://github.com/streamtree-dev/streamtree/blob/main/docs/PLAN.md) | Vision, architecture, risks |
|
|
178
|
+
| [Roadmap](https://github.com/streamtree-dev/streamtree/blob/main/docs/ROADMAP.md) | Phased delivery |
|
|
179
|
+
| [Dependency strategy](https://github.com/streamtree-dev/streamtree/blob/main/docs/DEPENDENCY_STRATEGY.md) | Dependencies and optional extras |
|
|
180
|
+
| [CHANGELOG](https://github.com/streamtree-dev/streamtree/blob/main/CHANGELOG.md) | Release history |
|
|
181
|
+
|
|
182
|
+
## Contributing
|
|
183
|
+
|
|
184
|
+
Install dev tools, then run lint, type check, and tests (mirrors CI on Python 3.10–3.12):
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
uv sync --extra dev
|
|
188
|
+
uv run ruff format .
|
|
189
|
+
uv run ruff check src tests
|
|
190
|
+
uv run ty check src
|
|
191
|
+
uv run pytest
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Equivalent with **pip**: `pip install -e ".[dev]"`, then `ruff`, `ty check src`, and `pytest` as above.
|
|
195
|
+
|
|
196
|
+
## Releases
|
|
197
|
+
|
|
198
|
+
**Automated:** Add a **`PYPI_API_TOKEN`** secret to the repository. When `main` is green, push a tag of the form **`v0.3.0`**. The [release workflow](https://github.com/streamtree-dev/streamtree/blob/main/.github/workflows/release.yml) runs lint, type check, pytest (including coverage), builds with `uv build`, and publishes to PyPI.
|
|
199
|
+
|
|
200
|
+
**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.
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT. See [LICENSE](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE).
|