grimoireplot 0.0.1__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.
- grimoireplot-0.0.1/PKG-INFO +217 -0
- grimoireplot-0.0.1/README.md +197 -0
- grimoireplot-0.0.1/grimoireplot/__init__.py +5 -0
- grimoireplot-0.0.1/grimoireplot/client.py +166 -0
- grimoireplot-0.0.1/grimoireplot/common.py +32 -0
- grimoireplot-0.0.1/grimoireplot/create_some_plots.py +352 -0
- grimoireplot-0.0.1/grimoireplot/main.py +284 -0
- grimoireplot-0.0.1/grimoireplot/models.py +210 -0
- grimoireplot-0.0.1/grimoireplot/server.py +90 -0
- grimoireplot-0.0.1/grimoireplot/ui.py +200 -0
- grimoireplot-0.0.1/grimoireplot/ui_elements.py +772 -0
- grimoireplot-0.0.1/pyproject.toml +43 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: grimoireplot
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: GrimoirePlot is a live dashboard of plotly-compatible plots of remote data
|
|
5
|
+
Author: William Droz
|
|
6
|
+
Author-email: William Droz <william.droz@idiap.ch>
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Requires-Dist: aiohttp[speedups]>=3.13.3
|
|
9
|
+
Requires-Dist: loguru>=0.7.3
|
|
10
|
+
Requires-Dist: nicegui>=3.5.0,<4
|
|
11
|
+
Requires-Dist: plotly>=6.5.2
|
|
12
|
+
Requires-Dist: python-dotenv>=1.2.1
|
|
13
|
+
Requires-Dist: requests>=2.32.5
|
|
14
|
+
Requires-Dist: sqlmodel>=0.0.31
|
|
15
|
+
Requires-Dist: pytest>=8.0 ; extra == 'dev'
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Project-URL: Home, https://github.com/idiap/GrimoirePlot
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# GrimoirePlot
|
|
22
|
+
|
|
23
|
+
*GrimoirePlot is a live dashboard of plotly-compatible plots of remote data*
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv pip install grimoireplot # not yet on pypi, will setup ci/cd on github later on
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or install from source:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# git clone the repo
|
|
37
|
+
cd grimoireplot
|
|
38
|
+
uv sync --extra dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Installation as a tool
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv tool install grimoireplot # not yet on pypi, will setup ci/cd on github later on
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### 1. Start the Server
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
grimoireplot serve --host localhost --port 8080
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then open your browser at `http://localhost:8080` to see the dashboard.
|
|
56
|
+
|
|
57
|
+
### 2. Push Sample Plots (Test the Server)
|
|
58
|
+
|
|
59
|
+
In another terminal, push some sample plots to verify everything works:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
grimoireplot push-samples --host localhost --port 8080
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## CLI Reference
|
|
66
|
+
|
|
67
|
+
### `grimoireplot serve`
|
|
68
|
+
|
|
69
|
+
Start the GrimoirePlot dashboard server.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
grimoireplot serve [--host HOST] [--port PORT]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| Option | Default | Description |
|
|
76
|
+
|--------|---------|-------------|
|
|
77
|
+
| `--host` | `localhost` | Host to bind the server |
|
|
78
|
+
| `--port` | `8080` | Port to bind the server |
|
|
79
|
+
|
|
80
|
+
### `grimoireplot push-samples`
|
|
81
|
+
|
|
82
|
+
Push sample plots to test the server.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
grimoireplot push-samples [--host HOST] [--port PORT] [--secret SECRET] [--grimoire-name NAME]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Option | Default | Description |
|
|
89
|
+
|--------|---------|-------------|
|
|
90
|
+
| `--host` | `localhost` | Server host |
|
|
91
|
+
| `--port` | `8080` | Server port |
|
|
92
|
+
| `--secret` | `IDidntSetASecret` | Authentication secret |
|
|
93
|
+
| `--grimoire-name` | `test_grimoire` | Name of the grimoire to create |
|
|
94
|
+
|
|
95
|
+
### `grimoireplot live-test`
|
|
96
|
+
|
|
97
|
+
Test live plot updates by continuously adding datapoints to a line plot.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
grimoireplot live-test [--host HOST] [--port PORT] [--secret SECRET] [--grimoire-name NAME] [--interval SECONDS] [--max-points N]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Option | Default | Description |
|
|
104
|
+
|--------|---------|-------------|
|
|
105
|
+
| `--host` | `localhost` | Server host |
|
|
106
|
+
| `--port` | `8080` | Server port |
|
|
107
|
+
| `--secret` | `IDidntSetASecret` | Authentication secret |
|
|
108
|
+
| `--grimoire-name` | `live_test` | Name of the grimoire to create |
|
|
109
|
+
| `--interval` | `0.2` | Interval between datapoints in seconds |
|
|
110
|
+
| `--max-points` | `0` | Maximum number of points (0 = unlimited) |
|
|
111
|
+
|
|
112
|
+
## Programmatic Usage
|
|
113
|
+
|
|
114
|
+
### Sending Plots from Python
|
|
115
|
+
|
|
116
|
+
GrimoirePlot organizes plots in a hierarchy: **Grimoire** → **Chapter** → **Plot**
|
|
117
|
+
|
|
118
|
+
#### Synchronous API
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import plotly.graph_objects as go
|
|
122
|
+
from grimoireplot.client import push_plot_sync
|
|
123
|
+
|
|
124
|
+
# Create a Plotly figure
|
|
125
|
+
fig = go.Figure()
|
|
126
|
+
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13], mode='lines+markers'))
|
|
127
|
+
fig.update_layout(title='My Plot')
|
|
128
|
+
|
|
129
|
+
# Push to the server
|
|
130
|
+
response = push_plot_sync(
|
|
131
|
+
grimoire_name="my_experiment",
|
|
132
|
+
chapter_name="training_metrics",
|
|
133
|
+
plot_name="loss_curve",
|
|
134
|
+
fig=fig,
|
|
135
|
+
grimoire_server="http://localhost:8080",
|
|
136
|
+
grimoire_secret="your-secret",
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Asynchronous API
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import asyncio
|
|
144
|
+
import plotly.graph_objects as go
|
|
145
|
+
from grimoireplot.client import push_plot
|
|
146
|
+
|
|
147
|
+
async def main():
|
|
148
|
+
fig = go.Figure()
|
|
149
|
+
fig.add_trace(go.Bar(x=['A', 'B', 'C'], y=[20, 14, 23]))
|
|
150
|
+
fig.update_layout(title='Async Plot')
|
|
151
|
+
|
|
152
|
+
response = await push_plot(
|
|
153
|
+
grimoire_name="my_experiment",
|
|
154
|
+
chapter_name="results",
|
|
155
|
+
plot_name="bar_chart",
|
|
156
|
+
fig=fig,
|
|
157
|
+
grimoire_server="http://localhost:8080",
|
|
158
|
+
grimoire_secret="your-secret",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
asyncio.run(main())
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Integration Example: Training Loop
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import plotly.graph_objects as go
|
|
168
|
+
from grimoireplot.client import push_plot_sync
|
|
169
|
+
|
|
170
|
+
losses = []
|
|
171
|
+
|
|
172
|
+
for epoch in range(100):
|
|
173
|
+
loss = train_one_epoch() # Your training code
|
|
174
|
+
losses.append(loss)
|
|
175
|
+
|
|
176
|
+
# Update the plot every 10 epochs
|
|
177
|
+
if epoch % 10 == 0:
|
|
178
|
+
fig = go.Figure()
|
|
179
|
+
fig.add_trace(go.Scatter(y=losses, mode='lines', name='Training Loss'))
|
|
180
|
+
fig.update_layout(title=f'Training Progress (Epoch {epoch})',
|
|
181
|
+
xaxis_title='Epoch', yaxis_title='Loss')
|
|
182
|
+
|
|
183
|
+
push_plot_sync(
|
|
184
|
+
grimoire_name="experiment_001",
|
|
185
|
+
chapter_name="training",
|
|
186
|
+
plot_name="loss",
|
|
187
|
+
fig=fig,
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Configuration
|
|
192
|
+
|
|
193
|
+
GrimoirePlot can be configured via environment variables:
|
|
194
|
+
|
|
195
|
+
| Variable | Default | Description |
|
|
196
|
+
|----------|---------|-------------|
|
|
197
|
+
| `GRIMOIRE_SERVER` | `http://localhost:8080` | Default server URL |
|
|
198
|
+
| `GRIMOIRE_SECRET` | `IDidntSetASecret` | Authentication secret |
|
|
199
|
+
|
|
200
|
+
You can also use a `.env` file in your project directory.
|
|
201
|
+
|
|
202
|
+
## Testing
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# you need to install with --extra dev
|
|
206
|
+
GRIMOIRE_TEST=true uv run pytest
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Concepts
|
|
210
|
+
|
|
211
|
+
- **Grimoire**: A collection of related visualizations (e.g., an experiment)
|
|
212
|
+
- **Chapter**: A group of plots within a grimoire (e.g., training metrics, evaluation results)
|
|
213
|
+
- **Plot**: A single Plotly figure
|
|
214
|
+
|
|
215
|
+
## Acknowledgments
|
|
216
|
+
|
|
217
|
+
GrimoirePlot is inspired by [visdom](https://github.com/fossasia/visdom)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# GrimoirePlot
|
|
2
|
+
|
|
3
|
+
*GrimoirePlot is a live dashboard of plotly-compatible plots of remote data*
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv pip install grimoireplot # not yet on pypi, will setup ci/cd on github later on
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or install from source:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# git clone the repo
|
|
17
|
+
cd grimoireplot
|
|
18
|
+
uv sync --extra dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Installation as a tool
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv tool install grimoireplot # not yet on pypi, will setup ci/cd on github later on
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Start the Server
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
grimoireplot serve --host localhost --port 8080
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then open your browser at `http://localhost:8080` to see the dashboard.
|
|
36
|
+
|
|
37
|
+
### 2. Push Sample Plots (Test the Server)
|
|
38
|
+
|
|
39
|
+
In another terminal, push some sample plots to verify everything works:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
grimoireplot push-samples --host localhost --port 8080
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## CLI Reference
|
|
46
|
+
|
|
47
|
+
### `grimoireplot serve`
|
|
48
|
+
|
|
49
|
+
Start the GrimoirePlot dashboard server.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
grimoireplot serve [--host HOST] [--port PORT]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
| Option | Default | Description |
|
|
56
|
+
|--------|---------|-------------|
|
|
57
|
+
| `--host` | `localhost` | Host to bind the server |
|
|
58
|
+
| `--port` | `8080` | Port to bind the server |
|
|
59
|
+
|
|
60
|
+
### `grimoireplot push-samples`
|
|
61
|
+
|
|
62
|
+
Push sample plots to test the server.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
grimoireplot push-samples [--host HOST] [--port PORT] [--secret SECRET] [--grimoire-name NAME]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Option | Default | Description |
|
|
69
|
+
|--------|---------|-------------|
|
|
70
|
+
| `--host` | `localhost` | Server host |
|
|
71
|
+
| `--port` | `8080` | Server port |
|
|
72
|
+
| `--secret` | `IDidntSetASecret` | Authentication secret |
|
|
73
|
+
| `--grimoire-name` | `test_grimoire` | Name of the grimoire to create |
|
|
74
|
+
|
|
75
|
+
### `grimoireplot live-test`
|
|
76
|
+
|
|
77
|
+
Test live plot updates by continuously adding datapoints to a line plot.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
grimoireplot live-test [--host HOST] [--port PORT] [--secret SECRET] [--grimoire-name NAME] [--interval SECONDS] [--max-points N]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
| Option | Default | Description |
|
|
84
|
+
|--------|---------|-------------|
|
|
85
|
+
| `--host` | `localhost` | Server host |
|
|
86
|
+
| `--port` | `8080` | Server port |
|
|
87
|
+
| `--secret` | `IDidntSetASecret` | Authentication secret |
|
|
88
|
+
| `--grimoire-name` | `live_test` | Name of the grimoire to create |
|
|
89
|
+
| `--interval` | `0.2` | Interval between datapoints in seconds |
|
|
90
|
+
| `--max-points` | `0` | Maximum number of points (0 = unlimited) |
|
|
91
|
+
|
|
92
|
+
## Programmatic Usage
|
|
93
|
+
|
|
94
|
+
### Sending Plots from Python
|
|
95
|
+
|
|
96
|
+
GrimoirePlot organizes plots in a hierarchy: **Grimoire** → **Chapter** → **Plot**
|
|
97
|
+
|
|
98
|
+
#### Synchronous API
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import plotly.graph_objects as go
|
|
102
|
+
from grimoireplot.client import push_plot_sync
|
|
103
|
+
|
|
104
|
+
# Create a Plotly figure
|
|
105
|
+
fig = go.Figure()
|
|
106
|
+
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13], mode='lines+markers'))
|
|
107
|
+
fig.update_layout(title='My Plot')
|
|
108
|
+
|
|
109
|
+
# Push to the server
|
|
110
|
+
response = push_plot_sync(
|
|
111
|
+
grimoire_name="my_experiment",
|
|
112
|
+
chapter_name="training_metrics",
|
|
113
|
+
plot_name="loss_curve",
|
|
114
|
+
fig=fig,
|
|
115
|
+
grimoire_server="http://localhost:8080",
|
|
116
|
+
grimoire_secret="your-secret",
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Asynchronous API
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import asyncio
|
|
124
|
+
import plotly.graph_objects as go
|
|
125
|
+
from grimoireplot.client import push_plot
|
|
126
|
+
|
|
127
|
+
async def main():
|
|
128
|
+
fig = go.Figure()
|
|
129
|
+
fig.add_trace(go.Bar(x=['A', 'B', 'C'], y=[20, 14, 23]))
|
|
130
|
+
fig.update_layout(title='Async Plot')
|
|
131
|
+
|
|
132
|
+
response = await push_plot(
|
|
133
|
+
grimoire_name="my_experiment",
|
|
134
|
+
chapter_name="results",
|
|
135
|
+
plot_name="bar_chart",
|
|
136
|
+
fig=fig,
|
|
137
|
+
grimoire_server="http://localhost:8080",
|
|
138
|
+
grimoire_secret="your-secret",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
asyncio.run(main())
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Integration Example: Training Loop
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import plotly.graph_objects as go
|
|
148
|
+
from grimoireplot.client import push_plot_sync
|
|
149
|
+
|
|
150
|
+
losses = []
|
|
151
|
+
|
|
152
|
+
for epoch in range(100):
|
|
153
|
+
loss = train_one_epoch() # Your training code
|
|
154
|
+
losses.append(loss)
|
|
155
|
+
|
|
156
|
+
# Update the plot every 10 epochs
|
|
157
|
+
if epoch % 10 == 0:
|
|
158
|
+
fig = go.Figure()
|
|
159
|
+
fig.add_trace(go.Scatter(y=losses, mode='lines', name='Training Loss'))
|
|
160
|
+
fig.update_layout(title=f'Training Progress (Epoch {epoch})',
|
|
161
|
+
xaxis_title='Epoch', yaxis_title='Loss')
|
|
162
|
+
|
|
163
|
+
push_plot_sync(
|
|
164
|
+
grimoire_name="experiment_001",
|
|
165
|
+
chapter_name="training",
|
|
166
|
+
plot_name="loss",
|
|
167
|
+
fig=fig,
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Configuration
|
|
172
|
+
|
|
173
|
+
GrimoirePlot can be configured via environment variables:
|
|
174
|
+
|
|
175
|
+
| Variable | Default | Description |
|
|
176
|
+
|----------|---------|-------------|
|
|
177
|
+
| `GRIMOIRE_SERVER` | `http://localhost:8080` | Default server URL |
|
|
178
|
+
| `GRIMOIRE_SECRET` | `IDidntSetASecret` | Authentication secret |
|
|
179
|
+
|
|
180
|
+
You can also use a `.env` file in your project directory.
|
|
181
|
+
|
|
182
|
+
## Testing
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# you need to install with --extra dev
|
|
186
|
+
GRIMOIRE_TEST=true uv run pytest
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Concepts
|
|
190
|
+
|
|
191
|
+
- **Grimoire**: A collection of related visualizations (e.g., an experiment)
|
|
192
|
+
- **Chapter**: A group of plots within a grimoire (e.g., training metrics, evaluation results)
|
|
193
|
+
- **Plot**: A single Plotly figure
|
|
194
|
+
|
|
195
|
+
## Acknowledgments
|
|
196
|
+
|
|
197
|
+
GrimoirePlot is inspired by [visdom](https://github.com/fossasia/visdom)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright © 2026 Idiap Research Institute <contact@idiap.ch>
|
|
2
|
+
# SPDX-FileContributor: William Droz <william.droz@idiap.ch>
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Client module that push plots
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from plotly.graph_objects import Figure
|
|
10
|
+
import aiohttp
|
|
11
|
+
import requests
|
|
12
|
+
from grimoireplot.common import get_grimoire_secret, get_grimoire_server
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
default_secret = get_grimoire_secret()
|
|
16
|
+
default_server = get_grimoire_server()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def push_plot_sync(
|
|
20
|
+
grimoire_name: str,
|
|
21
|
+
chapter_name: str,
|
|
22
|
+
plot_name: str,
|
|
23
|
+
fig: Figure,
|
|
24
|
+
grimoire_secret: str = default_secret,
|
|
25
|
+
grimoire_server: str = default_server,
|
|
26
|
+
) -> dict:
|
|
27
|
+
"""Push a plot to the grimoire server.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
grimoire_name (str): Name of the grimoire.
|
|
31
|
+
chapter_name (str): Name of the chapter.
|
|
32
|
+
plot_name (str): Name of the plot.
|
|
33
|
+
fig (Figure): Plotly figure to push.
|
|
34
|
+
grimoire_secret (str, optional): Secret for authentication. Defaults to default_secret.
|
|
35
|
+
grimoire_server (str, optional): Grimoire server URL. Defaults to default_server.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
dict: Response from the server.
|
|
39
|
+
"""
|
|
40
|
+
json_data = fig.to_json()
|
|
41
|
+
match json_data:
|
|
42
|
+
case str():
|
|
43
|
+
return push_plot_json_sync(
|
|
44
|
+
grimoire_name=grimoire_name,
|
|
45
|
+
chapter_name=chapter_name,
|
|
46
|
+
plot_name=plot_name,
|
|
47
|
+
json_data=json_data,
|
|
48
|
+
grimoire_secret=grimoire_secret,
|
|
49
|
+
grimoire_server=grimoire_server,
|
|
50
|
+
)
|
|
51
|
+
case _:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"fig.to_json() did not return a string, maybe fig is invalid?"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def push_plot_json_sync(
|
|
58
|
+
grimoire_name: str,
|
|
59
|
+
chapter_name: str,
|
|
60
|
+
plot_name: str,
|
|
61
|
+
json_data: str,
|
|
62
|
+
grimoire_secret: str = default_secret,
|
|
63
|
+
grimoire_server: str = default_server,
|
|
64
|
+
) -> dict:
|
|
65
|
+
"""Push a plot to the grimoire server.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
grimoire_name (str): Name of the grimoire.
|
|
69
|
+
chapter_name (str): Name of the chapter.
|
|
70
|
+
plot_name (str): Name of the plot.
|
|
71
|
+
json_data (str): JSON representation of the plotly figure.
|
|
72
|
+
grimoire_secret (str, optional): Secret for authentication. Defaults to default_secret.
|
|
73
|
+
grimoire_server (str, optional): Grimoire server URL. Defaults to defualt_server.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
dict: Response from the server.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
url = f"{grimoire_server}/add_plot"
|
|
80
|
+
headers = {"grimoire-secret": grimoire_secret}
|
|
81
|
+
|
|
82
|
+
payload = {
|
|
83
|
+
"grimoire_name": grimoire_name,
|
|
84
|
+
"chapter_name": chapter_name,
|
|
85
|
+
"plot_name": plot_name,
|
|
86
|
+
"json_data": json_data,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
response = requests.post(url, headers=headers, json=payload)
|
|
90
|
+
response.raise_for_status()
|
|
91
|
+
return response.json()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def push_plot(
|
|
95
|
+
grimoire_name: str,
|
|
96
|
+
chapter_name: str,
|
|
97
|
+
plot_name: str,
|
|
98
|
+
fig: Figure,
|
|
99
|
+
grimoire_secret: str = default_secret,
|
|
100
|
+
grimoire_server: str = default_server,
|
|
101
|
+
) -> dict:
|
|
102
|
+
"""Push a plot to the grimoire server asynchronously.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
grimoire_name (str): Name of the grimoire.
|
|
106
|
+
chapter_name (str): Name of the chapter.
|
|
107
|
+
plot_name (str): Name of the plot.
|
|
108
|
+
fig (Figure): Plotly figure to push.
|
|
109
|
+
grimoire_secret (str, optional): Secret for authentication. Defaults to default_secret.
|
|
110
|
+
grimoire_server (str, optional): Grimoire server URL. Defaults to default_server.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
dict: Response from the server.
|
|
114
|
+
"""
|
|
115
|
+
json_data = fig.to_json()
|
|
116
|
+
match json_data:
|
|
117
|
+
case str():
|
|
118
|
+
return await push_plot_json(
|
|
119
|
+
grimoire_name=grimoire_name,
|
|
120
|
+
chapter_name=chapter_name,
|
|
121
|
+
plot_name=plot_name,
|
|
122
|
+
json_data=json_data,
|
|
123
|
+
grimoire_secret=grimoire_secret,
|
|
124
|
+
grimoire_server=grimoire_server,
|
|
125
|
+
)
|
|
126
|
+
case _:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
"fig.to_json() did not return a string, maybe fig is invalid?"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def push_plot_json(
|
|
133
|
+
grimoire_name: str,
|
|
134
|
+
chapter_name: str,
|
|
135
|
+
plot_name: str,
|
|
136
|
+
json_data: str,
|
|
137
|
+
grimoire_secret: str = default_secret,
|
|
138
|
+
grimoire_server: str = default_server,
|
|
139
|
+
) -> dict:
|
|
140
|
+
"""Push a plot to the grimoire server asynchronously.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
grimoire_name (str): Name of the grimoire.
|
|
144
|
+
chapter_name (str): Name of the chapter.
|
|
145
|
+
plot_name (str): Name of the plot.
|
|
146
|
+
json_data (str): JSON representation of the plotly figure.
|
|
147
|
+
grimoire_secret (str, optional): Secret for authentication. Defaults to default_secret.
|
|
148
|
+
grimoire_server (str, optional): Grimoire server URL. Defaults to default_server.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
dict: Response from the server.
|
|
152
|
+
"""
|
|
153
|
+
url = f"{grimoire_server}/add_plot"
|
|
154
|
+
headers = {"grimoire-secret": grimoire_secret}
|
|
155
|
+
|
|
156
|
+
payload = {
|
|
157
|
+
"grimoire_name": grimoire_name,
|
|
158
|
+
"chapter_name": chapter_name,
|
|
159
|
+
"plot_name": plot_name,
|
|
160
|
+
"json_data": json_data,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async with aiohttp.ClientSession() as session:
|
|
164
|
+
async with session.post(url, headers=headers, json=payload) as response:
|
|
165
|
+
response.raise_for_status()
|
|
166
|
+
return await response.json()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright © 2026 Idiap Research Institute <contact@idiap.ch>
|
|
2
|
+
# SPDX-FileContributor: William Droz <william.droz@idiap.ch>
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Common module for grimoireplot client and server
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
from loguru import logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_grimoire_secret() -> str:
|
|
18
|
+
if (grimoire_secret := os.environ.get("GRIMOIRE_SECRET")) is None:
|
|
19
|
+
logger.warning("GRIMOIRE_SECRET not set; using default secret")
|
|
20
|
+
grimoire_secret = "IDidntSetASecret"
|
|
21
|
+
|
|
22
|
+
return grimoire_secret
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_grimoire_server() -> str:
|
|
26
|
+
if (grimoire_server := os.environ.get("GRIMOIRE_SERVER")) is None:
|
|
27
|
+
grimoire_server = "http://localhost:8080"
|
|
28
|
+
logger.warning(
|
|
29
|
+
f"GRIMOIRE_SERVER not set; using default server {grimoire_server}"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return grimoire_server
|