streamtree 0.2.0__tar.gz → 0.4.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 (72) hide show
  1. streamtree-0.4.0/CHANGELOG.md +92 -0
  2. {streamtree-0.2.0 → streamtree-0.4.0}/LICENSE +1 -1
  3. streamtree-0.4.0/PKG-INFO +255 -0
  4. streamtree-0.4.0/README.md +209 -0
  5. streamtree-0.2.0/docs/STREAMTREE_DEPENDENCY_STRATEGY.md → streamtree-0.4.0/docs/DEPENDENCY_STRATEGY.md +70 -43
  6. streamtree-0.2.0/docs/STREAMTREE_PLAN.md → streamtree-0.4.0/docs/PLAN.md +19 -15
  7. streamtree-0.4.0/docs/ROADMAP.md +285 -0
  8. streamtree-0.4.0/examples/app_shell.py +34 -0
  9. streamtree-0.4.0/examples/async_bg.py +24 -0
  10. streamtree-0.4.0/examples/model_form.py +62 -0
  11. streamtree-0.4.0/examples/numeric_nav_demo.py +40 -0
  12. {streamtree-0.2.0 → streamtree-0.4.0}/examples/routed_app.py +4 -1
  13. {streamtree-0.2.0 → streamtree-0.4.0}/pyproject.toml +19 -7
  14. streamtree-0.4.0/src/streamtree/__init__.py +47 -0
  15. streamtree-0.4.0/src/streamtree/app.py +58 -0
  16. streamtree-0.4.0/src/streamtree/asyncio/__init__.py +147 -0
  17. streamtree-0.4.0/src/streamtree/cli.py +64 -0
  18. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/core/__init__.py +2 -1
  19. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/core/component.py +17 -2
  20. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/elements/__init__.py +5 -0
  21. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/elements/layout.py +20 -2
  22. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/elements/widgets.py +57 -2
  23. streamtree-0.4.0/src/streamtree/forms.py +240 -0
  24. streamtree-0.4.0/src/streamtree/helpers/__init__.py +1 -0
  25. streamtree-0.4.0/src/streamtree/helpers/runner.py +32 -0
  26. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/renderers/streamlit.py +110 -25
  27. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/routing.py +16 -8
  28. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/state/__init__.py +2 -0
  29. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/testing/__init__.py +24 -1
  30. streamtree-0.4.0/src/streamtree/theme.py +104 -0
  31. streamtree-0.4.0/src/streamtree.egg-info/PKG-INFO +255 -0
  32. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree.egg-info/SOURCES.txt +21 -3
  33. streamtree-0.4.0/src/streamtree.egg-info/entry_points.txt +2 -0
  34. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree.egg-info/requires.txt +9 -1
  35. streamtree-0.4.0/tests/test_app.py +118 -0
  36. streamtree-0.4.0/tests/test_asyncio_pkg.py +183 -0
  37. streamtree-0.4.0/tests/test_cli.py +59 -0
  38. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_component_and_testing.py +18 -0
  39. streamtree-0.4.0/tests/test_forms_builder.py +61 -0
  40. streamtree-0.4.0/tests/test_forms_numeric.py +217 -0
  41. streamtree-0.4.0/tests/test_helpers_runner.py +34 -0
  42. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_layout_routes.py +21 -2
  43. streamtree-0.4.0/tests/test_package_meta.py +23 -0
  44. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_render_streamlit_mocked.py +156 -15
  45. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_routing.py +50 -10
  46. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_state.py +2 -0
  47. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_streamlit_app.py +3 -1
  48. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_testing_all_nodes.py +4 -0
  49. streamtree-0.4.0/tests/test_theme.py +94 -0
  50. streamtree-0.2.0/CHANGELOG.md +0 -37
  51. streamtree-0.2.0/PKG-INFO +0 -235
  52. streamtree-0.2.0/README.md +0 -194
  53. streamtree-0.2.0/docs/STREAMTREE_ROADMAP.md +0 -292
  54. streamtree-0.2.0/src/streamtree/__init__.py +0 -22
  55. streamtree-0.2.0/src/streamtree/forms.py +0 -68
  56. streamtree-0.2.0/src/streamtree.egg-info/PKG-INFO +0 -235
  57. streamtree-0.2.0/tests/test_package_meta.py +0 -9
  58. {streamtree-0.2.0 → streamtree-0.4.0}/MANIFEST.in +0 -0
  59. {streamtree-0.2.0 → streamtree-0.4.0}/examples/counter.py +0 -0
  60. {streamtree-0.2.0 → streamtree-0.4.0}/setup.cfg +0 -0
  61. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/app_context.py +0 -0
  62. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/core/context.py +0 -0
  63. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/core/element.py +0 -0
  64. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/py.typed +0 -0
  65. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree/renderers/__init__.py +0 -0
  66. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree.egg-info/dependency_links.txt +0 -0
  67. {streamtree-0.2.0 → streamtree-0.4.0}/src/streamtree.egg-info/top_level.txt +0 -0
  68. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_app_context.py +0 -0
  69. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_context.py +0 -0
  70. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_element.py +0 -0
  71. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_forms.py +0 -0
  72. {streamtree-0.2.0 → streamtree-0.4.0}/tests/test_tree.py +0 -0
@@ -0,0 +1,92 @@
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.4.0] — 2026-05-12
9
+
10
+ ### Added
11
+
12
+ - **CLI (optional `[cli]`):** **`streamtree run`** delegates to **`python -m streamlit run`** with forwarded argv; **`streamtree doctor`** prints versions and Typer availability. **`[project.scripts]`** entry **`streamtree`**.
13
+ - **`streamtree.helpers.runner`:** **`build_streamlit_run_argv`**, **`run_streamlit_sync`** (stdlib subprocess; **`FileNotFoundError`** → exit code **127**; empty args → **2**).
14
+ - **`PageLink`** element mapping to **`st.page_link`** (Streamlit **≥ 1.30** in core dependencies).
15
+ - **`App`:** **`initial_sidebar_state`** and **`menu_items`** passed through to **`st.set_page_config`** when set.
16
+ - **`streamtree.forms`:** **`numeric_field_names`**, **`bind_numeric_fields`**, **`number_inputs`** for **`int`** / **`float`** and optional numeric fields.
17
+ - Example **`examples/numeric_nav_demo.py`** and **`examples/streamtree_run_demo.md`** (CLI usage).
18
+
19
+ ### Changed
20
+
21
+ - **Minimum Streamlit** raised to **`>=1.30.0`** (for **`st.page_link`**).
22
+ - **`[cli]`** extra now lists **Typer**; stub extras **`[pages]`** and **`[runner]`** documented (runner helper ships in the default install; **`[runner]`** remains metadata-only).
23
+
24
+ ### Documentation
25
+
26
+ - README: **`streamtree[cli]`**, **`streamtree run`**, Streamlit **1.30+** requirement, **`v0.4.0`** release tag example.
27
+ - [DEPENDENCY_STRATEGY.md](docs/DEPENDENCY_STRATEGY.md): CLI / **`[pages]`** / **`[runner]`** notes.
28
+ - [ROADMAP.md](docs/ROADMAP.md): **0.4.0** release index; Phase 2 “Next” adjusted.
29
+
30
+ ### Fixed
31
+
32
+ - **CI:** **`cli-smoke`** sets job-level **`UV_PYTHON`** so **`uv run streamtree doctor`** reuses the **`[cli]`** sync environment (otherwise **`uv run`** could follow **`.python-version`** and miss Typer).
33
+
34
+ ## [0.3.0] — 2026-05-12
35
+
36
+ ### Added
37
+
38
+ - **Packaging:** optional extra **`[async]`** as a TOML-quoted alias of the empty **`[asyncio]`** stub extra (matches install examples in the plan and dependency strategy).
39
+ - **`streamtree.app.App`** shell with optional sidebar composition, **`apply_page_config`**, **`app_root_element`**, and **`render_app()`** (one-time `st.set_page_config` guard).
40
+ - **`streamtree.theme`**: **`Theme`** (Pydantic), **`theme()`** / **`theme_css()`**, and **`ThemeRoot`** element for CSS injection via `app_context.provider(theme=...)`.
41
+ - **`streamtree.asyncio`**: **`submit()`** / **`TaskHandle`** for daemon-thread background work with session-scoped poll keys (stdlib-only; optional `[asyncio]` / `[async]` stub extras unchanged as meta).
42
+ - **`streamtree.forms`**: **`bind_str_fields`** and **`str_text_inputs`** for declarative `TextInput` grids from Pydantic string fields.
43
+ - Examples **`examples/app_shell.py`**, **`examples/async_bg.py`**, **`examples/model_form.py`**.
44
+
45
+ ### Changed
46
+
47
+ - **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.
48
+ - **`ErrorBoundary.on_error`** is typed as ``Callable[[Exception], None]``, matching the renderer (only `Exception` subclasses are passed through).
49
+ - **`Routes`:** duplicate route names are rejected at construction.
50
+ - **`@component`:** returning ``None`` or a non-``Element`` value raises a clear ``TypeError`` (including in ``render_to_tree(..., expand_components=True)``).
51
+ - **`streamtree.asyncio`:** task status dict updates are serialized with a lock; module and ``submit`` docstrings document rerun-polling semantics.
52
+ - **`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).
53
+ - **`HStack`:** non-empty ``gap`` inserts gutter columns between children (CSS ``min-width`` on a spacer).
54
+
55
+ ### Documentation
56
+
57
+ - README refresh (badges, absolute GitHub links for design docs and workflows); install examples pin **0.3.0**.
58
+ - Design docs live at `docs/PLAN.md`, `docs/ROADMAP.md`, and `docs/DEPENDENCY_STRATEGY.md` with updated cross-links.
59
+ - **StreamTree** is used as the product name in metadata and user-facing defaults (the PyPI / import name remains **`streamtree`**).
60
+
61
+ ## [0.2.0] — 2026-05-12
62
+
63
+ ### Added
64
+
65
+ - **Pydantic** and **typing-extensions** as core dependencies; stub optional extras (`tables`, `charts`, `ui`, `auth`, `asyncio`, `cli`, `all`) per dependency strategy.
66
+ - **`streamtree.app_context`**: `provider()` / `lookup()` / `current_bag()` for rerun-scoped DI-style values.
67
+ - **`streamtree.routing`**: `sync_route` / `set_route` for query-param + session alignment.
68
+ - **`Routes`** layout element (with Streamlit renderer) for one-of-many pages keyed by the active route.
69
+ - **`ErrorBoundary`** element with Streamlit renderer fallback and optional `on_error` callback.
70
+ - **`streamtree.forms`**: `str_field_names`, `model_validate_json`, `format_validation_errors` for Pydantic-first forms.
71
+ - Example **`examples/routed_app.py`**: multi-page navigation + JSON profile validation.
72
+
73
+ ### Changed
74
+
75
+ - 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.
76
+ - Pytest coverage targets `src/streamtree` by path to avoid import-order coverage warnings.
77
+
78
+ ## [0.1.0] — 2026-05-11
79
+
80
+ ### Added
81
+
82
+ - Initial public API: `@component`, `render`, virtual elements (`Page`, layouts, widgets).
83
+ - Session helpers: `state`, `toggle_state`, `form_state`, `session_state`, `memo`, `cache`.
84
+ - Streamlit renderer mapping the virtual tree to `st.*` calls (including `Form` and submit flows).
85
+ - `streamtree.testing.render_to_tree` for structure-focused tests without a live Streamlit session.
86
+ - Runnable example: `examples/counter.py`.
87
+ - Design docs under `docs/` (plan, roadmap, dependency strategy).
88
+
89
+ [0.4.0]: https://github.com/streamtree-dev/streamtree/releases/tag/v0.4.0
90
+ [0.3.0]: https://github.com/streamtree-dev/streamtree/releases/tag/v0.3.0
91
+ [0.2.0]: https://github.com/streamtree-dev/streamtree/releases/tag/v0.2.0
92
+ [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,255 @@
1
+ Metadata-Version: 2.4
2
+ Name: streamtree
3
+ Version: 0.4.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.30.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
+ Requires-Dist: typer>=0.12.3; extra == "cli"
34
+ Provides-Extra: pages
35
+ Provides-Extra: runner
36
+ Provides-Extra: all
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
39
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
40
+ Requires-Dist: pytest-xdist>=3.5.0; extra == "dev"
41
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
42
+ Requires-Dist: ruff>=0.2.0; extra == "dev"
43
+ Requires-Dist: ty>=0.0.30; extra == "dev"
44
+ Requires-Dist: typer>=0.12.3; extra == "dev"
45
+ Dynamic: license-file
46
+
47
+ # StreamTree
48
+
49
+ [![PyPI version](https://img.shields.io/pypi/v/streamtree)](https://pypi.org/project/streamtree/)
50
+ [![Python versions](https://img.shields.io/pypi/pyversions/streamtree)](https://pypi.org/project/streamtree/)
51
+ [![License](https://img.shields.io/pypi/l/streamtree)](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE)
52
+ [![CI](https://img.shields.io/github/actions/workflow/status/streamtree-dev/streamtree/ci.yml?branch=main&label=CI)](https://github.com/streamtree-dev/streamtree/actions/workflows/ci.yml?query=branch%3Amain)
53
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://docs.astral.sh/ruff/)
54
+
55
+ 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.
56
+
57
+ ## Overview
58
+
59
+ | Traditional scripts | With StreamTree |
60
+ |---------------------|-----------------|
61
+ | Layout and widgets mixed in one long file | `@component` functions return an **element tree** |
62
+ | Many ad hoc `st.session_state` keys | `state()` and helpers scoped to the render path |
63
+ | Structure is hard to inspect or snapshot | `streamtree.testing.render_to_tree()` for tests and docs |
64
+
65
+ StreamTree is an **architecture layer** for Streamlit, not a React-style web framework.
66
+
67
+ ## Capabilities
68
+
69
+ - **Components and elements** — `@component`, `render` / `render_app`, layouts (`Page`, `Card`, `Grid`, `VStack`, `Form`, `Tabs`, `Sidebar`, `Routes`, `ErrorBoundary`, and more), and widget wrappers.
70
+ - **App shell (0.3+)** — `App` with a guarded `st.set_page_config`, plus optional sidebar and main composition via `render_app`.
71
+ - **Theming (0.3+)** — `Theme`, `ThemeRoot`, `theme()`, `theme_css()`, and `app_context.provider(theme=...)`.
72
+ - **Background work (0.3+)** — `streamtree.asyncio.submit` and `TaskHandle` for stdlib-thread jobs you poll across reruns.
73
+ - **Forms (0.3+)** — Pydantic-oriented helpers: `bind_str_fields` / `str_text_inputs`, plus **`bind_numeric_fields` / `number_inputs`** (0.4+) for `int` / `float` fields (optional `int | None` / `float | None` use model defaults or `None` for an empty number input).
74
+ - **CLI (0.4+)** — Optional **`streamtree[cli]`**: `streamtree run` delegates to Streamlit; `streamtree doctor` prints versions (see [examples/streamtree_run_demo.md](examples/streamtree_run_demo.md)).
75
+ - **State** — `state`, `toggle_state`, `form_state`, `memo`, `cache`.
76
+ - **Routing and context** — Query-param routing (`streamtree.routing`), `ErrorBoundary`, `streamtree.forms` utilities, and `app_context.provider` / `lookup` for shared values.
77
+ - **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.
78
+ - **Quality** — Pydantic v2 in the default install, typing-first APIs, and `render_to_tree` for structural tests.
79
+
80
+ Optional extras (`tables`, `charts`, `ui`, `auth`, `asyncio`, `async`, `pages`, `runner`) are mostly stubs for future wrappers; **`[cli]`** adds **Typer** and the **`streamtree`** console script (`run`, `doctor`). See [Dependency strategy](https://github.com/streamtree-dev/streamtree/blob/main/docs/DEPENDENCY_STRATEGY.md). The `streamtree.asyncio` module and **`streamtree.helpers.runner`** ship in the default package.
81
+
82
+ ## Requirements
83
+
84
+ Python **3.10+**, with **Streamlit ≥ 1.30** (for `st.page_link` used by `PageLink`), **Pydantic v2**, and **typing-extensions** (see `pyproject.toml`).
85
+
86
+ ## Installation
87
+
88
+ ```bash
89
+ pip install streamtree==0.4.0
90
+ pip install "streamtree[cli]" # Typer + ``streamtree run`` / ``streamtree doctor``
91
+ ```
92
+
93
+ From a clone, with dev dependencies:
94
+
95
+ ```bash
96
+ git clone https://github.com/streamtree-dev/streamtree.git
97
+ cd streamtree
98
+ uv sync --extra dev
99
+ # or: pip install -e ".[dev]"
100
+ ```
101
+
102
+ ## Quick start
103
+
104
+ ```python
105
+ from streamtree import component, render
106
+ from streamtree.elements import Button, Card, Page, Text
107
+ from streamtree.state import state
108
+
109
+
110
+ @component
111
+ def Counter():
112
+ count = state(0)
113
+ return Card(
114
+ Text(f"Count: {count()}"),
115
+ Button("Increment", on_click=lambda: count.increment(1)),
116
+ Button("Reset", on_click=lambda: count.set(0)),
117
+ )
118
+
119
+
120
+ if __name__ == "__main__":
121
+ render(Page(Counter()))
122
+ ```
123
+
124
+ Run examples from the repository root:
125
+
126
+ ```bash
127
+ streamlit run examples/counter.py
128
+ streamlit run examples/routed_app.py
129
+ streamlit run examples/app_shell.py
130
+ streamlit run examples/async_bg.py
131
+ streamlit run examples/model_form.py
132
+ streamlit run examples/numeric_nav_demo.py
133
+ # With Typer installed (``pip install "streamtree[cli]"``):
134
+ streamtree run examples/counter.py
135
+ ```
136
+
137
+ ## Using Streamlit inside components
138
+
139
+ 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()`.
140
+
141
+ ```python
142
+ import streamlit as st
143
+
144
+ from streamtree import component, fragment, render
145
+ from streamtree.elements import Button, Markdown, Page, TextInput, VStack
146
+ from streamtree.state import state
147
+
148
+
149
+ @component
150
+ def DashboardHeader():
151
+ band, meta = st.columns([3, 1])
152
+ with band:
153
+ st.title("Operations")
154
+ with meta:
155
+ st.metric("Queue depth", 12, delta=-2)
156
+
157
+ notes = state("", key="header_notes")
158
+ return VStack(
159
+ Markdown("**Notes** (StreamTree `TextInput`):"),
160
+ TextInput("Session notes", value=notes),
161
+ Button("Clear notes", on_click=lambda: notes.set("")),
162
+ )
163
+
164
+
165
+ @component
166
+ def MetricsStrip():
167
+ cols = st.columns(4)
168
+ for i, col in enumerate(cols):
169
+ with col:
170
+ st.metric(f"M{i + 1}", 100 + i * 7, delta=i - 1)
171
+ return fragment()
172
+
173
+
174
+ if __name__ == "__main__":
175
+ render(Page(DashboardHeader(), MetricsStrip()))
176
+ ```
177
+
178
+ ## App shell, theme, and background tasks
179
+
180
+ `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.
181
+
182
+ ```python
183
+ from streamtree import asyncio, component, render_app
184
+ from streamtree.app import App
185
+ from streamtree.app_context import provider
186
+ from streamtree.elements import Page, Text, ThemeRoot, VStack
187
+ from streamtree.theme import Theme
188
+
189
+
190
+ @component
191
+ def Body():
192
+ handle = asyncio.submit(lambda: 7, key="demo_job")
193
+ return VStack(
194
+ ThemeRoot(),
195
+ Text(f"status={handle.status()} result={handle.result()}"),
196
+ )
197
+
198
+
199
+ if __name__ == "__main__":
200
+ with provider(theme=Theme(primary_color="#0068c9")):
201
+ render_app(App(page_title="Demo", body=Body()))
202
+ ```
203
+
204
+ ## Patterns
205
+
206
+ **Grid of components**
207
+
208
+ ```python
209
+ from streamtree.elements import Grid
210
+
211
+ Grid(UserCard(user1), UserCard(user2), columns=2)
212
+ ```
213
+
214
+ **Bound text input**
215
+
216
+ ```python
217
+ from streamtree.elements import TextInput
218
+ from streamtree.state import state
219
+
220
+ search = state("")
221
+ TextInput(label="Search", value=search)
222
+ ```
223
+
224
+ ## Documentation
225
+
226
+ | Resource | Description |
227
+ |----------|-------------|
228
+ | [Plan](https://github.com/streamtree-dev/streamtree/blob/main/docs/PLAN.md) | Vision, architecture, risks |
229
+ | [Roadmap](https://github.com/streamtree-dev/streamtree/blob/main/docs/ROADMAP.md) | Phased delivery |
230
+ | [Dependency strategy](https://github.com/streamtree-dev/streamtree/blob/main/docs/DEPENDENCY_STRATEGY.md) | Dependencies and optional extras |
231
+ | [CHANGELOG](https://github.com/streamtree-dev/streamtree/blob/main/CHANGELOG.md) | Release history |
232
+
233
+ ## Contributing
234
+
235
+ Install dev tools, then run lint, type check, and tests (mirrors CI on Python 3.10–3.12):
236
+
237
+ ```bash
238
+ uv sync --extra dev
239
+ uv run ruff format .
240
+ uv run ruff check src tests
241
+ uv run ty check src
242
+ uv run pytest
243
+ ```
244
+
245
+ Equivalent with **pip**: `pip install -e ".[dev]"`, then `ruff`, `ty check src`, and `pytest` as above.
246
+
247
+ ## Releases
248
+
249
+ **Automated:** Add a **`PYPI_API_TOKEN`** secret to the repository. When `main` is green, push a tag of the form **`v0.4.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.
250
+
251
+ **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.
252
+
253
+ ## License
254
+
255
+ MIT. See [LICENSE](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE).
@@ -0,0 +1,209 @@
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/streamtree-dev/streamtree/ci.yml?branch=main&label=CI)](https://github.com/streamtree-dev/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: `bind_str_fields` / `str_text_inputs`, plus **`bind_numeric_fields` / `number_inputs`** (0.4+) for `int` / `float` fields (optional `int | None` / `float | None` use model defaults or `None` for an empty number input).
28
+ - **CLI (0.4+)** — Optional **`streamtree[cli]`**: `streamtree run` delegates to Streamlit; `streamtree doctor` prints versions (see [examples/streamtree_run_demo.md](examples/streamtree_run_demo.md)).
29
+ - **State** — `state`, `toggle_state`, `form_state`, `memo`, `cache`.
30
+ - **Routing and context** — Query-param routing (`streamtree.routing`), `ErrorBoundary`, `streamtree.forms` utilities, and `app_context.provider` / `lookup` for shared values.
31
+ - **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.
32
+ - **Quality** — Pydantic v2 in the default install, typing-first APIs, and `render_to_tree` for structural tests.
33
+
34
+ Optional extras (`tables`, `charts`, `ui`, `auth`, `asyncio`, `async`, `pages`, `runner`) are mostly stubs for future wrappers; **`[cli]`** adds **Typer** and the **`streamtree`** console script (`run`, `doctor`). See [Dependency strategy](https://github.com/streamtree-dev/streamtree/blob/main/docs/DEPENDENCY_STRATEGY.md). The `streamtree.asyncio` module and **`streamtree.helpers.runner`** ship in the default package.
35
+
36
+ ## Requirements
37
+
38
+ Python **3.10+**, with **Streamlit ≥ 1.30** (for `st.page_link` used by `PageLink`), **Pydantic v2**, and **typing-extensions** (see `pyproject.toml`).
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install streamtree==0.4.0
44
+ pip install "streamtree[cli]" # Typer + ``streamtree run`` / ``streamtree doctor``
45
+ ```
46
+
47
+ From a clone, with dev dependencies:
48
+
49
+ ```bash
50
+ git clone https://github.com/streamtree-dev/streamtree.git
51
+ cd streamtree
52
+ uv sync --extra dev
53
+ # or: pip install -e ".[dev]"
54
+ ```
55
+
56
+ ## Quick start
57
+
58
+ ```python
59
+ from streamtree import component, render
60
+ from streamtree.elements import Button, Card, Page, Text
61
+ from streamtree.state import state
62
+
63
+
64
+ @component
65
+ def Counter():
66
+ count = state(0)
67
+ return Card(
68
+ Text(f"Count: {count()}"),
69
+ Button("Increment", on_click=lambda: count.increment(1)),
70
+ Button("Reset", on_click=lambda: count.set(0)),
71
+ )
72
+
73
+
74
+ if __name__ == "__main__":
75
+ render(Page(Counter()))
76
+ ```
77
+
78
+ Run examples from the repository root:
79
+
80
+ ```bash
81
+ streamlit run examples/counter.py
82
+ streamlit run examples/routed_app.py
83
+ streamlit run examples/app_shell.py
84
+ streamlit run examples/async_bg.py
85
+ streamlit run examples/model_form.py
86
+ streamlit run examples/numeric_nav_demo.py
87
+ # With Typer installed (``pip install "streamtree[cli]"``):
88
+ streamtree run examples/counter.py
89
+ ```
90
+
91
+ ## Using Streamlit inside components
92
+
93
+ 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()`.
94
+
95
+ ```python
96
+ import streamlit as st
97
+
98
+ from streamtree import component, fragment, render
99
+ from streamtree.elements import Button, Markdown, Page, TextInput, VStack
100
+ from streamtree.state import state
101
+
102
+
103
+ @component
104
+ def DashboardHeader():
105
+ band, meta = st.columns([3, 1])
106
+ with band:
107
+ st.title("Operations")
108
+ with meta:
109
+ st.metric("Queue depth", 12, delta=-2)
110
+
111
+ notes = state("", key="header_notes")
112
+ return VStack(
113
+ Markdown("**Notes** (StreamTree `TextInput`):"),
114
+ TextInput("Session notes", value=notes),
115
+ Button("Clear notes", on_click=lambda: notes.set("")),
116
+ )
117
+
118
+
119
+ @component
120
+ def MetricsStrip():
121
+ cols = st.columns(4)
122
+ for i, col in enumerate(cols):
123
+ with col:
124
+ st.metric(f"M{i + 1}", 100 + i * 7, delta=i - 1)
125
+ return fragment()
126
+
127
+
128
+ if __name__ == "__main__":
129
+ render(Page(DashboardHeader(), MetricsStrip()))
130
+ ```
131
+
132
+ ## App shell, theme, and background tasks
133
+
134
+ `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.
135
+
136
+ ```python
137
+ from streamtree import asyncio, component, render_app
138
+ from streamtree.app import App
139
+ from streamtree.app_context import provider
140
+ from streamtree.elements import Page, Text, ThemeRoot, VStack
141
+ from streamtree.theme import Theme
142
+
143
+
144
+ @component
145
+ def Body():
146
+ handle = asyncio.submit(lambda: 7, key="demo_job")
147
+ return VStack(
148
+ ThemeRoot(),
149
+ Text(f"status={handle.status()} result={handle.result()}"),
150
+ )
151
+
152
+
153
+ if __name__ == "__main__":
154
+ with provider(theme=Theme(primary_color="#0068c9")):
155
+ render_app(App(page_title="Demo", body=Body()))
156
+ ```
157
+
158
+ ## Patterns
159
+
160
+ **Grid of components**
161
+
162
+ ```python
163
+ from streamtree.elements import Grid
164
+
165
+ Grid(UserCard(user1), UserCard(user2), columns=2)
166
+ ```
167
+
168
+ **Bound text input**
169
+
170
+ ```python
171
+ from streamtree.elements import TextInput
172
+ from streamtree.state import state
173
+
174
+ search = state("")
175
+ TextInput(label="Search", value=search)
176
+ ```
177
+
178
+ ## Documentation
179
+
180
+ | Resource | Description |
181
+ |----------|-------------|
182
+ | [Plan](https://github.com/streamtree-dev/streamtree/blob/main/docs/PLAN.md) | Vision, architecture, risks |
183
+ | [Roadmap](https://github.com/streamtree-dev/streamtree/blob/main/docs/ROADMAP.md) | Phased delivery |
184
+ | [Dependency strategy](https://github.com/streamtree-dev/streamtree/blob/main/docs/DEPENDENCY_STRATEGY.md) | Dependencies and optional extras |
185
+ | [CHANGELOG](https://github.com/streamtree-dev/streamtree/blob/main/CHANGELOG.md) | Release history |
186
+
187
+ ## Contributing
188
+
189
+ Install dev tools, then run lint, type check, and tests (mirrors CI on Python 3.10–3.12):
190
+
191
+ ```bash
192
+ uv sync --extra dev
193
+ uv run ruff format .
194
+ uv run ruff check src tests
195
+ uv run ty check src
196
+ uv run pytest
197
+ ```
198
+
199
+ Equivalent with **pip**: `pip install -e ".[dev]"`, then `ruff`, `ty check src`, and `pytest` as above.
200
+
201
+ ## Releases
202
+
203
+ **Automated:** Add a **`PYPI_API_TOKEN`** secret to the repository. When `main` is green, push a tag of the form **`v0.4.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.
204
+
205
+ **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.
206
+
207
+ ## License
208
+
209
+ MIT. See [LICENSE](https://github.com/streamtree-dev/streamtree/blob/main/LICENSE).