streamlit 1.53.1__py3-none-any.whl → 1.54.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit/__init__.py +1 -31
- streamlit/auth_util.py +91 -2
- streamlit/cli_util.py +3 -2
- streamlit/commands/echo.py +2 -2
- streamlit/commands/execution_control.py +1 -1
- streamlit/commands/logo.py +76 -24
- streamlit/commands/navigation.py +1 -1
- streamlit/components/types/base_custom_component.py +0 -2
- streamlit/components/v1/custom_component.py +0 -2
- streamlit/components/v2/bidi_component/main.py +2 -2
- streamlit/components/v2/component_path_utils.py +17 -29
- streamlit/components/v2/manifest_scanner.py +8 -3
- streamlit/components/v2/presentation.py +1 -1
- streamlit/config.py +57 -13
- streamlit/config_util.py +5 -5
- streamlit/connections/snowflake_connection.py +5 -3
- streamlit/dataframe_util.py +10 -10
- streamlit/deprecation_util.py +19 -1
- streamlit/elements/arrow.py +18 -8
- streamlit/elements/deck_gl_json_chart.py +6 -2
- streamlit/elements/exception.py +4 -2
- streamlit/elements/form.py +1 -1
- streamlit/elements/layouts.py +1 -1
- streamlit/elements/lib/built_in_chart_utils.py +36 -13
- streamlit/elements/lib/color_util.py +21 -2
- streamlit/elements/lib/column_config_utils.py +9 -7
- streamlit/elements/lib/dialog.py +1 -1
- streamlit/elements/lib/image_utils.py +5 -5
- streamlit/elements/lib/layout_utils.py +1 -1
- streamlit/elements/lib/options_selector_utils.py +72 -22
- streamlit/elements/lib/policies.py +1 -1
- streamlit/elements/lib/streamlit_plotly_theme.py +9 -11
- streamlit/elements/lib/utils.py +1 -1
- streamlit/elements/map.py +6 -6
- streamlit/elements/plotly_chart.py +2 -2
- streamlit/elements/toast.py +1 -1
- streamlit/elements/vega_charts.py +30 -7
- streamlit/elements/widgets/button.py +3 -3
- streamlit/elements/widgets/button_group.py +3 -3
- streamlit/elements/widgets/chat.py +1 -1
- streamlit/elements/widgets/data_editor.py +6 -6
- streamlit/elements/widgets/multiselect.py +1 -1
- streamlit/elements/widgets/number_input.py +1 -1
- streamlit/elements/widgets/radio.py +91 -31
- streamlit/elements/widgets/select_slider.py +123 -37
- streamlit/elements/widgets/slider.py +5 -5
- streamlit/elements/widgets/time_widgets.py +150 -18
- streamlit/elements/write.py +2 -3
- streamlit/env_util.py +1 -1
- streamlit/errors.py +2 -14
- streamlit/external/langchain/streamlit_callback_handler.py +1 -1
- streamlit/hello/dataframe_demo.py +1 -1
- streamlit/hello/plotting_demo.py +19 -12
- streamlit/path_security.py +98 -0
- streamlit/proto/Alert_pb2.py +2 -3
- streamlit/proto/AppPage_pb2.py +2 -3
- streamlit/proto/ArrowData_pb2.py +2 -3
- streamlit/proto/ArrowNamedDataSet_pb2.py +2 -3
- streamlit/proto/ArrowVegaLiteChart_pb2.py +2 -3
- streamlit/proto/Arrow_pb2.py +2 -3
- streamlit/proto/AudioInput_pb2.py +2 -3
- streamlit/proto/Audio_pb2.py +2 -3
- streamlit/proto/AuthRedirect_pb2.py +2 -3
- streamlit/proto/AutoRerun_pb2.py +2 -3
- streamlit/proto/BackMsg_pb2.py +2 -3
- streamlit/proto/Balloons_pb2.py +2 -3
- streamlit/proto/BidiComponent_pb2.py +2 -3
- streamlit/proto/Block_pb2.py +2 -3
- streamlit/proto/BokehChart_pb2.py +2 -3
- streamlit/proto/ButtonGroup_pb2.py +2 -3
- streamlit/proto/ButtonLikeIconPosition_pb2.py +2 -3
- streamlit/proto/Button_pb2.py +2 -3
- streamlit/proto/CameraInput_pb2.py +2 -3
- streamlit/proto/ChatInput_pb2.py +2 -3
- streamlit/proto/Checkbox_pb2.py +2 -3
- streamlit/proto/ClientState_pb2.py +2 -3
- streamlit/proto/Code_pb2.py +2 -3
- streamlit/proto/ColorPicker_pb2.py +2 -3
- streamlit/proto/Common_pb2.py +2 -3
- streamlit/proto/Components_pb2.py +2 -3
- streamlit/proto/DataFrame_pb2.py +2 -3
- streamlit/proto/DateInput_pb2.py +2 -3
- streamlit/proto/DateTimeInput_pb2.py +2 -3
- streamlit/proto/DeckGlJsonChart_pb2.py +2 -3
- streamlit/proto/Delta_pb2.py +2 -3
- streamlit/proto/DocString_pb2.py +2 -3
- streamlit/proto/DownloadButton_pb2.py +2 -3
- streamlit/proto/Element_pb2.py +2 -3
- streamlit/proto/Empty_pb2.py +2 -3
- streamlit/proto/Exception_pb2.py +2 -3
- streamlit/proto/Favicon_pb2.py +2 -3
- streamlit/proto/FileUploader_pb2.py +2 -3
- streamlit/proto/ForwardMsg_pb2.py +2 -3
- streamlit/proto/GapSize_pb2.py +2 -3
- streamlit/proto/GitInfo_pb2.py +2 -3
- streamlit/proto/GraphVizChart_pb2.py +2 -3
- streamlit/proto/Heading_pb2.py +2 -3
- streamlit/proto/HeightConfig_pb2.py +2 -3
- streamlit/proto/Html_pb2.py +2 -3
- streamlit/proto/IFrame_pb2.py +2 -3
- streamlit/proto/Image_pb2.py +2 -3
- streamlit/proto/Json_pb2.py +2 -3
- streamlit/proto/LabelVisibilityMessage_pb2.py +2 -3
- streamlit/proto/LinkButton_pb2.py +2 -3
- streamlit/proto/Logo_pb2.py +6 -5
- streamlit/proto/Logo_pb2.pyi +25 -1
- streamlit/proto/Markdown_pb2.py +2 -3
- streamlit/proto/Metric_pb2.py +2 -3
- streamlit/proto/MetricsEvent_pb2.py +2 -3
- streamlit/proto/MultiSelect_pb2.py +2 -3
- streamlit/proto/NamedDataSet_pb2.py +2 -3
- streamlit/proto/Navigation_pb2.py +2 -3
- streamlit/proto/NewSession_pb2.py +25 -24
- streamlit/proto/NewSession_pb2.pyi +28 -2
- streamlit/proto/NumberInput_pb2.py +2 -3
- streamlit/proto/PageConfig_pb2.py +2 -3
- streamlit/proto/PageInfo_pb2.py +2 -3
- streamlit/proto/PageLink_pb2.py +2 -3
- streamlit/proto/PageNotFound_pb2.py +2 -3
- streamlit/proto/PageProfile_pb2.py +2 -3
- streamlit/proto/PagesChanged_pb2.py +2 -3
- streamlit/proto/ParentMessage_pb2.py +2 -3
- streamlit/proto/PlotlyChart_pb2.py +2 -3
- streamlit/proto/Progress_pb2.py +2 -3
- streamlit/proto/Radio_pb2.py +5 -4
- streamlit/proto/Radio_pb2.pyi +20 -3
- streamlit/proto/RootContainer_pb2.py +2 -3
- streamlit/proto/Selectbox_pb2.py +2 -3
- streamlit/proto/SessionEvent_pb2.py +2 -3
- streamlit/proto/SessionStatus_pb2.py +2 -3
- streamlit/proto/Skeleton_pb2.py +2 -3
- streamlit/proto/Slider_pb2.py +7 -8
- streamlit/proto/Slider_pb2.pyi +9 -1
- streamlit/proto/Snow_pb2.py +2 -3
- streamlit/proto/Space_pb2.py +2 -3
- streamlit/proto/Spinner_pb2.py +2 -3
- streamlit/proto/TextAlignmentConfig_pb2.py +2 -3
- streamlit/proto/TextArea_pb2.py +2 -3
- streamlit/proto/TextInput_pb2.py +2 -3
- streamlit/proto/Text_pb2.py +2 -3
- streamlit/proto/TimeInput_pb2.py +2 -3
- streamlit/proto/Toast_pb2.py +2 -3
- streamlit/proto/Transient_pb2.py +2 -3
- streamlit/proto/VegaLiteChart_pb2.py +2 -3
- streamlit/proto/Video_pb2.py +2 -3
- streamlit/proto/WidgetStates_pb2.py +2 -3
- streamlit/proto/WidthConfig_pb2.py +2 -3
- streamlit/proto/openmetrics_data_model_pb2.py +2 -3
- streamlit/runtime/app_session.py +106 -60
- streamlit/runtime/caching/cache_data_api.py +3 -3
- streamlit/runtime/caching/cache_errors.py +0 -2
- streamlit/runtime/caching/cache_resource_api.py +1 -1
- streamlit/runtime/caching/cache_utils.py +2 -2
- streamlit/runtime/caching/hashing.py +1 -3
- streamlit/runtime/caching/storage/cache_storage_protocol.py +0 -3
- streamlit/runtime/connection_factory.py +1 -1
- streamlit/runtime/credentials.py +2 -2
- streamlit/runtime/metrics_util.py +3 -3
- streamlit/runtime/runtime.py +6 -6
- streamlit/runtime/scriptrunner/script_runner.py +17 -0
- streamlit/runtime/scriptrunner_utils/exceptions.py +0 -4
- streamlit/runtime/scriptrunner_utils/script_run_context.py +13 -31
- streamlit/runtime/secrets.py +3 -4
- streamlit/runtime/state/__init__.py +7 -1
- streamlit/runtime/state/common.py +13 -0
- streamlit/runtime/state/query_params.py +493 -24
- streamlit/runtime/state/session_state.py +179 -4
- streamlit/runtime/state/widgets.py +26 -1
- streamlit/runtime/stats.py +1 -10
- streamlit/static/index.html +1 -1
- streamlit/static/manifest.json +304 -304
- streamlit/static/static/js/{ErrorOutline.esm.CScZvf44.js → ErrorOutline.esm.BWk6F-Tz.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.COCxTZxP.js → FileDownload.esm.AllYUuOW.js} +1 -1
- streamlit/static/static/js/{FileHelper.Bhs-iVRI.js → FileHelper.BvVTNdmy.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.CA_5b-Ut.js → FormClearHelper.C__r5Llk.js} +1 -1
- streamlit/static/static/js/{InputInstructions.Bzb0MCfv.js → InputInstructions.DOtkdOMV.js} +1 -1
- streamlit/static/static/js/Particles.DCsqQZlE.js +1 -0
- streamlit/static/static/js/{ProgressBar.DyQNhVsJ.js → ProgressBar.DLCRvt4m.js} +2 -2
- streamlit/static/static/js/{StreamlitSyntaxHighlighter.BOkJThtV.js → StreamlitSyntaxHighlighter.CYFWoZHb.js} +1 -1
- streamlit/static/static/js/{TableChart.esm.a60nntBC.js → TableChart.esm.D6ydHcIm.js} +1 -1
- streamlit/static/static/js/Toolbar.BHDNzWBx.js +1 -0
- streamlit/static/static/js/{WidgetLabelHelpIconInline.BjIku2ic.js → WidgetLabelHelpIconInline.DEXBrVlc.js} +1 -1
- streamlit/static/static/js/{base-input.avGkArOc.js → base-input.TSQjctlq.js} +4 -4
- streamlit/static/static/js/{checkbox.Q8mCuqps.js → checkbox.BKgfzJZV.js} +1 -1
- streamlit/static/static/js/{createDownloadLinkElement.CfqHRpxo.js → createDownloadLinkElement.CG7nr2a4.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.PuoMl3yV.js → data-grid-overlay-editor.ChXO__lP.js} +1 -1
- streamlit/static/static/js/{downloader.CjG2csSm.js → downloader.DJ3R_zWA.js} +1 -1
- streamlit/static/static/js/embed.u3PPfLkw.js +193 -0
- streamlit/static/static/js/{es6.CQD6uUK7.js → es6.C5Mfy8nd.js} +2 -2
- streamlit/static/static/js/{formatNumber.CtjUO-if.js → formatNumber.CMRgW9EJ.js} +1 -1
- streamlit/static/static/js/{iconPosition.7Qt6oUiI.js → iconPosition.B4EEXI3E.js} +1 -1
- streamlit/static/static/js/{iframeResizer.contentWindow._oj2Xh0v.js → iframeResizer.contentWindow.WSvOiTW0.js} +1 -1
- streamlit/static/static/js/index.-FOBV3nz.js +1 -0
- streamlit/static/static/js/{index.BuBkymZd.js → index.-NF8OSF5.js} +1 -1
- streamlit/static/static/js/{index.B-XrnnK6.js → index.4cBg8kn5.js} +1 -1
- streamlit/static/static/js/{index.B_ylV_tl.js → index.B0pzzCsH.js} +1 -1
- streamlit/static/static/js/{index.BhJwyXH6.js → index.BID6ND5j.js} +2 -2
- streamlit/static/static/js/index.BMp5bGjh.js +1 -0
- streamlit/static/static/js/{index.Cptu1tS-.js → index.BQcmlvas.js} +1 -1
- streamlit/static/static/js/{index.DXQ_Fvpt.js → index.BRcmclgI.js} +1 -1
- streamlit/static/static/js/index.BaUZR4IG.js +1 -0
- streamlit/static/static/js/{index.CMBgAPh6.js → index.BbMJj4PN.js} +1 -1
- streamlit/static/static/js/{index.CVRgrLT-.js → index.BdCTJtq3.js} +2 -2
- streamlit/static/static/js/index.BdETLMuI.js +1 -0
- streamlit/static/static/js/index.BnKMWhs1.js +1 -0
- streamlit/static/static/js/index.Br1kXwQW.js +2 -0
- streamlit/static/static/js/{index.XGft6-dq.js → index.Bt2olRE4.js} +1 -1
- streamlit/static/static/js/{index.B2fAYU1N.js → index.Bxwsv5T8.js} +1 -1
- streamlit/static/static/js/index.C4KskYz6.js +1 -0
- streamlit/static/static/js/{index.DZE_91Ym.js → index.C6bmbXk0.js} +1 -1
- streamlit/static/static/js/{index.Egabyb7u.js → index.CEfKfbta.js} +1 -1
- streamlit/static/static/js/index.CIuaA8q0.js +2 -0
- streamlit/static/static/js/{index.DVtfSohT.js → index.CV1sObFX.js} +1 -1
- streamlit/static/static/js/{index.BlJhnb4M.js → index.CbR6dgaV.js} +1 -1
- streamlit/static/static/js/index.Cq6szKqJ.js +1 -0
- streamlit/static/static/js/index.CyouXqCz.js +1 -0
- streamlit/static/static/js/{index.B5wmZkRW.js → index.D1NUgMFI.js} +1 -1
- streamlit/static/static/js/{index.euRMkmNi.js → index.D7SWG4Om.js} +1 -1
- streamlit/static/static/js/{index.Bg-9YNUa.js → index.DAYPEwLI.js} +1 -1
- streamlit/static/static/js/index.DKS75Vfg.js +11 -0
- streamlit/static/static/js/{index.CIizdLeb.js → index.DOXrMIxB.js} +1 -1
- streamlit/static/static/js/{index.BRegnbUa.js → index.DOzYX8yS.js} +3 -3
- streamlit/static/static/js/{index.BksGMsW0.js → index.DRFMYcC4.js} +4 -4
- streamlit/static/static/js/{index.B8PovXCX.js → index.Divl5FCY.js} +1 -1
- streamlit/static/static/js/{index.DxQuXlXH.js → index.DjAJ_CUa.js} +1 -1
- streamlit/static/static/js/{index.BrRuSP42.js → index.Dncue2pm.js} +33 -33
- streamlit/static/static/js/{index.DSTThs-t.js → index.Drusyo5m.js} +47 -47
- streamlit/static/static/js/{index.BOafPwIE.js → index.DuUyDGnP.js} +1 -1
- streamlit/static/static/js/{index.D1bkwsLT.js → index.DvgT2rB2.js} +223 -223
- streamlit/static/static/js/{index.BmDXWfgx.js → index.DzutABu5.js} +2 -2
- streamlit/static/static/js/index.Dzw2iPzi.js +3 -0
- streamlit/static/static/js/{index.DJsqD2Sc.js → index.FsTmxLbT.js} +1 -1
- streamlit/static/static/js/{index.BOTEMJfV.js → index.OIwPqGYN.js} +1 -1
- streamlit/static/static/js/{index.CBqST2Yj.js → index.RXLN7YFT.js} +2 -2
- streamlit/static/static/js/{index.Ft2Zxbhr.js → index.YYb2u0jk.js} +2 -2
- streamlit/static/static/js/{index.BWCFtBS4.js → index.h8ejt-W3.js} +1 -1
- streamlit/static/static/js/{index.KuLql7H0.js → index.lFMCi9am.js} +1 -1
- streamlit/static/static/js/{index.D8t7R4QQ.js → index.pOgf4cEj.js} +1 -1
- streamlit/static/static/js/{index.CsoN0h7K.js → index.s_E0s7LB.js} +51 -51
- streamlit/static/static/js/{index.BVX_bqnf.js → index.xLCbzoqj.js} +1 -1
- streamlit/static/static/js/{input.Cf97CQME.js → input.BLG7kWaj.js} +2 -2
- streamlit/static/static/js/{main.Ccuk53yQ.js → main.D_CmqChN.js} +1 -1
- streamlit/static/static/js/{memory.Bng6Ij0g.js → memory.T8u9KqIQ.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.CFLv-CWC.js → number-overlay-editor.BKBSXkAM.js} +2 -2
- streamlit/static/static/js/{pandasStylerUtils.C2hcAKiv.js → pandasStylerUtils.B4tLYMwS.js} +1 -1
- streamlit/static/static/js/{sandbox.BXdeD-wA.js → sandbox.jRlkcPem.js} +1 -1
- streamlit/static/static/js/{styled-components.Br04Ogac.js → styled-components.D2QhNwzd.js} +1 -1
- streamlit/static/static/js/{throttle.mI9ItGre.js → throttle.Cyw_V0Dq.js} +1 -1
- streamlit/static/static/js/{timepicker.poFdB0sd.js → timepicker.PzyuDDWl.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.92-fANS-.js → toConsumableArray.gE9fMkLj.js} +1 -1
- streamlit/static/static/js/uniqueId.B1GeHnT1.js +1 -0
- streamlit/static/static/js/{useBasicWidgetState.DzKGLAv_.js → useBasicWidgetState.DFklfao0.js} +1 -1
- streamlit/static/static/js/{useIntlLocale.BMma2iiY.js → useIntlLocale.C3tUGWTU.js} +8 -8
- streamlit/static/static/js/{useTextInputAutoExpand.DQbIhdma.js → useTextInputAutoExpand.D9nU_y-e.js} +1 -1
- streamlit/static/static/js/useUpdateUiValue.ClTdrkJN.js +1 -0
- streamlit/static/static/js/{useWaveformController.AH0ggRyc.js → useWaveformController.lzTbjMW2.js} +1 -1
- streamlit/static/static/js/{withCalculatedWidth.G5xJ-MbS.js → withCalculatedWidth.Dxs9I5Oe.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.rdRu6zZ4.js → withFullScreenWrapper.DfpAcJxf.js} +1 -1
- streamlit/string_util.py +2 -2
- streamlit/testing/v1/app_test.py +1 -1
- streamlit/testing/v1/element_tree.py +33 -20
- streamlit/type_util.py +2 -2
- streamlit/url_util.py +2 -2
- streamlit/user_info.py +2 -41
- streamlit/util.py +1 -1
- streamlit/watcher/event_based_path_watcher.py +37 -7
- streamlit/watcher/path_watcher.py +61 -2
- streamlit/watcher/util.py +26 -10
- streamlit/web/bootstrap.py +16 -4
- streamlit/web/cli.py +1 -4
- streamlit/web/server/app_discovery.py +2 -1
- streamlit/web/server/app_static_file_handler.py +9 -0
- streamlit/web/server/bidi_component_request_handler.py +4 -4
- streamlit/web/server/component_file_utils.py +14 -6
- streamlit/web/server/component_request_handler.py +2 -2
- streamlit/web/server/oauth_authlib_routes.py +14 -42
- streamlit/web/server/server.py +1 -1
- streamlit/web/server/server_util.py +23 -1
- streamlit/web/server/starlette/starlette_app.py +7 -1
- streamlit/web/server/starlette/starlette_auth_routes.py +94 -16
- streamlit/web/server/starlette/starlette_path_security_middleware.py +97 -0
- streamlit/web/server/starlette/starlette_routes.py +16 -9
- streamlit/web/server/starlette/starlette_server.py +2 -2
- streamlit/web/server/starlette/starlette_static_routes.py +14 -4
- streamlit/web/server/stats_request_handler.py +1 -3
- {streamlit-1.53.1.dist-info → streamlit-1.54.0.dist-info}/METADATA +10 -25
- {streamlit-1.53.1.dist-info → streamlit-1.54.0.dist-info}/RECORD +290 -290
- {streamlit-1.53.1.dist-info → streamlit-1.54.0.dist-info}/WHEEL +1 -1
- streamlit/commands/experimental_query_params.py +0 -169
- streamlit/static/static/js/Particles.ix5_l22I.js +0 -1
- streamlit/static/static/js/Toolbar.CxkcuBQ8.js +0 -1
- streamlit/static/static/js/embed.DZ-CLCPz.js +0 -195
- streamlit/static/static/js/index.B6ZAXv47.js +0 -1
- streamlit/static/static/js/index.BDm-Ia27.js +0 -1
- streamlit/static/static/js/index.BeCZLkzg.js +0 -1
- streamlit/static/static/js/index.BuEBeckn.js +0 -11
- streamlit/static/static/js/index.CL2eCR01.js +0 -1
- streamlit/static/static/js/index.CdLlbsiN.js +0 -1
- streamlit/static/static/js/index.CwIIk90V.js +0 -1
- streamlit/static/static/js/index.DDk0U8rh.js +0 -2
- streamlit/static/static/js/index.DNB79dOd.js +0 -3
- streamlit/static/static/js/index.DNj5S4tY.js +0 -1
- streamlit/static/static/js/index.DOY0ZriT.js +0 -2
- streamlit/static/static/js/index.r0gCrMFP.js +0 -1
- streamlit/static/static/js/uniqueId.BUj-C6GA.js +0 -1
- streamlit/static/static/js/useUpdateUiValue.Bk5OIXup.js +0 -1
- streamlit-1.53.1.data/scripts/streamlit.cmd +0 -16
- {streamlit-1.53.1.dist-info → streamlit-1.54.0.dist-info}/entry_points.txt +0 -0
- {streamlit-1.53.1.dist-info → streamlit-1.54.0.dist-info}/top_level.txt +0 -0
|
@@ -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:
|
|
@@ -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 (
|
|
20
19
|
TYPE_CHECKING,
|
|
@@ -30,9 +29,12 @@ from streamlit.dataframe_util import OptionSequence, convert_anything_to_list
|
|
|
30
29
|
from streamlit.elements.lib.form_utils import current_form_id
|
|
31
30
|
from streamlit.elements.lib.layout_utils import LayoutConfig, validate_width
|
|
32
31
|
from streamlit.elements.lib.options_selector_utils import (
|
|
32
|
+
create_mappings,
|
|
33
33
|
index_,
|
|
34
34
|
maybe_coerce_enum,
|
|
35
35
|
maybe_coerce_enum_sequence,
|
|
36
|
+
validate_and_sync_range_value_with_options,
|
|
37
|
+
validate_and_sync_value_with_options,
|
|
36
38
|
)
|
|
37
39
|
from streamlit.elements.lib.policies import (
|
|
38
40
|
check_widget_policies,
|
|
@@ -72,37 +74,79 @@ def _is_range_value(value: T | Sequence[T]) -> TypeGuard[Sequence[T]]:
|
|
|
72
74
|
return isinstance(value, (list, tuple))
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
@dataclass
|
|
76
77
|
class SelectSliderSerde(Generic[T]):
|
|
77
|
-
|
|
78
|
-
value: list[int]
|
|
79
|
-
is_range_value: bool
|
|
78
|
+
"""Serializer/deserializer for select_slider widget values.
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
Uses formatted option strings for robust handling of dynamic option changes.
|
|
81
|
+
"""
|
|
83
82
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
options: Sequence[T],
|
|
86
|
+
*,
|
|
87
|
+
formatted_option_to_index: dict[str, int],
|
|
88
|
+
default_indices: list[int],
|
|
89
|
+
format_func: Callable[[Any], str] = str,
|
|
90
|
+
) -> None:
|
|
91
|
+
self.options = options
|
|
92
|
+
self.formatted_option_to_index = formatted_option_to_index
|
|
93
|
+
self.default_indices = default_indices
|
|
94
|
+
self.format_func = format_func
|
|
95
|
+
|
|
96
|
+
def _get_default(self, is_range: bool) -> T | tuple[T, T]:
|
|
97
|
+
"""Return the default value based on default_indices."""
|
|
98
|
+
if is_range or len(self.default_indices) >= 2:
|
|
99
|
+
end_idx = (
|
|
100
|
+
self.default_indices[1]
|
|
101
|
+
if len(self.default_indices) > 1
|
|
102
|
+
else len(self.options) - 1
|
|
103
|
+
)
|
|
104
|
+
return (self.options[self.default_indices[0]], self.options[end_idx])
|
|
105
|
+
return self.options[self.default_indices[0]]
|
|
88
106
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
def serialize(self, v: T | tuple[T, T] | list[T]) -> list[str]:
|
|
108
|
+
"""Convert option value(s) to formatted string list."""
|
|
109
|
+
# Check if v is a single option (handles options that are tuples/lists)
|
|
110
|
+
try:
|
|
111
|
+
formatted = self.format_func(v)
|
|
112
|
+
if formatted in self.formatted_option_to_index:
|
|
113
|
+
return [formatted]
|
|
114
|
+
except Exception: # noqa: S110
|
|
115
|
+
pass
|
|
94
116
|
|
|
95
|
-
#
|
|
96
|
-
|
|
117
|
+
# Handle as range/sequence
|
|
118
|
+
if isinstance(v, (tuple, list)):
|
|
119
|
+
return [self.format_func(x) for x in v]
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
121
|
+
return [self.format_func(v)]
|
|
122
|
+
|
|
123
|
+
def deserialize(self, ui_value: list[str] | None) -> T | tuple[T, T]:
|
|
124
|
+
"""Convert formatted string list back to option value(s)."""
|
|
125
|
+
is_range = ui_value is not None and len(ui_value) >= 2
|
|
126
|
+
|
|
127
|
+
if not ui_value:
|
|
128
|
+
return self._get_default(is_range=len(self.default_indices) >= 2)
|
|
129
|
+
|
|
130
|
+
# Look up each string value
|
|
131
|
+
results: list[tuple[int, T]] = []
|
|
132
|
+
for i, s in enumerate(ui_value):
|
|
133
|
+
idx = self.formatted_option_to_index.get(s)
|
|
134
|
+
if idx is not None and idx < len(self.options):
|
|
135
|
+
results.append((idx, self.options[idx]))
|
|
136
|
+
else:
|
|
137
|
+
# Fallback to default for this position
|
|
138
|
+
default_idx = self.default_indices[
|
|
139
|
+
min(i, len(self.default_indices) - 1)
|
|
140
|
+
]
|
|
141
|
+
results.append((default_idx, self.options[default_idx]))
|
|
142
|
+
|
|
143
|
+
if is_range and len(results) >= 2:
|
|
144
|
+
# Ensure start <= end by returning deserialized range value in ascending order
|
|
145
|
+
if results[0][0] > results[1][0]:
|
|
146
|
+
return (results[1][1], results[0][1])
|
|
147
|
+
return (results[0][1], results[1][1])
|
|
148
|
+
|
|
149
|
+
return results[0][1]
|
|
106
150
|
|
|
107
151
|
|
|
108
152
|
class SelectSliderMixin:
|
|
@@ -378,16 +422,18 @@ class SelectSliderMixin:
|
|
|
378
422
|
# Convert element to index of the elements
|
|
379
423
|
slider_value = as_index_list(value)
|
|
380
424
|
|
|
425
|
+
# Create formatted options and mapping for string-based storage
|
|
426
|
+
formatted_options, formatted_option_to_option_index = create_mappings(
|
|
427
|
+
opt, format_func
|
|
428
|
+
)
|
|
429
|
+
|
|
381
430
|
element_id = compute_and_register_element_id(
|
|
382
431
|
"select_slider",
|
|
383
432
|
user_key=key,
|
|
384
|
-
|
|
385
|
-
# changes to the options (and implicitly their formatting) in the
|
|
386
|
-
# identity computation as those can invalidate the current value.
|
|
387
|
-
key_as_main_identity={"options", "format_func"},
|
|
433
|
+
key_as_main_identity=True,
|
|
388
434
|
dg=self.dg,
|
|
389
435
|
label=label,
|
|
390
|
-
options=
|
|
436
|
+
options=formatted_options,
|
|
391
437
|
value=slider_value,
|
|
392
438
|
help=help,
|
|
393
439
|
width=width,
|
|
@@ -403,7 +449,7 @@ class SelectSliderMixin:
|
|
|
403
449
|
slider_proto.max = len(opt) - 1
|
|
404
450
|
slider_proto.step = 1 # default for index changes
|
|
405
451
|
slider_proto.data_type = SliderProto.INT
|
|
406
|
-
slider_proto.options[:] =
|
|
452
|
+
slider_proto.options[:] = formatted_options
|
|
407
453
|
slider_proto.form_id = current_form_id(self.dg)
|
|
408
454
|
slider_proto.disabled = disabled
|
|
409
455
|
slider_proto.label_visibility.value = get_label_visibility_proto_value(
|
|
@@ -415,7 +461,12 @@ class SelectSliderMixin:
|
|
|
415
461
|
validate_width(width)
|
|
416
462
|
layout_config = LayoutConfig(width=width)
|
|
417
463
|
|
|
418
|
-
serde = SelectSliderSerde(
|
|
464
|
+
serde = SelectSliderSerde(
|
|
465
|
+
opt,
|
|
466
|
+
formatted_option_to_index=formatted_option_to_option_index,
|
|
467
|
+
default_indices=slider_value,
|
|
468
|
+
format_func=format_func,
|
|
469
|
+
)
|
|
419
470
|
|
|
420
471
|
widget_state = register_widget(
|
|
421
472
|
slider_proto.id,
|
|
@@ -425,7 +476,7 @@ class SelectSliderMixin:
|
|
|
425
476
|
deserializer=serde.deserialize,
|
|
426
477
|
serializer=serde.serialize,
|
|
427
478
|
ctx=ctx,
|
|
428
|
-
value_type="
|
|
479
|
+
value_type="string_array_value",
|
|
429
480
|
)
|
|
430
481
|
if isinstance(widget_state.value, tuple):
|
|
431
482
|
widget_state = maybe_coerce_enum_sequence(
|
|
@@ -434,15 +485,50 @@ class SelectSliderMixin:
|
|
|
434
485
|
else:
|
|
435
486
|
widget_state = maybe_coerce_enum(widget_state, options, opt)
|
|
436
487
|
|
|
437
|
-
|
|
438
|
-
|
|
488
|
+
# Validate the current value against the new options.
|
|
489
|
+
# If the value is no longer valid (not in options), reset to default.
|
|
490
|
+
# This handles the case where options change dynamically and the
|
|
491
|
+
# previously selected value is no longer available.
|
|
492
|
+
# Determine if we're dealing with a range value based on the actual
|
|
493
|
+
# widget state value, not just the value parameter (range can come from
|
|
494
|
+
# session state even when value param is None).
|
|
495
|
+
actual_is_range = isinstance(widget_state.value, tuple)
|
|
496
|
+
if actual_is_range:
|
|
497
|
+
# Range value: validate using range-specific function.
|
|
498
|
+
range_value = cast("tuple[T, T]", widget_state.value)
|
|
499
|
+
validated_range, value_needs_reset = (
|
|
500
|
+
validate_and_sync_range_value_with_options(
|
|
501
|
+
range_value,
|
|
502
|
+
opt,
|
|
503
|
+
slider_value,
|
|
504
|
+
key,
|
|
505
|
+
format_func,
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
current_value: T | tuple[T, T] = validated_range
|
|
509
|
+
else:
|
|
510
|
+
# Single value: use the standard validation function.
|
|
511
|
+
validated_single, value_needs_reset = validate_and_sync_value_with_options(
|
|
512
|
+
widget_state.value,
|
|
513
|
+
opt,
|
|
514
|
+
slider_value[0],
|
|
515
|
+
key,
|
|
516
|
+
format_func,
|
|
517
|
+
)
|
|
518
|
+
# validated_single is guaranteed to be T (not None) because
|
|
519
|
+
# deserialize() always returns a default value, never None.
|
|
520
|
+
current_value = cast("T", validated_single)
|
|
521
|
+
|
|
522
|
+
if value_needs_reset or widget_state.value_changed:
|
|
523
|
+
serialized_value = serde.serialize(current_value)
|
|
524
|
+
slider_proto.raw_value[:] = serialized_value
|
|
439
525
|
slider_proto.set_value = True
|
|
440
526
|
|
|
441
527
|
if ctx:
|
|
442
528
|
save_for_app_testing(ctx, element_id, format_func)
|
|
443
529
|
|
|
444
530
|
self.dg._enqueue("slider", slider_proto, layout_config=layout_config)
|
|
445
|
-
return
|
|
531
|
+
return current_value
|
|
446
532
|
|
|
447
533
|
@property
|
|
448
534
|
def dg(self) -> DeltaGenerator:
|
|
@@ -739,7 +739,7 @@ class SliderMixin:
|
|
|
739
739
|
|
|
740
740
|
# Ensure that the value is either a single value or a range of values.
|
|
741
741
|
single_value = isinstance(value, tuple(SUPPORTED_TYPES.keys()))
|
|
742
|
-
range_value = isinstance(value, (list, tuple)) and len(value) in
|
|
742
|
+
range_value = isinstance(value, (list, tuple)) and len(value) in {0, 1, 2}
|
|
743
743
|
if not single_value and not range_value:
|
|
744
744
|
raise StreamlitAPIException(
|
|
745
745
|
"Slider value should either be an int/float/datetime or a list/tuple of "
|
|
@@ -778,7 +778,7 @@ class SliderMixin:
|
|
|
778
778
|
|
|
779
779
|
datetime_min = time.min.replace(tzinfo=prepared_value[0].tzinfo)
|
|
780
780
|
datetime_max = time.max.replace(tzinfo=prepared_value[0].tzinfo)
|
|
781
|
-
if data_type in
|
|
781
|
+
if data_type in {SliderProto.DATETIME, SliderProto.DATE}:
|
|
782
782
|
prepared_value = cast("Sequence[datetime]", prepared_value)
|
|
783
783
|
|
|
784
784
|
datetime_min = prepared_value[0] - timedelta(days=14)
|
|
@@ -823,10 +823,10 @@ class SliderMixin:
|
|
|
823
823
|
max_value = defaults[data_type]["max_value"]
|
|
824
824
|
if step is None:
|
|
825
825
|
step = defaults[data_type]["step"]
|
|
826
|
-
if data_type in
|
|
826
|
+
if data_type in {
|
|
827
827
|
SliderProto.DATETIME,
|
|
828
828
|
SliderProto.DATE,
|
|
829
|
-
|
|
829
|
+
} and max_value - min_value < timedelta(days=1):
|
|
830
830
|
step = timedelta(minutes=15)
|
|
831
831
|
if format is None:
|
|
832
832
|
format = cast("str", defaults[data_type]["format"]) # noqa: A001
|
|
@@ -945,7 +945,7 @@ class SliderMixin:
|
|
|
945
945
|
# Restore times/datetimes to original timezone (dates are always naive)
|
|
946
946
|
orig_tz = (
|
|
947
947
|
prepared_value[0].tzinfo
|
|
948
|
-
if data_type in
|
|
948
|
+
if data_type in {SliderProto.TIME, SliderProto.DATETIME}
|
|
949
949
|
else None
|
|
950
950
|
)
|
|
951
951
|
|