syd 0.1.6__py3-none-any.whl → 0.2.0__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.
@@ -4,35 +4,21 @@ import inspect
4
4
  from contextlib import contextmanager
5
5
  from matplotlib.figure import Figure
6
6
 
7
- from .parameters import (
8
- ParameterType,
9
- ActionType,
10
- Parameter,
11
- ParameterAddError,
12
- ParameterUpdateError,
13
- )
7
+ from .parameters import ParameterType, ActionType, Parameter
8
+ from .support import NoUpdate, NoInitialValue, ParameterAddError, ParameterUpdateError
14
9
 
15
10
 
16
- class _NoUpdate:
17
- """Singleton class to represent a non-update in parameter operations."""
18
-
19
- _instance = None
20
-
21
- def __new__(cls):
22
- if cls._instance is None:
23
- cls._instance = super().__new__(cls)
24
- return cls._instance
25
-
26
-
27
- # Create the singleton instance
28
- _NO_UPDATE = _NoUpdate()
11
+ # Create the singleton instances
12
+ NO_UPDATE = NoUpdate()
13
+ NO_INITIAL_VALUE = NoInitialValue()
29
14
 
30
15
 
31
16
  def validate_parameter_operation(
32
- operation: str, parameter_type: Union[ParameterType, ActionType]
17
+ operation: str,
18
+ parameter_type: Union[ParameterType, ActionType],
33
19
  ) -> Callable:
34
20
  """
35
- Decorator that validates parameter operations for the InteractiveViewer class.
21
+ Decorator that validates parameter operations for the viewer class.
36
22
 
37
23
  This decorator ensures that:
38
24
  1. The operation type matches the method name (add/update)
@@ -72,7 +58,7 @@ def validate_parameter_operation(
72
58
  )
73
59
 
74
60
  @wraps(func)
75
- def wrapper(self: "InteractiveViewer", name: Any, *args, **kwargs):
61
+ def wrapper(self: "Viewer", name: Any, *args, **kwargs):
76
62
  # Validate parameter name is a string
77
63
  if not isinstance(name, str):
78
64
  if operation == "add":
@@ -108,22 +94,14 @@ def validate_parameter_operation(
108
94
  msg = f"Parameter called {name} was found but is registered as a different parameter type ({type(self.parameters[name])}). Expecting {parameter_type.value}."
109
95
  raise ParameterUpdateError(name, parameter_type.name, msg)
110
96
 
111
- try:
112
- return func(self, name, *args, **kwargs)
113
- except Exception as e:
114
- if operation == "add":
115
- raise ParameterAddError(name, parameter_type.name, str(e)) from e
116
- elif operation == "update":
117
- raise ParameterUpdateError(name, parameter_type.name, str(e)) from e
118
- else:
119
- raise e
97
+ return func(self, name, *args, **kwargs)
120
98
 
121
99
  return wrapper
122
100
 
123
101
  return decorator
124
102
 
125
103
 
126
- class InteractiveViewer:
104
+ class Viewer:
127
105
  """
128
106
  Base class for creating interactive matplotlib figures with GUI controls.
129
107
 
@@ -138,7 +116,7 @@ class InteractiveViewer:
138
116
 
139
117
  Examples
140
118
  --------
141
- >>> class MyViewer(InteractiveViewer):
119
+ >>> class MyViewer(Viewer):
142
120
  ... def plot(self, state: Dict[str, Any]):
143
121
  ... fig = plt.figure()
144
122
  ... plt.plot([0, state['x']])
@@ -148,13 +126,12 @@ class InteractiveViewer:
148
126
  ... self.update_float('x', value=state['x'])
149
127
  ...
150
128
  >>> viewer = MyViewer()
151
- >>> viewer.add_float('x', value=1.0, min_value=0, max_value=10)
129
+ >>> viewer.add_float('x', value=1.0, min=0, max=10)
152
130
  >>> viewer.on_change('x', viewer.update_based_on_x)
153
131
  """
154
132
 
155
133
  parameters: Dict[str, Parameter]
156
134
  callbacks: Dict[str, List[Callable]]
157
- state: Dict[str, Any]
158
135
  _app_deployed: bool
159
136
  _in_callbacks: bool
160
137
 
@@ -162,15 +139,16 @@ class InteractiveViewer:
162
139
  instance = super().__new__(cls)
163
140
  instance.parameters = {}
164
141
  instance.callbacks = {}
165
- instance.state = {}
166
142
  instance._app_deployed = False
167
143
  instance._in_callbacks = False
168
144
  return instance
169
145
 
170
- def get_state(self) -> Dict[str, Any]:
146
+ @property
147
+ def state(self) -> Dict[str, Any]:
171
148
  """
172
149
  Get the current values of all parameters.
173
150
 
151
+
174
152
  Returns
175
153
  -------
176
154
  dict
@@ -178,12 +156,16 @@ class InteractiveViewer:
178
156
 
179
157
  Examples
180
158
  --------
181
- >>> viewer.add_float('x', value=1.0, min_value=0, max_value=10)
159
+ >>> viewer.add_float('x', value=1.0, min=0, max=10)
182
160
  >>> viewer.add_text('label', value='data')
183
- >>> viewer.get_state()
161
+ >>> viewer.state
184
162
  {'x': 1.0, 'label': 'data'}
185
163
  """
186
- return {name: param.value for name, param in self.parameters.items()}
164
+ return {
165
+ name: param.value
166
+ for name, param in self.parameters.items()
167
+ if not param._is_action
168
+ }
187
169
 
188
170
  def plot(self, state: Dict[str, Any]) -> Figure:
189
171
  """Create and return a matplotlib figure.
@@ -192,14 +174,14 @@ class InteractiveViewer:
192
174
 
193
175
  1. Call set_plot() with your plotting function
194
176
  This will look like this:
195
- >>> def plot(viewer, state):
177
+ >>> def plot(state):
196
178
  >>> ... generate figure, plot stuff ...
197
179
  >>> return fig
198
180
  >>> viewer.set_plot(plot))
199
181
 
200
- 2. Subclass InteractiveViewer and override this method
182
+ 2. Subclass Viewer and override this method
201
183
  This will look like this:
202
- >>> class YourViewer(InteractiveViewer):
184
+ >>> class YourViewer(Viewer):
203
185
  >>> def plot(self, state):
204
186
  >>> ... generate figure, plot stuff ...
205
187
  >>> return fig
@@ -223,7 +205,7 @@ class InteractiveViewer:
223
205
  """
224
206
  raise NotImplementedError(
225
207
  "Plot method not implemented. Either subclass "
226
- "InteractiveViewer and override plot(), or use "
208
+ "Viewer and override plot(), or use "
227
209
  "set_plot() to provide a plotting function."
228
210
  )
229
211
 
@@ -233,16 +215,26 @@ class InteractiveViewer:
233
215
 
234
216
  def deploy(self, env: str = "notebook", **kwargs):
235
217
  """Deploy the app in a notebook or standalone environment"""
218
+ env = env.lower()
236
219
  if env == "notebook":
237
- from .notebook_deployment import NotebookDeployment
220
+ from .notebook_deployment import NotebookDeployer
238
221
 
239
- deployer = NotebookDeployment(self, **kwargs)
222
+ deployer = NotebookDeployer(self, **kwargs)
240
223
  deployer.deploy()
224
+ return self
225
+
226
+ elif env == "browser" or env == "flask":
227
+ from .flask_deployment import deploy_flask
228
+
229
+ # Ensure port is None by default if not specified
230
+ if "port" not in kwargs:
231
+ kwargs["port"] = None
241
232
 
233
+ deployer = deploy_flask(self, **kwargs)
242
234
  return self
243
235
  else:
244
236
  raise ValueError(
245
- f"Unsupported environment: {env}, only 'notebook' is supported right now."
237
+ f"Unsupported environment: {env}, only 'notebook', 'plotly', 'plotly-inline', and 'flask' are supported right now."
246
238
  )
247
239
 
248
240
  @contextmanager
@@ -265,7 +257,6 @@ class InteractiveViewer:
265
257
 
266
258
  # Handle partial functions
267
259
  if isinstance(func, partial):
268
- # Create new partial with self as first arg if not already there
269
260
  get_self = (
270
261
  lambda func: hasattr(func.func, "__self__") and func.func.__self__
271
262
  )
@@ -274,17 +265,6 @@ class InteractiveViewer:
274
265
  get_self = lambda func: hasattr(func, "__self__") and func.__self__
275
266
  get_name = lambda func: func.__name__
276
267
 
277
- # Check if it's a class method
278
- class_method = get_self(func) is self.__class__
279
- if class_method:
280
- raise ValueError(context + "Class methods are not supported.")
281
-
282
- # Check if it's a bound method to another instance other than this one
283
- if get_self(func) and get_self(func) is not self:
284
- raise ValueError(
285
- context + "Bound methods to other instances are not supported."
286
- )
287
-
288
268
  # Get function signature
289
269
  try:
290
270
  params = list(inspect.signature(func).parameters.values())
@@ -294,17 +274,18 @@ class InteractiveViewer:
294
274
 
295
275
  # Look through params and check if there are two positional parameters (including self for bound methods)
296
276
  bound_method = get_self(func) is self
297
- positional_params = 0 + bound_method
277
+ positional_params = 0
278
+ required_kwargs = 0
298
279
  optional_part = ""
299
280
  for param in params:
300
281
  # Check if it's a positional parameter. If it is, count it.
301
- # As long as we have less than 2 positional parameters, we're good.
302
- # When we already have 2 positional parameters, we need to make sure any other positional parameters have defaults.
282
+ # We need at least 1 positional parameter. When we already have 1,
283
+ # we need to make sure any other positional parameters have defaults.
303
284
  if param.kind in (
304
285
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
305
286
  inspect.Parameter.POSITIONAL_ONLY,
306
287
  ):
307
- if positional_params < 2:
288
+ if positional_params < 1:
308
289
  positional_params += 1
309
290
  else:
310
291
  if param.default == inspect.Parameter.empty:
@@ -317,10 +298,12 @@ class InteractiveViewer:
317
298
  optional_part += (
318
299
  f", {param.name}={param.default!r}"
319
300
  if param.default != inspect.Parameter.empty
320
- else f", {param.name}"
301
+ else f""
321
302
  )
303
+ if param.default == inspect.Parameter.empty:
304
+ required_kwargs += 1
322
305
 
323
- if positional_params != 2:
306
+ if positional_params != 1 or required_kwargs != 0:
324
307
  func_name = get_name(func)
325
308
  if isinstance(func, partial):
326
309
  func_sig = str(inspect.signature(func))
@@ -330,9 +313,8 @@ class InteractiveViewer:
330
313
  context
331
314
  + "\n"
332
315
  + f"Your partial function '{func_name}' has an incorrect signature.\n"
333
- "Partial functions must have exactly two positional parameters.\n"
334
- "The first parameter should be self -- it corresponds to the viewer.\n"
335
- "The second parameter should be state -- a dictionary of the current state of the viewer.\n"
316
+ "Partial functions must have exactly one positional parameter\n"
317
+ "which corresponds to a dictionary of the current state of the viewer.\n"
336
318
  "\nYour partial function effectivelylooks like this:\n"
337
319
  f"def {func_name}{func_sig}:\n"
338
320
  " ... your function code ..."
@@ -347,45 +329,44 @@ class InteractiveViewer:
347
329
  context + "\n"
348
330
  f"Your bound method '{func_name}{func_sig}' has an incorrect signature.\n"
349
331
  "Bound methods must have exactly one positional parameter in addition to self.\n"
350
- "The first parameter should be self -- it corresponds to the viewer.\n"
332
+ "The first parameter should be self (required for bound methods).\n"
351
333
  "The second parameter should be state -- a dictionary of the current state of the viewer.\n"
352
334
  "\nYour method looks like this:\n"
353
- "class YourViewer(InteractiveViewer):\n"
335
+ "class YourViewer(Viewer):\n"
354
336
  f" def {func_name}{func_sig}:\n"
355
337
  " ... your function code ...\n"
356
338
  "\nIt should look like this:\n"
357
- "class YourViewer(InteractiveViewer):\n"
339
+ "class YourViewer(Viewer):\n"
358
340
  f" def {func_name}(self, state{optional_part}):\n"
359
341
  " ... your function code ..."
360
342
  )
361
343
  raise ValueError(msg)
362
344
  else:
363
345
  func_sig = str(inspect.signature(func))
364
-
346
+ bound_elsewhere = get_self(func) and get_self(func) is not self
347
+ if bound_elsewhere:
348
+ func_name = f"self.{func_name}"
349
+ func_sig = f"(self, {func_sig[1:]})"
350
+ add_self = True
351
+ else:
352
+ add_self = False
365
353
  msg = (
366
354
  context + "\n"
367
355
  f"Your function '{func_name}{func_sig}' has an incorrect signature.\n"
368
- "External functions must have exactly two positional parameters.\n"
369
- "The first parameter should be viewer -- it corresponds to the viewer.\n"
370
- "The second parameter should be state -- a dictionary of the current state of the viewer.\n"
356
+ "Functions must have exactly one positional parameter\n"
357
+ "which corresponds to a dictionary of the current state of the viewer.\n"
371
358
  "\nYour function looks like this:\n"
372
359
  f"def {func_name}{func_sig}:\n"
373
360
  " ... your function code ...\n"
374
361
  "\nIt should look like this:\n"
375
- f"def {func_name}(viewer, state{optional_part}):\n"
362
+ f"def {func_name}({'self, ' if add_self else ''}state{optional_part}):\n"
376
363
  " ... your function code ..."
377
364
  )
378
365
  raise ValueError(msg)
379
366
 
380
- # If not a bound method, wrap it to inject self when called with just the state
381
- if bound_method:
382
- return func
383
-
384
- @wraps(func)
385
- def func_with_self(*args, **kwargs):
386
- return func(self, *args, **kwargs)
387
-
388
- return func_with_self
367
+ # If we've made it here, the function has exactly one required positional parameter
368
+ # which means it's callable by the viewer.
369
+ return func
389
370
 
390
371
  def perform_callbacks(self, name: str) -> bool:
391
372
  """Perform callbacks for all parameters that have changed"""
@@ -394,7 +375,7 @@ class InteractiveViewer:
394
375
  try:
395
376
  self._in_callbacks = True
396
377
  if name in self.callbacks:
397
- state = self.get_state()
378
+ state = self.state
398
379
  for callback in self.callbacks[name]:
399
380
  callback(state)
400
381
  finally:
@@ -466,8 +447,25 @@ class InteractiveViewer:
466
447
  self.perform_callbacks(name)
467
448
 
468
449
  # -------------------- parameter registration methods --------------------
450
+ def remove_parameter(self, name: str) -> None:
451
+ """
452
+ Remove a parameter from the viewer.
453
+
454
+ Parameters
455
+ ----------
456
+ name : str
457
+ Name of the parameter to remove
458
+ """
459
+ if name in self.parameters:
460
+ del self.parameters[name]
461
+
469
462
  @validate_parameter_operation("add", ParameterType.text)
470
- def add_text(self, name: str, *, value: str) -> None:
463
+ def add_text(
464
+ self,
465
+ name: str,
466
+ *,
467
+ value: Union[str, NoInitialValue] = NO_INITIAL_VALUE,
468
+ ) -> None:
471
469
  """
472
470
  Add a text input parameter to the viewer.
473
471
 
@@ -478,13 +476,14 @@ class InteractiveViewer:
478
476
  ----------
479
477
  name : str
480
478
  Name of the parameter (used as label in GUI)
481
- value : str
479
+ value : Union[str, NoInitialValue]
482
480
  Initial text value
481
+ If not provided, the parameter will be empty.
483
482
 
484
483
  Examples
485
484
  --------
486
485
  >>> viewer.add_text('title', value='My Plot')
487
- >>> viewer.get_state()['title']
486
+ >>> viewer.state['title']
488
487
  'My Plot'
489
488
  """
490
489
  try:
@@ -495,7 +494,12 @@ class InteractiveViewer:
495
494
  self.parameters[name] = new_param
496
495
 
497
496
  @validate_parameter_operation("add", ParameterType.boolean)
498
- def add_boolean(self, name: str, *, value: bool) -> None:
497
+ def add_boolean(
498
+ self,
499
+ name: str,
500
+ *,
501
+ value: Union[bool, NoInitialValue] = NO_INITIAL_VALUE,
502
+ ) -> None:
499
503
  """
500
504
  Add a boolean parameter to the viewer.
501
505
 
@@ -506,13 +510,14 @@ class InteractiveViewer:
506
510
  ----------
507
511
  name : str
508
512
  Name of the parameter (used as label in GUI)
509
- value : bool
513
+ value : Union[bool, NoInitialValue]
510
514
  Initial state (True=checked, False=unchecked)
515
+ If not provided, the parameter will be checked.
511
516
 
512
517
  Examples
513
518
  --------
514
519
  >>> viewer.add_boolean('show_grid', value=True)
515
- >>> viewer.get_state()['show_grid']
520
+ >>> viewer.state['show_grid']
516
521
  True
517
522
  """
518
523
  try:
@@ -523,7 +528,13 @@ class InteractiveViewer:
523
528
  self.parameters[name] = new_param
524
529
 
525
530
  @validate_parameter_operation("add", ParameterType.selection)
526
- def add_selection(self, name: str, *, value: Any, options: List[Any]) -> None:
531
+ def add_selection(
532
+ self,
533
+ name: str,
534
+ *,
535
+ value: Union[Any, NoInitialValue] = NO_INITIAL_VALUE,
536
+ options: List[Any],
537
+ ) -> None:
527
538
  """
528
539
  Add a single-selection parameter to the viewer.
529
540
 
@@ -543,7 +554,7 @@ class InteractiveViewer:
543
554
  --------
544
555
  >>> viewer.add_selection('color', value='red',
545
556
  ... options=['red', 'green', 'blue'])
546
- >>> viewer.get_state()['color']
557
+ >>> viewer.state['color']
547
558
  'red'
548
559
  """
549
560
  try:
@@ -555,7 +566,11 @@ class InteractiveViewer:
555
566
 
556
567
  @validate_parameter_operation("add", ParameterType.multiple_selection)
557
568
  def add_multiple_selection(
558
- self, name: str, *, value: List[Any], options: List[Any]
569
+ self,
570
+ name: str,
571
+ *,
572
+ value: Union[List[Any], NoInitialValue] = NO_INITIAL_VALUE,
573
+ options: List[Any],
559
574
  ) -> None:
560
575
  """
561
576
  Add a multiple-selection parameter to the viewer.
@@ -568,8 +583,9 @@ class InteractiveViewer:
568
583
  ----------
569
584
  name : str
570
585
  Name of the parameter (used as label in GUI)
571
- value : list
586
+ value : Union[list, NoInitialValue]
572
587
  Initially selected values (must all be in options)
588
+ If not provided, the parameter will be empty.
573
589
  options : list
574
590
  List of values that can be selected
575
591
 
@@ -578,7 +594,7 @@ class InteractiveViewer:
578
594
  >>> viewer.add_multiple_selection('toppings',
579
595
  ... value=['cheese'],
580
596
  ... options=['cheese', 'pepperoni', 'mushrooms'])
581
- >>> viewer.get_state()['toppings']
597
+ >>> viewer.state['toppings']
582
598
  ['cheese']
583
599
  """
584
600
  try:
@@ -593,46 +609,38 @@ class InteractiveViewer:
593
609
  self,
594
610
  name: str,
595
611
  *,
596
- value: Union[float, int],
597
- min_value: Union[float, int],
598
- max_value: Union[float, int],
612
+ value: Union[Union[float, int], NoInitialValue] = NO_INITIAL_VALUE,
613
+ min: Union[float, int],
614
+ max: Union[float, int],
599
615
  ) -> None:
600
616
  """
601
617
  Add an integer parameter to the viewer.
602
618
 
603
- Creates a slider in the GUI that lets users select whole numbers between
604
- min_value and max_value. Values will be clamped to stay within bounds.
619
+ Creates a slider to select whole numbers between a minimum and maximum.
605
620
  See :class:`~syd.parameters.IntegerParameter` for details.
606
621
 
607
622
  Parameters
608
623
  ----------
609
624
  name : str
610
- Name of the parameter (used as label in GUI)
611
- value : int
612
- Initial value (will be clamped between min_value and max_value)
613
- min_value : int
625
+ Name of the parameter (used as label in GUI and internal identifier)
626
+ value : Union[int, NoInitialValue]
627
+ Initial value (default position of the slider)
628
+ If not provided, the parameter will be set to the minimum value.
629
+ min : int
614
630
  Minimum allowed value
615
- max_value : int
631
+ max : int
616
632
  Maximum allowed value
617
633
 
618
634
  Examples
619
635
  --------
620
- >>> viewer.add_integer('count', value=5, min_value=0, max_value=10)
621
- >>> viewer.get_state()['count']
622
- 5
623
- >>> viewer.update_integer('count', value=15) # Will be clamped to 10
624
- >>> viewer.get_state()['count']
625
- 10
636
+ >>> viewer.add_integer('age', value=25, min=0, max=120)
637
+
638
+ >>> viewer.add_integer('year', value=2023, min=1900, max=2100)
626
639
  """
627
640
  try:
628
- new_param = ParameterType.integer.value(
629
- name,
630
- value,
631
- min_value,
632
- max_value,
633
- )
641
+ new_param = ParameterType.integer.value(name, value, min, max)
634
642
  except Exception as e:
635
- raise ParameterAddError(name, "number", str(e)) from e
643
+ raise ParameterAddError(name, "integer", str(e)) from e
636
644
  else:
637
645
  self.parameters[name] = new_param
638
646
 
@@ -641,51 +649,41 @@ class InteractiveViewer:
641
649
  self,
642
650
  name: str,
643
651
  *,
644
- value: Union[float, int],
645
- min_value: Union[float, int],
646
- max_value: Union[float, int],
647
- step: float = 0.1,
652
+ value: Union[Union[float, int], NoInitialValue] = NO_INITIAL_VALUE,
653
+ min: Union[float, int],
654
+ max: Union[float, int],
655
+ step: float = 0.01,
648
656
  ) -> None:
649
657
  """
650
- Add a decimal number parameter to the viewer.
658
+ Add a float parameter to the viewer.
651
659
 
652
- Creates a slider in the GUI that lets users select numbers between
653
- min_value and max_value. Values will be rounded to the nearest step
654
- and clamped to stay within bounds.
660
+ Creates a slider to select decimal numbers between a minimum and maximum.
661
+ See :class:`~syd.parameters.FloatParameter` for details.
655
662
 
656
663
  Parameters
657
664
  ----------
658
665
  name : str
659
- Name of the parameter (used as label in GUI)
660
- value : float
661
- Initial value (will be clamped between min_value and max_value)
662
- min_value : float
666
+ Name of the parameter (internal identifier)
667
+ value : Union[float, NoInitialValue]
668
+ Initial value (default position of the slider)
669
+ If not provided, the parameter will be set to the minimum value.
670
+ min : float
663
671
  Minimum allowed value
664
- max_value : float
672
+ max : float
665
673
  Maximum allowed value
666
674
  step : float, optional
667
- Size of each increment (default: 0.1)
675
+ Step size for the slider (default: 0.01)
668
676
 
669
677
  Examples
670
678
  --------
671
- >>> viewer.add_float('temperature', value=20.0,
672
- ... min_value=0.0, max_value=100.0, step=0.5)
673
- >>> viewer.get_state()['temperature']
674
- 20.0
675
- >>> viewer.update_float('temperature', value=20.7) # Will round to 20.5
676
- >>> viewer.get_state()['temperature']
677
- 20.5
679
+ >>> viewer.add_float('temperature', value=98.6, min=95.0, max=105.0, step=0.1)
680
+
681
+ >>> viewer.add_float('price', value=9.99, min=0.0, max=100.0, step=0.01)
678
682
  """
679
683
  try:
680
- new_param = ParameterType.float.value(
681
- name,
682
- value,
683
- min_value,
684
- max_value,
685
- step,
686
- )
684
+ new_param = ParameterType.float.value(name, value, min, max, step)
687
685
  except Exception as e:
688
- raise ParameterAddError(name, "number", str(e)) from e
686
+ raise ParameterAddError(name, "float", str(e)) from e
689
687
  else:
690
688
  self.parameters[name] = new_param
691
689
 
@@ -694,48 +692,38 @@ class InteractiveViewer:
694
692
  self,
695
693
  name: str,
696
694
  *,
697
- value: Tuple[Union[float, int], Union[float, int]],
698
- min_value: Union[float, int],
699
- max_value: Union[float, int],
695
+ value: Union[
696
+ Tuple[Union[float, int], Union[float, int]], NoInitialValue
697
+ ] = NO_INITIAL_VALUE,
698
+ min: Union[float, int],
699
+ max: Union[float, int],
700
700
  ) -> None:
701
701
  """
702
- Add a range parameter for whole numbers to the viewer.
702
+ Add an integer range parameter to the viewer.
703
703
 
704
- Creates a range slider in the GUI that lets users select a range of integers
705
- between min_value and max_value. The range is specified as (low, high) and
706
- both values will be clamped to stay within bounds.
704
+ Creates a range slider to select a range of whole numbers between bounds.
707
705
  See :class:`~syd.parameters.IntegerRangeParameter` for details.
708
706
 
709
707
  Parameters
710
708
  ----------
711
709
  name : str
712
- Name of the parameter (used as label in GUI)
713
- value : tuple[int, int]
714
- Initial (low, high) values
715
- min_value : int
716
- Minimum allowed value for both low and high
717
- max_value : int
718
- Maximum allowed value for both low and high
710
+ Name of the parameter (internal identifier)
711
+ value : Union[tuple[int, int], NoInitialValue]
712
+ Initial (low, high) values for the range
713
+ If not provided, the parameter will be set to the full range.
714
+ min : int
715
+ Minimum allowed value for the range
716
+ max : int
717
+ Maximum allowed value for the range
719
718
 
720
719
  Examples
721
720
  --------
722
- >>> viewer.add_integer_range('age_range',
723
- ... value=(25, 35),
724
- ... min_value=18, max_value=100)
725
- >>> viewer.get_state()['age_range']
726
- (25, 35)
727
- >>> # Values will be swapped if low > high
728
- >>> viewer.update_integer_range('age_range', value=(40, 30))
729
- >>> viewer.get_state()['age_range']
730
- (30, 40)
721
+ >>> viewer.add_integer_range('age_range', value=(25, 45), min=18, max=100)
722
+
723
+ >>> viewer.add_integer_range('year_range', value=(2000, 2020), min=1900, max=2100)
731
724
  """
732
725
  try:
733
- new_param = ParameterType.integer_range.value(
734
- name,
735
- value,
736
- min_value,
737
- max_value,
738
- )
726
+ new_param = ParameterType.integer_range.value(name, value, min, max)
739
727
  except Exception as e:
740
728
  raise ParameterAddError(name, "integer_range", str(e)) from e
741
729
  else:
@@ -746,52 +734,41 @@ class InteractiveViewer:
746
734
  self,
747
735
  name: str,
748
736
  *,
749
- value: Tuple[Union[float, int], Union[float, int]],
750
- min_value: Union[float, int],
751
- max_value: Union[float, int],
752
- step: float = 0.1,
737
+ value: Union[
738
+ Tuple[Union[float, int], Union[float, int]], NoInitialValue
739
+ ] = NO_INITIAL_VALUE,
740
+ min: Union[float, int],
741
+ max: Union[float, int],
742
+ step: float = 0.01,
753
743
  ) -> None:
754
744
  """
755
- Add a range parameter for decimal numbers to the viewer.
745
+ Add a float range parameter to the viewer.
756
746
 
757
- Creates a range slider in the GUI that lets users select a range of numbers
758
- between min_value and max_value. The range is specified as (low, high) and
759
- both values will be rounded to the nearest step and clamped to stay within bounds.
747
+ Creates a range slider to select a range of decimal numbers between bounds.
760
748
  See :class:`~syd.parameters.FloatRangeParameter` for details.
761
749
 
762
750
  Parameters
763
751
  ----------
764
752
  name : str
765
- Name of the parameter (used as label in GUI)
766
- value : tuple[float, float]
767
- Initial (low, high) values
768
- min_value : float
769
- Minimum allowed value for both low and high
770
- max_value : float
771
- Maximum allowed value for both low and high
753
+ Name of the parameter (internal identifier)
754
+ value : Union[tuple[float, float], NoInitialValue]
755
+ Initial (low, high) values for the range
756
+ If not provided, the parameter will be set to the full range.
757
+ min : float
758
+ Minimum allowed value for the range
759
+ max : float
760
+ Maximum allowed value for the range
772
761
  step : float, optional
773
- Size of each increment (default: 0.1)
762
+ Step size for the slider (default: 0.01)
774
763
 
775
764
  Examples
776
765
  --------
777
- >>> viewer.add_float_range('price_range',
778
- ... value=(10.0, 20.0),
779
- ... min_value=0.0, max_value=100.0, step=0.5)
780
- >>> viewer.get_state()['price_range']
781
- (10.0, 20.0)
782
- >>> # Values will be rounded to nearest step
783
- >>> viewer.update_float_range('price_range', value=(10.7, 19.2))
784
- >>> viewer.get_state()['price_range']
785
- (10.5, 19.0)
766
+ >>> viewer.add_float_range('temp_range', value=(97.0, 99.0), min=95.0, max=105.0, step=0.1)
767
+
768
+ >>> viewer.add_float_range('price_range', value=(10.0, 50.0), min=0.0, max=100.0, step=0.01)
786
769
  """
787
770
  try:
788
- new_param = ParameterType.float_range.value(
789
- name,
790
- value,
791
- min_value,
792
- max_value,
793
- step,
794
- )
771
+ new_param = ParameterType.float_range.value(name, value, min, max, step)
795
772
  except Exception as e:
796
773
  raise ParameterAddError(name, "float_range", str(e)) from e
797
774
  else:
@@ -802,48 +779,31 @@ class InteractiveViewer:
802
779
  self,
803
780
  name: str,
804
781
  *,
805
- value: Union[float, int],
806
- min_value: Optional[Union[float, int]] = None,
807
- max_value: Optional[Union[float, int]] = None,
782
+ value: Union[Union[float, int], NoInitialValue] = NO_INITIAL_VALUE,
808
783
  ) -> None:
809
784
  """
810
785
  Add an unbounded integer parameter to the viewer.
811
786
 
812
787
  Creates a text input box in the GUI for entering whole numbers. Unlike
813
- add_integer(), this allows very large numbers and optionally no minimum
814
- or maximum bounds.
788
+ add_integer(), this allows very large numbers without bounds.
815
789
  See :class:`~syd.parameters.UnboundedIntegerParameter` for details.
816
790
 
817
791
  Parameters
818
792
  ----------
819
793
  name : str
820
794
  Name of the parameter (used as label in GUI)
821
- value : int
795
+ value : Union[int, NoInitialValue]
822
796
  Initial value
823
- min_value : int, optional
824
- Minimum allowed value (or None for no minimum)
825
- max_value : int, optional
826
- Maximum allowed value (or None for no maximum)
797
+ If not provided, the parameter will be set to 0.
827
798
 
828
799
  Examples
829
800
  --------
830
- >>> viewer.add_unbounded_integer('population',
831
- ... value=1000000,
832
- ... min_value=0) # No maximum
833
- >>> viewer.get_state()['population']
801
+ >>> viewer.add_unbounded_integer('population', value=1000000)
802
+ >>> viewer.state['population']
834
803
  1000000
835
- >>> # Values below minimum will be clamped
836
- >>> viewer.update_unbounded_integer('population', value=-5)
837
- >>> viewer.get_state()['population']
838
- 0
839
804
  """
840
805
  try:
841
- new_param = ParameterType.unbounded_integer.value(
842
- name,
843
- value,
844
- min_value,
845
- max_value,
846
- )
806
+ new_param = ParameterType.unbounded_integer.value(name, value)
847
807
  except Exception as e:
848
808
  raise ParameterAddError(name, "unbounded_integer", str(e)) from e
849
809
  else:
@@ -854,53 +814,39 @@ class InteractiveViewer:
854
814
  self,
855
815
  name: str,
856
816
  *,
857
- value: Union[float, int],
858
- min_value: Optional[Union[float, int]] = None,
859
- max_value: Optional[Union[float, int]] = None,
817
+ value: Union[Union[float, int], NoInitialValue] = NO_INITIAL_VALUE,
860
818
  step: Optional[float] = None,
861
819
  ) -> None:
862
820
  """
863
821
  Add an unbounded decimal number parameter to the viewer.
864
822
 
865
823
  Creates a text input box in the GUI for entering numbers. Unlike add_float(),
866
- this allows very large or precise numbers and optionally no minimum or
867
- maximum bounds. Values can optionally be rounded to a step size.
824
+ this allows very large or precise numbers without bounds. Values can optionally
825
+ be rounded to a step size.
868
826
  See :class:`~syd.parameters.UnboundedFloatParameter` for details.
869
827
 
870
828
  Parameters
871
829
  ----------
872
830
  name : str
873
831
  Name of the parameter (used as label in GUI)
874
- value : float
832
+ value : Union[float, NoInitialValue]
875
833
  Initial value
876
- min_value : float, optional
877
- Minimum allowed value (or None for no minimum)
878
- max_value : float, optional
879
- Maximum allowed value (or None for no maximum)
834
+ If not provided, the parameter will be set to 0.
880
835
  step : float, optional
881
836
  Size of each increment (or None for no rounding)
882
837
 
883
838
  Examples
884
839
  --------
885
- >>> viewer.add_unbounded_float('wavelength',
886
- ... value=550e-9, # Nanometers
887
- ... min_value=0.0,
888
- ... step=1e-9) # Round to nearest nanometer
889
- >>> viewer.get_state()['wavelength']
840
+ >>> viewer.add_unbounded_float('wavelength', value=550e-9, step=1e-9)
841
+ >>> viewer.state['wavelength']
890
842
  5.5e-07
891
843
  >>> # Values will be rounded if step is provided
892
844
  >>> viewer.update_unbounded_float('wavelength', value=550.7e-9)
893
- >>> viewer.get_state()['wavelength']
845
+ >>> viewer.state['wavelength']
894
846
  5.51e-07
895
847
  """
896
848
  try:
897
- new_param = ParameterType.unbounded_float.value(
898
- name,
899
- value,
900
- min_value,
901
- max_value,
902
- step,
903
- )
849
+ new_param = ParameterType.unbounded_float.value(name, value, step)
904
850
  except Exception as e:
905
851
  raise ParameterAddError(name, "unbounded_float", str(e)) from e
906
852
  else:
@@ -911,7 +857,7 @@ class InteractiveViewer:
911
857
  self,
912
858
  name: str,
913
859
  *,
914
- label: str,
860
+ label: Union[str, NoInitialValue] = NO_INITIAL_VALUE,
915
861
  callback: Callable[[], None],
916
862
  ) -> None:
917
863
  """
@@ -925,19 +871,23 @@ class InteractiveViewer:
925
871
  ----------
926
872
  name : str
927
873
  Name of the parameter (internal identifier)
928
- label : str
874
+ label : Union[str, NoInitialValue]
929
875
  Text to display on the button
876
+ If not provided, the parameter's label will be set to the name.
930
877
  callback : callable
931
- Function to call when the button is clicked (takes no arguments)
878
+ Function to call when the button is clicked (takes state as a single argument)
932
879
 
933
880
  Examples
934
881
  --------
935
- >>> def reset_plot():
882
+ >>> def reset_plot(state):
936
883
  ... print("Resetting plot...")
937
884
  >>> viewer.add_button('reset', label='Reset Plot', callback=reset_plot)
885
+
886
+ >>> def print_plot_info(state):
887
+ ... print(f"Current plot info: {state['plot_info']}")
888
+ >>> viewer.add_button('print_info', label='Print Plot Info', callback=print_plot_info)
938
889
  """
939
890
  try:
940
-
941
891
  callback = self._prepare_function(
942
892
  callback,
943
893
  context="Setting button callback:",
@@ -952,60 +902,60 @@ class InteractiveViewer:
952
902
  # -------------------- parameter update methods --------------------
953
903
  @validate_parameter_operation("update", ParameterType.text)
954
904
  def update_text(
955
- self, name: str, *, value: Union[str, _NoUpdate] = _NO_UPDATE
905
+ self, name: str, *, value: Union[str, NoUpdate] = NO_UPDATE
956
906
  ) -> None:
957
907
  """
958
908
  Update a text parameter's value.
959
909
 
960
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_text`.
910
+ Updates a parameter created by :meth:`~syd.viewer.Viewer.add_text`.
961
911
  See :class:`~syd.parameters.TextParameter` for details about value validation.
962
912
 
963
913
  Parameters
964
914
  ----------
965
915
  name : str
966
916
  Name of the text parameter to update
967
- value : str, optional
917
+ value : Union[str, NoUpdate], optional
968
918
  New text value (if not provided, no change)
969
919
 
970
920
  Examples
971
921
  --------
972
922
  >>> viewer.add_text('title', value='Original Title')
973
923
  >>> viewer.update_text('title', value='New Title')
974
- >>> viewer.get_state()['title']
924
+ >>> viewer.state['title']
975
925
  'New Title'
976
926
  """
977
927
  updates = {}
978
- if value is not _NO_UPDATE:
928
+ if not value == NO_UPDATE:
979
929
  updates["value"] = value
980
930
  if updates:
981
931
  self.parameters[name].update(updates)
982
932
 
983
933
  @validate_parameter_operation("update", ParameterType.boolean)
984
934
  def update_boolean(
985
- self, name: str, *, value: Union[bool, _NoUpdate] = _NO_UPDATE
935
+ self, name: str, *, value: Union[bool, NoUpdate] = NO_UPDATE
986
936
  ) -> None:
987
937
  """
988
938
  Update a boolean parameter's value.
989
939
 
990
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_boolean`.
940
+ Updates a parameter created by :meth:`~syd.viewer.Viewer.add_boolean`.
991
941
  See :class:`~syd.parameters.BooleanParameter` for details about value validation.
992
942
 
993
943
  Parameters
994
944
  ----------
995
945
  name : str
996
946
  Name of the boolean parameter to update
997
- value : bool, optional
947
+ value : Union[bool, NoUpdate], optional
998
948
  New state (True/False) (if not provided, no change)
999
949
 
1000
950
  Examples
1001
951
  --------
1002
952
  >>> viewer.add_boolean('show_grid', value=True)
1003
953
  >>> viewer.update_boolean('show_grid', value=False)
1004
- >>> viewer.get_state()['show_grid']
954
+ >>> viewer.state['show_grid']
1005
955
  False
1006
956
  """
1007
957
  updates = {}
1008
- if value is not _NO_UPDATE:
958
+ if not value == NO_UPDATE:
1009
959
  updates["value"] = value
1010
960
  if updates:
1011
961
  self.parameters[name].update(updates)
@@ -1015,22 +965,22 @@ class InteractiveViewer:
1015
965
  self,
1016
966
  name: str,
1017
967
  *,
1018
- value: Union[Any, _NoUpdate] = _NO_UPDATE,
1019
- options: Union[List[Any], _NoUpdate] = _NO_UPDATE,
968
+ value: Union[Any, NoUpdate] = NO_UPDATE,
969
+ options: Union[List[Any], NoUpdate] = NO_UPDATE,
1020
970
  ) -> None:
1021
971
  """
1022
972
  Update a selection parameter's value and/or options.
1023
973
 
1024
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_selection`.
974
+ Updates a parameter created by :meth:`~syd.viewer.Viewer.add_selection`.
1025
975
  See :class:`~syd.parameters.SelectionParameter` for details about value validation.
1026
976
 
1027
977
  Parameters
1028
978
  ----------
1029
979
  name : str
1030
980
  Name of the selection parameter to update
1031
- value : Any, optional
981
+ value : Union[Any, NoUpdate], optional
1032
982
  New selected value (must be in options) (if not provided, no change)
1033
- options : list, optional
983
+ options : Union[list, NoUpdate], optional
1034
984
  New list of selectable options (if not provided, no change)
1035
985
 
1036
986
  Examples
@@ -1045,9 +995,9 @@ class InteractiveViewer:
1045
995
  ... value='purple')
1046
996
  """
1047
997
  updates = {}
1048
- if value is not _NO_UPDATE:
998
+ if not value == NO_UPDATE:
1049
999
  updates["value"] = value
1050
- if options is not _NO_UPDATE:
1000
+ if not options == NO_UPDATE:
1051
1001
  updates["options"] = options
1052
1002
  if updates:
1053
1003
  self.parameters[name].update(updates)
@@ -1057,22 +1007,22 @@ class InteractiveViewer:
1057
1007
  self,
1058
1008
  name: str,
1059
1009
  *,
1060
- value: Union[List[Any], _NoUpdate] = _NO_UPDATE,
1061
- options: Union[List[Any], _NoUpdate] = _NO_UPDATE,
1010
+ value: Union[List[Any], NoUpdate] = NO_UPDATE,
1011
+ options: Union[List[Any], NoUpdate] = NO_UPDATE,
1062
1012
  ) -> None:
1063
1013
  """
1064
1014
  Update a multiple selection parameter's values and/or options.
1065
1015
 
1066
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_multiple_selection`.
1016
+ Updates a parameter created by :meth:`~syd.viewer.Viewer.add_multiple_selection`.
1067
1017
  See :class:`~syd.parameters.MultipleSelectionParameter` for details about value validation.
1068
1018
 
1069
1019
  Parameters
1070
1020
  ----------
1071
1021
  name : str
1072
1022
  Name of the multiple selection parameter to update
1073
- value : list, optional
1023
+ value : Union[list, NoUpdate], optional
1074
1024
  New list of selected values (all must be in options) (if not provided, no change)
1075
- options : list, optional
1025
+ options : Union[list, NoUpdate], optional
1076
1026
  New list of selectable options (if not provided, no change)
1077
1027
 
1078
1028
  Examples
@@ -1089,9 +1039,9 @@ class InteractiveViewer:
1089
1039
  ... value=['cheese', 'bacon'])
1090
1040
  """
1091
1041
  updates = {}
1092
- if value is not _NO_UPDATE:
1042
+ if not value == NO_UPDATE:
1093
1043
  updates["value"] = value
1094
- if options is not _NO_UPDATE:
1044
+ if not options == NO_UPDATE:
1095
1045
  updates["options"] = options
1096
1046
  if updates:
1097
1047
  self.parameters[name].update(updates)
@@ -1101,239 +1051,219 @@ class InteractiveViewer:
1101
1051
  self,
1102
1052
  name: str,
1103
1053
  *,
1104
- value: Union[int, _NoUpdate] = _NO_UPDATE,
1105
- min_value: Union[int, _NoUpdate] = _NO_UPDATE,
1106
- max_value: Union[int, _NoUpdate] = _NO_UPDATE,
1054
+ value: Union[int, NoUpdate] = NO_UPDATE,
1055
+ min: Union[int, NoUpdate] = NO_UPDATE,
1056
+ max: Union[int, NoUpdate] = NO_UPDATE,
1107
1057
  ) -> None:
1108
1058
  """
1109
- Update an integer parameter's value and/or bounds.
1059
+ Update an integer parameter.
1110
1060
 
1111
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_integer`.
1112
- See :class:`~syd.parameters.IntegerParameter` for details about value validation.
1061
+ Change the value or bounds of an existing integer parameter.
1062
+ See :class:`~syd.parameters.IntegerParameter` for details.
1113
1063
 
1114
1064
  Parameters
1115
1065
  ----------
1116
1066
  name : str
1117
- Name of the integer parameter to update
1118
- value : int, optional
1119
- New value (will be clamped to bounds) (if not provided, no change)
1120
- min_value : int, optional
1121
- New minimum value (if not provided, no change)
1122
- max_value : int, optional
1123
- New maximum value (if not provided, no change)
1067
+ Name of the parameter to update
1068
+ value : Union[int, NoUpdate], optional
1069
+ New value
1070
+ min : Union[int, NoUpdate], optional
1071
+ New minimum allowed value
1072
+ max : Union[int, NoUpdate], optional
1073
+ New maximum allowed value
1124
1074
 
1125
1075
  Examples
1126
1076
  --------
1127
- >>> viewer.add_integer('count', value=5, min_value=0, max_value=10)
1128
- >>> # Update just the value
1129
- >>> viewer.update_integer('count', value=8)
1130
- >>> # Update bounds (current value will be clamped if needed)
1131
- >>> viewer.update_integer('count', min_value=7, max_value=15)
1077
+ >>> viewer.update_integer('age', value=30) # Update just the value
1078
+
1079
+ >>> viewer.update_integer('year', min=2000, max=2023) # Update just the bounds
1132
1080
  """
1133
1081
  updates = {}
1134
- if value is not _NO_UPDATE:
1135
- updates["value"] = value
1136
- if min_value is not _NO_UPDATE:
1137
- updates["min_value"] = min_value
1138
- if max_value is not _NO_UPDATE:
1139
- updates["max_value"] = max_value
1140
- if updates:
1141
- self.parameters[name].update(updates)
1082
+ if not isinstance(value, NoUpdate):
1083
+ updates["value"] = int(value)
1084
+ if not isinstance(min, NoUpdate):
1085
+ updates["min"] = int(min)
1086
+ if not isinstance(max, NoUpdate):
1087
+ updates["max"] = int(max)
1088
+
1089
+ parameter = self.parameters[name]
1090
+ parameter.update(updates)
1142
1091
 
1143
1092
  @validate_parameter_operation("update", ParameterType.float)
1144
1093
  def update_float(
1145
1094
  self,
1146
1095
  name: str,
1147
1096
  *,
1148
- value: Union[float, _NoUpdate] = _NO_UPDATE,
1149
- min_value: Union[float, _NoUpdate] = _NO_UPDATE,
1150
- max_value: Union[float, _NoUpdate] = _NO_UPDATE,
1151
- step: Union[float, _NoUpdate] = _NO_UPDATE,
1097
+ value: Union[float, NoUpdate] = NO_UPDATE,
1098
+ min: Union[float, NoUpdate] = NO_UPDATE,
1099
+ max: Union[float, NoUpdate] = NO_UPDATE,
1100
+ step: Union[float, NoUpdate] = NO_UPDATE,
1152
1101
  ) -> None:
1153
1102
  """
1154
- Update a float parameter's value, bounds, and/or step size.
1103
+ Update a float parameter.
1155
1104
 
1156
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_float`.
1157
- See :class:`~syd.parameters.FloatParameter` for details about value validation.
1105
+ Change the value, bounds, or step size of an existing float parameter.
1106
+ See :class:`~syd.parameters.FloatParameter` for details.
1158
1107
 
1159
1108
  Parameters
1160
1109
  ----------
1161
1110
  name : str
1162
- Name of the float parameter to update
1163
- value : float, optional
1164
- New value (will be rounded and clamped) (if not provided, no change)
1165
- min_value : float, optional
1166
- New minimum value (if not provided, no change)
1167
- max_value : float, optional
1168
- New maximum value (if not provided, no change)
1169
- step : float, optional
1170
- New step size (if not provided, no change)
1111
+ Name of the parameter to update
1112
+ value : Union[float, NoUpdate], optional
1113
+ New value
1114
+ min : Union[float, NoUpdate], optional
1115
+ New minimum allowed value
1116
+ max : Union[float, NoUpdate], optional
1117
+ New maximum allowed value
1118
+ step : Union[float, NoUpdate], optional
1119
+ New step size for the slider
1171
1120
 
1172
1121
  Examples
1173
1122
  --------
1174
- >>> viewer.add_float('temperature', value=20.0,
1175
- ... min_value=0.0, max_value=100.0, step=0.5)
1176
- >>> # Update just the value (will round to step)
1177
- >>> viewer.update_float('temperature', value=20.7) # Becomes 20.5
1178
- >>> # Update bounds and step size
1179
- >>> viewer.update_float('temperature',
1180
- ... min_value=15.0, max_value=30.0, step=0.1)
1123
+ >>> viewer.update_float('temperature', value=99.5) # Update just the value
1124
+
1125
+ >>> viewer.update_float('price', min=5.0, max=200.0, step=0.05) # Update bounds and step
1181
1126
  """
1182
1127
  updates = {}
1183
- if value is not _NO_UPDATE:
1184
- updates["value"] = value
1185
- if min_value is not _NO_UPDATE:
1186
- updates["min_value"] = min_value
1187
- if max_value is not _NO_UPDATE:
1188
- updates["max_value"] = max_value
1189
- if step is not _NO_UPDATE:
1190
- updates["step"] = step
1191
- if updates:
1192
- self.parameters[name].update(updates)
1128
+ if not isinstance(value, NoUpdate):
1129
+ updates["value"] = float(value)
1130
+ if not isinstance(min, NoUpdate):
1131
+ updates["min"] = float(min)
1132
+ if not isinstance(max, NoUpdate):
1133
+ updates["max"] = float(max)
1134
+ if not isinstance(step, NoUpdate):
1135
+ updates["step"] = float(step)
1136
+
1137
+ parameter = self.parameters[name]
1138
+ parameter.update(updates)
1193
1139
 
1194
1140
  @validate_parameter_operation("update", ParameterType.integer_range)
1195
1141
  def update_integer_range(
1196
1142
  self,
1197
1143
  name: str,
1198
1144
  *,
1199
- value: Union[Tuple[int, int], _NoUpdate] = _NO_UPDATE,
1200
- min_value: Union[int, _NoUpdate] = _NO_UPDATE,
1201
- max_value: Union[int, _NoUpdate] = _NO_UPDATE,
1145
+ value: Union[Tuple[int, int], NoUpdate] = NO_UPDATE,
1146
+ min: Union[int, NoUpdate] = NO_UPDATE,
1147
+ max: Union[int, NoUpdate] = NO_UPDATE,
1202
1148
  ) -> None:
1203
1149
  """
1204
- Update an integer range parameter's values and/or bounds.
1150
+ Update an integer range parameter.
1205
1151
 
1206
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_integer_range`.
1207
- See :class:`~syd.parameters.IntegerRangeParameter` for details about value validation.
1152
+ Change the range values or bounds of an existing integer range parameter.
1153
+ See :class:`~syd.parameters.IntegerRangeParameter` for details.
1208
1154
 
1209
1155
  Parameters
1210
1156
  ----------
1211
1157
  name : str
1212
- Name of the integer range parameter to update
1213
- value : tuple[int, int], optional
1214
- New (low, high) values (will be clamped) (if not provided, no change)
1215
- min_value : int, optional
1216
- New minimum value for both low and high (if not provided, no change)
1217
- max_value : int, optional
1218
- New maximum value for both low and high (if not provided, no change)
1158
+ Name of the parameter to update
1159
+ value : Union[tuple[int, int], NoUpdate], optional
1160
+ New (low, high) values
1161
+ min : Union[int, NoUpdate], optional
1162
+ New minimum allowed value
1163
+ max : Union[int, NoUpdate], optional
1164
+ New maximum allowed value
1219
1165
 
1220
1166
  Examples
1221
1167
  --------
1222
- >>> viewer.add_integer_range('age_range',
1223
- ... value=(25, 35),
1224
- ... min_value=18, max_value=100)
1225
- >>> # Update just the range (values will be swapped if needed)
1226
- >>> viewer.update_integer_range('age_range', value=(40, 30)) # Becomes (30, 40)
1227
- >>> # Update bounds (current values will be clamped if needed)
1228
- >>> viewer.update_integer_range('age_range', min_value=20, max_value=80)
1168
+ >>> viewer.update_integer_range('age_range', value=(30, 50)) # Update just the values
1169
+
1170
+ >>> viewer.update_integer_range('year_range', min=1950, max=2023) # Update just the bounds
1229
1171
  """
1230
1172
  updates = {}
1231
- if value is not _NO_UPDATE:
1232
- updates["value"] = value
1233
- if min_value is not _NO_UPDATE:
1234
- updates["min_value"] = min_value
1235
- if max_value is not _NO_UPDATE:
1236
- updates["max_value"] = max_value
1237
- if updates:
1238
- self.parameters[name].update(updates)
1173
+ if not isinstance(value, NoUpdate):
1174
+ val_low, val_high = value
1175
+ updates["value"] = (int(val_low), int(val_high))
1176
+ if not isinstance(min, NoUpdate):
1177
+ updates["min"] = int(min)
1178
+ if not isinstance(max, NoUpdate):
1179
+ updates["max"] = int(max)
1180
+
1181
+ parameter = self.parameters[name]
1182
+ parameter.update(updates)
1239
1183
 
1240
1184
  @validate_parameter_operation("update", ParameterType.float_range)
1241
1185
  def update_float_range(
1242
1186
  self,
1243
1187
  name: str,
1244
1188
  *,
1245
- value: Union[Tuple[float, float], _NoUpdate] = _NO_UPDATE,
1246
- min_value: Union[float, _NoUpdate] = _NO_UPDATE,
1247
- max_value: Union[float, _NoUpdate] = _NO_UPDATE,
1248
- step: Union[float, _NoUpdate] = _NO_UPDATE,
1189
+ value: Union[Tuple[float, float], NoUpdate] = NO_UPDATE,
1190
+ min: Union[float, NoUpdate] = NO_UPDATE,
1191
+ max: Union[float, NoUpdate] = NO_UPDATE,
1192
+ step: Union[float, NoUpdate] = NO_UPDATE,
1249
1193
  ) -> None:
1250
1194
  """
1251
- Update a float range parameter's values, bounds, and/or step size.
1195
+ Update a float range parameter.
1252
1196
 
1253
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_float_range`.
1254
- See :class:`~syd.parameters.FloatRangeParameter` for details about value validation.
1197
+ Change the range values, bounds, or step size of an existing float range parameter.
1198
+ See :class:`~syd.parameters.FloatRangeParameter` for details.
1255
1199
 
1256
1200
  Parameters
1257
1201
  ----------
1258
1202
  name : str
1259
- Name of the float range parameter to update
1260
- value : tuple[float, float], optional
1261
- New (low, high) values (will be rounded and clamped) (if not provided, no change)
1262
- min_value : float, optional
1263
- New minimum value for both low and high (if not provided, no change)
1264
- max_value : float, optional
1265
- New maximum value for both low and high (if not provided, no change)
1266
- step : float, optional
1267
- New step size for rounding values (if not provided, no change)
1203
+ Name of the parameter to update
1204
+ value : Union[tuple[float, float], NoUpdate], optional
1205
+ New (low, high) values
1206
+ min : Union[float, NoUpdate], optional
1207
+ New minimum allowed value
1208
+ max : Union[float, NoUpdate], optional
1209
+ New maximum allowed value
1210
+ step : Union[float, NoUpdate], optional
1211
+ New step size for the slider
1268
1212
 
1269
1213
  Examples
1270
1214
  --------
1271
- >>> viewer.add_float_range('price_range',
1272
- ... value=(10.0, 20.0),
1273
- ... min_value=0.0, max_value=100.0, step=0.5)
1274
- >>> # Update just the range (values will be rounded and swapped if needed)
1275
- >>> viewer.update_float_range('price_range', value=(15.7, 14.2)) # Becomes (14.0, 15.5)
1276
- >>> # Update bounds and step size
1277
- >>> viewer.update_float_range('price_range',
1278
- ... min_value=5.0, max_value=50.0, step=0.1)
1215
+ >>> viewer.update_float_range('temp_range', value=(97.5, 98.5)) # Update just the values
1216
+
1217
+ >>> viewer.update_float_range(
1218
+ ... 'price_range',
1219
+ ... min=10.0,
1220
+ ... max=500.0,
1221
+ ... step=0.5
1222
+ ... ) # Update bounds and step
1279
1223
  """
1280
1224
  updates = {}
1281
- if value is not _NO_UPDATE:
1282
- updates["value"] = value
1283
- if min_value is not _NO_UPDATE:
1284
- updates["min_value"] = min_value
1285
- if max_value is not _NO_UPDATE:
1286
- updates["max_value"] = max_value
1287
- if step is not _NO_UPDATE:
1288
- updates["step"] = step
1289
- if updates:
1290
- self.parameters[name].update(updates)
1225
+ if not isinstance(value, NoUpdate):
1226
+ val_low, val_high = value
1227
+ updates["value"] = (float(val_low), float(val_high))
1228
+ if not isinstance(min, NoUpdate):
1229
+ updates["min"] = float(min)
1230
+ if not isinstance(max, NoUpdate):
1231
+ updates["max"] = float(max)
1232
+ if not isinstance(step, NoUpdate):
1233
+ updates["step"] = float(step)
1234
+
1235
+ parameter = self.parameters[name]
1236
+ parameter.update(updates)
1291
1237
 
1292
1238
  @validate_parameter_operation("update", ParameterType.unbounded_integer)
1293
1239
  def update_unbounded_integer(
1294
1240
  self,
1295
1241
  name: str,
1296
1242
  *,
1297
- value: Union[int, _NoUpdate] = _NO_UPDATE,
1298
- min_value: Union[Optional[int], _NoUpdate] = _NO_UPDATE,
1299
- max_value: Union[Optional[int], _NoUpdate] = _NO_UPDATE,
1243
+ value: Union[int, NoUpdate] = NO_UPDATE,
1300
1244
  ) -> None:
1301
1245
  """
1302
1246
  Update an unbounded integer parameter's value and/or bounds.
1303
1247
 
1304
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_unbounded_integer`.
1248
+ Updates a parameter created by :meth:`~syd.viewer.Viewer.add_unbounded_integer`.
1305
1249
  See :class:`~syd.parameters.UnboundedIntegerParameter` for details about value validation.
1306
1250
 
1307
1251
  Parameters
1308
1252
  ----------
1309
1253
  name : str
1310
1254
  Name of the unbounded integer parameter to update
1311
- value : int, optional
1312
- New value (will be clamped to any bounds) (if not provided, no change)
1313
- min_value : int or None, optional
1314
- New minimum value, or None for no minimum (if not provided, no change)
1315
- max_value : int or None, optional
1316
- New maximum value, or None for no maximum (if not provided, no change)
1255
+ value : Union[int, NoUpdate], optional
1256
+ New value (if not provided, no change)
1317
1257
 
1318
1258
  Examples
1319
1259
  --------
1320
- >>> viewer.add_unbounded_integer('population',
1321
- ... value=1000000,
1322
- ... min_value=0) # No maximum
1260
+ >>> viewer.add_unbounded_integer('population', value=1000000)
1323
1261
  >>> # Update just the value
1324
1262
  >>> viewer.update_unbounded_integer('population', value=2000000)
1325
- >>> # Add a maximum bound (current value will be clamped if needed)
1326
- >>> viewer.update_unbounded_integer('population', max_value=1500000)
1327
- >>> # Remove the minimum bound
1328
- >>> viewer.update_unbounded_integer('population', min_value=None)
1329
1263
  """
1330
1264
  updates = {}
1331
- if value is not _NO_UPDATE:
1265
+ if not value == NO_UPDATE:
1332
1266
  updates["value"] = value
1333
- if min_value is not _NO_UPDATE:
1334
- updates["min_value"] = min_value
1335
- if max_value is not _NO_UPDATE:
1336
- updates["max_value"] = max_value
1337
1267
  if updates:
1338
1268
  self.parameters[name].update(updates)
1339
1269
 
@@ -1342,53 +1272,38 @@ class InteractiveViewer:
1342
1272
  self,
1343
1273
  name: str,
1344
1274
  *,
1345
- value: Union[float, _NoUpdate] = _NO_UPDATE,
1346
- min_value: Union[Optional[float], _NoUpdate] = _NO_UPDATE,
1347
- max_value: Union[Optional[float], _NoUpdate] = _NO_UPDATE,
1348
- step: Union[Optional[float], _NoUpdate] = _NO_UPDATE,
1275
+ value: Union[float, NoUpdate] = NO_UPDATE,
1276
+ step: Union[Optional[float], NoUpdate] = NO_UPDATE,
1349
1277
  ) -> None:
1350
1278
  """
1351
1279
  Update an unbounded float parameter's value, bounds, and/or step size.
1352
1280
 
1353
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_unbounded_float`.
1281
+ Updates a parameter created by :meth:`~syd.viewer.Viewer.add_unbounded_float`.
1354
1282
  See :class:`~syd.parameters.UnboundedFloatParameter` for details about value validation.
1355
1283
 
1356
1284
  Parameters
1357
1285
  ----------
1358
1286
  name : str
1359
1287
  Name of the unbounded float parameter to update
1360
- value : float, optional
1288
+ value : Union[float, NoUpdate], optional
1361
1289
  New value (will be rounded if step is set) (if not provided, no change)
1362
- min_value : float or None, optional
1363
- New minimum value, or None for no minimum (if not provided, no change)
1364
- max_value : float or None, optional
1365
- New maximum value, or None for no maximum (if not provided, no change)
1366
- step : float or None, optional
1290
+ step : Union[Optional[float], NoUpdate], optional
1367
1291
  New step size for rounding, or None for no rounding (if not provided, no change)
1368
1292
 
1369
1293
  Examples
1370
1294
  --------
1371
- >>> viewer.add_unbounded_float('wavelength',
1372
- ... value=550e-9, # Nanometers
1373
- ... min_value=0.0,
1374
- ... step=1e-9)
1295
+ >>> viewer.add_unbounded_float('wavelength', value=550e-9, step=1e-9)
1375
1296
  >>> # Update value (will be rounded if step is set)
1376
- >>> viewer.update_unbounded_float('wavelength', value=632.8e-9) # HeNe laser
1377
- >>> # Change step size and add maximum
1378
- >>> viewer.update_unbounded_float('wavelength',
1379
- ... step=0.1e-9, # Finer control
1380
- ... max_value=1000e-9) # Infrared limit
1297
+ >>> viewer.update_unbounded_float('wavelength', value=632.8e-9)
1298
+ >>> # Change step size
1299
+ >>> viewer.update_unbounded_float('wavelength', step=0.1e-9)
1381
1300
  >>> # Remove step size (allow any precision)
1382
1301
  >>> viewer.update_unbounded_float('wavelength', step=None)
1383
1302
  """
1384
1303
  updates = {}
1385
- if value is not _NO_UPDATE:
1304
+ if not value == NO_UPDATE:
1386
1305
  updates["value"] = value
1387
- if min_value is not _NO_UPDATE:
1388
- updates["min_value"] = min_value
1389
- if max_value is not _NO_UPDATE:
1390
- updates["max_value"] = max_value
1391
- if step is not _NO_UPDATE:
1306
+ if not step == NO_UPDATE:
1392
1307
  updates["step"] = step
1393
1308
  if updates:
1394
1309
  self.parameters[name].update(updates)
@@ -1398,40 +1313,48 @@ class InteractiveViewer:
1398
1313
  self,
1399
1314
  name: str,
1400
1315
  *,
1401
- label: Union[str, _NoUpdate] = _NO_UPDATE,
1402
- callback: Union[Callable[[], None], _NoUpdate] = _NO_UPDATE,
1316
+ label: Union[str, NoUpdate] = NO_UPDATE,
1317
+ callback: Union[Callable[[], None], NoUpdate] = NO_UPDATE,
1403
1318
  ) -> None:
1404
1319
  """
1405
1320
  Update a button parameter's label and/or callback function.
1406
1321
 
1407
- Updates a parameter created by :meth:`~syd.interactive_viewer.InteractiveViewer.add_button`.
1322
+ Updates a parameter created by :meth:`~syd.viewer.Viewer.add_button`.
1408
1323
  See :class:`~syd.parameters.ButtonAction` for details.
1409
1324
 
1410
1325
  Parameters
1411
1326
  ----------
1412
1327
  name : str
1413
1328
  Name of the button parameter to update
1414
- label : str, optional
1329
+ label : Union[str, NoUpdate], optional
1415
1330
  New text to display on the button (if not provided, no change)
1416
- callback : callable, optional
1331
+ callback : Union[callable, NoUpdate], optional
1417
1332
  New function to call when clicked (if not provided, no change)
1418
1333
 
1419
1334
  Examples
1420
1335
  --------
1421
- >>> def new_callback():
1336
+ >>> def new_callback(state):
1422
1337
  ... print("New action...")
1423
1338
  >>> viewer.update_button('reset',
1424
- ... label='Clear Plot',
1339
+ ... label='New Action!',
1425
1340
  ... callback=new_callback)
1426
1341
  """
1427
1342
  updates = {}
1428
- if label is not _NO_UPDATE:
1343
+ if not label == NO_UPDATE:
1429
1344
  updates["label"] = label
1430
- if callback is not _NO_UPDATE:
1431
- callback = self._prepare_function(
1432
- callback,
1433
- context="Updating button callback:",
1434
- )
1435
- updates["callback"] = callback
1345
+ if not callback == NO_UPDATE:
1346
+ try:
1347
+ callback = self._prepare_function(
1348
+ callback,
1349
+ context="Updating button callback:",
1350
+ )
1351
+ except Exception as e:
1352
+ raise ParameterUpdateError(
1353
+ name,
1354
+ "button",
1355
+ str(e),
1356
+ ) from e
1357
+ else:
1358
+ updates["callback"] = callback
1436
1359
  if updates:
1437
1360
  self.parameters[name].update(updates)