grimoireplot 0.0.1__py3-none-any.whl

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.
@@ -0,0 +1,5 @@
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
+ """Module for GrimoirePlot"""
grimoireplot/client.py ADDED
@@ -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()
grimoireplot/common.py ADDED
@@ -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
@@ -0,0 +1,352 @@
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
+ Script to manually test the grimoire server by pushing some sample plots.
7
+ """
8
+
9
+ import plotly.graph_objects as go
10
+ from grimoireplot.client import push_plot_sync
11
+
12
+
13
+ def create_sample_line_plot():
14
+ """Create a simple line plot."""
15
+ fig = go.Figure()
16
+ fig.add_trace(
17
+ go.Scatter(
18
+ x=[1, 2, 3, 4], y=[10, 11, 12, 13], mode="lines+markers", name="Line 1"
19
+ )
20
+ )
21
+ fig.add_trace(
22
+ go.Scatter(
23
+ x=[1, 2, 3, 4], y=[16, 15, 14, 13], mode="lines+markers", name="Line 2"
24
+ )
25
+ )
26
+ fig.update_layout(
27
+ title="Sample Line Plot", xaxis_title="X Axis", yaxis_title="Y Axis"
28
+ )
29
+ return fig
30
+
31
+
32
+ def create_sample_bar_plot():
33
+ """Create a simple bar plot."""
34
+ fig = go.Figure()
35
+ fig.add_trace(
36
+ go.Bar(
37
+ x=["Category A", "Category B", "Category C"],
38
+ y=[20, 14, 23],
39
+ name="Series 1",
40
+ )
41
+ )
42
+ fig.add_trace(
43
+ go.Bar(
44
+ x=["Category A", "Category B", "Category C"],
45
+ y=[12, 18, 29],
46
+ name="Series 2",
47
+ )
48
+ )
49
+ fig.update_layout(
50
+ title="Sample Bar Plot", xaxis_title="Categories", yaxis_title="Values"
51
+ )
52
+ return fig
53
+
54
+
55
+ def create_sample_scatter_plot():
56
+ """Create a simple scatter plot."""
57
+ fig = go.Figure()
58
+ fig.add_trace(
59
+ go.Scatter(
60
+ x=[1, 2, 3, 4, 5],
61
+ y=[1, 4, 9, 16, 25],
62
+ mode="markers",
63
+ marker=dict(size=10, color="blue"),
64
+ name="Quadratic",
65
+ )
66
+ )
67
+ fig.update_layout(title="Sample Scatter Plot", xaxis_title="X", yaxis_title="Y")
68
+ return fig
69
+
70
+
71
+ def create_sample_histogram():
72
+ """Create a simple histogram."""
73
+ fig = go.Figure()
74
+ fig.add_trace(go.Histogram(x=[1, 1, 2, 3, 3, 3, 4, 4, 5], name="Data"))
75
+ fig.update_layout(
76
+ title="Sample Histogram", xaxis_title="Value", yaxis_title="Frequency"
77
+ )
78
+ return fig
79
+
80
+
81
+ def create_sample_pie_chart():
82
+ """Create a simple pie chart."""
83
+ fig = go.Figure()
84
+ fig.add_trace(
85
+ go.Pie(labels=["A", "B", "C", "D"], values=[15, 30, 45, 10], name="Pie")
86
+ )
87
+ fig.update_layout(title="Sample Pie Chart")
88
+ return fig
89
+
90
+
91
+ def create_sample_box_plot():
92
+ """Create a simple box plot."""
93
+ fig = go.Figure()
94
+ fig.add_trace(go.Box(y=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], name="Box 1"))
95
+ fig.add_trace(go.Box(y=[2, 3, 4, 5, 6, 7, 8, 9, 10, 11], name="Box 2"))
96
+ fig.update_layout(title="Sample Box Plot", yaxis_title="Values")
97
+ return fig
98
+
99
+
100
+ def create_sample_heatmap():
101
+ """Create a simple heatmap."""
102
+ fig = go.Figure()
103
+ fig.add_trace(
104
+ go.Heatmap(
105
+ z=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], x=["A", "B", "C"], y=["X", "Y", "Z"]
106
+ )
107
+ )
108
+ fig.update_layout(title="Sample Heatmap")
109
+ return fig
110
+
111
+
112
+ def create_sample_3d_scatter():
113
+ """Create a simple 3D scatter plot."""
114
+ fig = go.Figure()
115
+ fig.add_trace(
116
+ go.Scatter3d(
117
+ x=[1, 2, 3, 4],
118
+ y=[5, 6, 7, 8],
119
+ z=[9, 10, 11, 12],
120
+ mode="markers",
121
+ name="3D Points",
122
+ )
123
+ )
124
+ fig.update_layout(title="Sample 3D Scatter Plot")
125
+ return fig
126
+
127
+
128
+ def create_sample_area_plot():
129
+ """Create a simple area plot."""
130
+ fig = go.Figure()
131
+ fig.add_trace(
132
+ go.Scatter(x=[1, 2, 3, 4], y=[0, 2, 3, 5], fill="tozeroy", name="Area 1")
133
+ )
134
+ fig.add_trace(
135
+ go.Scatter(x=[1, 2, 3, 4], y=[3, 5, 1, 7], fill="tonexty", name="Area 2")
136
+ )
137
+ fig.update_layout(title="Sample Area Plot", xaxis_title="X", yaxis_title="Y")
138
+ return fig
139
+
140
+
141
+ def create_sample_violin_plot():
142
+ """Create a simple violin plot."""
143
+ fig = go.Figure()
144
+ fig.add_trace(go.Violin(y=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], name="Violin 1"))
145
+ fig.add_trace(go.Violin(y=[2, 3, 4, 5, 6, 7, 8, 9, 10, 11], name="Violin 2"))
146
+ fig.update_layout(title="Sample Violin Plot", yaxis_title="Values")
147
+ return fig
148
+
149
+
150
+ def create_sample_contour_plot():
151
+ """Create a simple contour plot."""
152
+ fig = go.Figure()
153
+ fig.add_trace(
154
+ go.Contour(
155
+ z=[
156
+ [10, 10.625, 12.5, 15.625, 20],
157
+ [5.625, 6.25, 8.125, 11.25, 15.625],
158
+ [2.5, 3.125, 5.0, 8.125, 12.5],
159
+ [0.625, 1.25, 3.125, 6.25, 10.625],
160
+ [0, 0.625, 2.5, 5.625, 10],
161
+ ],
162
+ name="Contour",
163
+ )
164
+ )
165
+ fig.update_layout(title="Sample Contour Plot")
166
+ return fig
167
+
168
+
169
+ def create_sample_surface_plot():
170
+ """Create a simple 3D surface plot."""
171
+ fig = go.Figure()
172
+ fig.add_trace(go.Surface(z=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], name="Surface"))
173
+ fig.update_layout(title="Sample Surface Plot")
174
+ return fig
175
+
176
+
177
+ def create_sample_waterfall():
178
+ """Create a simple waterfall plot."""
179
+ fig = go.Figure()
180
+ fig.add_trace(
181
+ go.Waterfall(
182
+ x=["Start", "A", "B", "C", "Total"],
183
+ y=[0, 10, -5, 8, 0],
184
+ measure=["absolute", "relative", "relative", "relative", "total"],
185
+ name="Waterfall",
186
+ )
187
+ )
188
+ fig.update_layout(title="Sample Waterfall Plot")
189
+ return fig
190
+
191
+
192
+ def create_sample_funnel():
193
+ """Create a simple funnel plot."""
194
+ fig = go.Figure()
195
+ fig.add_trace(
196
+ go.Funnel(
197
+ y=["Stage 1", "Stage 2", "Stage 3", "Stage 4"],
198
+ x=[100, 80, 60, 40],
199
+ name="Funnel",
200
+ )
201
+ )
202
+ fig.update_layout(title="Sample Funnel Plot")
203
+ return fig
204
+
205
+
206
+ if __name__ == "__main__":
207
+ grimoires = [
208
+ "test_grimoire",
209
+ "analysis_grimoire",
210
+ "dashboard_grimoire",
211
+ "extra_grimoire",
212
+ ]
213
+
214
+ for grimoire_name in grimoires:
215
+ print(f"\n--- Pushing plots to {grimoire_name} ---")
216
+
217
+ # Chapter 1: Basic plots (3 plots)
218
+ print("Pushing line plot...")
219
+ line_fig = create_sample_line_plot()
220
+ response = push_plot_sync(grimoire_name, "Chapter 1", "Line Plot", line_fig)
221
+ print(f"Response: {response}")
222
+
223
+ print("Pushing bar plot...")
224
+ bar_fig = create_sample_bar_plot()
225
+ response = push_plot_sync(grimoire_name, "Chapter 1", "Bar Plot", bar_fig)
226
+ print(f"Response: {response}")
227
+
228
+ print("Pushing area plot...")
229
+ area_fig = create_sample_area_plot()
230
+ response = push_plot_sync(grimoire_name, "Chapter 1", "Area Plot", area_fig)
231
+ print(f"Response: {response}")
232
+
233
+ # Chapter 2: Scatter and distribution (3 plots)
234
+ print("Pushing scatter plot...")
235
+ scatter_fig = create_sample_scatter_plot()
236
+ response = push_plot_sync(
237
+ grimoire_name, "Chapter 2", "Scatter Plot", scatter_fig
238
+ )
239
+ print(f"Response: {response}")
240
+
241
+ print("Pushing histogram...")
242
+ hist_fig = create_sample_histogram()
243
+ response = push_plot_sync(grimoire_name, "Chapter 2", "Histogram", hist_fig)
244
+ print(f"Response: {response}")
245
+
246
+ print("Pushing violin plot...")
247
+ violin_fig = create_sample_violin_plot()
248
+ response = push_plot_sync(grimoire_name, "Chapter 2", "Violin Plot", violin_fig)
249
+ print(f"Response: {response}")
250
+
251
+ # Chapter 3: Categorical (2 plots)
252
+ print("Pushing pie chart...")
253
+ pie_fig = create_sample_pie_chart()
254
+ response = push_plot_sync(grimoire_name, "Chapter 3", "Pie Chart", pie_fig)
255
+ print(f"Response: {response}")
256
+
257
+ print("Pushing box plot...")
258
+ box_fig = create_sample_box_plot()
259
+ response = push_plot_sync(grimoire_name, "Chapter 3", "Box Plot", box_fig)
260
+ print(f"Response: {response}")
261
+
262
+ # Chapter 4: Matrix and contours (2 plots)
263
+ print("Pushing heatmap...")
264
+ heat_fig = create_sample_heatmap()
265
+ response = push_plot_sync(grimoire_name, "Chapter 4", "Heatmap", heat_fig)
266
+ print(f"Response: {response}")
267
+
268
+ print("Pushing contour plot...")
269
+ contour_fig = create_sample_contour_plot()
270
+ response = push_plot_sync(
271
+ grimoire_name, "Chapter 4", "Contour Plot", contour_fig
272
+ )
273
+ print(f"Response: {response}")
274
+
275
+ # Chapter 5: 3D visualizations (2 plots)
276
+ print("Pushing 3D scatter plot...")
277
+ scatter3d_fig = create_sample_3d_scatter()
278
+ response = push_plot_sync(
279
+ grimoire_name, "Chapter 5", "3D Scatter", scatter3d_fig
280
+ )
281
+ print(f"Response: {response}")
282
+
283
+ print("Pushing surface plot...")
284
+ surface_fig = create_sample_surface_plot()
285
+ response = push_plot_sync(
286
+ grimoire_name, "Chapter 5", "Surface Plot", surface_fig
287
+ )
288
+ print(f"Response: {response}")
289
+
290
+ # Chapter 6: Specialized plots (2 plots)
291
+ print("Pushing waterfall plot...")
292
+ waterfall_fig = create_sample_waterfall()
293
+ response = push_plot_sync(
294
+ grimoire_name, "Chapter 6", "Waterfall Plot", waterfall_fig
295
+ )
296
+ print(f"Response: {response}")
297
+
298
+ print("Pushing funnel plot...")
299
+ funnel_fig = create_sample_funnel()
300
+ response = push_plot_sync(grimoire_name, "Chapter 6", "Funnel Plot", funnel_fig)
301
+ print(f"Response: {response}")
302
+
303
+ # Chapter 7: Additional basic plots (2 plots)
304
+ print("Pushing additional line plot...")
305
+ line_fig2 = create_sample_line_plot()
306
+ line_fig2.update_layout(title="Additional Line Plot")
307
+ response = push_plot_sync(
308
+ grimoire_name, "Chapter 7", "Additional Line", line_fig2
309
+ )
310
+ print(f"Response: {response}")
311
+
312
+ print("Pushing additional bar plot...")
313
+ bar_fig2 = create_sample_bar_plot()
314
+ bar_fig2.update_layout(title="Additional Bar Plot")
315
+ response = push_plot_sync(
316
+ grimoire_name, "Chapter 7", "Additional Bar", bar_fig2
317
+ )
318
+ print(f"Response: {response}")
319
+
320
+ # Chapter 8: More distribution plots (2 plots)
321
+ print("Pushing additional histogram...")
322
+ hist_fig2 = create_sample_histogram()
323
+ hist_fig2.update_layout(title="Additional Histogram")
324
+ response = push_plot_sync(
325
+ grimoire_name, "Chapter 8", "Additional Histogram", hist_fig2
326
+ )
327
+ print(f"Response: {response}")
328
+
329
+ print("Pushing additional box plot...")
330
+ box_fig2 = create_sample_box_plot()
331
+ box_fig2.update_layout(title="Additional Box Plot")
332
+ response = push_plot_sync(
333
+ grimoire_name, "Chapter 8", "Additional Box", box_fig2
334
+ )
335
+ print(f"Response: {response}")
336
+
337
+ # Chapter 9: Final visualizations (2 plots)
338
+ print("Pushing additional scatter plot...")
339
+ scatter_fig2 = create_sample_scatter_plot()
340
+ scatter_fig2.update_layout(title="Additional Scatter Plot")
341
+ response = push_plot_sync(
342
+ grimoire_name, "Chapter 9", "Additional Scatter", scatter_fig2
343
+ )
344
+ print(f"Response: {response}")
345
+
346
+ print("Pushing additional pie chart...")
347
+ pie_fig2 = create_sample_pie_chart()
348
+ pie_fig2.update_layout(title="Additional Pie Chart")
349
+ response = push_plot_sync(
350
+ grimoire_name, "Chapter 9", "Additional Pie", pie_fig2
351
+ )
352
+ print(f"Response: {response}")