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.
Files changed (62) hide show
  1. streamtree-0.3.0/CHANGELOG.md +65 -0
  2. {streamtree-0.2.0 → streamtree-0.3.0}/LICENSE +1 -1
  3. streamtree-0.3.0/PKG-INFO +246 -0
  4. streamtree-0.3.0/README.md +204 -0
  5. streamtree-0.2.0/docs/STREAMTREE_DEPENDENCY_STRATEGY.md → streamtree-0.3.0/docs/DEPENDENCY_STRATEGY.md +44 -42
  6. streamtree-0.2.0/docs/STREAMTREE_PLAN.md → streamtree-0.3.0/docs/PLAN.md +16 -14
  7. streamtree-0.2.0/docs/STREAMTREE_ROADMAP.md → streamtree-0.3.0/docs/ROADMAP.md +32 -24
  8. streamtree-0.3.0/examples/app_shell.py +34 -0
  9. streamtree-0.3.0/examples/async_bg.py +24 -0
  10. streamtree-0.3.0/examples/model_form.py +62 -0
  11. {streamtree-0.2.0 → streamtree-0.3.0}/examples/routed_app.py +4 -1
  12. {streamtree-0.2.0 → streamtree-0.3.0}/pyproject.toml +7 -5
  13. streamtree-0.3.0/src/streamtree/__init__.py +47 -0
  14. streamtree-0.3.0/src/streamtree/app.py +53 -0
  15. streamtree-0.3.0/src/streamtree/asyncio/__init__.py +147 -0
  16. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/core/__init__.py +2 -1
  17. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/core/component.py +17 -2
  18. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/elements/__init__.py +3 -0
  19. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/elements/layout.py +9 -2
  20. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/forms.py +48 -1
  21. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/renderers/streamlit.py +47 -4
  22. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/routing.py +14 -7
  23. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/testing/__init__.py +15 -1
  24. streamtree-0.3.0/src/streamtree/theme.py +104 -0
  25. streamtree-0.3.0/src/streamtree.egg-info/PKG-INFO +246 -0
  26. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree.egg-info/SOURCES.txt +13 -3
  27. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree.egg-info/requires.txt +2 -0
  28. streamtree-0.3.0/tests/test_app.py +93 -0
  29. streamtree-0.3.0/tests/test_asyncio_pkg.py +203 -0
  30. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_component_and_testing.py +18 -0
  31. streamtree-0.3.0/tests/test_forms_builder.py +61 -0
  32. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_layout_routes.py +5 -0
  33. streamtree-0.3.0/tests/test_package_meta.py +22 -0
  34. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_render_streamlit_mocked.py +52 -2
  35. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_routing.py +22 -10
  36. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_testing_all_nodes.py +2 -0
  37. streamtree-0.3.0/tests/test_theme.py +94 -0
  38. streamtree-0.2.0/CHANGELOG.md +0 -37
  39. streamtree-0.2.0/PKG-INFO +0 -235
  40. streamtree-0.2.0/README.md +0 -194
  41. streamtree-0.2.0/src/streamtree/__init__.py +0 -22
  42. streamtree-0.2.0/src/streamtree.egg-info/PKG-INFO +0 -235
  43. streamtree-0.2.0/tests/test_package_meta.py +0 -9
  44. {streamtree-0.2.0 → streamtree-0.3.0}/MANIFEST.in +0 -0
  45. {streamtree-0.2.0 → streamtree-0.3.0}/examples/counter.py +0 -0
  46. {streamtree-0.2.0 → streamtree-0.3.0}/setup.cfg +0 -0
  47. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/app_context.py +0 -0
  48. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/core/context.py +0 -0
  49. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/core/element.py +0 -0
  50. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/elements/widgets.py +0 -0
  51. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/py.typed +0 -0
  52. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/renderers/__init__.py +0 -0
  53. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree/state/__init__.py +0 -0
  54. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree.egg-info/dependency_links.txt +0 -0
  55. {streamtree-0.2.0 → streamtree-0.3.0}/src/streamtree.egg-info/top_level.txt +0 -0
  56. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_app_context.py +0 -0
  57. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_context.py +0 -0
  58. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_element.py +0 -0
  59. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_forms.py +0 -0
  60. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_state.py +0 -0
  61. {streamtree-0.2.0 → streamtree-0.3.0}/tests/test_streamlit_app.py +0 -0
  62. {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
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Streamtree contributors
3
+ Copyright (c) 2026 StreamTree contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/streamtree)](https://pypi.org/project/streamtree/)
46
+ [![Python versions](https://img.shields.io/pypi/pyversions/streamtree)](https://pypi.org/project/streamtree/)
47
+ [![License](https://img.shields.io/pypi/l/streamtree)](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE)
48
+ [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/streamtree/ci.yml?branch=main&label=CI)](https://github.com/eddiethedean/streamtree/actions/workflows/ci.yml?query=branch%3Amain)
49
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](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
+ [![PyPI version](https://img.shields.io/pypi/v/streamtree)](https://pypi.org/project/streamtree/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/streamtree)](https://pypi.org/project/streamtree/)
5
+ [![License](https://img.shields.io/pypi/l/streamtree)](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE)
6
+ [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/streamtree/ci.yml?branch=main&label=CI)](https://github.com/eddiethedean/streamtree/actions/workflows/ci.yml?query=branch%3Amain)
7
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](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).