streamlit-nightly 1.53.1.dev20260120__py3-none-any.whl → 1.53.2.dev20260122__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/connections/snowflake_connection.py +5 -2
- streamlit/elements/lib/options_selector_utils.py +63 -19
- streamlit/elements/widgets/multiselect.py +31 -7
- streamlit/elements/widgets/radio.py +91 -31
- streamlit/elements/widgets/selectbox.py +38 -16
- streamlit/proto/Radio_pb2.py +4 -2
- streamlit/proto/Radio_pb2.pyi +20 -3
- streamlit/static/index.html +1 -1
- streamlit/static/manifest.json +308 -308
- streamlit/static/static/js/{ErrorOutline.esm.CqVEQ-uJ.js → ErrorOutline.esm.CIFYUdwC.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.G-HJG6nt.js → FileDownload.esm.DWVTnTHm.js} +1 -1
- streamlit/static/static/js/{FileHelper.Bd4bPp0Y.js → FileHelper.BPYQIPd1.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.B74VElyu.js → FormClearHelper.CypmvhYZ.js} +1 -1
- streamlit/static/static/js/{InputInstructions.ClBLOwfm.js → InputInstructions.Bi62hDTQ.js} +1 -1
- streamlit/static/static/js/{Particles.CyYskark.js → Particles.yebG0VuV.js} +1 -1
- streamlit/static/static/js/{ProgressBar.vWX1Igzt.js → ProgressBar.Dy9CI6w4.js} +1 -1
- streamlit/static/static/js/{StreamlitSyntaxHighlighter.BNhyeEtl.js → StreamlitSyntaxHighlighter.Btk92CPv.js} +1 -1
- streamlit/static/static/js/{TableChart.esm.Mk-Hvy1t.js → TableChart.esm.DBeVaFNt.js} +1 -1
- streamlit/static/static/js/{Toolbar.NhlhlTzK.js → Toolbar.DC2Tp-qb.js} +1 -1
- streamlit/static/static/js/{WidgetLabelHelpIconInline.Ch9VDnrM.js → WidgetLabelHelpIconInline.3DnEd9BK.js} +1 -1
- streamlit/static/static/js/{base-input.DSh9H2uv.js → base-input.7Sj6pVk0.js} +1 -1
- streamlit/static/static/js/{checkbox.DCFAQrRB.js → checkbox.CcUx3XuQ.js} +1 -1
- streamlit/static/static/js/{createDownloadLinkElement.D5zpcd24.js → createDownloadLinkElement.DZuwkCqy.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.GAqbIurd.js → data-grid-overlay-editor.Dw-AewlN.js} +1 -1
- streamlit/static/static/js/{downloader.C1MBZFo2.js → downloader.Bsx5M2Du.js} +1 -1
- streamlit/static/static/js/{embed.CZG6oOsc.js → embed.C7by6AoE.js} +1 -1
- streamlit/static/static/js/{es6.toHnBzIb.js → es6.BpAqZaR_.js} +2 -2
- streamlit/static/static/js/{formatNumber.BsU-5o3V.js → formatNumber.DjehVPVS.js} +1 -1
- streamlit/static/static/js/{iconPosition.OKOYrH6A.js → iconPosition.D02OPE-d.js} +1 -1
- streamlit/static/static/js/{iframeResizer.contentWindow.D6yxU4DL.js → iframeResizer.contentWindow.xtstqPd7.js} +1 -1
- streamlit/static/static/js/{index.CV4niwLB.js → index.-faJDV20.js} +1 -1
- streamlit/static/static/js/{index.D7Hgmilz.js → index.5H98WqjT.js} +1 -1
- streamlit/static/static/js/{index.0iCXW13-.js → index.8FPw0_gD.js} +1 -1
- streamlit/static/static/js/{index.D94k70Hg.js → index.B5tD5YeV.js} +1 -1
- streamlit/static/static/js/{index.YRiVxrFw.js → index.B8-HOwf1.js} +1 -1
- streamlit/static/static/js/{index.BW0F_Pbs.js → index.B9gbSNsw.js} +1 -1
- streamlit/static/static/js/{index.qjO5OK90.js → index.BB_iwaVr.js} +6 -6
- streamlit/static/static/js/{index.BXqtIpgi.js → index.BDlI2pRp.js} +1 -1
- streamlit/static/static/js/{index.EOvPT8N-.js → index.BGTMh3Uu.js} +1 -1
- streamlit/static/static/js/{index.Bhr3IHkD.js → index.BIcJe97b.js} +1 -1
- streamlit/static/static/js/{index.E4x2UBkh.js → index.BK9S5qug.js} +1 -1
- streamlit/static/static/js/{index._t3w-7R0.js → index.BOkpEbJS.js} +1 -1
- streamlit/static/static/js/{index.SBwNLdli.js → index.BV6XgCij.js} +1 -1
- streamlit/static/static/js/{index.CqywI2eW.js → index.BVhVdVeE.js} +1 -1
- streamlit/static/static/js/{index.CXLtvxrQ.js → index.Bhy8EBYI.js} +1 -1
- streamlit/static/static/js/{index.P0_E3iNi.js → index.Bo1ztye0.js} +1 -1
- streamlit/static/static/js/{index.C6LOxzFC.js → index.BqfJJr3c.js} +1 -1
- streamlit/static/static/js/{index.CJCEiqv2.js → index.Bri1T2TS.js} +1 -1
- streamlit/static/static/js/{index.MZ7ugsN-.js → index.BvZbnSMC.js} +1 -1
- streamlit/static/static/js/{index.CGt5hnLi.js → index.C5ehUqNt.js} +1 -1
- streamlit/static/static/js/{index.Bh0_7PvD.js → index.C9v49R-a.js} +1 -1
- streamlit/static/static/js/{index.Ch4X_YdF.js → index.CA0RmxJF.js} +1 -1
- streamlit/static/static/js/{index.B8mzpc1l.js → index.CEwnDCn9.js} +1 -1
- streamlit/static/static/js/{index.DhGqPx_f.js → index.CGbvkEtg.js} +1 -1
- streamlit/static/static/js/{index.ia5Ub9p7.js → index.CKUBdVQ9.js} +1 -1
- streamlit/static/static/js/{index.DGrQJk9x.js → index.CdRwiHPm.js} +1 -1
- streamlit/static/static/js/{index.dGk9EWLh.js → index.CgARjn28.js} +1 -1
- streamlit/static/static/js/{index.Dq0ZbGYZ.js → index.CyDHwK5P.js} +1 -1
- streamlit/static/static/js/{index.Blo8DAcU.js → index.D6J2UPzF.js} +1 -1
- streamlit/static/static/js/{index.DR8Ty3j4.js → index.D6Z9hKJY.js} +1 -1
- streamlit/static/static/js/{index.BVbT0yWJ.js → index.D9RL5sRp.js} +1 -1
- streamlit/static/static/js/{index.DwgEkDLk.js → index.DDu_qTm0.js} +1 -1
- streamlit/static/static/js/{index.5CwvMIZq.js → index.DJjSqPAx.js} +1 -1
- streamlit/static/static/js/{index.C_w1nWis.js → index.DO2T-QzF.js} +1 -1
- streamlit/static/static/js/{index.D0h2EJ_T.js → index.DSSapl3Q.js} +1 -1
- streamlit/static/static/js/{index.BkYhb--A.js → index.DZv5AoR1.js} +1 -1
- streamlit/static/static/js/{index.Bs4E_0D7.js → index.D_TIyPF4.js} +1 -1
- streamlit/static/static/js/{index.Dl2MB5Tx.js → index.DdxofXV8.js} +1 -1
- streamlit/static/static/js/{index.DJ8OTQbk.js → index.DgLRJfs3.js} +1 -1
- streamlit/static/static/js/{index.AkleAg_w.js → index.JL0uGAeJ.js} +1 -1
- streamlit/static/static/js/{index.BU8aUWlo.js → index.S-mjkUeF.js} +1 -1
- streamlit/static/static/js/{index.XgBfXgN1.js → index.XFMDBL5n.js} +1 -1
- streamlit/static/static/js/{index.-pgbbiGJ.js → index.ZIA43eTF.js} +1 -1
- streamlit/static/static/js/index.iF5zYERg.js +3 -0
- streamlit/static/static/js/{index.COcj-WpN.js → index.iXzAofuY.js} +1 -1
- streamlit/static/static/js/index.m3dn5Bai.js +188 -0
- streamlit/static/static/js/{index.BHWCIpbw.js → index.m4WkwGMu.js} +1 -1
- streamlit/static/static/js/{index.DbCS2N3T.js → index.x1B588Xu.js} +1 -1
- streamlit/static/static/js/{input.Bavj6HHJ.js → input.VYKyGuhi.js} +1 -1
- streamlit/static/static/js/{main.dnUTEH_j.js → main.u5Bb3MY7.js} +1 -1
- streamlit/static/static/js/{memory.Czf1Sxzc.js → memory.BOMt4yAV.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.CRQIke3a.js → number-overlay-editor.CihlAHgl.js} +1 -1
- streamlit/static/static/js/{pandasStylerUtils.agmr-LQ2.js → pandasStylerUtils.BuqSgXpk.js} +1 -1
- streamlit/static/static/js/{sandbox.BlCiIomw.js → sandbox.COGR4pqz.js} +1 -1
- streamlit/static/static/js/{styled-components.Bz3KSbhj.js → styled-components.BEf3c4IJ.js} +1 -1
- streamlit/static/static/js/{throttle.B1o314FW.js → throttle.Bl-XsA9N.js} +1 -1
- streamlit/static/static/js/{timepicker.3x4ndo9E.js → timepicker.B-HgBYlK.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.BeHbBK8g.js → toConsumableArray.BrQebwtE.js} +1 -1
- streamlit/static/static/js/uniqueId.8R4hbkYl.js +1 -0
- streamlit/static/static/js/{useBasicWidgetState.CFP4_PTk.js → useBasicWidgetState.8WwISl9r.js} +1 -1
- streamlit/static/static/js/{useIntlLocale.BJubkaPQ.js → useIntlLocale.D37LWdCR.js} +1 -1
- streamlit/static/static/js/{useTextInputAutoExpand.DBGwhM4R.js → useTextInputAutoExpand.Bb_KqJvq.js} +1 -1
- streamlit/static/static/js/{useUpdateUiValue.CyufNQfR.js → useUpdateUiValue.D1BLS5t7.js} +1 -1
- streamlit/static/static/js/{useWaveformController.Dj5h0D8Y.js → useWaveformController.Ce0-qTws.js} +1 -1
- streamlit/static/static/js/{withCalculatedWidth.D0IRb-7w.js → withCalculatedWidth.BX2K3UVv.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.CHBnw0Co.js → withFullScreenWrapper.CqfGs8T2.js} +1 -1
- streamlit/testing/v1/element_tree.py +2 -2
- streamlit/watcher/event_based_path_watcher.py +37 -7
- streamlit/watcher/path_watcher.py +60 -1
- streamlit/watcher/util.py +26 -10
- streamlit/web/bootstrap.py +11 -2
- {streamlit_nightly-1.53.1.dev20260120.dist-info → streamlit_nightly-1.53.2.dev20260122.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.53.1.dev20260120.dist-info → streamlit_nightly-1.53.2.dev20260122.dist-info}/RECORD +107 -107
- {streamlit_nightly-1.53.1.dev20260120.dist-info → streamlit_nightly-1.53.2.dev20260122.dist-info}/WHEEL +1 -1
- streamlit/static/static/js/index.1sBZt_1I.js +0 -188
- streamlit/static/static/js/index.DRGDE5oo.js +0 -3
- streamlit/static/static/js/uniqueId.Zn1vZBLX.js +0 -1
- {streamlit_nightly-1.53.1.dev20260120.data → streamlit_nightly-1.53.2.dev20260122.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.53.1.dev20260120.dist-info → streamlit_nightly-1.53.2.dev20260122.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.53.1.dev20260120.dist-info → streamlit_nightly-1.53.2.dev20260122.dist-info}/top_level.txt +0 -0
|
@@ -135,7 +135,9 @@ class BaseSnowflakeConnection(BaseConnection["InternalSnowflakeConnection"]):
|
|
|
135
135
|
),
|
|
136
136
|
wait=wait_fixed(1),
|
|
137
137
|
)
|
|
138
|
-
|
|
138
|
+
# `params` must be an explicit parameter (not captured from closure) so that
|
|
139
|
+
# `@st.cache_data` includes it in the cache key.
|
|
140
|
+
def _query(sql: str, params: Any = None) -> DataFrame:
|
|
139
141
|
cur = self._instance.cursor()
|
|
140
142
|
cur.execute(sql, params=params, **kwargs)
|
|
141
143
|
return cur.fetch_pandas_all() # type: ignore
|
|
@@ -153,7 +155,7 @@ class BaseSnowflakeConnection(BaseConnection["InternalSnowflakeConnection"]):
|
|
|
153
155
|
ttl=ttl,
|
|
154
156
|
)(_query)
|
|
155
157
|
|
|
156
|
-
return _query(sql)
|
|
158
|
+
return _query(sql, params)
|
|
157
159
|
|
|
158
160
|
def write_pandas(
|
|
159
161
|
self,
|
|
@@ -349,6 +351,7 @@ class BaseSnowflakeConnection(BaseConnection["InternalSnowflakeConnection"]):
|
|
|
349
351
|
"""Closes the underlying Snowflake connection."""
|
|
350
352
|
if self._raw_instance is not None:
|
|
351
353
|
self._raw_instance.close()
|
|
354
|
+
self._raw_instance = None
|
|
352
355
|
|
|
353
356
|
|
|
354
357
|
class SnowflakeConnection(BaseSnowflakeConnection):
|
|
@@ -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
|
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from dataclasses import dataclass
|
|
18
17
|
from textwrap import dedent
|
|
19
18
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast, overload
|
|
20
19
|
|
|
@@ -27,7 +26,11 @@ from streamlit.elements.lib.layout_utils import (
|
|
|
27
26
|
Width,
|
|
28
27
|
validate_width,
|
|
29
28
|
)
|
|
30
|
-
from streamlit.elements.lib.options_selector_utils import
|
|
29
|
+
from streamlit.elements.lib.options_selector_utils import (
|
|
30
|
+
create_mappings,
|
|
31
|
+
maybe_coerce_enum,
|
|
32
|
+
validate_and_sync_value_with_options,
|
|
33
|
+
)
|
|
31
34
|
from streamlit.elements.lib.policies import (
|
|
32
35
|
check_widget_policies,
|
|
33
36
|
maybe_raise_label_warnings,
|
|
@@ -63,27 +66,70 @@ if TYPE_CHECKING:
|
|
|
63
66
|
T = TypeVar("T")
|
|
64
67
|
|
|
65
68
|
|
|
66
|
-
@dataclass
|
|
67
69
|
class RadioSerde(Generic[T]):
|
|
70
|
+
"""Serializer/deserializer for Radio widget values.
|
|
71
|
+
|
|
72
|
+
Uses string-based values (formatted option strings) for robust handling
|
|
73
|
+
of dynamic option changes, similar to SelectboxSerde.
|
|
74
|
+
"""
|
|
75
|
+
|
|
68
76
|
options: Sequence[T]
|
|
69
|
-
|
|
77
|
+
formatted_options: list[str]
|
|
78
|
+
formatted_option_to_option_index: dict[str, int]
|
|
79
|
+
default_option_index: int | None
|
|
80
|
+
format_func: Callable[[Any], str]
|
|
70
81
|
|
|
71
|
-
def
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
options: Sequence[T],
|
|
85
|
+
*,
|
|
86
|
+
formatted_options: list[str],
|
|
87
|
+
formatted_option_to_option_index: dict[str, int],
|
|
88
|
+
default_option_index: int | None = None,
|
|
89
|
+
format_func: Callable[[Any], str] = str,
|
|
90
|
+
) -> None:
|
|
91
|
+
self.options = options
|
|
92
|
+
self.formatted_options = formatted_options
|
|
93
|
+
self.formatted_option_to_option_index = formatted_option_to_option_index
|
|
94
|
+
self.default_option_index = default_option_index
|
|
95
|
+
self.format_func = format_func
|
|
96
|
+
|
|
97
|
+
def serialize(self, v: T | str | None) -> str | None:
|
|
72
98
|
if v is None:
|
|
73
99
|
return None
|
|
100
|
+
if len(self.options) == 0:
|
|
101
|
+
return None
|
|
74
102
|
|
|
75
|
-
|
|
103
|
+
# Use format_func to find the formatted option instead of using
|
|
104
|
+
# index_(self.options, v) which relies on == comparison. This is necessary
|
|
105
|
+
# because widget values are deepcopied, and for custom classes without
|
|
106
|
+
# __eq__, the deepcopied instances would fail identity comparison.
|
|
107
|
+
try:
|
|
108
|
+
formatted_value = self.format_func(v)
|
|
109
|
+
except Exception:
|
|
110
|
+
# format_func failed (e.g., v is a string but format_func expects
|
|
111
|
+
# an object with specific attributes). Treat v as a raw string.
|
|
112
|
+
return cast("str", v)
|
|
113
|
+
|
|
114
|
+
if formatted_value in self.formatted_option_to_option_index:
|
|
115
|
+
return formatted_value
|
|
116
|
+
# Value not found in options - return as raw string
|
|
117
|
+
return cast("str", v)
|
|
118
|
+
|
|
119
|
+
def deserialize(self, ui_value: str | None) -> T | str | None:
|
|
120
|
+
# If no options, there's no valid value - return None
|
|
121
|
+
if len(self.options) == 0:
|
|
122
|
+
return None
|
|
76
123
|
|
|
77
|
-
|
|
78
|
-
|
|
124
|
+
if ui_value is None:
|
|
125
|
+
return (
|
|
126
|
+
self.options[self.default_option_index]
|
|
127
|
+
if self.default_option_index is not None
|
|
128
|
+
else None
|
|
129
|
+
)
|
|
79
130
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if idx is not None
|
|
83
|
-
and len(self.options) > 0
|
|
84
|
-
and self.options[idx] is not None
|
|
85
|
-
else None
|
|
86
|
-
)
|
|
131
|
+
option_index = self.formatted_option_to_option_index.get(ui_value)
|
|
132
|
+
return self.options[option_index] if option_index is not None else ui_value
|
|
87
133
|
|
|
88
134
|
|
|
89
135
|
class RadioMixin:
|
|
@@ -368,18 +414,17 @@ class RadioMixin:
|
|
|
368
414
|
opt = convert_anything_to_list(options)
|
|
369
415
|
check_python_comparable(opt)
|
|
370
416
|
|
|
417
|
+
formatted_options, formatted_option_to_option_index = create_mappings(
|
|
418
|
+
opt, format_func
|
|
419
|
+
)
|
|
420
|
+
|
|
371
421
|
element_id = compute_and_register_element_id(
|
|
372
422
|
"radio",
|
|
373
423
|
user_key=key,
|
|
374
|
-
|
|
375
|
-
# following parameters in the identity computation since they can
|
|
376
|
-
# invalidate the current selection mapping.
|
|
377
|
-
# Changes to format_func also invalidate the current selection,
|
|
378
|
-
# but this is already handled via the `options` parameter below:
|
|
379
|
-
key_as_main_identity={"options"},
|
|
424
|
+
key_as_main_identity=True,
|
|
380
425
|
dg=self.dg,
|
|
381
426
|
label=label,
|
|
382
|
-
options=
|
|
427
|
+
options=formatted_options,
|
|
383
428
|
index=index,
|
|
384
429
|
help=help,
|
|
385
430
|
horizontal=horizontal,
|
|
@@ -415,7 +460,7 @@ class RadioMixin:
|
|
|
415
460
|
radio_proto.label = label
|
|
416
461
|
if index is not None:
|
|
417
462
|
radio_proto.default = index
|
|
418
|
-
radio_proto.options[:] =
|
|
463
|
+
radio_proto.options[:] = formatted_options
|
|
419
464
|
radio_proto.form_id = current_form_id(self.dg)
|
|
420
465
|
radio_proto.horizontal = horizontal
|
|
421
466
|
radio_proto.disabled = disabled
|
|
@@ -429,7 +474,13 @@ class RadioMixin:
|
|
|
429
474
|
if help is not None:
|
|
430
475
|
radio_proto.help = dedent(help)
|
|
431
476
|
|
|
432
|
-
serde = RadioSerde(
|
|
477
|
+
serde = RadioSerde(
|
|
478
|
+
opt,
|
|
479
|
+
formatted_options=formatted_options,
|
|
480
|
+
formatted_option_to_option_index=formatted_option_to_option_index,
|
|
481
|
+
default_option_index=index,
|
|
482
|
+
format_func=format_func,
|
|
483
|
+
)
|
|
433
484
|
|
|
434
485
|
widget_state = register_widget(
|
|
435
486
|
radio_proto.id,
|
|
@@ -439,21 +490,30 @@ class RadioMixin:
|
|
|
439
490
|
deserializer=serde.deserialize,
|
|
440
491
|
serializer=serde.serialize,
|
|
441
492
|
ctx=ctx,
|
|
442
|
-
value_type="
|
|
493
|
+
value_type="string_value",
|
|
443
494
|
)
|
|
444
495
|
widget_state = maybe_coerce_enum(widget_state, options, opt)
|
|
445
496
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
497
|
+
# Validate the current value against the new options.
|
|
498
|
+
# If the value is no longer valid (not in options), reset to default.
|
|
499
|
+
# This handles the case where options change dynamically and the
|
|
500
|
+
# previously selected value is no longer available.
|
|
501
|
+
# Cast to T | None since radio doesn't support accept_new_options,
|
|
502
|
+
# so string values that aren't in options will be reset to default.
|
|
503
|
+
current_value, value_needs_reset = validate_and_sync_value_with_options(
|
|
504
|
+
cast("T | None", widget_state.value), opt, index, key
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
if value_needs_reset or widget_state.value_changed:
|
|
508
|
+
serialized_value = serde.serialize(current_value)
|
|
509
|
+
if serialized_value is not None:
|
|
510
|
+
radio_proto.raw_value = serialized_value
|
|
451
511
|
radio_proto.set_value = True
|
|
452
512
|
|
|
453
513
|
if ctx:
|
|
454
514
|
save_for_app_testing(ctx, element_id, format_func)
|
|
455
515
|
self.dg._enqueue("radio", radio_proto, layout_config=layout_config)
|
|
456
|
-
return
|
|
516
|
+
return current_value
|
|
457
517
|
|
|
458
518
|
@property
|
|
459
519
|
def dg(self) -> DeltaGenerator:
|
|
@@ -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/proto/Radio_pb2.py
CHANGED
|
@@ -15,7 +15,7 @@ _sym_db = _symbol_database.Default()
|
|
|
15
15
|
from streamlit.proto import LabelVisibilityMessage_pb2 as streamlit_dot_proto_dot_LabelVisibilityMessage__pb2
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bstreamlit/proto/Radio.proto\x1a,streamlit/proto/LabelVisibilityMessage.proto\"\
|
|
18
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bstreamlit/proto/Radio.proto\x1a,streamlit/proto/LabelVisibilityMessage.proto\"\xba\x02\n\x05Radio\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05label\x18\x02 \x01(\t\x12\x14\n\x07\x64\x65\x66\x61ult\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x0f\n\x07options\x18\x04 \x03(\t\x12\x0c\n\x04help\x18\x05 \x01(\t\x12\x0f\n\x07\x66orm_id\x18\x06 \x01(\t\x12\x16\n\x05value\x18\x07 \x01(\x05\x42\x02\x18\x01H\x01\x88\x01\x01\x12\x16\n\traw_value\x18\r \x01(\tH\x02\x88\x01\x01\x12\x11\n\tset_value\x18\x08 \x01(\x08\x12\x10\n\x08\x64isabled\x18\t \x01(\x08\x12\x12\n\nhorizontal\x18\n \x01(\x08\x12\x31\n\x10label_visibility\x18\x0b \x01(\x0b\x32\x17.LabelVisibilityMessage\x12\x10\n\x08\x63\x61ptions\x18\x0c \x03(\tB\n\n\x08_defaultB\x08\n\x06_valueB\x0c\n\n_raw_valueB*\n\x1c\x63om.snowflake.apps.streamlitB\nRadioProtob\x06proto3')
|
|
19
19
|
|
|
20
20
|
_globals = globals()
|
|
21
21
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -23,6 +23,8 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'streamlit.proto.Radio_pb2',
|
|
|
23
23
|
if not _descriptor._USE_C_DESCRIPTORS:
|
|
24
24
|
_globals['DESCRIPTOR']._loaded_options = None
|
|
25
25
|
_globals['DESCRIPTOR']._serialized_options = b'\n\034com.snowflake.apps.streamlitB\nRadioProto'
|
|
26
|
+
_globals['_RADIO'].fields_by_name['value']._loaded_options = None
|
|
27
|
+
_globals['_RADIO'].fields_by_name['value']._serialized_options = b'\030\001'
|
|
26
28
|
_globals['_RADIO']._serialized_start=78
|
|
27
|
-
_globals['_RADIO']._serialized_end=
|
|
29
|
+
_globals['_RADIO']._serialized_end=392
|
|
28
30
|
# @@protoc_insertion_point(module_scope)
|
streamlit/proto/Radio_pb2.pyi
CHANGED
|
@@ -31,6 +31,11 @@ if sys.version_info >= (3, 10):
|
|
|
31
31
|
else:
|
|
32
32
|
from typing_extensions import TypeAlias as _TypeAlias
|
|
33
33
|
|
|
34
|
+
if sys.version_info >= (3, 13):
|
|
35
|
+
from warnings import deprecated as _deprecated
|
|
36
|
+
else:
|
|
37
|
+
from typing_extensions import deprecated as _deprecated
|
|
38
|
+
|
|
34
39
|
DESCRIPTOR: _descriptor.FileDescriptor
|
|
35
40
|
|
|
36
41
|
@_typing.final
|
|
@@ -44,6 +49,7 @@ class Radio(_message.Message):
|
|
|
44
49
|
HELP_FIELD_NUMBER: _builtins.int
|
|
45
50
|
FORM_ID_FIELD_NUMBER: _builtins.int
|
|
46
51
|
VALUE_FIELD_NUMBER: _builtins.int
|
|
52
|
+
RAW_VALUE_FIELD_NUMBER: _builtins.int
|
|
47
53
|
SET_VALUE_FIELD_NUMBER: _builtins.int
|
|
48
54
|
DISABLED_FIELD_NUMBER: _builtins.int
|
|
49
55
|
HORIZONTAL_FIELD_NUMBER: _builtins.int
|
|
@@ -54,7 +60,13 @@ class Radio(_message.Message):
|
|
|
54
60
|
default: _builtins.int
|
|
55
61
|
help: _builtins.str
|
|
56
62
|
form_id: _builtins.str
|
|
57
|
-
|
|
63
|
+
@_builtins.property
|
|
64
|
+
@_deprecated("""This field has been marked as deprecated using proto field options.""")
|
|
65
|
+
def value(self) -> _builtins.int: ...
|
|
66
|
+
@value.setter
|
|
67
|
+
@_deprecated("""This field has been marked as deprecated using proto field options.""")
|
|
68
|
+
def value(self, value: _builtins.int) -> None: ...
|
|
69
|
+
raw_value: _builtins.str
|
|
58
70
|
set_value: _builtins.bool
|
|
59
71
|
disabled: _builtins.bool
|
|
60
72
|
horizontal: _builtins.bool
|
|
@@ -74,23 +86,28 @@ class Radio(_message.Message):
|
|
|
74
86
|
help: _builtins.str = ...,
|
|
75
87
|
form_id: _builtins.str = ...,
|
|
76
88
|
value: _builtins.int | None = ...,
|
|
89
|
+
raw_value: _builtins.str | None = ...,
|
|
77
90
|
set_value: _builtins.bool = ...,
|
|
78
91
|
disabled: _builtins.bool = ...,
|
|
79
92
|
horizontal: _builtins.bool = ...,
|
|
80
93
|
label_visibility: _LabelVisibilityMessage_pb2.LabelVisibilityMessage | None = ...,
|
|
81
94
|
captions: _abc.Iterable[_builtins.str] | None = ...,
|
|
82
95
|
) -> None: ...
|
|
83
|
-
_HasFieldArgType: _TypeAlias = _typing.Literal["_default", b"_default", "_value", b"_value", "default", b"default", "label_visibility", b"label_visibility", "value", b"value"] # noqa: Y015
|
|
96
|
+
_HasFieldArgType: _TypeAlias = _typing.Literal["_default", b"_default", "_raw_value", b"_raw_value", "_value", b"_value", "default", b"default", "label_visibility", b"label_visibility", "raw_value", b"raw_value", "value", b"value"] # noqa: Y015
|
|
84
97
|
def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ...
|
|
85
|
-
_ClearFieldArgType: _TypeAlias = _typing.Literal["_default", b"_default", "_value", b"_value", "captions", b"captions", "default", b"default", "disabled", b"disabled", "form_id", b"form_id", "help", b"help", "horizontal", b"horizontal", "id", b"id", "label", b"label", "label_visibility", b"label_visibility", "options", b"options", "set_value", b"set_value", "value", b"value"] # noqa: Y015
|
|
98
|
+
_ClearFieldArgType: _TypeAlias = _typing.Literal["_default", b"_default", "_raw_value", b"_raw_value", "_value", b"_value", "captions", b"captions", "default", b"default", "disabled", b"disabled", "form_id", b"form_id", "help", b"help", "horizontal", b"horizontal", "id", b"id", "label", b"label", "label_visibility", b"label_visibility", "options", b"options", "raw_value", b"raw_value", "set_value", b"set_value", "value", b"value"] # noqa: Y015
|
|
86
99
|
def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
|
|
87
100
|
_WhichOneofReturnType__default: _TypeAlias = _typing.Literal["default"] # noqa: Y015
|
|
88
101
|
_WhichOneofArgType__default: _TypeAlias = _typing.Literal["_default", b"_default"] # noqa: Y015
|
|
102
|
+
_WhichOneofReturnType__raw_value: _TypeAlias = _typing.Literal["raw_value"] # noqa: Y015
|
|
103
|
+
_WhichOneofArgType__raw_value: _TypeAlias = _typing.Literal["_raw_value", b"_raw_value"] # noqa: Y015
|
|
89
104
|
_WhichOneofReturnType__value: _TypeAlias = _typing.Literal["value"] # noqa: Y015
|
|
90
105
|
_WhichOneofArgType__value: _TypeAlias = _typing.Literal["_value", b"_value"] # noqa: Y015
|
|
91
106
|
@_typing.overload
|
|
92
107
|
def WhichOneof(self, oneof_group: _WhichOneofArgType__default) -> _WhichOneofReturnType__default | None: ...
|
|
93
108
|
@_typing.overload
|
|
109
|
+
def WhichOneof(self, oneof_group: _WhichOneofArgType__raw_value) -> _WhichOneofReturnType__raw_value | None: ...
|
|
110
|
+
@_typing.overload
|
|
94
111
|
def WhichOneof(self, oneof_group: _WhichOneofArgType__value) -> _WhichOneofReturnType__value | None: ...
|
|
95
112
|
|
|
96
113
|
Global___Radio: _TypeAlias = Radio # noqa: Y015
|
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>
|