streamlit-nightly 1.53.1.dev20260121__py3-none-any.whl → 1.53.2.dev20260123__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.
- streamlit/elements/lib/options_selector_utils.py +63 -19
- streamlit/elements/widgets/multiselect.py +31 -7
- streamlit/elements/widgets/selectbox.py +38 -16
- streamlit/static/index.html +1 -1
- streamlit/static/manifest.json +295 -295
- streamlit/static/static/js/{ErrorOutline.esm.C6VdLmcj.js → ErrorOutline.esm.CIFYUdwC.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.g3HiTWnX.js → FileDownload.esm.DWVTnTHm.js} +1 -1
- streamlit/static/static/js/{FileHelper.DkZahZIe.js → FileHelper.BPYQIPd1.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.CnDHLc6s.js → FormClearHelper.CypmvhYZ.js} +1 -1
- streamlit/static/static/js/{InputInstructions.CJwgA0Sj.js → InputInstructions.Bi62hDTQ.js} +1 -1
- streamlit/static/static/js/{Particles.BA0kQsJM.js → Particles.yebG0VuV.js} +1 -1
- streamlit/static/static/js/{ProgressBar.N6Nzla-b.js → ProgressBar.Dy9CI6w4.js} +1 -1
- streamlit/static/static/js/{StreamlitSyntaxHighlighter.DF8uiu4N.js → StreamlitSyntaxHighlighter.Btk92CPv.js} +1 -1
- streamlit/static/static/js/{TableChart.esm.Ne8F0PNs.js → TableChart.esm.DBeVaFNt.js} +1 -1
- streamlit/static/static/js/{Toolbar.h597G9NG.js → Toolbar.DC2Tp-qb.js} +1 -1
- streamlit/static/static/js/{WidgetLabelHelpIconInline.BMPpOfi3.js → WidgetLabelHelpIconInline.3DnEd9BK.js} +1 -1
- streamlit/static/static/js/{base-input.CTx0w60a.js → base-input.7Sj6pVk0.js} +1 -1
- streamlit/static/static/js/{checkbox.otYZO7w2.js → checkbox.CcUx3XuQ.js} +1 -1
- streamlit/static/static/js/{createDownloadLinkElement.Az9SilWU.js → createDownloadLinkElement.DZuwkCqy.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.Blj_5PJn.js → data-grid-overlay-editor.Dw-AewlN.js} +1 -1
- streamlit/static/static/js/{downloader.ur6VTRo6.js → downloader.Bsx5M2Du.js} +1 -1
- streamlit/static/static/js/{embed.Bo5Mp3yp.js → embed.C7by6AoE.js} +1 -1
- streamlit/static/static/js/{es6.Q-xD3Lx8.js → es6.BpAqZaR_.js} +2 -2
- streamlit/static/static/js/{formatNumber._OZtRfH-.js → formatNumber.DjehVPVS.js} +1 -1
- streamlit/static/static/js/{iconPosition.owgMmPXc.js → iconPosition.D02OPE-d.js} +1 -1
- streamlit/static/static/js/{iframeResizer.contentWindow.CVCEnOeT.js → iframeResizer.contentWindow.xtstqPd7.js} +1 -1
- streamlit/static/static/js/{index.C5MX205D.js → index.-faJDV20.js} +1 -1
- streamlit/static/static/js/{index.DPa8c-h5.js → index.5H98WqjT.js} +1 -1
- streamlit/static/static/js/{index.BlZAOJTf.js → index.8FPw0_gD.js} +1 -1
- streamlit/static/static/js/{index.DA1Zwrh6.js → index.B5tD5YeV.js} +1 -1
- streamlit/static/static/js/{index.BTy77A4v.js → index.B8-HOwf1.js} +1 -1
- streamlit/static/static/js/{index.DcZ6Y6tq.js → index.B9gbSNsw.js} +1 -1
- streamlit/static/static/js/{index.Cpn4aUEp.js → index.BB_iwaVr.js} +5 -5
- streamlit/static/static/js/{index.DAjvUscA.js → index.BDlI2pRp.js} +1 -1
- streamlit/static/static/js/{index.BdKmWvyd.js → index.BGTMh3Uu.js} +1 -1
- streamlit/static/static/js/{index.X86xhkRz.js → index.BIcJe97b.js} +1 -1
- streamlit/static/static/js/{index.CZKvO8_W.js → index.BK9S5qug.js} +1 -1
- streamlit/static/static/js/{index.BBFXXyOE.js → index.BOkpEbJS.js} +1 -1
- streamlit/static/static/js/{index.BkFxYQoo.js → index.BV6XgCij.js} +1 -1
- streamlit/static/static/js/{index.DYqiNZKP.js → index.BVhVdVeE.js} +1 -1
- streamlit/static/static/js/{index.Bw5t0A1a.js → index.Bhy8EBYI.js} +1 -1
- streamlit/static/static/js/{index.CHfpPdI8.js → index.Bo1ztye0.js} +1 -1
- streamlit/static/static/js/{index.oieFJFkX.js → index.BqfJJr3c.js} +1 -1
- streamlit/static/static/js/{index.CT93Qeic.js → index.Bri1T2TS.js} +1 -1
- streamlit/static/static/js/{index.zR1le1Pe.js → index.BvZbnSMC.js} +1 -1
- streamlit/static/static/js/{index.jADyDjmJ.js → index.C5ehUqNt.js} +1 -1
- streamlit/static/static/js/{index.ByzGGHA-.js → index.C9v49R-a.js} +1 -1
- streamlit/static/static/js/{index.DkmkM-3F.js → index.CA0RmxJF.js} +1 -1
- streamlit/static/static/js/{index.ByHx8_go.js → index.CEwnDCn9.js} +1 -1
- streamlit/static/static/js/{index.2pNQcghF.js → index.CGbvkEtg.js} +1 -1
- streamlit/static/static/js/{index.jZuoBudL.js → index.CKUBdVQ9.js} +1 -1
- streamlit/static/static/js/{index.BtKtuLWl.js → index.CdRwiHPm.js} +1 -1
- streamlit/static/static/js/{index.DfDGWP9w.js → index.CgARjn28.js} +1 -1
- streamlit/static/static/js/{index.Vo6SwZW9.js → index.CyDHwK5P.js} +1 -1
- streamlit/static/static/js/{index.CECvn-YK.js → index.D6J2UPzF.js} +1 -1
- streamlit/static/static/js/{index.BZMDU-qx.js → index.D6Z9hKJY.js} +1 -1
- streamlit/static/static/js/{index.C-xuUZos.js → index.D9RL5sRp.js} +1 -1
- streamlit/static/static/js/{index.1wQkO9-P.js → index.DDu_qTm0.js} +1 -1
- streamlit/static/static/js/{index.D-VZVj73.js → index.DJjSqPAx.js} +1 -1
- streamlit/static/static/js/{index.Buip23l3.js → index.DO2T-QzF.js} +1 -1
- streamlit/static/static/js/{index.DiEHlHgD.js → index.DSSapl3Q.js} +1 -1
- streamlit/static/static/js/{index.CeCHrefy.js → index.DZv5AoR1.js} +1 -1
- streamlit/static/static/js/{index.Byo1IU24.js → index.D_TIyPF4.js} +1 -1
- streamlit/static/static/js/{index.C0X75KQC.js → index.DdxofXV8.js} +1 -1
- streamlit/static/static/js/{index.d4eN08zp.js → index.DgLRJfs3.js} +1 -1
- streamlit/static/static/js/{index.Ccjs4i9w.js → index.JL0uGAeJ.js} +1 -1
- streamlit/static/static/js/{index.CT8A-jH3.js → index.S-mjkUeF.js} +1 -1
- streamlit/static/static/js/{index.DUoWDMOZ.js → index.XFMDBL5n.js} +1 -1
- streamlit/static/static/js/{index.rpudgXQV.js → index.ZIA43eTF.js} +1 -1
- streamlit/static/static/js/{index.Z5FSW1PS.js → index.iF5zYERg.js} +1 -1
- streamlit/static/static/js/{index.DAddHkEE.js → index.iXzAofuY.js} +1 -1
- streamlit/static/static/js/index.m3dn5Bai.js +188 -0
- streamlit/static/static/js/{index.9GyNEnVn.js → index.m4WkwGMu.js} +1 -1
- streamlit/static/static/js/{index.BOH_U_Ua.js → index.x1B588Xu.js} +1 -1
- streamlit/static/static/js/{input.FrKOgyiF.js → input.VYKyGuhi.js} +1 -1
- streamlit/static/static/js/{main.DRehwjQT.js → main.u5Bb3MY7.js} +1 -1
- streamlit/static/static/js/{memory.B2wgPAbQ.js → memory.BOMt4yAV.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.CnTj7Q_W.js → number-overlay-editor.CihlAHgl.js} +1 -1
- streamlit/static/static/js/{pandasStylerUtils.CWancxgS.js → pandasStylerUtils.BuqSgXpk.js} +1 -1
- streamlit/static/static/js/{sandbox.DuleaI7G.js → sandbox.COGR4pqz.js} +1 -1
- streamlit/static/static/js/{styled-components.g3zaW9ch.js → styled-components.BEf3c4IJ.js} +1 -1
- streamlit/static/static/js/{throttle.Dta7xRnz.js → throttle.Bl-XsA9N.js} +1 -1
- streamlit/static/static/js/{timepicker.Bg1hWKQe.js → timepicker.B-HgBYlK.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.C3xlVjBM.js → toConsumableArray.BrQebwtE.js} +1 -1
- streamlit/static/static/js/uniqueId.8R4hbkYl.js +1 -0
- streamlit/static/static/js/{useBasicWidgetState.CERZ06R-.js → useBasicWidgetState.8WwISl9r.js} +1 -1
- streamlit/static/static/js/{useIntlLocale.DTs1RN9G.js → useIntlLocale.D37LWdCR.js} +1 -1
- streamlit/static/static/js/{useTextInputAutoExpand.B6f9P3jg.js → useTextInputAutoExpand.Bb_KqJvq.js} +1 -1
- streamlit/static/static/js/{useUpdateUiValue.ByVBXV8J.js → useUpdateUiValue.D1BLS5t7.js} +1 -1
- streamlit/static/static/js/{useWaveformController.D6wABMbB.js → useWaveformController.Ce0-qTws.js} +1 -1
- streamlit/static/static/js/{withCalculatedWidth.Bd4vFDHU.js → withCalculatedWidth.BX2K3UVv.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.CDnDcnIh.js → withFullScreenWrapper.CqfGs8T2.js} +1 -1
- {streamlit_nightly-1.53.1.dev20260121.dist-info → streamlit_nightly-1.53.2.dev20260123.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.53.1.dev20260121.dist-info → streamlit_nightly-1.53.2.dev20260123.dist-info}/RECORD +98 -98
- streamlit/static/static/js/index.hri4IXS4.js +0 -188
- streamlit/static/static/js/uniqueId.2p-nMqYS.js +0 -1
- {streamlit_nightly-1.53.1.dev20260121.data → streamlit_nightly-1.53.2.dev20260123.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.53.1.dev20260121.dist-info → streamlit_nightly-1.53.2.dev20260123.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.53.1.dev20260121.dist-info → streamlit_nightly-1.53.2.dev20260123.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.53.1.dev20260121.dist-info → streamlit_nightly-1.53.2.dev20260123.dist-info}/top_level.txt +0 -0
|
@@ -290,6 +290,7 @@ def validate_and_sync_value_with_options(
|
|
|
290
290
|
opt: Sequence[T],
|
|
291
291
|
default_index: int | None,
|
|
292
292
|
key: str | int | None,
|
|
293
|
+
format_func: Callable[[Any], str] = str,
|
|
293
294
|
) -> tuple[T | None, bool]:
|
|
294
295
|
"""Validate current value against options, resetting session state if invalid.
|
|
295
296
|
|
|
@@ -306,6 +307,11 @@ def validate_and_sync_value_with_options(
|
|
|
306
307
|
The default index to reset to if value is invalid.
|
|
307
308
|
key
|
|
308
309
|
The widget key for session state updates.
|
|
310
|
+
format_func
|
|
311
|
+
Function to format options for comparison. Used to compare values by their
|
|
312
|
+
string representation instead of using == directly. This is necessary because
|
|
313
|
+
widget values are deepcopied, and for custom classes without __eq__, the
|
|
314
|
+
deepcopied instances would fail identity comparison.
|
|
309
315
|
|
|
310
316
|
Returns
|
|
311
317
|
-------
|
|
@@ -315,30 +321,50 @@ def validate_and_sync_value_with_options(
|
|
|
315
321
|
if current_value is None:
|
|
316
322
|
return current_value, False
|
|
317
323
|
|
|
318
|
-
#
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
324
|
+
# For Enum values, use the original index_() approach which uses == comparison.
|
|
325
|
+
# This correctly handles enum class identity - enums from different classes
|
|
326
|
+
# (e.g., after script rerun) should NOT be considered equal, which is important
|
|
327
|
+
# for enum coercion to work correctly when coercion is disabled.
|
|
328
|
+
if isinstance(current_value, Enum):
|
|
329
|
+
try:
|
|
330
|
+
index_(opt, current_value)
|
|
331
|
+
return current_value, False
|
|
332
|
+
except ValueError:
|
|
333
|
+
pass # Fall through to reset logic below
|
|
334
|
+
else:
|
|
335
|
+
# For non-Enum values, use format_func comparison. This handles custom objects
|
|
336
|
+
# without __eq__ where widget values are deepcopied and the deepcopied instances
|
|
337
|
+
# would fail identity comparison with ==.
|
|
338
|
+
try:
|
|
339
|
+
formatted_value = format_func(current_value)
|
|
340
|
+
except Exception:
|
|
341
|
+
# format_func failed - value is invalid
|
|
342
|
+
formatted_value = None
|
|
343
|
+
|
|
344
|
+
formatted_options_set = {format_func(o) for o in opt}
|
|
345
|
+
if formatted_value is not None and formatted_value in formatted_options_set:
|
|
346
|
+
return current_value, False
|
|
347
|
+
|
|
348
|
+
# Value not in options - reset to default
|
|
349
|
+
if default_index is not None and len(opt) > 0:
|
|
350
|
+
new_value: T | None = opt[default_index]
|
|
351
|
+
else:
|
|
352
|
+
new_value = None
|
|
328
353
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
354
|
+
if key is not None:
|
|
355
|
+
# Update session_state so subsequent accesses in this run
|
|
356
|
+
# return the corrected value. Use reset_state_value to avoid
|
|
357
|
+
# the "cannot be modified after widget instantiated" error.
|
|
358
|
+
get_session_state().reset_state_value(str(key), new_value)
|
|
334
359
|
|
|
335
|
-
|
|
360
|
+
return new_value, True
|
|
336
361
|
|
|
337
362
|
|
|
338
363
|
def validate_and_sync_multiselect_value_with_options(
|
|
339
364
|
current_values: list[T] | list[T | str],
|
|
340
365
|
opt: Sequence[T],
|
|
341
366
|
key: str | int | None,
|
|
367
|
+
format_func: Callable[[Any], str] = str,
|
|
342
368
|
) -> tuple[list[T] | list[T | str], bool]:
|
|
343
369
|
"""Validate multiselect values against options, syncing session state if needed.
|
|
344
370
|
|
|
@@ -356,6 +382,11 @@ def validate_and_sync_multiselect_value_with_options(
|
|
|
356
382
|
The sequence of valid options.
|
|
357
383
|
key
|
|
358
384
|
The widget key for session state updates.
|
|
385
|
+
format_func
|
|
386
|
+
Function to format options for comparison. Used to compare values by their
|
|
387
|
+
string representation instead of using == directly. This is necessary because
|
|
388
|
+
widget values are deepcopied, and for custom classes without __eq__, the
|
|
389
|
+
deepcopied instances would fail identity comparison.
|
|
359
390
|
|
|
360
391
|
Returns
|
|
361
392
|
-------
|
|
@@ -365,13 +396,26 @@ def validate_and_sync_multiselect_value_with_options(
|
|
|
365
396
|
if not current_values:
|
|
366
397
|
return current_values, False
|
|
367
398
|
|
|
399
|
+
# Create a set of formatted options for O(1) lookup.
|
|
400
|
+
# We use format_func to compare values by their string representation
|
|
401
|
+
# instead of using == directly. This is necessary because widget values
|
|
402
|
+
# are deepcopied, and for custom classes without __eq__, the deepcopied
|
|
403
|
+
# instances would fail identity comparison.
|
|
404
|
+
formatted_options_set = {format_func(o) for o in opt}
|
|
405
|
+
|
|
368
406
|
valid_values: list[T | str] = []
|
|
369
407
|
for value in current_values:
|
|
370
408
|
try:
|
|
371
|
-
|
|
409
|
+
formatted_value = format_func(value)
|
|
410
|
+
except Exception: # noqa: S112
|
|
411
|
+
# format_func failed on this value (e.g., a string value from a previous
|
|
412
|
+
# session when format_func expects an object with specific attributes).
|
|
413
|
+
# In this case, the value is definitely not valid since the current options
|
|
414
|
+
# can be formatted successfully.
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
if formatted_value in formatted_options_set:
|
|
372
418
|
valid_values.append(value)
|
|
373
|
-
except ValueError: # noqa: PERF203
|
|
374
|
-
pass
|
|
375
419
|
|
|
376
420
|
if len(valid_values) == len(current_values):
|
|
377
421
|
return current_values, False
|
|
@@ -82,6 +82,7 @@ class MultiSelectSerde(Generic[T]):
|
|
|
82
82
|
formatted_options: list[str]
|
|
83
83
|
formatted_option_to_option_index: dict[str, int]
|
|
84
84
|
default_options_indices: list[int]
|
|
85
|
+
format_func: Callable[[Any], str]
|
|
85
86
|
|
|
86
87
|
def __init__(
|
|
87
88
|
self,
|
|
@@ -90,6 +91,7 @@ class MultiSelectSerde(Generic[T]):
|
|
|
90
91
|
formatted_options: list[str],
|
|
91
92
|
formatted_option_to_option_index: dict[str, int],
|
|
92
93
|
default_options_indices: list[int] | None = None,
|
|
94
|
+
format_func: Callable[[Any], str] = str,
|
|
93
95
|
) -> None:
|
|
94
96
|
"""Initialize the MultiSelectSerde.
|
|
95
97
|
|
|
@@ -111,24 +113,45 @@ class MultiSelectSerde(Generic[T]):
|
|
|
111
113
|
default_option_index : int or None, optional
|
|
112
114
|
The index of the default option to use when no selection is made.
|
|
113
115
|
If None, no default option is selected.
|
|
116
|
+
format_func : Callable[[Any], str], optional
|
|
117
|
+
Function to format options for comparison. Used to compare values by their
|
|
118
|
+
string representation instead of using == directly. This is necessary because
|
|
119
|
+
widget values are deepcopied, and for custom classes without __eq__, the
|
|
120
|
+
deepcopied instances would fail identity comparison.
|
|
114
121
|
"""
|
|
115
122
|
|
|
116
123
|
self.options = options
|
|
117
124
|
self.formatted_options = formatted_options
|
|
118
125
|
self.formatted_option_to_option_index = formatted_option_to_option_index
|
|
119
126
|
self.default_options_indices = default_options_indices or []
|
|
127
|
+
self.format_func = format_func
|
|
120
128
|
|
|
121
129
|
def serialize(self, value: list[T | str] | list[T]) -> list[str]:
|
|
122
130
|
converted_value = convert_anything_to_list(value)
|
|
123
131
|
values: list[str] = []
|
|
124
132
|
for v in converted_value:
|
|
133
|
+
# Use format_func to find the formatted option instead of using
|
|
134
|
+
# self.options.index(v) which relies on == comparison. This is necessary
|
|
135
|
+
# because widget values are deepcopied, and for custom classes without
|
|
136
|
+
# __eq__, the deepcopied instances would fail identity comparison.
|
|
125
137
|
try:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
|
|
138
|
+
formatted_value = self.format_func(v)
|
|
139
|
+
except Exception:
|
|
140
|
+
# format_func failed (e.g., v is a string but format_func expects
|
|
141
|
+
# an object with specific attributes). Use str(v) to ensure we append
|
|
142
|
+
# a proper string, not the original object. This handles both cases:
|
|
143
|
+
# - v is already a string -> str(v) returns it unchanged
|
|
144
|
+
# - v is a custom object -> str(v) gives its string representation
|
|
145
|
+
values.append(str(v))
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
if formatted_value in self.formatted_option_to_option_index:
|
|
149
|
+
values.append(formatted_value)
|
|
150
|
+
else:
|
|
151
|
+
# Value not found in options - it's likely a user-entered string
|
|
152
|
+
# (when accept_new_options=True) or an invalid value. Use the
|
|
153
|
+
# formatted string (not the original object) for type consistency.
|
|
154
|
+
values.append(formatted_value)
|
|
132
155
|
return values
|
|
133
156
|
|
|
134
157
|
def deserialize(self, ui_value: list[str] | None) -> list[T | str] | list[T]:
|
|
@@ -530,6 +553,7 @@ class MultiSelectMixin:
|
|
|
530
553
|
formatted_options=formatted_options,
|
|
531
554
|
formatted_option_to_option_index=formatted_option_to_option_index,
|
|
532
555
|
default_options_indices=default_values,
|
|
556
|
+
format_func=format_func,
|
|
533
557
|
)
|
|
534
558
|
|
|
535
559
|
widget_state = register_widget(
|
|
@@ -560,7 +584,7 @@ class MultiSelectMixin:
|
|
|
560
584
|
# previously selected values are no longer available.
|
|
561
585
|
current_values, value_needs_reset = (
|
|
562
586
|
validate_and_sync_multiselect_value_with_options(
|
|
563
|
-
widget_state.value, indexable_options, key
|
|
587
|
+
widget_state.value, indexable_options, key, format_func
|
|
564
588
|
)
|
|
565
589
|
)
|
|
566
590
|
|
|
@@ -35,7 +35,6 @@ from streamlit.elements.lib.layout_utils import (
|
|
|
35
35
|
)
|
|
36
36
|
from streamlit.elements.lib.options_selector_utils import (
|
|
37
37
|
create_mappings,
|
|
38
|
-
index_,
|
|
39
38
|
maybe_coerce_enum,
|
|
40
39
|
validate_and_sync_value_with_options,
|
|
41
40
|
)
|
|
@@ -79,6 +78,7 @@ class SelectboxSerde(Generic[T]):
|
|
|
79
78
|
formatted_options: list[str]
|
|
80
79
|
formatted_option_to_option_index: dict[str, int]
|
|
81
80
|
default_option_index: int | None
|
|
81
|
+
format_func: Callable[[Any], str]
|
|
82
82
|
|
|
83
83
|
def __init__(
|
|
84
84
|
self,
|
|
@@ -87,6 +87,7 @@ class SelectboxSerde(Generic[T]):
|
|
|
87
87
|
formatted_options: list[str],
|
|
88
88
|
formatted_option_to_option_index: dict[str, int],
|
|
89
89
|
default_option_index: int | None = None,
|
|
90
|
+
format_func: Callable[[Any], str] = str,
|
|
90
91
|
) -> None:
|
|
91
92
|
"""Initialize the SelectboxSerde.
|
|
92
93
|
|
|
@@ -108,33 +109,53 @@ class SelectboxSerde(Generic[T]):
|
|
|
108
109
|
default_option_index : int or None, optional
|
|
109
110
|
The index of the default option to use when no selection is made.
|
|
110
111
|
If None, no default option is selected.
|
|
112
|
+
format_func : Callable[[Any], str], optional
|
|
113
|
+
Function to format options for comparison. Used to compare values by their
|
|
114
|
+
string representation instead of using == directly. This is necessary because
|
|
115
|
+
widget values are deepcopied, and for custom classes without __eq__, the
|
|
116
|
+
deepcopied instances would fail identity comparison.
|
|
111
117
|
"""
|
|
112
118
|
|
|
113
119
|
self.options = options
|
|
114
120
|
self.formatted_options = formatted_options
|
|
115
121
|
self.formatted_option_to_option_index = formatted_option_to_option_index
|
|
116
122
|
self.default_option_index = default_option_index
|
|
123
|
+
self.format_func = format_func
|
|
117
124
|
|
|
118
125
|
def serialize(self, v: T | str | None) -> str | None:
|
|
119
126
|
if v is None:
|
|
120
127
|
return None
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
#
|
|
126
|
-
#
|
|
128
|
+
# Note: We don't short-circuit for empty options here because
|
|
129
|
+
# accept_new_options=True allows user-entered values even with no options.
|
|
130
|
+
# The normal flow below handles this correctly.
|
|
131
|
+
|
|
132
|
+
# Use format_func to find the formatted option instead of using
|
|
133
|
+
# index_(self.options, v) which relies on == comparison. This is necessary
|
|
134
|
+
# because widget values are deepcopied, and for custom classes without
|
|
135
|
+
# __eq__, the deepcopied instances would fail identity comparison.
|
|
127
136
|
try:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
|
|
137
|
+
formatted_value = self.format_func(v)
|
|
138
|
+
except Exception:
|
|
139
|
+
# format_func failed (e.g., v is a string but format_func expects
|
|
140
|
+
# an object with specific attributes). Use str(v) to ensure we return
|
|
141
|
+
# a proper string, not the original object. This handles both cases:
|
|
142
|
+
# - v is already a string -> str(v) returns it unchanged
|
|
143
|
+
# - v is a custom object -> str(v) gives its string representation
|
|
144
|
+
return str(v)
|
|
145
|
+
|
|
146
|
+
if formatted_value in self.formatted_option_to_option_index:
|
|
147
|
+
return formatted_value
|
|
148
|
+
# Value not found in options - return the formatted string (not the original
|
|
149
|
+
# object) to maintain type consistency since serialize() must return str|None
|
|
150
|
+
return formatted_value
|
|
134
151
|
|
|
135
152
|
def deserialize(self, ui_value: str | None) -> T | str | None:
|
|
136
|
-
#
|
|
137
|
-
#
|
|
153
|
+
# Note: We don't short-circuit for empty options here because
|
|
154
|
+
# accept_new_options=True allows user-entered values even with no options.
|
|
155
|
+
# The normal flow below handles this: ui_value not in options -> return ui_value.
|
|
156
|
+
|
|
157
|
+
# Check if the option is pointing to a generic option type T,
|
|
158
|
+
# otherwise return the option itself.
|
|
138
159
|
if ui_value is None:
|
|
139
160
|
return (
|
|
140
161
|
self.options[self.default_option_index]
|
|
@@ -583,6 +604,7 @@ class SelectboxMixin:
|
|
|
583
604
|
formatted_options=formatted_options,
|
|
584
605
|
formatted_option_to_option_index=formatted_option_to_option_index,
|
|
585
606
|
default_option_index=index,
|
|
607
|
+
format_func=format_func,
|
|
586
608
|
)
|
|
587
609
|
widget_state = register_widget(
|
|
588
610
|
selectbox_proto.id,
|
|
@@ -605,7 +627,7 @@ class SelectboxMixin:
|
|
|
605
627
|
# This handles the case where options change dynamically and the
|
|
606
628
|
# previously selected value is no longer available.
|
|
607
629
|
current_value, value_needs_reset = validate_and_sync_value_with_options(
|
|
608
|
-
widget_state.value, opt, index, key
|
|
630
|
+
widget_state.value, opt, index, key, format_func
|
|
609
631
|
)
|
|
610
632
|
|
|
611
633
|
if value_needs_reset or widget_state.value_changed:
|
streamlit/static/index.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<script>
|
|
38
38
|
window.prerenderReady = false
|
|
39
39
|
</script>
|
|
40
|
-
<script type="module" crossorigin src="./static/js/index.
|
|
40
|
+
<script type="module" crossorigin src="./static/js/index.BB_iwaVr.js"></script>
|
|
41
41
|
<link rel="stylesheet" crossorigin href="./static/css/index.BUP6fTcR.css">
|
|
42
42
|
</head>
|
|
43
43
|
<body>
|