syd 0.1.4__py3-none-any.whl → 0.1.5__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/parameters.py CHANGED
@@ -1,269 +1,1250 @@
1
- from typing import List, Any, Tuple, Generic, TypeVar, cast
1
+ from typing import List, Any, Tuple, Generic, TypeVar, Optional, Dict, Callable
2
2
  from dataclasses import dataclass
3
3
  from abc import ABC, abstractmethod
4
4
  from enum import Enum
5
+ from copy import deepcopy
5
6
  from warnings import warn
6
7
 
7
8
  T = TypeVar("T")
8
9
 
9
10
 
11
+ # Keep original Parameter class and exceptions unchanged
12
+ class ParameterAddError(Exception):
13
+ """
14
+ Exception raised when there is an error creating a new parameter.
15
+
16
+ Parameters
17
+ ----------
18
+ parameter_name : str
19
+ Name of the parameter that failed to be created
20
+ parameter_type : str
21
+ Type of the parameter that failed to be created
22
+ message : str, optional
23
+ Additional error details
24
+ """
25
+
26
+ def __init__(self, parameter_name: str, parameter_type: str, message: str = None):
27
+ self.parameter_name = parameter_name
28
+ self.parameter_type = parameter_type
29
+ super().__init__(
30
+ f"Failed to create {parameter_type} parameter '{parameter_name}'"
31
+ + (f": {message}" if message else "")
32
+ )
33
+
34
+
35
+ class ParameterUpdateError(Exception):
36
+ """
37
+ Exception raised when there is an error updating an existing parameter.
38
+
39
+ Parameters
40
+ ----------
41
+ parameter_name : str
42
+ Name of the parameter that failed to update
43
+ parameter_type : str
44
+ Type of the parameter that failed to update
45
+ message : str, optional
46
+ Additional error details
47
+ """
48
+
49
+ def __init__(self, parameter_name: str, parameter_type: str, message: str = None):
50
+ self.parameter_name = parameter_name
51
+ self.parameter_type = parameter_type
52
+ super().__init__(
53
+ f"Failed to update {parameter_type} parameter '{parameter_name}'"
54
+ + (f": {message}" if message else "")
55
+ )
56
+
57
+
58
+ def get_parameter_attributes(param_class) -> List[str]:
59
+ """
60
+ Get all valid attributes for a parameter class.
61
+
62
+ Parameters
63
+ ----------
64
+ param_class : class
65
+ The parameter class to inspect
66
+
67
+ Returns
68
+ -------
69
+ list of str
70
+ Names of all valid attributes for the parameter class
71
+ """
72
+ attributes = []
73
+
74
+ # Walk through class hierarchy in reverse (most specific to most general)
75
+ for cls in reversed(param_class.__mro__):
76
+ if hasattr(cls, "__annotations__"):
77
+ # Only add annotations that haven't been specified by a more specific class
78
+ for name in cls.__annotations__:
79
+ if not name.startswith("_"):
80
+ attributes.append(name)
81
+
82
+ return attributes
83
+
84
+
10
85
  @dataclass
11
86
  class Parameter(Generic[T], ABC):
12
- """Abstract base class for parameters that should not be instantiated directly."""
87
+ """
88
+ Base class for all parameter types. Parameters are the building blocks
89
+ for creating interactive GUI elements.
90
+
91
+ Each parameter has a name and a value, and ensures the value stays valid
92
+ through validation rules.
93
+
94
+ Parameters
95
+ ----------
96
+ name : str
97
+ The name of the parameter, used as a label in the GUI
98
+ value : T
99
+ The current value of the parameter
100
+
101
+ Notes
102
+ -----
103
+ This is an abstract base class - you should use one of the concrete parameter
104
+ types like TextParameter, BooleanParameter, etc. instead of using this directly.
105
+ """
13
106
 
14
107
  name: str
108
+ value: T
15
109
 
16
110
  @abstractmethod
17
- def __init__(self, name: str, default: T):
111
+ def __init__(self, name: str, value: T):
18
112
  raise NotImplementedError("Need to define in subclass for proper IDE support")
19
113
 
20
114
  @property
21
115
  def value(self) -> T:
116
+ """
117
+ Get the current value of the parameter.
118
+
119
+ Returns
120
+ -------
121
+ T
122
+ The current value
123
+ """
22
124
  return self._value
23
125
 
24
126
  @value.setter
25
- def value(self, new_value: T):
127
+ def value(self, new_value: T) -> None:
128
+ """
129
+ Set a new value for the parameter. The value will be validated before being set.
130
+
131
+ Parameters
132
+ ----------
133
+ new_value : T
134
+ The new value to set
135
+
136
+ Raises
137
+ ------
138
+ ValueError
139
+ If the new value is invalid for this parameter type
140
+ """
26
141
  self._value = self._validate(new_value)
27
142
 
28
143
  @abstractmethod
29
144
  def _validate(self, new_value: Any) -> T:
30
145
  raise NotImplementedError
31
146
 
147
+ def update(self, updates: Dict[str, Any]) -> None:
148
+ """
149
+ Safely update multiple parameter attributes at once.
150
+
151
+ Parameters
152
+ ----------
153
+ updates : dict
154
+ Dictionary of attribute names and their new values
155
+
156
+ Raises
157
+ ------
158
+ ParameterUpdateError
159
+ If any of the updates are invalid
160
+
161
+ Examples
162
+ --------
163
+ >>> param = FloatParameter("temperature", 20.0, min_value=0, max_value=100)
164
+ >>> param.update({"value": 25.0, "max_value": 150})
165
+ """
166
+ param_copy = deepcopy(self)
167
+
168
+ try:
169
+ param_copy._unsafe_update(updates)
170
+
171
+ for key, value in vars(param_copy).items():
172
+ if not key.startswith("_"):
173
+ setattr(self, key, value)
174
+ self.value = param_copy.value
175
+
176
+ except Exception as e:
177
+ if isinstance(e, ValueError):
178
+ raise ParameterUpdateError(
179
+ self.name, type(self).__name__, str(e)
180
+ ) from e
181
+ else:
182
+ raise ParameterUpdateError(
183
+ self.name, type(self).__name__, f"Update failed: {str(e)}"
184
+ ) from e
185
+
186
+ def _unsafe_update(self, updates: Dict[str, Any]) -> None:
187
+ """
188
+ Internal update method that applies changes without safety copies.
189
+
190
+ Validates attribute names but applies updates directly to instance.
191
+ Called by public update() method inside a deepcopy context.
192
+
193
+ Args:
194
+ updates: Dict mapping attribute names to new values
195
+
196
+ Raises:
197
+ ValueError: If trying to update 'name' or invalid attributes
198
+ """
199
+ valid_attributes = get_parameter_attributes(type(self))
200
+
201
+ for key, new_value in updates.items():
202
+ if key == "name":
203
+ raise ValueError("Cannot update parameter name")
204
+ elif key not in valid_attributes:
205
+ raise ValueError(f"Update failed, {key} is not a valid attribute")
206
+
207
+ for key, new_value in updates.items():
208
+ if key != "value":
209
+ setattr(self, key, new_value)
210
+
211
+ if "value" in updates:
212
+ self.value = updates["value"]
213
+
214
+ self._validate_update()
215
+
216
+ def _validate_update(self) -> None:
217
+ """
218
+ Hook for validating complete parameter state after updates.
219
+
220
+ Called at end of _unsafe_update(). Default implementation does nothing.
221
+ Override in subclasses to add validation logic.
222
+ """
223
+ pass
224
+
32
225
 
33
226
  @dataclass(init=False)
34
227
  class TextParameter(Parameter[str]):
35
- def __init__(self, name: str, default: str):
228
+ """
229
+ Parameter for text input.
230
+
231
+ Creates a text box in the GUI that accepts any string input.
232
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_text` and
233
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_text` for usage.
234
+
235
+ Parameters
236
+ ----------
237
+ name : str
238
+ The name of the parameter
239
+ value : str
240
+ The initial text value
241
+
242
+ Examples
243
+ --------
244
+ >>> name_param = TextParameter("username", "Alice")
245
+ >>> name_param.value
246
+ 'Alice'
247
+ >>> name_param.update({"value": "Bob"})
248
+ >>> name_param.value
249
+ 'Bob'
250
+ """
251
+
252
+ def __init__(self, name: str, value: str):
36
253
  self.name = name
37
- self.default = default
38
- self._value = self._validate(default)
254
+ self._value = self._validate(value)
39
255
 
40
256
  def _validate(self, new_value: Any) -> str:
257
+ """
258
+ Convert input to string.
259
+
260
+ Args:
261
+ new_value: Value to convert
262
+
263
+ Returns:
264
+ String representation of input value
265
+ """
41
266
  return str(new_value)
42
267
 
43
268
 
44
269
  @dataclass(init=False)
45
- class SingleSelectionParameter(Parameter[Any]):
270
+ class BooleanParameter(Parameter[bool]):
271
+ """
272
+ Parameter for boolean values.
273
+
274
+ Creates a checkbox in the GUI that can be toggled on/off.
275
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_boolean` and
276
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_boolean` for usage.
277
+
278
+ Parameters
279
+ ----------
280
+ name : str
281
+ The name of the parameter
282
+ value : bool, optional
283
+ The initial state (default is True)
284
+
285
+ Examples
286
+ --------
287
+ >>> active = BooleanParameter("is_active", True)
288
+ >>> active.value
289
+ True
290
+ >>> active.update({"value": False})
291
+ >>> active.value
292
+ False
293
+ """
294
+
295
+ def __init__(self, name: str, value: bool = True):
296
+ self.name = name
297
+ self._value = self._validate(value)
298
+
299
+ def _validate(self, new_value: Any) -> bool:
300
+ """
301
+ Convert input to boolean.
302
+
303
+ Args:
304
+ new_value: Value to convert
305
+
306
+ Returns:
307
+ Boolean interpretation of input value using Python's bool() rules
308
+ """
309
+ return bool(new_value)
310
+
311
+
312
+ @dataclass(init=False)
313
+ class SelectionParameter(Parameter[Any]):
314
+ """
315
+ Parameter for single selection from a list of options.
316
+
317
+ Creates a dropdown menu in the GUI where users can select one option.
318
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_selection` and
319
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_selection` for usage.
320
+
321
+ Parameters
322
+ ----------
323
+ name : str
324
+ The name of the parameter
325
+ value : Any
326
+ The initially selected value (must be one of the options)
327
+ options : list
328
+ List of valid choices that can be selected
329
+
330
+ Examples
331
+ --------
332
+ >>> color = SelectionParameter("color", "red", options=["red", "green", "blue"])
333
+ >>> color.value
334
+ 'red'
335
+ >>> color.update({"value": "blue"})
336
+ >>> color.value
337
+ 'blue'
338
+ >>> color.update({"value": "yellow"}) # This will raise an error
339
+ """
340
+
46
341
  options: List[Any]
47
342
 
48
- def __init__(self, name: str, options: List[Any], default: Any = None):
343
+ def __init__(self, name: str, value: Any, options: List[Any]):
49
344
  self.name = name
50
345
  self.options = options
51
- self.default = default or options[0]
52
- self._value = self._validate(self.default)
346
+ self._value = self._validate(value)
53
347
 
54
348
  def _validate(self, new_value: Any) -> Any:
349
+ """
350
+ Validate that value is one of the allowed options.
351
+
352
+ Args:
353
+ new_value: Value to validate
354
+
355
+ Returns:
356
+ Input value if valid
357
+
358
+ Raises:
359
+ ValueError: If value is not in options list
360
+ """
55
361
  if new_value not in self.options:
56
362
  raise ValueError(f"Value {new_value} not in options: {self.options}")
57
363
  return new_value
58
364
 
365
+ def _validate_update(self) -> None:
366
+ """
367
+ Validate complete parameter state after updates.
368
+
369
+ Ensures options is a list/tuple and current value is valid.
370
+ Sets value to first option if current value becomes invalid.
371
+
372
+ Raises:
373
+ TypeError: If options is not a list or tuple
374
+ """
375
+ if not isinstance(self.options, (list, tuple)):
376
+ raise TypeError(
377
+ f"Options for parameter {self.name} are not a list or tuple: {self.options}"
378
+ )
379
+ if self.value not in self.options:
380
+ warn(
381
+ f"Value {self.value} not in options: {self.options}, setting to first option"
382
+ )
383
+ self.value = self.options[0]
384
+
59
385
 
60
386
  @dataclass(init=False)
61
387
  class MultipleSelectionParameter(Parameter[List[Any]]):
388
+ """
389
+ Parameter for multiple selections from a list of options.
390
+
391
+ Creates a set of checkboxes or multi-select dropdown in the GUI.
392
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_multiple_selection` and
393
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_multiple_selection` for usage.
394
+
395
+ Parameters
396
+ ----------
397
+ name : str
398
+ The name of the parameter
399
+ value : list
400
+ List of initially selected values (must all be from options)
401
+ options : list
402
+ List of valid choices that can be selected
403
+
404
+ Examples
405
+ --------
406
+ >>> toppings = MultipleSelectionParameter("pizza_toppings",
407
+ ... value=["cheese", "mushrooms"],
408
+ ... options=["cheese", "mushrooms", "pepperoni", "olives"])
409
+ >>> toppings.value
410
+ ['cheese', 'mushrooms']
411
+ >>> toppings.update({"value": ["cheese", "pepperoni"]})
412
+ >>> toppings.value
413
+ ['cheese', 'pepperoni']
414
+ """
415
+
62
416
  options: List[Any]
63
417
 
64
- def __init__(self, name: str, options: List[Any], default: List[Any] = None):
418
+ def __init__(self, name: str, value: List[Any], options: List[Any]):
65
419
  self.name = name
66
- self.default = default or []
67
420
  self.options = options
68
- self._value = self._validate(self.default)
421
+ self._value = self._validate(value)
422
+
423
+ def _validate(self, new_value: Any) -> List[Any]:
424
+ """
425
+ Validate list of selected values against options.
69
426
 
70
- def _validate(self, new_value: List[Any]) -> List[Any]:
427
+ Ensures value is a list/tuple and all elements are in options.
428
+ Preserves order based on options list while removing duplicates.
429
+
430
+ Args:
431
+ new_value: List of selected values
432
+
433
+ Returns:
434
+ Validated list of unique values in options order
435
+
436
+ Raises:
437
+ TypeError: If value is not a list/tuple
438
+ ValueError: If any value is not in options
439
+ """
71
440
  if not isinstance(new_value, (list, tuple)):
72
- raise TypeError(f"Expected list or tuple, got {type(new_value)}")
73
- if not all(val in self.options for val in new_value):
74
- invalid = [val for val in new_value if val not in self.options]
441
+ raise TypeError(f"Value must be a list or tuple")
442
+ invalid = [val for val in new_value if val not in self.options]
443
+ if invalid:
75
444
  raise ValueError(f"Values {invalid} not in options: {self.options}")
76
- return list(new_value)
445
+ # Keep only unique values while preserving order based on self.options
446
+ return [x for x in self.options if x in new_value]
447
+
448
+ def _validate_update(self) -> None:
449
+ if not isinstance(self.options, (list, tuple)):
450
+ raise TypeError(
451
+ f"Options for parameter {self.name} are not a list or tuple: {self.options}"
452
+ )
453
+ if not isinstance(self.value, (list, tuple)):
454
+ warn(
455
+ f"For parameter {self.name}, value {self.value} is not a list or tuple. Setting to empty list."
456
+ )
457
+ self.value = []
458
+ if not all(val in self.options for val in self.value):
459
+ invalid = [val for val in self.value if val not in self.options]
460
+ warn(
461
+ f"For parameter {self.name}, value {self.value} contains invalid options: {invalid}. Setting to empty list."
462
+ )
463
+ self.value = []
464
+ # Keep only unique values while preserving order based on self.options
465
+ seen = set()
466
+ self.options = [x for x in self.options if not (x in seen or seen.add(x))]
77
467
 
78
468
 
79
469
  @dataclass(init=False)
80
- class BooleanParameter(Parameter[bool]):
81
- def __init__(self, name: str, default: bool = True):
470
+ class IntegerParameter(Parameter[int]):
471
+ """
472
+ Parameter for bounded integer values.
473
+
474
+ Creates a slider in the GUI for selecting whole numbers between bounds.
475
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_integer` and
476
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_integer` for usage.
477
+
478
+ Parameters
479
+ ----------
480
+ name : str
481
+ The name of the parameter
482
+ value : int
483
+ Initial value (will be clamped to fit between min_value and max_value)
484
+ min_value : int
485
+ Minimum allowed value
486
+ max_value : int
487
+ Maximum allowed value
488
+
489
+ Examples
490
+ --------
491
+ >>> age = IntegerParameter("age", value=25, min_value=0, max_value=120)
492
+ >>> age.value
493
+ 25
494
+ >>> age.update({"value": 150}) # Will be clamped to max_value
495
+ >>> age.value
496
+ 120
497
+ >>> age.update({"value": -10}) # Will be clamped to min_value
498
+ >>> age.value
499
+ 0
500
+ """
501
+
502
+ min_value: int
503
+ max_value: int
504
+
505
+ def __init__(
506
+ self,
507
+ name: str,
508
+ value: int,
509
+ min_value: int,
510
+ max_value: int,
511
+ ):
82
512
  self.name = name
83
- self.default = default
84
- self._value = self._validate(default)
513
+ self.min_value = self._validate(min_value, compare_to_range=False)
514
+ self.max_value = self._validate(max_value, compare_to_range=False)
515
+ self._value = self._validate(value)
85
516
 
86
- def _validate(self, new_value: Any) -> bool:
87
- return bool(new_value)
517
+ def _validate(self, new_value: Any, compare_to_range: bool = True) -> int:
518
+ """
519
+ Validate and convert value to integer, optionally checking bounds.
520
+
521
+ Args:
522
+ new_value: Value to validate
523
+ compare_to_range: If True, clamps value to min/max bounds
524
+
525
+ Returns:
526
+ Validated integer value
527
+
528
+ Raises:
529
+ ValueError: If value cannot be converted to int
530
+ """
531
+ try:
532
+ new_value = int(new_value)
533
+ except ValueError:
534
+ raise ValueError(f"Value {new_value} cannot be converted to int")
535
+
536
+ if compare_to_range:
537
+ if new_value < self.min_value:
538
+ warn(f"Value {new_value} below minimum {self.min_value}, clamping")
539
+ new_value = self.min_value
540
+ if new_value > self.max_value:
541
+ warn(f"Value {new_value} above maximum {self.max_value}, clamping")
542
+ new_value = self.max_value
543
+ return int(new_value)
544
+
545
+ def _validate_update(self) -> None:
546
+ """
547
+ Validate complete parameter state after updates.
548
+
549
+ Ensures min_value <= max_value, swapping if needed.
550
+ Re-validates current value against potentially updated bounds.
551
+
552
+ Raises:
553
+ ParameterUpdateError: If bounds are invalid (e.g. None when required)
554
+ """
555
+ if self.min_value is None or self.max_value is None:
556
+ raise ParameterUpdateError(
557
+ self.name,
558
+ type(self).__name__,
559
+ "IntegerParameter must have both min_value and max_value bounds",
560
+ )
561
+ if self.min_value > self.max_value:
562
+ warn(f"Min value greater than max value, swapping")
563
+ self.min_value, self.max_value = self.max_value, self.min_value
564
+ self.value = self._validate(self.value)
88
565
 
89
566
 
90
567
  @dataclass(init=False)
91
- class NumericParameter(Parameter[T], ABC):
92
- min_value: T
93
- max_value: T
568
+ class FloatParameter(Parameter[float]):
569
+ """
570
+ Parameter for bounded decimal numbers.
571
+
572
+ Creates a slider in the GUI for selecting numbers between bounds.
573
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_float` and
574
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_float` for usage.
575
+
576
+ Parameters
577
+ ----------
578
+ name : str
579
+ The name of the parameter
580
+ value : float
581
+ Initial value (will be clamped to fit between min_value and max_value)
582
+ min_value : float
583
+ Minimum allowed value
584
+ max_value : float
585
+ Maximum allowed value
586
+ step : float, optional
587
+ Size of each increment (default is 0.1)
588
+
589
+ Examples
590
+ --------
591
+ >>> temp = FloatParameter("temperature", value=98.6,
592
+ ... min_value=95.0, max_value=105.0, step=0.1)
593
+ >>> temp.value
594
+ 98.6
595
+ >>> temp.update({"value": 98.67}) # Will be rounded to nearest step
596
+ >>> temp.value
597
+ 98.7
598
+ >>> temp.update({"value": 110.0}) # Will be clamped to max_value
599
+ >>> temp.value
600
+ 105.0
601
+
602
+ Notes
603
+ -----
604
+ The step parameter determines how finely you can adjust the value. For example:
605
+ - step=0.1 allows values like 1.0, 1.1, 1.2, etc.
606
+ - step=0.01 allows values like 1.00, 1.01, 1.02, etc.
607
+ - step=5.0 allows values like 0.0, 5.0, 10.0, etc.
608
+ """
94
609
 
95
- def __init__(self, name: str, min_value: T = None, max_value: T = None, default: T = 0):
610
+ min_value: float
611
+ max_value: float
612
+ step: float
613
+
614
+ def __init__(
615
+ self,
616
+ name: str,
617
+ value: float,
618
+ min_value: float,
619
+ max_value: float,
620
+ step: float = 0.1,
621
+ ):
96
622
  self.name = name
97
- self.default = default
98
- self.min_value = min_value
99
- self.max_value = max_value
100
- self._value = self._validate(default)
623
+ self.step = step
624
+ self.min_value = self._validate(min_value, compare_to_range=False)
625
+ self.max_value = self._validate(max_value, compare_to_range=False)
626
+ self._value = self._validate(value)
101
627
 
102
- @abstractmethod
103
- def _validate(self, new_value: Any) -> T:
104
- # Subclasses must implement this
105
- raise NotImplementedError
628
+ def _validate(self, new_value: Any, compare_to_range: bool = True) -> float:
629
+ """
630
+ Validate and convert value to float, optionally checking bounds.
631
+
632
+ Rounds value to nearest step increment before range checking.
633
+
634
+ Args:
635
+ new_value: Value to validate
636
+ compare_to_range: If True, clamps value to min/max bounds
637
+
638
+ Returns:
639
+ Validated and potentially rounded float value
640
+
641
+ Raises:
642
+ ValueError: If value cannot be converted to float
643
+ """
644
+ try:
645
+ new_value = float(new_value)
646
+ except ValueError:
647
+ raise ValueError(f"Value {new_value} cannot be converted to float")
648
+
649
+ # Round to the nearest step
650
+ new_value = round(new_value / self.step) * self.step
651
+
652
+ if compare_to_range:
653
+ if new_value < self.min_value:
654
+ warn(f"Value {new_value} below minimum {self.min_value}, clamping")
655
+ new_value = self.min_value
656
+ if new_value > self.max_value:
657
+ warn(f"Value {new_value} above maximum {self.max_value}, clamping")
658
+ new_value = self.max_value
659
+
660
+ return float(new_value)
661
+
662
+ def _validate_update(self) -> None:
663
+ """
664
+ Validate complete parameter state after updates.
665
+
666
+ Ensures min_value <= max_value, swapping if needed.
667
+ Re-validates current value against potentially updated bounds.
668
+
669
+ Raises:
670
+ ParameterUpdateError: If bounds are invalid (e.g. None when required)
671
+ """
672
+ if self.min_value is None or self.max_value is None:
673
+ raise ParameterUpdateError(
674
+ self.name,
675
+ type(self).__name__,
676
+ "FloatParameter must have both min_value and max_value bounds",
677
+ )
678
+ if self.min_value > self.max_value:
679
+ warn(f"Min value greater than max value, swapping")
680
+ self.min_value, self.max_value = self.max_value, self.min_value
681
+ self.value = self._validate(self.value)
106
682
 
107
683
 
108
684
  @dataclass(init=False)
109
- class IntegerParameter(NumericParameter[int]):
110
- def __init__(self, name: str, min_value: int = None, max_value: int = None, default: int = 0):
685
+ class IntegerRangeParameter(Parameter[Tuple[int, int]]):
686
+ """
687
+ Parameter for a range of bounded integer values.
688
+
689
+ Creates a range slider in the GUI for selecting a range of whole numbers.
690
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_integer_range` and
691
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_integer_range` for usage.
692
+
693
+ Parameters
694
+ ----------
695
+ name : str
696
+ The name of the parameter
697
+ value : tuple[int, int]
698
+ Initial (low, high) values
699
+ min_value : int
700
+ Minimum allowed value for both low and high
701
+ max_value : int
702
+ Maximum allowed value for both low and high
703
+
704
+ Examples
705
+ --------
706
+ >>> age_range = IntegerRangeParameter("age_range",
707
+ ... value=(25, 35), min_value=18, max_value=100)
708
+ >>> age_range.value
709
+ (25, 35)
710
+ >>> age_range.update({"value": (35, 25)}) # Values will be swapped
711
+ >>> age_range.value
712
+ (25, 35)
713
+ >>> age_range.update({"value": (15, 40)}) # Low will be clamped
714
+ >>> age_range.value
715
+ (18, 40)
716
+ """
717
+
718
+ min_value: int
719
+ max_value: int
720
+
721
+ def __init__(
722
+ self,
723
+ name: str,
724
+ value: Tuple[int, int],
725
+ min_value: int,
726
+ max_value: int,
727
+ ):
111
728
  self.name = name
729
+ self.min_value = self._validate_single(min_value)
730
+ self.max_value = self._validate_single(max_value)
731
+ self._value = self._validate(value)
732
+
733
+ def _validate_single(self, new_value: Any) -> int:
734
+ """
735
+ Validate and convert a single numeric value.
736
+
737
+ Used by _validate() to handle each number in the range tuple.
738
+ Does not perform range checking.
739
+
740
+ Args:
741
+ new_value: Value to validate
742
+
743
+ Returns:
744
+ Converted numeric value
745
+
746
+ Raises:
747
+ ValueError: If value cannot be converted to required numeric type
748
+ """
112
749
  try:
113
- self.min_value = int(min_value)
114
- self.max_value = int(max_value)
115
- except TypeError as e:
116
- raise TypeError(f"Cannot convert {min_value} and {max_value} to integer") from e
117
- if self.min_value is not None and self.max_value is not None:
118
- if self.min_value > self.max_value:
119
- raise ValueError(f"Minimum value {self.min_value} is greater than maximum value {self.max_value}")
120
- valid_default = self._validate(default)
121
- if valid_default != default:
122
- warn(f"Default value {default} is not in the range [{self.min_value}, {self.max_value}]. Clamping to {valid_default}.")
123
- self.default = valid_default
124
- self._value = self._validate(self.default)
125
-
126
- def _validate(self, new_value: Any) -> int:
127
- try:
128
- value = int(new_value)
129
- except (TypeError, ValueError):
130
- raise TypeError(f"Cannot convert {new_value} to integer")
750
+ return int(new_value)
751
+ except ValueError:
752
+ raise ValueError(f"Value {new_value} cannot be converted to int")
753
+
754
+ def _validate(self, new_value: Any) -> Tuple[int, int]:
755
+ """
756
+ Validate numeric value against parameter constraints.
757
+
758
+ Args:
759
+ new_value: Value to validate
760
+ compare_to_range: If True, clamps value to min/max bounds
761
+
762
+ Returns:
763
+ Validated and potentially clamped value
764
+
765
+ Raises:
766
+ ValueError: If value cannot be converted to required numeric type
767
+ """
768
+ if not isinstance(new_value, (tuple, list)) or len(new_value) != 2:
769
+ raise ValueError("Value must be a tuple of (low, high)")
770
+
771
+ low = self._validate_single(new_value[0])
772
+ high = self._validate_single(new_value[1])
773
+
774
+ if low > high:
775
+ warn(f"Low value {low} greater than high value {high}, swapping")
776
+ low, high = high, low
131
777
 
132
- if self.min_value is not None:
133
- value = max(self.min_value, value)
134
- if self.max_value is not None:
135
- value = min(self.max_value, value)
136
- return value
778
+ if low < self.min_value:
779
+ warn(f"Low value {low} below minimum {self.min_value}, clamping")
780
+ low = self.min_value
781
+ if high > self.max_value:
782
+ warn(f"High value {high} above maximum {self.max_value}, clamping")
783
+ high = self.max_value
784
+
785
+ return (low, high)
786
+
787
+ def _validate_update(self) -> None:
788
+ """
789
+ Validate complete parameter state after updates.
790
+
791
+ Ensures min_value <= max_value, swapping if needed.
792
+ Re-validates current value against potentially updated bounds.
793
+
794
+ Raises:
795
+ ParameterUpdateError: If bounds are invalid (e.g. None when required)
796
+ """
797
+ if self.min_value is None or self.max_value is None:
798
+ raise ParameterUpdateError(
799
+ self.name,
800
+ type(self).__name__,
801
+ "IntegerRangeParameter must have both min_value and max_value bounds",
802
+ )
803
+ if self.min_value > self.max_value:
804
+ warn(f"Min value greater than max value, swapping")
805
+ self.min_value, self.max_value = self.max_value, self.min_value
806
+ self.value = self._validate(self.value)
137
807
 
138
808
 
139
809
  @dataclass(init=False)
140
- class FloatParameter(NumericParameter[float]):
810
+ class FloatRangeParameter(Parameter[Tuple[float, float]]):
811
+ """
812
+ Parameter for a range of bounded decimal numbers.
813
+
814
+ Creates a range slider in the GUI for selecting a range of numbers.
815
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_float_range` and
816
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_float_range` for usage.
817
+
818
+ Parameters
819
+ ----------
820
+ name : str
821
+ The name of the parameter
822
+ value : tuple[float, float]
823
+ Initial (low, high) values
824
+ min_value : float
825
+ Minimum allowed value for both low and high
826
+ max_value : float
827
+ Maximum allowed value for both low and high
828
+ step : float, optional
829
+ Size of each increment (default is 0.1)
830
+
831
+ Examples
832
+ --------
833
+ >>> temp_range = FloatRangeParameter("temperature_range",
834
+ ... value=(98.6, 100.4), min_value=95.0, max_value=105.0, step=0.1)
835
+ >>> temp_range.value
836
+ (98.6, 100.4)
837
+ >>> temp_range.update({"value": (98.67, 100.0)}) # Low will be rounded
838
+ >>> temp_range.value
839
+ (98.7, 100.0)
840
+ >>> temp_range.update({"value": (101.0, 99.0)}) # Values will be swapped
841
+ >>> temp_range.value
842
+ (99.0, 101.0)
843
+
844
+ Notes
845
+ -----
846
+ The step parameter determines how finely you can adjust the values. For example:
847
+ - step=0.1 allows values like 1.0, 1.1, 1.2, etc.
848
+ - step=0.01 allows values like 1.00, 1.01, 1.02, etc.
849
+ - step=5.0 allows values like 0.0, 5.0, 10.0, etc.
850
+ """
851
+
852
+ min_value: float
853
+ max_value: float
141
854
  step: float
142
855
 
143
- def __init__(self, name: str, min_value: float = None, max_value: float = None, default: float = 0.0, step: float = 0.1):
856
+ def __init__(
857
+ self,
858
+ name: str,
859
+ value: Tuple[float, float],
860
+ min_value: float,
861
+ max_value: float,
862
+ step: float = 0.1,
863
+ ):
144
864
  self.name = name
145
- self.default = default
146
- try:
147
- self.min_value = float(min_value)
148
- self.max_value = float(max_value)
149
- except TypeError as e:
150
- raise TypeError(f"Cannot convert {min_value} and {max_value} to float") from e
151
- if self.min_value is not None and self.max_value is not None:
152
- if self.min_value > self.max_value:
153
- raise ValueError(f"Minimum value {self.min_value} is greater than maximum value {self.max_value}")
154
865
  self.step = step
155
- valid_default = self._validate(default)
156
- if valid_default != default:
157
- warn(f"Default value {default} is not in the range [{self.min_value}, {self.max_value}]. Clamping to {valid_default}.")
158
- self.default = valid_default
159
- self._value = self._validate(self.default)
866
+ self.min_value = self._validate_single(min_value)
867
+ self.max_value = self._validate_single(max_value)
868
+ self._value = self._validate(value)
869
+
870
+ def _validate_single(self, new_value: Any) -> float:
871
+ """
872
+ Validate and convert a single numeric value.
873
+
874
+ Used by _validate() to handle each number in the range tuple.
875
+ Does not perform range checking.
876
+
877
+ Args:
878
+ new_value: Value to validate
160
879
 
161
- def _validate(self, new_value: Any) -> float:
880
+ Returns:
881
+ Converted numeric value
882
+
883
+ Raises:
884
+ ValueError: If value cannot be converted to required numeric type
885
+ """
162
886
  try:
163
- value = float(new_value)
164
- except (TypeError, ValueError):
165
- raise TypeError(f"Cannot convert {new_value} to float")
887
+ new_value = float(new_value)
888
+ except ValueError:
889
+ raise ValueError(f"Value {new_value} cannot be converted to float")
166
890
 
167
- if self.min_value is not None:
168
- value = max(self.min_value, value)
169
- if self.max_value is not None:
170
- value = min(self.max_value, value)
171
- return value
891
+ # Round to the nearest step
892
+ new_value = round(new_value / self.step) * self.step
893
+ return new_value
172
894
 
895
+ def _validate(self, new_value: Any) -> Tuple[float, float]:
896
+ """
897
+ Validate numeric value against parameter constraints.
173
898
 
174
- @dataclass(init=False)
175
- class PairParameter(Parameter[Tuple[T, T]], ABC):
176
- min_value: T
177
- max_value: T
178
- default: Tuple[T, T]
899
+ Args:
900
+ new_value: Value to validate
901
+ compare_to_range: If True, clamps value to min/max bounds
179
902
 
180
- @abstractmethod
181
- def __init__(self, name: str, default: Tuple[T, T], min_value: T = None, max_value: T = None):
182
- raise NotImplementedError("Need to define in subclass for proper IDE support")
903
+ Returns:
904
+ Validated and potentially clamped value
183
905
 
184
- @abstractmethod
185
- def _validate(self, value: Any) -> T:
186
- raise NotImplementedError
906
+ Raises:
907
+ ValueError: If value cannot be converted to required numeric type
908
+ """
909
+ if not isinstance(new_value, (tuple, list)) or len(new_value) != 2:
910
+ raise ValueError("Value must be a tuple of (low, high)")
911
+
912
+ low = self._validate_single(new_value[0])
913
+ high = self._validate_single(new_value[1])
914
+
915
+ if low > high:
916
+ warn(f"Low value {low} greater than high value {high}, swapping")
917
+ low, high = high, low
918
+
919
+ if low < self.min_value:
920
+ warn(f"Low value {low} below minimum {self.min_value}, clamping")
921
+ low = self.min_value
922
+ if high > self.max_value:
923
+ warn(f"High value {high} above maximum {self.max_value}, clamping")
924
+ high = self.max_value
925
+
926
+ return (low, high)
927
+
928
+ def _validate_update(self) -> None:
929
+ """
930
+ Validate complete parameter state after updates.
931
+
932
+ Ensures min_value <= max_value, swapping if needed.
933
+ Re-validates current value against potentially updated bounds.
934
+
935
+ Raises:
936
+ ParameterUpdateError: If bounds are invalid (e.g. None when required)
937
+ """
938
+ if self.min_value is None or self.max_value is None:
939
+ raise ParameterUpdateError(
940
+ self.name,
941
+ type(self).__name__,
942
+ "FloatRangeParameter must have both min_value and max_value bounds",
943
+ )
944
+ if self.min_value > self.max_value:
945
+ warn(f"Min value greater than max value, swapping")
946
+ self.min_value, self.max_value = self.max_value, self.min_value
947
+ self.value = self._validate(self.value)
187
948
 
188
949
 
189
950
  @dataclass(init=False)
190
- class IntegerPairParameter(PairParameter[int]):
191
- def __init__(self, name: str, default: Tuple[int, int], min_value: int = None, max_value: int = None):
951
+ class UnboundedIntegerParameter(Parameter[int]):
952
+ """
953
+ Parameter for optionally bounded integer values.
954
+
955
+ Creates a text input box in the GUI for entering whole numbers.
956
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_unbounded_integer` and
957
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_unbounded_integer` for usage.
958
+
959
+ Parameters
960
+ ----------
961
+ name : str
962
+ The name of the parameter
963
+ value : int
964
+ Initial value
965
+ min_value : int, optional
966
+ Minimum allowed value (or None for no minimum)
967
+ max_value : int, optional
968
+ Maximum allowed value (or None for no maximum)
969
+
970
+ Examples
971
+ --------
972
+ >>> count = UnboundedIntegerParameter("count", value=10, min_value=0)
973
+ >>> count.value
974
+ 10
975
+ >>> count.update({"value": -5}) # Will be clamped to min_value
976
+ >>> count.value
977
+ 0
978
+ >>> count.update({"value": 1000000}) # No maximum, so this is allowed
979
+ >>> count.value
980
+ 1000000
981
+
982
+ Notes
983
+ -----
984
+ Use this instead of IntegerParameter when you:
985
+ - Don't know a reasonable maximum value
986
+ - Only want to enforce a minimum or maximum, but not both
987
+ - Need to allow very large numbers that would be impractical with a slider
988
+ """
989
+
990
+ min_value: Optional[int]
991
+ max_value: Optional[int]
992
+
993
+ def __init__(
994
+ self,
995
+ name: str,
996
+ value: int,
997
+ min_value: Optional[int] = None,
998
+ max_value: Optional[int] = None,
999
+ ):
192
1000
  self.name = name
1001
+ self.min_value = (
1002
+ self._validate(min_value, compare_to_range=False)
1003
+ if min_value is not None
1004
+ else None
1005
+ )
1006
+ self.max_value = (
1007
+ self._validate(max_value, compare_to_range=False)
1008
+ if max_value is not None
1009
+ else None
1010
+ )
1011
+ self._value = self._validate(value)
1012
+
1013
+ def _validate(self, new_value: Any, compare_to_range: bool = True) -> int:
1014
+ """
1015
+ Validate and convert value to integer, optionally checking bounds.
1016
+
1017
+ Handles None min/max values by skipping those bound checks.
1018
+
1019
+ Args:
1020
+ new_value: Value to validate
1021
+ compare_to_range: If True, clamps value to any defined min/max bounds
1022
+
1023
+ Returns:
1024
+ Validated integer value
1025
+
1026
+ Raises:
1027
+ ValueError: If value cannot be converted to int
1028
+ """
193
1029
  try:
194
- self.min_value = int(min_value)
195
- self.max_value = int(max_value)
196
- except TypeError as e:
197
- raise TypeError(f"Cannot convert {min_value} and {max_value} to integer") from e
198
- if self.min_value is not None and self.max_value is not None:
199
- if self.min_value > self.max_value:
200
- raise ValueError(f"Minimum value {self.min_value} is greater than maximum value {self.max_value}")
201
- valid_default = self._validate(default)
202
- if valid_default != default:
203
- warn(f"Default value {default} is not in the range [{self.min_value}, {self.max_value}]. Clamping to {valid_default}.")
204
- self.default = valid_default
205
- self._value = self._validate(self.default)
206
-
207
- def _validate(self, new_value: Tuple[Any, Any]) -> Tuple[int, int]:
208
- try:
209
- values = (int(new_value[0]), int(new_value[1]))
210
- except (TypeError, ValueError):
211
- raise TypeError(f"Cannot convert {new_value} to integer pair")
1030
+ new_value = int(new_value)
1031
+ except ValueError:
1032
+ raise ValueError(f"Value {new_value} cannot be converted to int")
1033
+
1034
+ if compare_to_range:
1035
+ if self.min_value is not None and new_value < self.min_value:
1036
+ warn(f"Value {new_value} below minimum {self.min_value}, clamping")
1037
+ new_value = self.min_value
1038
+ if self.max_value is not None and new_value > self.max_value:
1039
+ warn(f"Value {new_value} above maximum {self.max_value}, clamping")
1040
+ new_value = self.max_value
1041
+ return int(new_value)
1042
+
1043
+ def _validate_update(self) -> None:
1044
+ """
1045
+ Validate complete parameter state after updates.
212
1046
 
213
- if self.min_value is not None:
214
- values = (max(self.min_value, values[0]), max(self.min_value, values[1]))
215
- if self.max_value is not None:
216
- values = (min(self.max_value, values[0]), min(self.max_value, values[1]))
217
- return values
1047
+ Ensures min_value <= max_value, swapping if needed.
1048
+ Re-validates current value against potentially updated bounds.
1049
+
1050
+ Raises:
1051
+ ParameterUpdateError: If bounds are invalid (e.g. None when required)
1052
+ """
1053
+ if (
1054
+ self.min_value is not None
1055
+ and self.max_value is not None
1056
+ and self.min_value > self.max_value
1057
+ ):
1058
+ warn(f"Min value greater than max value, swapping")
1059
+ self.min_value, self.max_value = self.max_value, self.min_value
1060
+ self.value = self._validate(self.value)
218
1061
 
219
1062
 
220
1063
  @dataclass(init=False)
221
- class FloatPairParameter(PairParameter[float]):
1064
+ class UnboundedFloatParameter(Parameter[float]):
1065
+ """
1066
+ Parameter for optionally bounded decimal numbers.
1067
+
1068
+ Creates a text input box in the GUI for entering numbers.
1069
+ See :meth:`~syd.interactive_viewer.InteractiveViewer.add_unbounded_float` and
1070
+ :meth:`~syd.interactive_viewer.InteractiveViewer.update_unbounded_float` for usage.
1071
+
1072
+ Parameters
1073
+ ----------
1074
+ name : str
1075
+ The name of the parameter
1076
+ value : float
1077
+ Initial value
1078
+ min_value : float, optional
1079
+ Minimum allowed value (or None for no minimum)
1080
+ max_value : float, optional
1081
+ Maximum allowed value (or None for no maximum)
1082
+ step : float, optional
1083
+ Size of each increment (default is None, meaning no rounding)
1084
+
1085
+ Examples
1086
+ --------
1087
+ >>> price = UnboundedFloatParameter("price", value=19.99, min_value=0.0, step=0.01)
1088
+ >>> price.value
1089
+ 19.99
1090
+ >>> price.update({"value": -5.0}) # Will be clamped to min_value
1091
+ >>> price.value
1092
+ 0.0
1093
+ >>> price.update({"value": 19.987}) # Will be rounded to step
1094
+ >>> price.value
1095
+ 19.99
1096
+
1097
+ Notes
1098
+ -----
1099
+ Use this instead of FloatParameter when you:
1100
+ - Don't know a reasonable maximum value
1101
+ - Only want to enforce a minimum or maximum, but not both
1102
+ - Need to allow very large or precise numbers that would be impractical with a slider
1103
+
1104
+ If step is provided, values will be rounded:
1105
+ - step=0.1 rounds to 1.0, 1.1, 1.2, etc.
1106
+ - step=0.01 rounds to 1.00, 1.01, 1.02, etc.
1107
+ - step=5.0 rounds to 0.0, 5.0, 10.0, etc.
1108
+ """
1109
+
1110
+ min_value: Optional[float]
1111
+ max_value: Optional[float]
222
1112
  step: float
223
1113
 
224
1114
  def __init__(
225
1115
  self,
226
1116
  name: str,
227
- default: Tuple[float, float],
228
- min_value: float = None,
229
- max_value: float = None,
230
- step: float = 0.1,
1117
+ value: float,
1118
+ min_value: Optional[float] = None,
1119
+ max_value: Optional[float] = None,
1120
+ step: Optional[float] = None,
231
1121
  ):
232
1122
  self.name = name
233
- try:
234
- self.min_value = float(min_value)
235
- self.max_value = float(max_value)
236
- except TypeError as e:
237
- raise TypeError(f"Cannot convert {min_value} and {max_value} to float") from e
238
- if self.min_value is not None and self.max_value is not None:
239
- if self.min_value > self.max_value:
240
- raise ValueError(f"Minimum value {self.min_value} is greater than maximum value {self.max_value}")
241
- valid_default = self._validate(default)
242
- if valid_default != default:
243
- warn(f"Default value {default} is not in the range [{self.min_value}, {self.max_value}]. Clamping to {valid_default}.")
244
- self.default = valid_default
245
1123
  self.step = step
246
- self._value = self._validate(self.default)
1124
+ self.min_value = (
1125
+ self._validate(min_value, compare_to_range=False)
1126
+ if min_value is not None
1127
+ else None
1128
+ )
1129
+ self.max_value = (
1130
+ self._validate(max_value, compare_to_range=False)
1131
+ if max_value is not None
1132
+ else None
1133
+ )
1134
+ self._value = self._validate(value)
1135
+
1136
+ def _validate(self, new_value: Any, compare_to_range: bool = True) -> float:
1137
+ """
1138
+ Validate and convert value to float, optionally checking bounds.
1139
+
1140
+ Handles None min/max values by skipping those bound checks.
1141
+ Only rounds to step if step is not None.
247
1142
 
248
- def _validate(self, new_value: Tuple[Any, Any]) -> Tuple[float, float]:
1143
+ Args:
1144
+ new_value: Value to validate
1145
+ compare_to_range: If True, clamps value to any defined min/max bounds
1146
+
1147
+ Returns:
1148
+ Validated and potentially rounded float value
1149
+
1150
+ Raises:
1151
+ ValueError: If value cannot be converted to float
1152
+ """
249
1153
  try:
250
- values = (float(new_value[0]), float(new_value[1]))
251
- except (TypeError, ValueError):
252
- raise TypeError(f"Cannot convert {new_value} to float pair")
1154
+ new_value = float(new_value)
1155
+ except ValueError:
1156
+ raise ValueError(f"Value {new_value} cannot be converted to float")
1157
+
1158
+ # Round to the nearest step if step is defined
1159
+ if self.step is not None:
1160
+ new_value = round(new_value / self.step) * self.step
1161
+
1162
+ if compare_to_range:
1163
+ if self.min_value is not None and new_value < self.min_value:
1164
+ warn(f"Value {new_value} below minimum {self.min_value}, clamping")
1165
+ new_value = self.min_value
1166
+ if self.max_value is not None and new_value > self.max_value:
1167
+ warn(f"Value {new_value} above maximum {self.max_value}, clamping")
1168
+ new_value = self.max_value
1169
+
1170
+ return float(new_value)
1171
+
1172
+ def _validate_update(self) -> None:
1173
+ """
1174
+ Validate complete parameter state after updates.
1175
+
1176
+ Ensures min_value <= max_value, swapping if needed.
1177
+ Re-validates current value against potentially updated bounds.
1178
+
1179
+ Raises:
1180
+ ParameterUpdateError: If bounds are invalid (e.g. None when required)
1181
+ """
1182
+ if (
1183
+ self.min_value is not None
1184
+ and self.max_value is not None
1185
+ and self.min_value > self.max_value
1186
+ ):
1187
+ warn(f"Min value greater than max value, swapping")
1188
+ self.min_value, self.max_value = self.max_value, self.min_value
1189
+ self.value = self._validate(self.value)
1190
+
1191
+
1192
+ class ButtonParameter(Parameter):
1193
+ """A parameter that represents a clickable button."""
253
1194
 
254
- if self.min_value is not None:
255
- values = (max(self.min_value, values[0]), max(self.min_value, values[1]))
256
- if self.max_value is not None:
257
- values = (min(self.max_value, values[0]), min(self.max_value, values[1]))
258
- return values
1195
+ _is_button: bool = True
1196
+
1197
+ def __init__(self, name: str, label: str, callback: Callable[[], None]):
1198
+ """
1199
+ Initialize a button parameter.
1200
+
1201
+ Args:
1202
+ name: Internal name of the parameter
1203
+ label: Text to display on the button
1204
+ callback: Function to call when button is clicked
1205
+ """
1206
+ self.name = name
1207
+ self.label = label
1208
+ self.callback = callback
1209
+ self._value = None # Buttons don't have a value in the traditional sense
1210
+
1211
+ def update(self, updates: Dict[str, Any]) -> None:
1212
+ """Update the button's label and/or callback."""
1213
+ if "label" in updates:
1214
+ self.label = updates["label"]
1215
+ if "callback" in updates:
1216
+ self.callback = updates["callback"]
1217
+
1218
+ @property
1219
+ def value(self) -> None:
1220
+ """Buttons don't have a value, always returns None."""
1221
+ return self._value
1222
+
1223
+ @value.setter
1224
+ def value(self, _: Any) -> None:
1225
+ """Buttons don't store values."""
1226
+ pass
1227
+
1228
+ def _validate_update(self) -> None:
1229
+ """Buttons don't need validation."""
1230
+ pass
1231
+
1232
+ def _validate(self, new_value: Any) -> None:
1233
+ """Buttons don't need validation."""
1234
+ pass
259
1235
 
260
1236
 
261
1237
  class ParameterType(Enum):
1238
+ """Registry of all available parameter types."""
1239
+
262
1240
  text = TextParameter
263
- selection = SingleSelectionParameter
264
- multiple_selection = MultipleSelectionParameter
265
1241
  boolean = BooleanParameter
1242
+ selection = SelectionParameter
1243
+ multiple_selection = MultipleSelectionParameter
266
1244
  integer = IntegerParameter
267
1245
  float = FloatParameter
268
- integer_pair = IntegerPairParameter
269
- float_pair = FloatPairParameter
1246
+ integer_range = IntegerRangeParameter
1247
+ float_range = FloatRangeParameter
1248
+ unbounded_integer = UnboundedIntegerParameter
1249
+ unbounded_float = UnboundedFloatParameter
1250
+ button = ButtonParameter