streamlit-nightly 1.53.1.dev20260117__py3-none-any.whl → 1.53.1.dev20260121__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/__init__.py +0 -4
- streamlit/connections/snowflake_connection.py +5 -2
- streamlit/dataframe_util.py +4 -2
- streamlit/elements/lib/built_in_chart_utils.py +3 -0
- streamlit/elements/lib/column_config_utils.py +3 -1
- streamlit/elements/widgets/radio.py +91 -31
- streamlit/elements/widgets/time_widgets.py +58 -7
- streamlit/hello/dataframe_demo.py +1 -1
- 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.C6VdLmcj.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.G-HJG6nt.js → FileDownload.esm.g3HiTWnX.js} +1 -1
- streamlit/static/static/js/{FileHelper.Bd4bPp0Y.js → FileHelper.DkZahZIe.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.B74VElyu.js → FormClearHelper.CnDHLc6s.js} +1 -1
- streamlit/static/static/js/{InputInstructions.ClBLOwfm.js → InputInstructions.CJwgA0Sj.js} +1 -1
- streamlit/static/static/js/{Particles.CyYskark.js → Particles.BA0kQsJM.js} +1 -1
- streamlit/static/static/js/{ProgressBar.vWX1Igzt.js → ProgressBar.N6Nzla-b.js} +1 -1
- streamlit/static/static/js/{StreamlitSyntaxHighlighter.BNhyeEtl.js → StreamlitSyntaxHighlighter.DF8uiu4N.js} +1 -1
- streamlit/static/static/js/{TableChart.esm.Mk-Hvy1t.js → TableChart.esm.Ne8F0PNs.js} +1 -1
- streamlit/static/static/js/{Toolbar.NhlhlTzK.js → Toolbar.h597G9NG.js} +1 -1
- streamlit/static/static/js/{WidgetLabelHelpIconInline.Ch9VDnrM.js → WidgetLabelHelpIconInline.BMPpOfi3.js} +1 -1
- streamlit/static/static/js/{base-input.DSh9H2uv.js → base-input.CTx0w60a.js} +1 -1
- streamlit/static/static/js/{checkbox.DCFAQrRB.js → checkbox.otYZO7w2.js} +1 -1
- streamlit/static/static/js/{createDownloadLinkElement.D5zpcd24.js → createDownloadLinkElement.Az9SilWU.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.GAqbIurd.js → data-grid-overlay-editor.Blj_5PJn.js} +1 -1
- streamlit/static/static/js/{downloader.C1MBZFo2.js → downloader.ur6VTRo6.js} +1 -1
- streamlit/static/static/js/{embed.CZG6oOsc.js → embed.Bo5Mp3yp.js} +1 -1
- streamlit/static/static/js/{es6.toHnBzIb.js → es6.Q-xD3Lx8.js} +2 -2
- streamlit/static/static/js/{formatNumber.BsU-5o3V.js → formatNumber._OZtRfH-.js} +1 -1
- streamlit/static/static/js/{iconPosition.OKOYrH6A.js → iconPosition.owgMmPXc.js} +1 -1
- streamlit/static/static/js/{iframeResizer.contentWindow.D6yxU4DL.js → iframeResizer.contentWindow.CVCEnOeT.js} +1 -1
- streamlit/static/static/js/{index.DwgEkDLk.js → index.1wQkO9-P.js} +1 -1
- streamlit/static/static/js/{index.DhGqPx_f.js → index.2pNQcghF.js} +1 -1
- streamlit/static/static/js/{index.BHWCIpbw.js → index.9GyNEnVn.js} +1 -1
- streamlit/static/static/js/{index._t3w-7R0.js → index.BBFXXyOE.js} +1 -1
- streamlit/static/static/js/{index.DbCS2N3T.js → index.BOH_U_Ua.js} +1 -1
- streamlit/static/static/js/{index.YRiVxrFw.js → index.BTy77A4v.js} +1 -1
- streamlit/static/static/js/{index.DR8Ty3j4.js → index.BZMDU-qx.js} +1 -1
- streamlit/static/static/js/{index.EOvPT8N-.js → index.BdKmWvyd.js} +1 -1
- streamlit/static/static/js/{index.SBwNLdli.js → index.BkFxYQoo.js} +1 -1
- streamlit/static/static/js/{index.0iCXW13-.js → index.BlZAOJTf.js} +1 -1
- streamlit/static/static/js/{index.DGrQJk9x.js → index.BtKtuLWl.js} +1 -1
- streamlit/static/static/js/{index.C_w1nWis.js → index.Buip23l3.js} +1 -1
- streamlit/static/static/js/{index.CXLtvxrQ.js → index.Bw5t0A1a.js} +1 -1
- streamlit/static/static/js/{index.B8mzpc1l.js → index.ByHx8_go.js} +1 -1
- streamlit/static/static/js/{index.Bs4E_0D7.js → index.Byo1IU24.js} +1 -1
- streamlit/static/static/js/{index.Bh0_7PvD.js → index.ByzGGHA-.js} +1 -1
- streamlit/static/static/js/{index.BVbT0yWJ.js → index.C-xuUZos.js} +1 -1
- streamlit/static/static/js/{index.Dl2MB5Tx.js → index.C0X75KQC.js} +1 -1
- streamlit/static/static/js/{index.CV4niwLB.js → index.C5MX205D.js} +1 -1
- streamlit/static/static/js/{index.Blo8DAcU.js → index.CECvn-YK.js} +1 -1
- streamlit/static/static/js/{index.P0_E3iNi.js → index.CHfpPdI8.js} +1 -1
- streamlit/static/static/js/{index.BU8aUWlo.js → index.CT8A-jH3.js} +1 -1
- streamlit/static/static/js/{index.CJCEiqv2.js → index.CT93Qeic.js} +1 -1
- streamlit/static/static/js/{index.E4x2UBkh.js → index.CZKvO8_W.js} +1 -1
- streamlit/static/static/js/{index.AkleAg_w.js → index.Ccjs4i9w.js} +1 -1
- streamlit/static/static/js/{index.BkYhb--A.js → index.CeCHrefy.js} +1 -1
- streamlit/static/static/js/{index.qjO5OK90.js → index.Cpn4aUEp.js} +6 -6
- streamlit/static/static/js/{index.5CwvMIZq.js → index.D-VZVj73.js} +1 -1
- streamlit/static/static/js/{index.D94k70Hg.js → index.DA1Zwrh6.js} +1 -1
- streamlit/static/static/js/{index.COcj-WpN.js → index.DAddHkEE.js} +1 -1
- streamlit/static/static/js/{index.BXqtIpgi.js → index.DAjvUscA.js} +1 -1
- streamlit/static/static/js/{index.D7Hgmilz.js → index.DPa8c-h5.js} +1 -1
- streamlit/static/static/js/{index.XgBfXgN1.js → index.DUoWDMOZ.js} +1 -1
- streamlit/static/static/js/{index.CqywI2eW.js → index.DYqiNZKP.js} +1 -1
- streamlit/static/static/js/{index.BW0F_Pbs.js → index.DcZ6Y6tq.js} +1 -1
- streamlit/static/static/js/{index.dGk9EWLh.js → index.DfDGWP9w.js} +1 -1
- streamlit/static/static/js/{index.D0h2EJ_T.js → index.DiEHlHgD.js} +1 -1
- streamlit/static/static/js/{index.Ch4X_YdF.js → index.DkmkM-3F.js} +1 -1
- streamlit/static/static/js/{index.Dq0ZbGYZ.js → index.Vo6SwZW9.js} +1 -1
- streamlit/static/static/js/{index.Bhr3IHkD.js → index.X86xhkRz.js} +1 -1
- streamlit/static/static/js/index.Z5FSW1PS.js +3 -0
- streamlit/static/static/js/{index.DJ8OTQbk.js → index.d4eN08zp.js} +1 -1
- streamlit/static/static/js/{index.1sBZt_1I.js → index.hri4IXS4.js} +5 -5
- streamlit/static/static/js/{index.CGt5hnLi.js → index.jADyDjmJ.js} +1 -1
- streamlit/static/static/js/{index.ia5Ub9p7.js → index.jZuoBudL.js} +1 -1
- streamlit/static/static/js/{index.C6LOxzFC.js → index.oieFJFkX.js} +1 -1
- streamlit/static/static/js/{index.-pgbbiGJ.js → index.rpudgXQV.js} +1 -1
- streamlit/static/static/js/{index.MZ7ugsN-.js → index.zR1le1Pe.js} +1 -1
- streamlit/static/static/js/{input.Bavj6HHJ.js → input.FrKOgyiF.js} +1 -1
- streamlit/static/static/js/{main.dnUTEH_j.js → main.DRehwjQT.js} +1 -1
- streamlit/static/static/js/{memory.Czf1Sxzc.js → memory.B2wgPAbQ.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.CRQIke3a.js → number-overlay-editor.CnTj7Q_W.js} +1 -1
- streamlit/static/static/js/{pandasStylerUtils.agmr-LQ2.js → pandasStylerUtils.CWancxgS.js} +1 -1
- streamlit/static/static/js/{sandbox.BlCiIomw.js → sandbox.DuleaI7G.js} +1 -1
- streamlit/static/static/js/{styled-components.Bz3KSbhj.js → styled-components.g3zaW9ch.js} +1 -1
- streamlit/static/static/js/{throttle.B1o314FW.js → throttle.Dta7xRnz.js} +1 -1
- streamlit/static/static/js/{timepicker.3x4ndo9E.js → timepicker.Bg1hWKQe.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.BeHbBK8g.js → toConsumableArray.C3xlVjBM.js} +1 -1
- streamlit/static/static/js/uniqueId.2p-nMqYS.js +1 -0
- streamlit/static/static/js/{useBasicWidgetState.CFP4_PTk.js → useBasicWidgetState.CERZ06R-.js} +1 -1
- streamlit/static/static/js/{useIntlLocale.BJubkaPQ.js → useIntlLocale.DTs1RN9G.js} +1 -1
- streamlit/static/static/js/{useTextInputAutoExpand.DBGwhM4R.js → useTextInputAutoExpand.B6f9P3jg.js} +1 -1
- streamlit/static/static/js/{useUpdateUiValue.CyufNQfR.js → useUpdateUiValue.ByVBXV8J.js} +1 -1
- streamlit/static/static/js/{useWaveformController.Dj5h0D8Y.js → useWaveformController.D6wABMbB.js} +1 -1
- streamlit/static/static/js/{withCalculatedWidth.D0IRb-7w.js → withCalculatedWidth.Bd4vFDHU.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.CHBnw0Co.js → withFullScreenWrapper.CDnDcnIh.js} +1 -1
- streamlit/testing/v1/element_tree.py +2 -2
- streamlit/user_info.py +0 -39
- 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.dev20260117.dist-info → streamlit_nightly-1.53.1.dev20260121.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.53.1.dev20260117.dist-info → streamlit_nightly-1.53.1.dev20260121.dist-info}/RECORD +111 -111
- {streamlit_nightly-1.53.1.dev20260117.dist-info → streamlit_nightly-1.53.1.dev20260121.dist-info}/WHEEL +1 -1
- streamlit/static/static/js/index.DRGDE5oo.js +0 -3
- streamlit/static/static/js/uniqueId.Zn1vZBLX.js +0 -1
- {streamlit_nightly-1.53.1.dev20260117.data → streamlit_nightly-1.53.1.dev20260121.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.53.1.dev20260117.dist-info → streamlit_nightly-1.53.1.dev20260121.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.53.1.dev20260117.dist-info → streamlit_nightly-1.53.1.dev20260121.dist-info}/top_level.txt +0 -0
streamlit/__init__.py
CHANGED
|
@@ -111,7 +111,6 @@ from streamlit.runtime.state import (
|
|
|
111
111
|
)
|
|
112
112
|
from streamlit.user_info import (
|
|
113
113
|
UserInfoProxy as _UserInfoProxy,
|
|
114
|
-
DeprecatedUserInfoProxy as _DeprecatedUserInfoProxy,
|
|
115
114
|
login as _login,
|
|
116
115
|
logout as _logout,
|
|
117
116
|
)
|
|
@@ -275,9 +274,6 @@ logout = _logout
|
|
|
275
274
|
# User
|
|
276
275
|
user = _UserInfoProxy()
|
|
277
276
|
|
|
278
|
-
# Experimental APIs
|
|
279
|
-
experimental_user = _DeprecatedUserInfoProxy()
|
|
280
|
-
|
|
281
277
|
# make it possible to call streamlit.components.v1.html etc. by importing it here
|
|
282
278
|
# import in the very end to avoid partially-initialized module import errors, because
|
|
283
279
|
# streamlit.components.v1 also uses some streamlit imports
|
|
@@ -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):
|
streamlit/dataframe_util.py
CHANGED
|
@@ -1072,8 +1072,10 @@ def is_colum_type_arrow_incompatible(column: Series[Any] | Index[Any]) -> bool:
|
|
|
1072
1072
|
return True
|
|
1073
1073
|
|
|
1074
1074
|
if column.dtype == "object":
|
|
1075
|
-
# The dtype of mixed type columns is always object
|
|
1076
|
-
#
|
|
1075
|
+
# The dtype of mixed type columns is always object. In pandas 3.0+, pure
|
|
1076
|
+
# string columns use StringDtype instead of object, so they won't enter
|
|
1077
|
+
# this block (and they're Arrow-compatible anyway). The actual type of
|
|
1078
|
+
# object dtype column values can be determined via the infer_dtype function:
|
|
1077
1079
|
# https://pandas.pydata.org/docs/reference/api/pandas.api.types.infer_dtype.html
|
|
1078
1080
|
inferred_type = infer_dtype(column, skipna=True)
|
|
1079
1081
|
|
|
@@ -611,6 +611,9 @@ def _melt_data(
|
|
|
611
611
|
|
|
612
612
|
y_series = melted_df[new_y_column_name]
|
|
613
613
|
if (
|
|
614
|
+
# After melting columns of different dtypes, the result has object dtype.
|
|
615
|
+
# In pandas 3.0+, melting columns with the same StringDtype keeps StringDtype,
|
|
616
|
+
# so this check correctly identifies only truly mixed-type scenarios.
|
|
614
617
|
y_series.dtype == "object"
|
|
615
618
|
and "mixed" in infer_dtype(y_series)
|
|
616
619
|
and len(y_series.unique()) > 100
|
|
@@ -255,7 +255,9 @@ def _determine_data_kind_via_pandas_dtype(
|
|
|
255
255
|
if pd.api.types.is_object_dtype(
|
|
256
256
|
column_dtype
|
|
257
257
|
) is False and pd.api.types.is_string_dtype(column_dtype):
|
|
258
|
-
#
|
|
258
|
+
# This handles pandas 3.0+ StringDtype (and PyArrow-backed string types).
|
|
259
|
+
# We exclude object dtype here because object columns with string values
|
|
260
|
+
# are handled via _determine_data_kind_via_inferred_type in the caller.
|
|
259
261
|
return ColumnDataKind.STRING
|
|
260
262
|
|
|
261
263
|
return ColumnDataKind.UNKNOWN
|
|
@@ -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:
|
|
@@ -539,6 +539,42 @@ def _validate_date_value(
|
|
|
539
539
|
return cast("DateWidgetReturn", tuple(parsed_values.value)), True
|
|
540
540
|
|
|
541
541
|
|
|
542
|
+
def _validate_datetime_value(
|
|
543
|
+
current_value: datetime | None,
|
|
544
|
+
parsed_values: _DateTimeInputValues,
|
|
545
|
+
has_explicit_bounds: bool,
|
|
546
|
+
) -> tuple[datetime | None, bool]:
|
|
547
|
+
"""Validate current datetime value against min/max bounds and determine if reset is needed.
|
|
548
|
+
|
|
549
|
+
Only validates when has_explicit_bounds is True (user provided min_value or max_value).
|
|
550
|
+
This avoids incorrectly determining if reset is needed against computed default bounds.
|
|
551
|
+
|
|
552
|
+
Parameters
|
|
553
|
+
----------
|
|
554
|
+
current_value : datetime | None
|
|
555
|
+
The current value of the datetime input widget.
|
|
556
|
+
parsed_values : _DateTimeInputValues
|
|
557
|
+
Parsed configuration containing min, max, and default value.
|
|
558
|
+
has_explicit_bounds : bool
|
|
559
|
+
Whether the user explicitly provided min_value or max_value. If False, validation
|
|
560
|
+
is skipped to avoid resetting against computed default bounds.
|
|
561
|
+
|
|
562
|
+
Returns
|
|
563
|
+
-------
|
|
564
|
+
tuple[datetime | None, bool]
|
|
565
|
+
A tuple of (validated_value, was_reset) where validated_value is either the
|
|
566
|
+
original value (if valid) or the default value (if reset was needed), and
|
|
567
|
+
was_reset indicates whether a reset occurred.
|
|
568
|
+
"""
|
|
569
|
+
if current_value is None or not has_explicit_bounds:
|
|
570
|
+
return current_value, False
|
|
571
|
+
|
|
572
|
+
if current_value < parsed_values.min or current_value > parsed_values.max:
|
|
573
|
+
return parsed_values.value, True
|
|
574
|
+
|
|
575
|
+
return current_value, False
|
|
576
|
+
|
|
577
|
+
|
|
542
578
|
@dataclass
|
|
543
579
|
class DateInputSerde:
|
|
544
580
|
value: _DateInputValues
|
|
@@ -1132,9 +1168,10 @@ class TimeWidgetsMixin:
|
|
|
1132
1168
|
element_id = compute_and_register_element_id(
|
|
1133
1169
|
"date_time_input",
|
|
1134
1170
|
user_key=key,
|
|
1135
|
-
#
|
|
1136
|
-
#
|
|
1137
|
-
|
|
1171
|
+
# Format is whitelisted because of a bug in the BaseWeb date input component.
|
|
1172
|
+
# Step is whitelisted because it invalidates the current selection.
|
|
1173
|
+
# We might be able to unlock this as a follow-up.
|
|
1174
|
+
key_as_main_identity={"format", "step"},
|
|
1138
1175
|
dg=self.dg,
|
|
1139
1176
|
label=label,
|
|
1140
1177
|
value=value_for_id,
|
|
@@ -1145,7 +1182,9 @@ class TimeWidgetsMixin:
|
|
|
1145
1182
|
step=step,
|
|
1146
1183
|
width=width,
|
|
1147
1184
|
)
|
|
1148
|
-
del
|
|
1185
|
+
# Track if user explicitly set bounds (before del)
|
|
1186
|
+
has_explicit_bounds = min_value is not None or max_value is not None
|
|
1187
|
+
del value, min_value, max_value
|
|
1149
1188
|
|
|
1150
1189
|
if not bool(ALLOWED_DATE_FORMATS.match(format)):
|
|
1151
1190
|
raise StreamlitAPIException(
|
|
@@ -1208,8 +1247,20 @@ class TimeWidgetsMixin:
|
|
|
1208
1247
|
value_type="string_array_value",
|
|
1209
1248
|
)
|
|
1210
1249
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1250
|
+
# Validate the current value against the new min/max bounds.
|
|
1251
|
+
# Only validate when user explicitly provided min_value or max_value.
|
|
1252
|
+
current_value, value_needs_reset = _validate_datetime_value(
|
|
1253
|
+
widget_state.value, datetime_values, has_explicit_bounds
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
if value_needs_reset and key is not None:
|
|
1257
|
+
# Update session_state so subsequent accesses in this run
|
|
1258
|
+
# return the corrected value. Use reset_state_value to avoid
|
|
1259
|
+
# the "cannot be modified after widget instantiated" error.
|
|
1260
|
+
get_session_state().reset_state_value(key, current_value)
|
|
1261
|
+
|
|
1262
|
+
if value_needs_reset or widget_state.value_changed:
|
|
1263
|
+
date_time_input_proto.value[:] = serde.serialize(current_value)
|
|
1213
1264
|
date_time_input_proto.set_value = True
|
|
1214
1265
|
|
|
1215
1266
|
validate_width(width)
|
|
@@ -1218,7 +1269,7 @@ class TimeWidgetsMixin:
|
|
|
1218
1269
|
self.dg._enqueue(
|
|
1219
1270
|
"date_time_input", date_time_input_proto, layout_config=layout_config
|
|
1220
1271
|
)
|
|
1221
|
-
return
|
|
1272
|
+
return current_value
|
|
1222
1273
|
|
|
1223
1274
|
@overload
|
|
1224
1275
|
def date_input(
|
|
@@ -54,7 +54,7 @@ def data_frame_demo() -> None:
|
|
|
54
54
|
color="Region:N",
|
|
55
55
|
)
|
|
56
56
|
)
|
|
57
|
-
st.altair_chart(chart,
|
|
57
|
+
st.altair_chart(chart, width="stretch")
|
|
58
58
|
except URLError as e:
|
|
59
59
|
st.error(f"This demo requires internet access. Connection error: {e.reason}")
|
|
60
60
|
|
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.Cpn4aUEp.js"></script>
|
|
41
41
|
<link rel="stylesheet" crossorigin href="./static/css/index.BUP6fTcR.css">
|
|
42
42
|
</head>
|
|
43
43
|
<body>
|