spyre2 0.1.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 (76) hide show
  1. spyre2-0.1.0/.claude/settings.json +33 -0
  2. spyre2-0.1.0/PKG-INFO +226 -0
  3. spyre2-0.1.0/README.md +187 -0
  4. spyre2-0.1.0/examples/grid_example.py +72 -0
  5. spyre2-0.1.0/examples/plotly_example.py +63 -0
  6. spyre2-0.1.0/examples/sine_example.py +36 -0
  7. spyre2-0.1.0/examples/site_example.py +22 -0
  8. spyre2-0.1.0/examples/slider_examples/slider_site.py +19 -0
  9. spyre2-0.1.0/examples/slider_examples/sliders_altair.py +62 -0
  10. spyre2-0.1.0/examples/slider_examples/sliders_migrate_from_spyre1.py +54 -0
  11. spyre2-0.1.0/examples/slider_examples/sliders_plotly.py +50 -0
  12. spyre2-0.1.0/examples/us_heatmap_example.py +106 -0
  13. spyre2-0.1.0/examples/us_heatmap_examples/unemployment_altair.py +79 -0
  14. spyre2-0.1.0/examples/us_heatmap_examples/unemployment_bokeh.py +88 -0
  15. spyre2-0.1.0/examples/us_heatmap_examples/unemployment_plotly.py +94 -0
  16. spyre2-0.1.0/examples/us_heatmap_examples/unemployment_site.py +19 -0
  17. spyre2-0.1.0/pyproject.toml +52 -0
  18. spyre2-0.1.0/spyre-demo-heatmaps/pyproject.toml +29 -0
  19. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/__init__.py +35 -0
  20. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/app.py +80 -0
  21. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/compat.py +158 -0
  22. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/components/__init__.py +0 -0
  23. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/components/inputs.py +86 -0
  24. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/components/outputs.py +40 -0
  25. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/layout.py +92 -0
  26. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/migrate.py +366 -0
  27. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/renderers/__init__.py +0 -0
  28. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/renderers/altair_renderer.py +10 -0
  29. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/renderers/base.py +47 -0
  30. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/renderers/matplotlib_renderer.py +17 -0
  31. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/renderers/pandas_renderer.py +12 -0
  32. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/renderers/plotly_renderer.py +10 -0
  33. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/server.py +236 -0
  34. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/static/alpine.min.js +5 -0
  35. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/static/spyre2.css +285 -0
  36. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/static/spyre2.js +165 -0
  37. spyre2-0.1.0/spyre-demo-heatmaps/spyre2/templates/base.html.jinja2 +155 -0
  38. spyre2-0.1.0/spyre-demo-sliders/pyproject.toml +29 -0
  39. spyre2-0.1.0/spyre-demo-sliders/spyre2/__init__.py +35 -0
  40. spyre2-0.1.0/spyre-demo-sliders/spyre2/app.py +80 -0
  41. spyre2-0.1.0/spyre-demo-sliders/spyre2/compat.py +158 -0
  42. spyre2-0.1.0/spyre-demo-sliders/spyre2/components/__init__.py +0 -0
  43. spyre2-0.1.0/spyre-demo-sliders/spyre2/components/inputs.py +86 -0
  44. spyre2-0.1.0/spyre-demo-sliders/spyre2/components/outputs.py +40 -0
  45. spyre2-0.1.0/spyre-demo-sliders/spyre2/layout.py +92 -0
  46. spyre2-0.1.0/spyre-demo-sliders/spyre2/migrate.py +366 -0
  47. spyre2-0.1.0/spyre-demo-sliders/spyre2/renderers/__init__.py +0 -0
  48. spyre2-0.1.0/spyre-demo-sliders/spyre2/renderers/altair_renderer.py +10 -0
  49. spyre2-0.1.0/spyre-demo-sliders/spyre2/renderers/base.py +47 -0
  50. spyre2-0.1.0/spyre-demo-sliders/spyre2/renderers/matplotlib_renderer.py +17 -0
  51. spyre2-0.1.0/spyre-demo-sliders/spyre2/renderers/pandas_renderer.py +12 -0
  52. spyre2-0.1.0/spyre-demo-sliders/spyre2/renderers/plotly_renderer.py +10 -0
  53. spyre2-0.1.0/spyre-demo-sliders/spyre2/server.py +236 -0
  54. spyre2-0.1.0/spyre-demo-sliders/spyre2/static/alpine.min.js +5 -0
  55. spyre2-0.1.0/spyre-demo-sliders/spyre2/static/spyre2.css +285 -0
  56. spyre2-0.1.0/spyre-demo-sliders/spyre2/static/spyre2.js +165 -0
  57. spyre2-0.1.0/spyre-demo-sliders/spyre2/templates/base.html.jinja2 +155 -0
  58. spyre2-0.1.0/spyre2/__init__.py +35 -0
  59. spyre2-0.1.0/spyre2/app.py +80 -0
  60. spyre2-0.1.0/spyre2/compat.py +158 -0
  61. spyre2-0.1.0/spyre2/components/__init__.py +0 -0
  62. spyre2-0.1.0/spyre2/components/inputs.py +86 -0
  63. spyre2-0.1.0/spyre2/components/outputs.py +40 -0
  64. spyre2-0.1.0/spyre2/layout.py +92 -0
  65. spyre2-0.1.0/spyre2/migrate.py +366 -0
  66. spyre2-0.1.0/spyre2/renderers/__init__.py +0 -0
  67. spyre2-0.1.0/spyre2/renderers/altair_renderer.py +10 -0
  68. spyre2-0.1.0/spyre2/renderers/base.py +47 -0
  69. spyre2-0.1.0/spyre2/renderers/matplotlib_renderer.py +17 -0
  70. spyre2-0.1.0/spyre2/renderers/pandas_renderer.py +12 -0
  71. spyre2-0.1.0/spyre2/renderers/plotly_renderer.py +10 -0
  72. spyre2-0.1.0/spyre2/server.py +236 -0
  73. spyre2-0.1.0/spyre2/static/alpine.min.js +5 -0
  74. spyre2-0.1.0/spyre2/static/spyre2.css +285 -0
  75. spyre2-0.1.0/spyre2/static/spyre2.js +165 -0
  76. spyre2-0.1.0/spyre2/templates/base.html.jinja2 +155 -0
@@ -0,0 +1,33 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read(//Users/adam/dev/spyre/**)",
5
+ "Bash(find /Users/adam/dev/spyre/tests -type f -name *.py)",
6
+ "WebFetch(domain:raw.githubusercontent.com)",
7
+ "WebFetch(domain:github.com)",
8
+ "Bash(curl -sL \"https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js\" -o /Users/adam/dev/spyre2/spyre2/static/alpine.min.js)",
9
+ "Bash(pip install:*)",
10
+ "Bash(python -c \"import spyre2; print\\(''import OK''\\); a = spyre2.App\\(\\); print\\(''App\\(\\) OK''\\)\")",
11
+ "Bash(python -c \":*)",
12
+ "Bash(MPLBACKEND=Agg python -c \":*)",
13
+ "Bash(.venv/bin/python -c \"import vegafusion\")",
14
+ "Bash(.venv/bin/python -c \":*)",
15
+ "Bash(.venv/bin/python -c \"import altair; print\\(altair.__version__\\)\")",
16
+ "Bash(curl -s \"https://cdn.jsdelivr.net/npm/vega-embed@6/package.json\")",
17
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(''''vega-embed:'''', d[''''version'''']\\); print\\(''''peerDeps:'''', d.get\\(''''peerDependencies'''',{}\\)\\)\")",
18
+ "Bash(open /tmp/unemp_test.html)",
19
+ "Read(//private/tmp/**)",
20
+ "Read(//Users/adam/dev/apps/huggingface/spyre-demo/**)",
21
+ "Read(//Users/adam/dev/apps/huggingface/**)",
22
+ "Bash(chmod +x ~/dev/apps/huggingface/sync_spyre2.sh)",
23
+ "Bash(~/dev/apps/huggingface/sync_spyre2.sh)",
24
+ "Bash(pip show:*)",
25
+ "Bash(python -m build)"
26
+ ],
27
+ "additionalDirectories": [
28
+ "/Users/adam/dev/apps/huggingface/spyre-demo-heatmaps",
29
+ "/Users/adam/dev/apps/huggingface/spyre-demo-sliders",
30
+ "/Users/adam/dev/apps/huggingface"
31
+ ]
32
+ }
33
+ }
spyre2-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: spyre2
3
+ Version: 0.1.0
4
+ Summary: Build interactive data web apps in pure Python
5
+ Project-URL: Homepage, https://github.com/adamhajari/spyre2
6
+ Project-URL: Repository, https://github.com/adamhajari/spyre2
7
+ Project-URL: Issues, https://github.com/adamhajari/spyre2/issues
8
+ Author-email: Adam Hajari <adamhajari@gmail.com>
9
+ License: MIT
10
+ Keywords: dashboard,data,interactive,visualization,web
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
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: Topic :: Internet :: WWW/HTTP :: Dynamic Content
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: fastapi>=0.110.0
23
+ Requires-Dist: jinja2>=3.1.0
24
+ Requires-Dist: numpy>=1.23.0
25
+ Requires-Dist: pandas>=1.5.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: uvicorn[standard]>=0.29.0
28
+ Provides-Extra: all
29
+ Requires-Dist: altair>=5.0.0; extra == 'all'
30
+ Requires-Dist: matplotlib>=3.6.0; extra == 'all'
31
+ Requires-Dist: plotly>=5.0.0; extra == 'all'
32
+ Provides-Extra: altair
33
+ Requires-Dist: altair>=5.0.0; extra == 'altair'
34
+ Provides-Extra: matplotlib
35
+ Requires-Dist: matplotlib>=3.6.0; extra == 'matplotlib'
36
+ Provides-Extra: plotly
37
+ Requires-Dist: plotly>=5.0.0; extra == 'plotly'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # spyre2
41
+
42
+ Build interactive data web apps in pure Python — no HTML, CSS, or JavaScript required.
43
+
44
+ Spyre2 is a ground-up rewrite of [spyre](https://github.com/adamhajari/spyre) using a modern stack: **FastAPI**, **Alpine.js**, and native support for **matplotlib**, **Plotly**, and **Altair**.
45
+
46
+ ---
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install spyre2
52
+ pip install spyre2[matplotlib] # + matplotlib
53
+ pip install spyre2[plotly] # + plotly
54
+ pip install spyre2[all] # everything
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Quickstart
60
+
61
+ ```python
62
+ import numpy as np
63
+ import matplotlib.pyplot as plt
64
+ import spyre2
65
+
66
+ class SineApp(spyre2.App):
67
+ title = "Sine Wave"
68
+
69
+ inputs = [
70
+ spyre2.Slider("frequency", label="Frequency", min=1, max=20, default=5),
71
+ spyre2.Dropdown("color", label="Color",
72
+ options=["steelblue", "crimson", "seagreen"]),
73
+ ]
74
+
75
+ outputs = [
76
+ spyre2.Plot("sine_plot"),
77
+ ]
78
+
79
+ def sine_plot(self, frequency, color):
80
+ fig, ax = plt.subplots(figsize=(8, 4))
81
+ x = np.linspace(0, 2 * np.pi, 500)
82
+ ax.plot(x, np.sin(frequency * x), color=color)
83
+ return fig
84
+
85
+ SineApp().launch()
86
+ ```
87
+
88
+ Open `http://127.0.0.1:8000`.
89
+
90
+ ---
91
+
92
+ ## Inputs
93
+
94
+ | Class | Description |
95
+ |---|---|
96
+ | `Slider(id, min, max, step, default)` | Numeric range slider |
97
+ | `Dropdown(id, options, default)` | Select dropdown |
98
+ | `RadioButtons(id, options, default)` | Radio button group |
99
+ | `CheckboxGroup(id, options, default)` | Multi-select checkboxes |
100
+ | `TextInput(id, default, placeholder)` | Free-text input |
101
+ | `FileUpload(id, accept)` | File picker |
102
+
103
+ All inputs accept a `label` keyword argument. If omitted, the label is inferred from the `id`.
104
+
105
+ ---
106
+
107
+ ## Outputs
108
+
109
+ | Class | Handler return type |
110
+ |---|---|
111
+ | `Plot(id)` | `matplotlib.figure.Figure` or `plotly.Figure` or `altair.Chart` |
112
+ | `Table(id)` | `pandas.DataFrame` |
113
+ | `HTML(id)` | `str` (HTML) |
114
+ | `Download(id)` | `(filename: str, content: str \| bytes)` |
115
+
116
+ The handler method name must match the output `id`:
117
+
118
+ ```python
119
+ outputs = [spyre2.Plot("my_plot")]
120
+
121
+ def my_plot(self, **kwargs): # method name = output id
122
+ ...
123
+ return fig
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Layouts
129
+
130
+ ### Sidebar (default)
131
+
132
+ Controls on the left, outputs stacked on the right. No configuration needed.
133
+
134
+ ### Grid
135
+
136
+ ```python
137
+ from spyre2 import Layout
138
+
139
+ class MyApp(spyre2.App):
140
+ layout = Layout.grid([
141
+ ["controls", "controls" ],
142
+ ["plot_a", "plot_b" ],
143
+ ["big_table", "big_table" ],
144
+ ])
145
+ ```
146
+
147
+ Repeat an ID across adjacent cells to span columns. `"controls"` is a reserved name for the input panel.
148
+
149
+ ### Tabs
150
+
151
+ ```python
152
+ layout = Layout.tabs({
153
+ "Overview": ["trend_plot"],
154
+ "Detail": ["data_table", "bar_chart"],
155
+ })
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Multiple chart libraries
161
+
162
+ The chart library is detected automatically from the return type — no configuration needed.
163
+
164
+ ```python
165
+ # matplotlib
166
+ def my_plot(self, x):
167
+ fig, ax = plt.subplots()
168
+ ax.plot(...)
169
+ return fig # → rendered as SVG
170
+
171
+ # Plotly
172
+ import plotly.express as px
173
+ def my_plot(self, x):
174
+ return px.scatter(df, x="a", y="b") # → rendered via Plotly.js
175
+
176
+ # Altair
177
+ import altair as alt
178
+ def my_plot(self, x):
179
+ return alt.Chart(df).mark_line()... # → rendered via Vega-Embed
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Jupyter notebooks
185
+
186
+ `app.launch()` detects when it's running inside a Jupyter kernel and automatically starts the server in a background thread and displays an inline `IFrame`.
187
+
188
+ ```python
189
+ SineApp().launch(port=8765) # displays inline in the notebook
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Migrating from spyre v1
195
+
196
+ Use the included CLI tool to mechanically convert a v1 app:
197
+
198
+ ```bash
199
+ spyre2-migrate myapp.py # preview conversion
200
+ spyre2-migrate myapp.py -o new.py # write to new file
201
+ spyre2-migrate myapp.py --in-place # overwrite (creates .bak backup)
202
+ ```
203
+
204
+ The tool converts imports, `inputs`/`outputs` list-of-dicts, and renames `getPlot`/`getTable` etc., leaving `# TODO` comments where manual cleanup is needed.
205
+
206
+ **What changes:**
207
+
208
+ | spyre v1 | spyre2 |
209
+ |---|---|
210
+ | `from spyre import server` | `import spyre2` |
211
+ | `class App(server.App)` | `class App(spyre2.App)` |
212
+ | `inputs = [{"type": "slider", "key": "x", ...}]` | `inputs = [spyre2.Slider("x", ...)]` |
213
+ | `def getPlot(self, params): x = params["freq"]` | `def my_plot(self, freq):` |
214
+ | CherryPy on port 9093 | uvicorn on port 8000 |
215
+
216
+ For apps that can't be fully migrated yet, `spyre2.compat.App` accepts the old dict-based syntax (deprecated, will be removed in a future release).
217
+
218
+ ---
219
+
220
+ ## Examples
221
+
222
+ | File | Demonstrates |
223
+ |---|---|
224
+ | `examples/sine_example.py` | Sidebar layout, matplotlib, slider + dropdown |
225
+ | `examples/grid_example.py` | Grid layout, matplotlib, multiple outputs + table |
226
+ | `examples/plotly_example.py` | Tabs layout, Plotly, scatter + histogram |
spyre2-0.1.0/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # spyre2
2
+
3
+ Build interactive data web apps in pure Python — no HTML, CSS, or JavaScript required.
4
+
5
+ Spyre2 is a ground-up rewrite of [spyre](https://github.com/adamhajari/spyre) using a modern stack: **FastAPI**, **Alpine.js**, and native support for **matplotlib**, **Plotly**, and **Altair**.
6
+
7
+ ---
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install spyre2
13
+ pip install spyre2[matplotlib] # + matplotlib
14
+ pip install spyre2[plotly] # + plotly
15
+ pip install spyre2[all] # everything
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Quickstart
21
+
22
+ ```python
23
+ import numpy as np
24
+ import matplotlib.pyplot as plt
25
+ import spyre2
26
+
27
+ class SineApp(spyre2.App):
28
+ title = "Sine Wave"
29
+
30
+ inputs = [
31
+ spyre2.Slider("frequency", label="Frequency", min=1, max=20, default=5),
32
+ spyre2.Dropdown("color", label="Color",
33
+ options=["steelblue", "crimson", "seagreen"]),
34
+ ]
35
+
36
+ outputs = [
37
+ spyre2.Plot("sine_plot"),
38
+ ]
39
+
40
+ def sine_plot(self, frequency, color):
41
+ fig, ax = plt.subplots(figsize=(8, 4))
42
+ x = np.linspace(0, 2 * np.pi, 500)
43
+ ax.plot(x, np.sin(frequency * x), color=color)
44
+ return fig
45
+
46
+ SineApp().launch()
47
+ ```
48
+
49
+ Open `http://127.0.0.1:8000`.
50
+
51
+ ---
52
+
53
+ ## Inputs
54
+
55
+ | Class | Description |
56
+ |---|---|
57
+ | `Slider(id, min, max, step, default)` | Numeric range slider |
58
+ | `Dropdown(id, options, default)` | Select dropdown |
59
+ | `RadioButtons(id, options, default)` | Radio button group |
60
+ | `CheckboxGroup(id, options, default)` | Multi-select checkboxes |
61
+ | `TextInput(id, default, placeholder)` | Free-text input |
62
+ | `FileUpload(id, accept)` | File picker |
63
+
64
+ All inputs accept a `label` keyword argument. If omitted, the label is inferred from the `id`.
65
+
66
+ ---
67
+
68
+ ## Outputs
69
+
70
+ | Class | Handler return type |
71
+ |---|---|
72
+ | `Plot(id)` | `matplotlib.figure.Figure` or `plotly.Figure` or `altair.Chart` |
73
+ | `Table(id)` | `pandas.DataFrame` |
74
+ | `HTML(id)` | `str` (HTML) |
75
+ | `Download(id)` | `(filename: str, content: str \| bytes)` |
76
+
77
+ The handler method name must match the output `id`:
78
+
79
+ ```python
80
+ outputs = [spyre2.Plot("my_plot")]
81
+
82
+ def my_plot(self, **kwargs): # method name = output id
83
+ ...
84
+ return fig
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Layouts
90
+
91
+ ### Sidebar (default)
92
+
93
+ Controls on the left, outputs stacked on the right. No configuration needed.
94
+
95
+ ### Grid
96
+
97
+ ```python
98
+ from spyre2 import Layout
99
+
100
+ class MyApp(spyre2.App):
101
+ layout = Layout.grid([
102
+ ["controls", "controls" ],
103
+ ["plot_a", "plot_b" ],
104
+ ["big_table", "big_table" ],
105
+ ])
106
+ ```
107
+
108
+ Repeat an ID across adjacent cells to span columns. `"controls"` is a reserved name for the input panel.
109
+
110
+ ### Tabs
111
+
112
+ ```python
113
+ layout = Layout.tabs({
114
+ "Overview": ["trend_plot"],
115
+ "Detail": ["data_table", "bar_chart"],
116
+ })
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Multiple chart libraries
122
+
123
+ The chart library is detected automatically from the return type — no configuration needed.
124
+
125
+ ```python
126
+ # matplotlib
127
+ def my_plot(self, x):
128
+ fig, ax = plt.subplots()
129
+ ax.plot(...)
130
+ return fig # → rendered as SVG
131
+
132
+ # Plotly
133
+ import plotly.express as px
134
+ def my_plot(self, x):
135
+ return px.scatter(df, x="a", y="b") # → rendered via Plotly.js
136
+
137
+ # Altair
138
+ import altair as alt
139
+ def my_plot(self, x):
140
+ return alt.Chart(df).mark_line()... # → rendered via Vega-Embed
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Jupyter notebooks
146
+
147
+ `app.launch()` detects when it's running inside a Jupyter kernel and automatically starts the server in a background thread and displays an inline `IFrame`.
148
+
149
+ ```python
150
+ SineApp().launch(port=8765) # displays inline in the notebook
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Migrating from spyre v1
156
+
157
+ Use the included CLI tool to mechanically convert a v1 app:
158
+
159
+ ```bash
160
+ spyre2-migrate myapp.py # preview conversion
161
+ spyre2-migrate myapp.py -o new.py # write to new file
162
+ spyre2-migrate myapp.py --in-place # overwrite (creates .bak backup)
163
+ ```
164
+
165
+ The tool converts imports, `inputs`/`outputs` list-of-dicts, and renames `getPlot`/`getTable` etc., leaving `# TODO` comments where manual cleanup is needed.
166
+
167
+ **What changes:**
168
+
169
+ | spyre v1 | spyre2 |
170
+ |---|---|
171
+ | `from spyre import server` | `import spyre2` |
172
+ | `class App(server.App)` | `class App(spyre2.App)` |
173
+ | `inputs = [{"type": "slider", "key": "x", ...}]` | `inputs = [spyre2.Slider("x", ...)]` |
174
+ | `def getPlot(self, params): x = params["freq"]` | `def my_plot(self, freq):` |
175
+ | CherryPy on port 9093 | uvicorn on port 8000 |
176
+
177
+ For apps that can't be fully migrated yet, `spyre2.compat.App` accepts the old dict-based syntax (deprecated, will be removed in a future release).
178
+
179
+ ---
180
+
181
+ ## Examples
182
+
183
+ | File | Demonstrates |
184
+ |---|---|
185
+ | `examples/sine_example.py` | Sidebar layout, matplotlib, slider + dropdown |
186
+ | `examples/grid_example.py` | Grid layout, matplotlib, multiple outputs + table |
187
+ | `examples/plotly_example.py` | Tabs layout, Plotly, scatter + histogram |
@@ -0,0 +1,72 @@
1
+ """
2
+ Grid layout example: two plots side-by-side + a data table below.
3
+ Demonstrates Layout.grid() and returning a pandas DataFrame from a Table output.
4
+ """
5
+ import numpy as np
6
+ import pandas as pd
7
+ import matplotlib.pyplot as plt
8
+ import sys
9
+ sys.path.insert(0, "/Users/adam/dev/spyre2")
10
+
11
+ import spyre2
12
+ from spyre2.layout import Layout
13
+
14
+
15
+ class WaveApp(spyre2.App):
16
+ title = "Wave Comparison"
17
+
18
+ layout = Layout.grid([
19
+ ["controls", "controls" ],
20
+ ["sine_plot", "cos_plot" ],
21
+ ["data_table","data_table"],
22
+ ])
23
+
24
+ inputs = [
25
+ spyre2.Slider("frequency", label="Frequency", min=1, max=10, default=3),
26
+ spyre2.Slider("points", label="Sample Points", min=10, max=200, default=100),
27
+ spyre2.Dropdown("style", label="Line Style",
28
+ options=["solid", "dashed", "dotted"], default="solid"),
29
+ ]
30
+
31
+ outputs = [
32
+ spyre2.Plot("sine_plot", label="Sine"),
33
+ spyre2.Plot("cos_plot", label="Cosine"),
34
+ spyre2.Table("data_table", label="Sample Data"),
35
+ ]
36
+
37
+ def _data(self, frequency, points):
38
+ x = np.linspace(0, 2 * np.pi, int(points))
39
+ return x, np.sin(float(frequency) * x), np.cos(float(frequency) * x)
40
+
41
+ def sine_plot(self, frequency, points, style):
42
+ x, y, _ = self._data(frequency, points)
43
+ fig, ax = plt.subplots(figsize=(5, 3))
44
+ ls = {"solid": "-", "dashed": "--", "dotted": ":"}[style]
45
+ ax.plot(x, y, ls, color="steelblue", linewidth=2)
46
+ ax.set_title(f"sin({frequency}x)")
47
+ ax.grid(True, alpha=0.3)
48
+ fig.tight_layout()
49
+ return fig
50
+
51
+ def cos_plot(self, frequency, points, style):
52
+ x, _, y = self._data(frequency, points)
53
+ fig, ax = plt.subplots(figsize=(5, 3))
54
+ ls = {"solid": "-", "dashed": "--", "dotted": ":"}[style]
55
+ ax.plot(x, y, ls, color="crimson", linewidth=2)
56
+ ax.set_title(f"cos({frequency}x)")
57
+ ax.grid(True, alpha=0.3)
58
+ fig.tight_layout()
59
+ return fig
60
+
61
+ def data_table(self, frequency, points, style):
62
+ x, s, c = self._data(frequency, points)
63
+ step = max(1, int(points) // 10)
64
+ return pd.DataFrame({
65
+ "x": x[::step].round(3),
66
+ "sin": s[::step].round(4),
67
+ "cos": c[::step].round(4),
68
+ })
69
+
70
+
71
+ if __name__ == "__main__":
72
+ WaveApp().launch()
@@ -0,0 +1,63 @@
1
+ """
2
+ Plotly example: interactive scatter + histogram using tabs layout.
3
+ Demonstrates that returning a plotly Figure is handled automatically.
4
+ """
5
+ import numpy as np
6
+ import pandas as pd
7
+ import sys
8
+ sys.path.insert(0, "/Users/adam/dev/spyre2")
9
+
10
+ import spyre2
11
+ from spyre2.layout import Layout
12
+
13
+ try:
14
+ import plotly.express as px
15
+ except ImportError:
16
+ raise SystemExit("Install plotly first: pip install plotly")
17
+
18
+
19
+ class ScatterApp(spyre2.App):
20
+ title = "Plotly Demo"
21
+
22
+ layout = Layout.tabs({
23
+ "Scatter": ["scatter_plot"],
24
+ "Distribution": ["hist_plot", "summary_table"],
25
+ })
26
+
27
+ inputs = [
28
+ spyre2.Slider("n_points", label="Points", min=50, max=500, default=200),
29
+ spyre2.Slider("noise", label="Noise", min=0.1, max=3.0, step=0.1, default=1.0),
30
+ spyre2.Dropdown("color_scale", label="Color Scale",
31
+ options=["viridis", "plasma", "blues"], default="viridis"),
32
+ ]
33
+
34
+ outputs = [
35
+ spyre2.Plot("scatter_plot", label="Scatter"),
36
+ spyre2.Plot("hist_plot", label="Distribution"),
37
+ spyre2.Table("summary_table", label="Stats"),
38
+ ]
39
+
40
+ def _make_df(self, n_points, noise):
41
+ rng = np.random.default_rng(42)
42
+ n = int(n_points)
43
+ x = rng.uniform(0, 10, n)
44
+ y = 2 * x + rng.normal(0, float(noise), n)
45
+ return pd.DataFrame({"x": x.round(3), "y": y.round(3)})
46
+
47
+ def scatter_plot(self, n_points, noise, color_scale):
48
+ df = self._make_df(n_points, noise)
49
+ return px.scatter(df, x="x", y="y", color="y",
50
+ color_continuous_scale=color_scale,
51
+ title=f"y = 2x + noise (σ={noise})")
52
+
53
+ def hist_plot(self, n_points, noise, color_scale):
54
+ df = self._make_df(n_points, noise)
55
+ return px.histogram(df, x="y", nbins=30, title="Distribution of y")
56
+
57
+ def summary_table(self, n_points, noise, color_scale):
58
+ df = self._make_df(n_points, noise)
59
+ return df.describe().reset_index().rename(columns={"index": "stat"})
60
+
61
+
62
+ if __name__ == "__main__":
63
+ ScatterApp().launch()
@@ -0,0 +1,36 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ import sys
4
+ sys.path.insert(0, "/Users/adam/dev/spyre2")
5
+
6
+ import spyre2
7
+
8
+
9
+ class SineApp(spyre2.App):
10
+ title = "Sine Wave Explorer"
11
+
12
+ inputs = [
13
+ spyre2.Slider("frequency", label="Frequency", min=1, max=20, default=5),
14
+ spyre2.Slider("amplitude", label="Amplitude", min=0.1, max=5.0, step=0.1, default=1.0),
15
+ spyre2.Dropdown("color", label="Line Color",
16
+ options=["steelblue", "crimson", "seagreen"], default="steelblue"),
17
+ ]
18
+
19
+ outputs = [
20
+ spyre2.Plot("sine_plot"),
21
+ ]
22
+
23
+ def sine_plot(self, frequency, amplitude, color):
24
+ fig, ax = plt.subplots(figsize=(8, 4))
25
+ x = np.linspace(0, 2 * np.pi, 500)
26
+ ax.plot(x, amplitude * np.sin(float(frequency) * x), color=color, linewidth=2)
27
+ ax.set_xlabel("x")
28
+ ax.set_ylabel("y")
29
+ ax.set_title(f"y = {amplitude} · sin({frequency}x)")
30
+ ax.grid(True, alpha=0.3)
31
+ fig.tight_layout()
32
+ return fig
33
+
34
+
35
+ if __name__ == "__main__":
36
+ SineApp().launch()
@@ -0,0 +1,22 @@
1
+ """
2
+ Multi-app site example: serves all spyre2 examples under one server.
3
+ """
4
+ import sys
5
+ sys.path.insert(0, "/Users/adam/dev/spyre2")
6
+
7
+ from sine_example import SineApp
8
+ from grid_example import WaveApp
9
+ from plotly_example import ScatterApp
10
+ from us_heatmap_example import USHeatmap
11
+
12
+ import spyre2
13
+
14
+ site = spyre2.Site(
15
+ (SineApp, "/sine", "Sine Wave"),
16
+ (WaveApp, "/waves", "Wave Comparison"),
17
+ (ScatterApp, "/scatter","Scatter / Distribution"),
18
+ (USHeatmap, "/map", "US Heatmap"),
19
+ )
20
+
21
+ if __name__ == "__main__":
22
+ site.launch()
@@ -0,0 +1,19 @@
1
+ """
2
+ Multi-app site example: serves all spyre2 examples under one server.
3
+ """
4
+ # import sys
5
+ # sys.path.insert(0, "/Users/adam/dev/spyre2")
6
+
7
+ from sliders_altair import SlidersApp as SlidersAppAltair
8
+ from sliders_migrate_from_spyre1 import SlidersApp as SlidersAppMigrated
9
+ from sliders_plotly import SlidersApp as SlidersAppPlotly
10
+ import spyre2
11
+
12
+ site = spyre2.Site(
13
+ (SlidersAppAltair, "/altair", "Example Using Altair"),
14
+ (SlidersAppPlotly, "/plotly", "Example Using Plotly"),
15
+ (SlidersAppMigrated, "/migrated_from_spyre_1","Example Migrated from Spyre1"),
16
+ )
17
+
18
+ if __name__ == "__main__":
19
+ site.launch()