syd 0.1.6__py3-none-any.whl → 0.1.7__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,531 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, Generic, List, TypeVar, Union, Callable
3
+ from dash import dcc, html
4
+
5
+
6
+ from ..parameters import (
7
+ Parameter,
8
+ TextParameter,
9
+ SelectionParameter,
10
+ MultipleSelectionParameter,
11
+ BooleanParameter,
12
+ IntegerParameter,
13
+ FloatParameter,
14
+ IntegerRangeParameter,
15
+ FloatRangeParameter,
16
+ UnboundedIntegerParameter,
17
+ UnboundedFloatParameter,
18
+ ButtonAction,
19
+ )
20
+
21
+ T = TypeVar("T", bound=Parameter[Any])
22
+ C = TypeVar("C")
23
+
24
+
25
+ class BaseComponent(Generic[T, C], ABC):
26
+ """
27
+ Abstract base class for all parameter components.
28
+
29
+ This class defines the common interface and shared functionality
30
+ for components that correspond to different parameter types.
31
+ """
32
+
33
+ _component: C
34
+ _callbacks: List[Dict[str, Union[Callable, Union[str, List[str]]]]]
35
+ _is_action: bool = False
36
+ _id: str
37
+
38
+ def __init__(
39
+ self,
40
+ parameter: T,
41
+ component_id: str,
42
+ width: str = "auto",
43
+ margin: str = "3px 0px",
44
+ label_width: str = "initial",
45
+ ):
46
+ self._id = component_id
47
+ self._component = self._create_component(parameter, width, margin, label_width)
48
+ self._callbacks = []
49
+
50
+ @abstractmethod
51
+ def _create_component(
52
+ self,
53
+ parameter: T,
54
+ width: str = "auto",
55
+ margin: str = "3px 0px",
56
+ label_width: str = "initial",
57
+ ) -> C:
58
+ """Create and return the appropriate Plotly component."""
59
+ pass
60
+
61
+ @property
62
+ def component(self) -> C:
63
+ """Get the underlying Plotly component."""
64
+ return self._component
65
+
66
+ @property
67
+ def id(self) -> str:
68
+ """Get the component ID."""
69
+ return self._id
70
+
71
+ def matches_parameter(self, parameter: T) -> bool:
72
+ """Check if the component matches the parameter."""
73
+ return True # Base implementation, override as needed
74
+
75
+ def update_from_parameter(self, parameter: T) -> None:
76
+ """Update the component from the parameter."""
77
+ self.extra_updates_from_parameter(parameter)
78
+
79
+ def extra_updates_from_parameter(self, parameter: T) -> None:
80
+ """Extra updates from the parameter."""
81
+ pass
82
+
83
+
84
+ class TextComponent(BaseComponent[TextParameter, Any]):
85
+ """Component for text parameters."""
86
+
87
+ def _create_component(
88
+ self,
89
+ parameter: TextParameter,
90
+ width: str = "auto",
91
+ margin: str = "3px 0px",
92
+ label_width: str = "initial",
93
+ ) -> Any:
94
+ return html.Div(
95
+ [
96
+ html.Label(parameter.name, style={"width": label_width}),
97
+ html.Div(
98
+ dcc.Input(
99
+ id=self._id,
100
+ type="text",
101
+ value=parameter.value,
102
+ ),
103
+ style={"width": width, "margin": margin},
104
+ ),
105
+ ]
106
+ )
107
+
108
+
109
+ class BooleanComponent(BaseComponent[BooleanParameter, Any]):
110
+ """Component for boolean parameters."""
111
+
112
+ def _create_component(
113
+ self,
114
+ parameter: BooleanParameter,
115
+ width: str = "auto",
116
+ margin: str = "3px 0px",
117
+ label_width: str = "initial",
118
+ ) -> Any:
119
+ return html.Div(
120
+ [
121
+ html.Div(
122
+ dcc.Checklist(
123
+ id=self._id,
124
+ options=[{"label": parameter.name, "value": "checked"}],
125
+ value=["checked"] if parameter.value else [],
126
+ ),
127
+ style={"width": width, "margin": margin},
128
+ )
129
+ ]
130
+ )
131
+
132
+
133
+ class SelectionComponent(BaseComponent[SelectionParameter, Any]):
134
+ """Component for single selection parameters."""
135
+
136
+ def _create_component(
137
+ self,
138
+ parameter: SelectionParameter,
139
+ width: str = "auto",
140
+ margin: str = "3px 0px",
141
+ label_width: str = "initial",
142
+ ) -> Any:
143
+ return html.Div(
144
+ [
145
+ html.Label(parameter.name, style={"width": label_width}),
146
+ html.Div(
147
+ dcc.Dropdown(
148
+ id=self._id,
149
+ options=[
150
+ {"label": opt, "value": opt} for opt in parameter.options
151
+ ],
152
+ value=parameter.value,
153
+ ),
154
+ style={"width": width, "margin": margin},
155
+ ),
156
+ ]
157
+ )
158
+
159
+ def matches_parameter(self, parameter: SelectionParameter) -> bool:
160
+ """Check if the component matches the parameter."""
161
+ current_options = [
162
+ opt["value"] for opt in self._component.children[1].children.options
163
+ ]
164
+ return current_options == parameter.options
165
+
166
+ def extra_updates_from_parameter(self, parameter: SelectionParameter) -> None:
167
+ """Extra updates from the parameter."""
168
+ self._component.children[1].children.options = [
169
+ {"label": opt, "value": opt} for opt in parameter.options
170
+ ]
171
+
172
+
173
+ class MultipleSelectionComponent(BaseComponent[MultipleSelectionParameter, Any]):
174
+ """Component for multiple selection parameters."""
175
+
176
+ def _create_component(
177
+ self,
178
+ parameter: MultipleSelectionParameter,
179
+ width: str = "auto",
180
+ margin: str = "3px 0px",
181
+ label_width: str = "initial",
182
+ ) -> Any:
183
+ return html.Div(
184
+ [
185
+ html.Label(parameter.name, style={"width": label_width}),
186
+ html.Div(
187
+ dcc.Dropdown(
188
+ id=self._id,
189
+ options=[
190
+ {"label": opt, "value": opt} for opt in parameter.options
191
+ ],
192
+ value=parameter.value,
193
+ multi=True,
194
+ ),
195
+ style={"width": width, "margin": margin},
196
+ ),
197
+ ]
198
+ )
199
+
200
+ def matches_parameter(self, parameter: MultipleSelectionParameter) -> bool:
201
+ """Check if the component matches the parameter."""
202
+ current_options = [
203
+ opt["value"] for opt in self._component.children[1].children.options
204
+ ]
205
+ return current_options == parameter.options
206
+
207
+ def extra_updates_from_parameter(
208
+ self, parameter: MultipleSelectionParameter
209
+ ) -> None:
210
+ """Extra updates from the parameter."""
211
+ self._component.children[1].children.options = [
212
+ {"label": opt, "value": opt} for opt in parameter.options
213
+ ]
214
+
215
+
216
+ class SliderComponent(BaseComponent[Union[IntegerParameter, FloatParameter], Any]):
217
+ """Base component for numeric sliders."""
218
+
219
+ def _create_component(
220
+ self,
221
+ parameter: Union[IntegerParameter, FloatParameter],
222
+ width: str = "auto",
223
+ margin: str = "3px 0px",
224
+ label_width: str = "initial",
225
+ ) -> Any:
226
+ return html.Div(
227
+ [
228
+ html.Label(parameter.name, style={"width": label_width}),
229
+ dcc.Slider(
230
+ id=self._id,
231
+ min=parameter.min_value,
232
+ max=parameter.max_value,
233
+ value=parameter.value,
234
+ step=getattr(parameter, "step", 1),
235
+ marks={
236
+ i: str(i)
237
+ for i in range(parameter.min_value, parameter.max_value + 1)
238
+ },
239
+ style={"width": width, "margin": margin},
240
+ ),
241
+ ]
242
+ )
243
+
244
+
245
+ class IntegerComponent(BaseComponent[IntegerParameter, Any]):
246
+ """Component for integer parameters."""
247
+
248
+ def _create_component(
249
+ self,
250
+ parameter: IntegerParameter,
251
+ width: str = "auto",
252
+ margin: str = "3px 0px",
253
+ label_width: str = "initial",
254
+ ) -> Any:
255
+ return html.Div(
256
+ [
257
+ html.Label(parameter.name, style={"width": label_width}),
258
+ html.Div(
259
+ dcc.Slider(
260
+ id=self._id,
261
+ min=parameter.min_value,
262
+ max=parameter.max_value,
263
+ value=parameter.value,
264
+ step=1,
265
+ marks={
266
+ i: str(i)
267
+ for i in range(parameter.min_value, parameter.max_value + 1)
268
+ },
269
+ ),
270
+ style={"width": width, "margin": margin},
271
+ ),
272
+ ]
273
+ )
274
+
275
+
276
+ class FloatComponent(BaseComponent[FloatParameter, Any]):
277
+ """Component for float parameters."""
278
+
279
+ def _create_component(
280
+ self,
281
+ parameter: FloatParameter,
282
+ width: str = "auto",
283
+ margin: str = "3px 0px",
284
+ label_width: str = "initial",
285
+ ) -> Any:
286
+ return html.Div(
287
+ [
288
+ html.Label(parameter.name, style={"width": label_width}),
289
+ html.Div(
290
+ dcc.Slider(
291
+ id=self._id,
292
+ min=parameter.min_value,
293
+ max=parameter.max_value,
294
+ value=parameter.value,
295
+ step=parameter.step,
296
+ marks={
297
+ i: str(i)
298
+ for i in range(
299
+ int(parameter.min_value), int(parameter.max_value) + 1
300
+ )
301
+ },
302
+ ),
303
+ style={"width": width, "margin": margin},
304
+ ),
305
+ ]
306
+ )
307
+
308
+
309
+ class RangeSliderComponent(
310
+ BaseComponent[Union[IntegerRangeParameter, FloatRangeParameter], Any]
311
+ ):
312
+ """Base component for range sliders."""
313
+
314
+ def _create_component(
315
+ self,
316
+ parameter: Union[IntegerRangeParameter, FloatRangeParameter],
317
+ width: str = "auto",
318
+ margin: str = "3px 0px",
319
+ label_width: str = "initial",
320
+ ) -> Any:
321
+ return html.Div(
322
+ [
323
+ html.Label(parameter.name, style={"width": label_width}),
324
+ dcc.RangeSlider(
325
+ id=self._id,
326
+ min=parameter.min_value,
327
+ max=parameter.max_value,
328
+ value=parameter.value,
329
+ step=getattr(parameter, "step", 1),
330
+ marks={
331
+ i: str(i)
332
+ for i in range(parameter.min_value, parameter.max_value + 1)
333
+ },
334
+ style={"width": width, "margin": margin},
335
+ ),
336
+ ]
337
+ )
338
+
339
+
340
+ class IntegerRangeComponent(BaseComponent[IntegerRangeParameter, Any]):
341
+ """Component for integer range parameters."""
342
+
343
+ def _create_component(
344
+ self,
345
+ parameter: IntegerRangeParameter,
346
+ width: str = "auto",
347
+ margin: str = "3px 0px",
348
+ label_width: str = "initial",
349
+ ) -> Any:
350
+ return html.Div(
351
+ [
352
+ html.Label(parameter.name, style={"width": label_width}),
353
+ html.Div(
354
+ dcc.RangeSlider(
355
+ id=self._id,
356
+ min=parameter.min_value,
357
+ max=parameter.max_value,
358
+ value=parameter.value,
359
+ step=1,
360
+ marks={
361
+ i: str(i)
362
+ for i in range(parameter.min_value, parameter.max_value + 1)
363
+ },
364
+ ),
365
+ style={"width": width, "margin": margin},
366
+ ),
367
+ ]
368
+ )
369
+
370
+
371
+ class FloatRangeComponent(BaseComponent[FloatRangeParameter, Any]):
372
+ """Component for float range parameters."""
373
+
374
+ def _create_component(
375
+ self,
376
+ parameter: FloatRangeParameter,
377
+ width: str = "auto",
378
+ margin: str = "3px 0px",
379
+ label_width: str = "initial",
380
+ ) -> Any:
381
+ return html.Div(
382
+ [
383
+ html.Label(parameter.name, style={"width": label_width}),
384
+ html.Div(
385
+ dcc.RangeSlider(
386
+ id=self._id,
387
+ min=parameter.min_value,
388
+ max=parameter.max_value,
389
+ value=parameter.value,
390
+ step=parameter.step,
391
+ marks={
392
+ i: str(i)
393
+ for i in range(
394
+ int(parameter.min_value), int(parameter.max_value) + 1
395
+ )
396
+ },
397
+ ),
398
+ style={"width": width, "margin": margin},
399
+ ),
400
+ ]
401
+ )
402
+
403
+
404
+ class UnboundedIntegerComponent(BaseComponent[UnboundedIntegerParameter, Any]):
405
+ """Component for unbounded integer parameters."""
406
+
407
+ def _create_component(
408
+ self,
409
+ parameter: UnboundedIntegerParameter,
410
+ width: str = "auto",
411
+ margin: str = "3px 0px",
412
+ label_width: str = "initial",
413
+ ) -> Any:
414
+ return html.Div(
415
+ [
416
+ html.Label(parameter.name, style={"width": label_width}),
417
+ html.Div(
418
+ dcc.Input(
419
+ id=self._id,
420
+ type="number",
421
+ value=parameter.value,
422
+ step=1,
423
+ ),
424
+ style={"width": width, "margin": margin},
425
+ ),
426
+ ]
427
+ )
428
+
429
+
430
+ class UnboundedFloatComponent(BaseComponent[UnboundedFloatParameter, Any]):
431
+ """Component for unbounded float parameters."""
432
+
433
+ def _create_component(
434
+ self,
435
+ parameter: UnboundedFloatParameter,
436
+ width: str = "auto",
437
+ margin: str = "3px 0px",
438
+ label_width: str = "initial",
439
+ ) -> Any:
440
+ return html.Div(
441
+ [
442
+ html.Label(parameter.name, style={"width": label_width}),
443
+ html.Div(
444
+ dcc.Input(
445
+ id=self._id,
446
+ type="number",
447
+ value=parameter.value,
448
+ step=parameter.step,
449
+ ),
450
+ style={"width": width, "margin": margin},
451
+ ),
452
+ ]
453
+ )
454
+
455
+
456
+ class ButtonComponent(BaseComponent[ButtonAction, Any]):
457
+ """Component for button actions."""
458
+
459
+ _is_action: bool = True
460
+
461
+ def _create_component(
462
+ self,
463
+ parameter: ButtonAction,
464
+ width: str = "auto",
465
+ margin: str = "3px 0px",
466
+ label_width: str = "initial",
467
+ ) -> Any:
468
+ return html.Div(
469
+ html.Button(
470
+ parameter.label,
471
+ id=self._id,
472
+ ),
473
+ style={"width": width, "margin": margin},
474
+ )
475
+
476
+
477
+ def create_component(
478
+ parameter: Union[Parameter[Any], ButtonAction],
479
+ component_id: str,
480
+ width: str = "auto",
481
+ margin: str = "3px 0px",
482
+ label_width: str = "initial",
483
+ ) -> BaseComponent[Union[Parameter[Any], ButtonAction], Any]:
484
+ """Create and return the appropriate component for the given parameter.
485
+
486
+ Args:
487
+ parameter: The parameter to create a component for
488
+ component_id: Unique ID for the component
489
+ width: Width of the component
490
+ margin: Margin of the component
491
+ label_width: Width of the label
492
+ """
493
+ component_map = {
494
+ TextParameter: TextComponent,
495
+ SelectionParameter: SelectionComponent,
496
+ MultipleSelectionParameter: MultipleSelectionComponent,
497
+ BooleanParameter: BooleanComponent,
498
+ IntegerParameter: IntegerComponent,
499
+ FloatParameter: FloatComponent,
500
+ IntegerRangeParameter: IntegerRangeComponent,
501
+ FloatRangeParameter: FloatRangeComponent,
502
+ UnboundedIntegerParameter: UnboundedIntegerComponent,
503
+ UnboundedFloatParameter: UnboundedFloatComponent,
504
+ ButtonAction: ButtonComponent,
505
+ }
506
+
507
+ # Try direct type lookup first
508
+ component_class = component_map.get(type(parameter))
509
+
510
+ # If that fails, try matching by class name
511
+ if component_class is None:
512
+ param_type_name = type(parameter).__name__
513
+ for key_class, value_class in component_map.items():
514
+ if key_class.__name__ == param_type_name:
515
+ component_class = value_class
516
+ break
517
+
518
+ if component_class is None:
519
+ raise ValueError(
520
+ f"No component implementation for parameter type: {type(parameter)}\n"
521
+ f"Parameter type name: {type(parameter).__name__}\n"
522
+ f"Available types: {[k.__name__ for k in component_map.keys()]}"
523
+ )
524
+
525
+ return component_class(
526
+ parameter,
527
+ component_id,
528
+ width=width,
529
+ margin=margin,
530
+ label_width=label_width,
531
+ )