syd 0.1.5__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.
syd/__init__.py CHANGED
@@ -1,15 +1,11 @@
1
- from typing import Callable
2
- from .interactive_viewer import InteractiveViewer
1
+ from typing import Callable, Optional
2
+ from .viewer import Viewer
3
3
 
4
- __version__ = "0.1.5"
4
+ __version__ = "0.1.7"
5
5
 
6
6
 
7
- class DefaultViewer(InteractiveViewer):
8
- def plot(self, state):
9
- pass
10
-
11
-
12
- def make_viewer(plot_func: Callable):
13
- viewer = DefaultViewer()
14
- viewer.plot = plot_func
7
+ def make_viewer(plot_func: Optional[Callable] = None):
8
+ viewer = Viewer()
9
+ if plot_func is not None:
10
+ viewer.set_plot(plot_func)
15
11
  return viewer
@@ -0,0 +1 @@
1
+ from .deployer import FlaskDeployer
@@ -0,0 +1,510 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, Generic, List, TypeVar, Union, Callable, Optional
3
+ from dataclasses import dataclass
4
+ from html import escape
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
+
23
+
24
+ @dataclass
25
+ class ComponentStyle:
26
+ """Style configuration for components."""
27
+
28
+ width: str = "auto"
29
+ margin: str = "3px 0px"
30
+ description_width: str = "initial"
31
+ input_class: str = "form-control"
32
+ label_class: str = "form-label"
33
+ container_class: str = "mb-3"
34
+
35
+
36
+ class BaseComponent(Generic[T], ABC):
37
+ """
38
+ Abstract base class for all parameter components.
39
+
40
+ This class defines the common interface and shared functionality
41
+ for components that correspond to different parameter types.
42
+ """
43
+
44
+ _callbacks: List[Callable]
45
+ _is_action: bool = False
46
+ _value: Any
47
+ _id: str
48
+
49
+ def __init__(
50
+ self,
51
+ parameter: T,
52
+ continuous: bool = False,
53
+ style: Optional[ComponentStyle] = None,
54
+ ):
55
+ self._id = f"param_{parameter.name}"
56
+ self._value = parameter.value
57
+ self._callbacks = []
58
+ self._continuous = continuous
59
+ self._style = style or ComponentStyle()
60
+ self._html = self._create_html(parameter)
61
+
62
+ @abstractmethod
63
+ def _create_html(self, parameter: T) -> str:
64
+ """Create and return the appropriate HTML markup."""
65
+ pass
66
+
67
+ @property
68
+ def html(self) -> str:
69
+ """Get the HTML representation of the component."""
70
+ return self._html
71
+
72
+ @property
73
+ def value(self) -> Any:
74
+ """Get the current value of the component."""
75
+ return self._value
76
+
77
+ @value.setter
78
+ def value(self, new_value: Any) -> None:
79
+ """Set the value of the component."""
80
+ self._value = new_value
81
+ # In a real implementation, we'd use JavaScript to update the DOM
82
+ # This would be handled by the Flask app's frontend code
83
+
84
+ def matches_parameter(self, parameter: T) -> bool:
85
+ """Check if the component matches the parameter."""
86
+ return self.value == parameter.value
87
+
88
+ def update_from_parameter(self, parameter: T) -> None:
89
+ """Update the component from the parameter."""
90
+ try:
91
+ self.disable_callbacks()
92
+ self.extra_updates_from_parameter(parameter)
93
+ self.value = parameter.value
94
+ finally:
95
+ self.reenable_callbacks()
96
+
97
+ def extra_updates_from_parameter(self, parameter: T) -> None:
98
+ """Extra updates from the parameter."""
99
+ pass
100
+
101
+ def observe(self, callback: Callable) -> None:
102
+ """Register a callback for value changes."""
103
+ self._callbacks.append(callback)
104
+
105
+ def unobserve(self, callback: Callable) -> None:
106
+ """Unregister a callback."""
107
+ self._callbacks.remove(callback)
108
+
109
+ def reenable_callbacks(self) -> None:
110
+ """Reenable all callbacks."""
111
+ pass # Handled by Flask routes and JavaScript
112
+
113
+ def disable_callbacks(self) -> None:
114
+ """Disable all callbacks."""
115
+ pass # Handled by Flask routes and JavaScript
116
+
117
+
118
+ class TextComponent(BaseComponent[TextParameter]):
119
+ """Component for text parameters."""
120
+
121
+ def _create_html(self, parameter: TextParameter) -> str:
122
+ return f"""
123
+ <div class="{self._style.container_class}">
124
+ <label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
125
+ <input type="text" class="{self._style.input_class}" id="{self._id}"
126
+ name="{parameter.name}" value="{escape(str(parameter.value))}"
127
+ style="width: {self._style.width}; margin: {self._style.margin};"
128
+ data-continuous="{str(self._continuous).lower()}">
129
+ </div>
130
+ """
131
+
132
+
133
+ class BooleanComponent(BaseComponent[BooleanParameter]):
134
+ """Component for boolean parameters."""
135
+
136
+ def _create_html(self, parameter: BooleanParameter) -> str:
137
+ checked = "checked" if parameter.value else ""
138
+ return f"""
139
+ <div class="{self._style.container_class}">
140
+ <div class="form-check">
141
+ <input type="checkbox" class="form-check-input" id="{self._id}"
142
+ name="{parameter.name}" {checked}
143
+ style="margin: {self._style.margin};"
144
+ data-continuous="{str(self._continuous).lower()}">
145
+ <label class="form-check-label" for="{self._id}">
146
+ {escape(parameter.name)}
147
+ </label>
148
+ </div>
149
+ </div>
150
+ """
151
+
152
+
153
+ class SelectionComponent(BaseComponent[SelectionParameter]):
154
+ """Component for single selection parameters."""
155
+
156
+ def _create_html(self, parameter: SelectionParameter) -> str:
157
+ options_html = ""
158
+ for option in parameter.options:
159
+ selected = "selected" if option == parameter.value else ""
160
+ options_html += f'<option value="{escape(str(option))}" {selected}>{escape(str(option))}</option>'
161
+
162
+ return f"""
163
+ <div class="{self._style.container_class}">
164
+ <label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
165
+ <select class="{self._style.input_class}" id="{self._id}"
166
+ name="{parameter.name}"
167
+ style="width: {self._style.width}; margin: {self._style.margin};"
168
+ data-continuous="{str(self._continuous).lower()}">
169
+ {options_html}
170
+ </select>
171
+ </div>
172
+ """
173
+
174
+ def matches_parameter(self, parameter: SelectionParameter) -> bool:
175
+ """Check if the component matches the parameter."""
176
+ return self.value == parameter.value and set(self._get_options()) == set(
177
+ parameter.options
178
+ )
179
+
180
+ def _get_options(self) -> List[Any]:
181
+ """Get current options from the HTML."""
182
+ # In a real implementation, this would parse the HTML
183
+ # For now, we'll just return an empty list
184
+ return []
185
+
186
+ def extra_updates_from_parameter(self, parameter: SelectionParameter) -> None:
187
+ """Extra updates from the parameter."""
188
+ # In a real implementation, this would update the options in the DOM
189
+ pass
190
+
191
+
192
+ class MultipleSelectionComponent(BaseComponent[MultipleSelectionParameter]):
193
+ """Component for multiple selection parameters."""
194
+
195
+ def _create_html(self, parameter: MultipleSelectionParameter) -> str:
196
+ options_html = ""
197
+ for option in parameter.options:
198
+ selected = "selected" if option in parameter.value else ""
199
+ options_html += f'<option value="{escape(str(option))}" {selected}>{escape(str(option))}</option>'
200
+
201
+ return f"""
202
+ <div class="{self._style.container_class}">
203
+ <label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
204
+ <select class="{self._style.input_class}" id="{self._id}"
205
+ name="{parameter.name}" multiple
206
+ style="width: {self._style.width}; margin: {self._style.margin};"
207
+ data-continuous="{str(self._continuous).lower()}">
208
+ {options_html}
209
+ </select>
210
+ </div>
211
+ """
212
+
213
+ def matches_parameter(self, parameter: MultipleSelectionParameter) -> bool:
214
+ """Check if the component matches the parameter."""
215
+ return set(self.value) == set(parameter.value) and set(
216
+ self._get_options()
217
+ ) == set(parameter.options)
218
+
219
+ def _get_options(self) -> List[Any]:
220
+ """Get current options from the HTML."""
221
+ # In a real implementation, this would parse the HTML
222
+ return []
223
+
224
+ def extra_updates_from_parameter(
225
+ self, parameter: MultipleSelectionParameter
226
+ ) -> None:
227
+ """Extra updates from the parameter."""
228
+ # In a real implementation, this would update the options in the DOM
229
+ pass
230
+
231
+
232
+ class IntegerComponent(BaseComponent[IntegerParameter]):
233
+ """Component for integer parameters."""
234
+
235
+ def _create_html(self, parameter: IntegerParameter) -> str:
236
+ return f"""
237
+ <div class="{self._style.container_class}">
238
+ <label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
239
+ <input type="range" class="{self._style.input_class}" id="{self._id}"
240
+ name="{parameter.name}" value="{parameter.value}"
241
+ min="{parameter.min_value}" max="{parameter.max_value}"
242
+ style="width: {self._style.width}; margin: {self._style.margin};"
243
+ data-continuous="{str(self._continuous).lower()}">
244
+ <output for="{self._id}">{parameter.value}</output>
245
+ </div>
246
+ """
247
+
248
+ def matches_parameter(self, parameter: IntegerParameter) -> bool:
249
+ """Check if the component matches the parameter."""
250
+ return (
251
+ self.value == parameter.value
252
+ and self._get_min() == parameter.min_value
253
+ and self._get_max() == parameter.max_value
254
+ )
255
+
256
+ def _get_min(self) -> int:
257
+ """Get minimum value from the HTML."""
258
+ return 0 # Placeholder
259
+
260
+ def _get_max(self) -> int:
261
+ """Get maximum value from the HTML."""
262
+ return 100 # Placeholder
263
+
264
+ def extra_updates_from_parameter(self, parameter: IntegerParameter) -> None:
265
+ """Extra updates from the parameter."""
266
+ # In a real implementation, this would update min/max in the DOM
267
+ pass
268
+
269
+
270
+ class FloatComponent(BaseComponent[FloatParameter]):
271
+ """Component for float parameters."""
272
+
273
+ def _create_html(self, parameter: FloatParameter) -> str:
274
+ return f"""
275
+ <div class="{self._style.container_class}">
276
+ <label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
277
+ <input type="range" class="{self._style.input_class}" id="{self._id}"
278
+ name="{parameter.name}" value="{parameter.value}"
279
+ min="{parameter.min_value}" max="{parameter.max_value}" step="{parameter.step}"
280
+ style="width: {self._style.width}; margin: {self._style.margin};"
281
+ data-continuous="{str(self._continuous).lower()}">
282
+ <output for="{self._id}">{parameter.value}</output>
283
+ </div>
284
+ """
285
+
286
+ def matches_parameter(self, parameter: FloatParameter) -> bool:
287
+ """Check if the component matches the parameter."""
288
+ return (
289
+ self.value == parameter.value
290
+ and self._get_min() == parameter.min_value
291
+ and self._get_max() == parameter.max_value
292
+ and self._get_step() == parameter.step
293
+ )
294
+
295
+ def _get_min(self) -> float:
296
+ """Get minimum value from the HTML."""
297
+ return 0.0 # Placeholder
298
+
299
+ def _get_max(self) -> float:
300
+ """Get maximum value from the HTML."""
301
+ return 1.0 # Placeholder
302
+
303
+ def _get_step(self) -> float:
304
+ """Get step value from the HTML."""
305
+ return 0.1 # Placeholder
306
+
307
+ def extra_updates_from_parameter(self, parameter: FloatParameter) -> None:
308
+ """Extra updates from the parameter."""
309
+ # In a real implementation, this would update min/max/step in the DOM
310
+ pass
311
+
312
+
313
+ class IntegerRangeComponent(BaseComponent[IntegerRangeParameter]):
314
+ """Component for integer range parameters."""
315
+
316
+ def _create_html(self, parameter: IntegerRangeParameter) -> str:
317
+ low, high = parameter.value
318
+ return f"""
319
+ <div class="{self._style.container_class}">
320
+ <label for="{self._id}_low" class="{self._style.label_class}">{escape(parameter.name)}</label>
321
+ <div class="d-flex align-items-center">
322
+ <input type="range" class="{self._style.input_class}" id="{self._id}_low"
323
+ name="{parameter.name}_low" value="{low}"
324
+ min="{parameter.min_value}" max="{parameter.max_value}"
325
+ style="width: {self._style.width}; margin: {self._style.margin};"
326
+ data-continuous="{str(self._continuous).lower()}">
327
+ <output for="{self._id}_low">{low}</output>
328
+ </div>
329
+ <div class="d-flex align-items-center">
330
+ <input type="range" class="{self._style.input_class}" id="{self._id}_high"
331
+ name="{parameter.name}_high" value="{high}"
332
+ min="{parameter.min_value}" max="{parameter.max_value}"
333
+ style="width: {self._style.width}; margin: {self._style.margin};"
334
+ data-continuous="{str(self._continuous).lower()}">
335
+ <output for="{self._id}_high">{high}</output>
336
+ </div>
337
+ </div>
338
+ """
339
+
340
+ def matches_parameter(self, parameter: IntegerRangeParameter) -> bool:
341
+ """Check if the component matches the parameter."""
342
+ return (
343
+ self.value == parameter.value
344
+ and self._get_min() == parameter.min_value
345
+ and self._get_max() == parameter.max_value
346
+ )
347
+
348
+ def _get_min(self) -> int:
349
+ """Get minimum value from the HTML."""
350
+ return 0 # Placeholder
351
+
352
+ def _get_max(self) -> int:
353
+ """Get maximum value from the HTML."""
354
+ return 100 # Placeholder
355
+
356
+ def extra_updates_from_parameter(self, parameter: IntegerRangeParameter) -> None:
357
+ """Extra updates from the parameter."""
358
+ # In a real implementation, this would update min/max in the DOM
359
+ pass
360
+
361
+
362
+ class FloatRangeComponent(BaseComponent[FloatRangeParameter]):
363
+ """Component for float range parameters."""
364
+
365
+ def _create_html(self, parameter: FloatRangeParameter) -> str:
366
+ low, high = parameter.value
367
+ return f"""
368
+ <div class="{self._style.container_class}">
369
+ <label for="{self._id}_low" class="{self._style.label_class}">{escape(parameter.name)}</label>
370
+ <div class="d-flex align-items-center">
371
+ <input type="range" class="{self._style.input_class}" id="{self._id}_low"
372
+ name="{parameter.name}_low" value="{low}"
373
+ min="{parameter.min_value}" max="{parameter.max_value}" step="{parameter.step}"
374
+ style="width: {self._style.width}; margin: {self._style.margin};"
375
+ data-continuous="{str(self._continuous).lower()}">
376
+ <output for="{self._id}_low">{low}</output>
377
+ </div>
378
+ <div class="d-flex align-items-center">
379
+ <input type="range" class="{self._style.input_class}" id="{self._id}_high"
380
+ name="{parameter.name}_high" value="{high}"
381
+ min="{parameter.min_value}" max="{parameter.max_value}" step="{parameter.step}"
382
+ style="width: {self._style.width}; margin: {self._style.margin};"
383
+ data-continuous="{str(self._continuous).lower()}">
384
+ <output for="{self._id}_high">{high}</output>
385
+ </div>
386
+ </div>
387
+ """
388
+
389
+ def matches_parameter(self, parameter: FloatRangeParameter) -> bool:
390
+ """Check if the component matches the parameter."""
391
+ return (
392
+ self.value == parameter.value
393
+ and self._get_min() == parameter.min_value
394
+ and self._get_max() == parameter.max_value
395
+ and self._get_step() == parameter.step
396
+ )
397
+
398
+ def _get_min(self) -> float:
399
+ """Get minimum value from the HTML."""
400
+ return 0.0 # Placeholder
401
+
402
+ def _get_max(self) -> float:
403
+ """Get maximum value from the HTML."""
404
+ return 1.0 # Placeholder
405
+
406
+ def _get_step(self) -> float:
407
+ """Get step value from the HTML."""
408
+ return 0.1 # Placeholder
409
+
410
+ def extra_updates_from_parameter(self, parameter: FloatRangeParameter) -> None:
411
+ """Extra updates from the parameter."""
412
+ # In a real implementation, this would update min/max/step in the DOM
413
+ pass
414
+
415
+
416
+ class UnboundedIntegerComponent(BaseComponent[UnboundedIntegerParameter]):
417
+ """Component for unbounded integer parameters."""
418
+
419
+ def _create_html(self, parameter: UnboundedIntegerParameter) -> str:
420
+ return f"""
421
+ <div class="{self._style.container_class}">
422
+ <label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
423
+ <input type="number" class="{self._style.input_class}" id="{self._id}"
424
+ name="{parameter.name}" value="{parameter.value}"
425
+ style="width: {self._style.width}; margin: {self._style.margin};"
426
+ data-continuous="{str(self._continuous).lower()}">
427
+ </div>
428
+ """
429
+
430
+
431
+ class UnboundedFloatComponent(BaseComponent[UnboundedFloatParameter]):
432
+ """Component for unbounded float parameters."""
433
+
434
+ def _create_html(self, parameter: UnboundedFloatParameter) -> str:
435
+ step = parameter.step if parameter.step is not None else "any"
436
+ return f"""
437
+ <div class="{self._style.container_class}">
438
+ <label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
439
+ <input type="number" class="{self._style.input_class}" id="{self._id}"
440
+ name="{parameter.name}" value="{parameter.value}" step="{step}"
441
+ style="width: {self._style.width}; margin: {self._style.margin};"
442
+ data-continuous="{str(self._continuous).lower()}">
443
+ </div>
444
+ """
445
+
446
+ def matches_parameter(self, parameter: UnboundedFloatParameter) -> bool:
447
+ """Check if the component matches the parameter."""
448
+ return self.value == parameter.value and self._get_step() == parameter.step
449
+
450
+ def _get_step(self) -> Optional[float]:
451
+ """Get step value from the HTML."""
452
+ return None # Placeholder
453
+
454
+ def extra_updates_from_parameter(self, parameter: UnboundedFloatParameter) -> None:
455
+ """Extra updates from the parameter."""
456
+ # In a real implementation, this would update step in the DOM
457
+ pass
458
+
459
+
460
+ class ButtonComponent(BaseComponent[ButtonAction]):
461
+ """Component for button parameters."""
462
+
463
+ _is_action: bool = True
464
+
465
+ def _create_html(self, parameter: ButtonAction) -> str:
466
+ return f"""
467
+ <div class="{self._style.container_class}">
468
+ <button type="button" class="btn btn-primary" id="{self._id}"
469
+ name="{parameter.name}"
470
+ style="width: {self._style.width}; margin: {self._style.margin};">
471
+ {escape(parameter.label)}
472
+ </button>
473
+ </div>
474
+ """
475
+
476
+ def matches_parameter(self, parameter: ButtonAction) -> bool:
477
+ """Check if the component matches the parameter."""
478
+ return True # Buttons don't have a value to match
479
+
480
+ def extra_updates_from_parameter(self, parameter: ButtonAction) -> None:
481
+ """Extra updates from the parameter."""
482
+ # In a real implementation, this would update the button label in the DOM
483
+ pass
484
+
485
+
486
+ def create_component(
487
+ parameter: Union[Parameter[Any], ButtonAction],
488
+ continuous: bool = False,
489
+ style: Optional[ComponentStyle] = None,
490
+ ) -> BaseComponent[Union[Parameter[Any], ButtonAction]]:
491
+ """Create the appropriate component for a parameter."""
492
+ component_map = {
493
+ TextParameter: TextComponent,
494
+ BooleanParameter: BooleanComponent,
495
+ SelectionParameter: SelectionComponent,
496
+ MultipleSelectionParameter: MultipleSelectionComponent,
497
+ IntegerParameter: IntegerComponent,
498
+ FloatParameter: FloatComponent,
499
+ IntegerRangeParameter: IntegerRangeComponent,
500
+ FloatRangeParameter: FloatRangeComponent,
501
+ UnboundedIntegerParameter: UnboundedIntegerComponent,
502
+ UnboundedFloatParameter: UnboundedFloatComponent,
503
+ ButtonAction: ButtonComponent,
504
+ }
505
+
506
+ for param_type, component_class in component_map.items():
507
+ if isinstance(parameter, param_type):
508
+ return component_class(parameter, continuous, style)
509
+
510
+ raise ValueError(f"No component available for parameter type: {type(parameter)}")