streamlit 1.51.0__py3-none-any.whl → 1.52.1__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 -0
- streamlit/commands/execution_control.py +89 -14
- streamlit/components/v1/component_arrow.py +7 -7
- streamlit/components/v2/__init__.py +59 -3
- streamlit/components/v2/bidi_component/main.py +161 -13
- streamlit/components/v2/bidi_component/serialization.py +13 -6
- streamlit/components/v2/component_manager.py +11 -3
- streamlit/components/v2/component_registry.py +18 -1
- streamlit/components/v2/types.py +2 -2
- streamlit/connections/snowflake_connection.py +1 -1
- streamlit/connections/snowpark_connection.py +1 -1
- streamlit/dataframe_util.py +18 -18
- streamlit/delta_generator.py +7 -0
- streamlit/delta_generator_singletons.py +8 -14
- streamlit/elements/alert.py +16 -0
- streamlit/elements/arrow.py +36 -6
- streamlit/elements/bokeh_chart.py +10 -78
- streamlit/elements/code.py +2 -2
- streamlit/elements/deck_gl_json_chart.py +1 -1
- streamlit/elements/exception.py +1 -1
- streamlit/elements/form.py +27 -0
- streamlit/elements/heading.py +60 -5
- streamlit/elements/html.py +13 -2
- streamlit/elements/image.py +1 -1
- streamlit/elements/layouts.py +28 -22
- streamlit/elements/lib/built_in_chart_utils.py +49 -16
- streamlit/elements/lib/color_util.py +1 -1
- streamlit/elements/lib/column_config_utils.py +6 -5
- streamlit/elements/lib/layout_utils.py +50 -0
- streamlit/elements/lib/pandas_styler_utils.py +17 -9
- streamlit/elements/lib/shortcut_utils.py +152 -0
- streamlit/elements/markdown.py +50 -3
- streamlit/elements/metric.py +31 -1
- streamlit/elements/plotly_chart.py +75 -6
- streamlit/elements/spinner.py +1 -1
- streamlit/elements/text.py +20 -3
- streamlit/elements/toast.py +2 -0
- streamlit/elements/vega_charts.py +17 -1
- streamlit/elements/widgets/audio_input.py +8 -7
- streamlit/elements/widgets/button.py +279 -40
- streamlit/elements/widgets/button_group.py +27 -2
- streamlit/elements/widgets/camera_input.py +1 -1
- streamlit/elements/widgets/chat.py +300 -42
- streamlit/elements/widgets/color_picker.py +7 -0
- streamlit/elements/widgets/data_editor.py +68 -28
- streamlit/elements/widgets/file_uploader.py +4 -1
- streamlit/elements/widgets/number_input.py +2 -0
- streamlit/elements/widgets/text_widgets.py +2 -0
- streamlit/elements/widgets/time_widgets.py +581 -9
- streamlit/errors.py +22 -0
- streamlit/git_util.py +1 -1
- streamlit/navigation/page.py +7 -0
- streamlit/net_util.py +2 -2
- streamlit/proto/Alert_pb2.pyi +3 -3
- streamlit/proto/AppPage_pb2.pyi +7 -1
- streamlit/proto/ArrowData_pb2.pyi +7 -1
- streamlit/proto/ArrowNamedDataSet_pb2.pyi +7 -1
- streamlit/proto/ArrowVegaLiteChart_pb2.pyi +7 -1
- streamlit/proto/Arrow_pb2.py +10 -10
- streamlit/proto/Arrow_pb2.pyi +19 -12
- streamlit/proto/AudioInput_pb2.pyi +7 -1
- streamlit/proto/Audio_pb2.pyi +7 -1
- streamlit/proto/AuthRedirect_pb2.pyi +7 -1
- streamlit/proto/AutoRerun_pb2.pyi +7 -1
- streamlit/proto/BackMsg_pb2.py +4 -2
- streamlit/proto/BackMsg_pb2.pyi +34 -4
- streamlit/proto/Balloons_pb2.pyi +7 -1
- streamlit/proto/BidiComponent_pb2.pyi +10 -4
- streamlit/proto/Block_pb2.pyi +35 -35
- streamlit/proto/BokehChart_pb2.pyi +7 -1
- streamlit/proto/ButtonGroup_pb2.pyi +9 -9
- streamlit/proto/Button_pb2.py +2 -2
- streamlit/proto/Button_pb2.pyi +11 -2
- streamlit/proto/CameraInput_pb2.pyi +7 -1
- streamlit/proto/ChatInput_pb2.py +6 -6
- streamlit/proto/ChatInput_pb2.pyi +18 -6
- streamlit/proto/Checkbox_pb2.pyi +3 -3
- streamlit/proto/ClientState_pb2.pyi +10 -4
- streamlit/proto/Code_pb2.pyi +7 -1
- streamlit/proto/ColorPicker_pb2.pyi +7 -1
- streamlit/proto/Common_pb2.py +3 -3
- streamlit/proto/Common_pb2.pyi +35 -23
- streamlit/proto/Components_pb2.pyi +19 -13
- streamlit/proto/DataFrame_pb2.pyi +55 -49
- streamlit/proto/DateInput_pb2.pyi +7 -1
- streamlit/proto/DateTimeInput_pb2.py +28 -0
- streamlit/proto/DateTimeInput_pb2.pyi +92 -0
- streamlit/proto/DeckGlJsonChart_pb2.pyi +3 -3
- streamlit/proto/Delta_pb2.pyi +7 -1
- streamlit/proto/DocString_pb2.pyi +10 -4
- streamlit/proto/DownloadButton_pb2.py +2 -2
- streamlit/proto/DownloadButton_pb2.pyi +16 -2
- streamlit/proto/Element_pb2.py +5 -3
- streamlit/proto/Element_pb2.pyi +23 -5
- streamlit/proto/Empty_pb2.pyi +7 -1
- streamlit/proto/Exception_pb2.pyi +7 -1
- streamlit/proto/Favicon_pb2.pyi +7 -1
- streamlit/proto/FileUploader_pb2.pyi +7 -1
- streamlit/proto/ForwardMsg_pb2.py +12 -10
- streamlit/proto/ForwardMsg_pb2.pyi +42 -15
- streamlit/proto/GapSize_pb2.pyi +4 -4
- streamlit/proto/GitInfo_pb2.pyi +3 -3
- streamlit/proto/GraphVizChart_pb2.pyi +7 -1
- streamlit/proto/Heading_pb2.pyi +7 -1
- streamlit/proto/HeightConfig_pb2.pyi +7 -1
- streamlit/proto/Html_pb2.py +2 -2
- streamlit/proto/Html_pb2.pyi +11 -2
- streamlit/proto/IFrame_pb2.pyi +7 -1
- streamlit/proto/Image_pb2.pyi +10 -4
- streamlit/proto/Json_pb2.pyi +7 -1
- streamlit/proto/LabelVisibilityMessage_pb2.pyi +3 -3
- streamlit/proto/LinkButton_pb2.py +2 -2
- streamlit/proto/LinkButton_pb2.pyi +15 -2
- streamlit/proto/Logo_pb2.pyi +7 -1
- streamlit/proto/Markdown_pb2.pyi +3 -3
- streamlit/proto/Metric_pb2.pyi +7 -7
- streamlit/proto/MetricsEvent_pb2.pyi +10 -4
- streamlit/proto/MultiSelect_pb2.pyi +7 -1
- streamlit/proto/NamedDataSet_pb2.pyi +7 -1
- streamlit/proto/Navigation_pb2.pyi +3 -3
- streamlit/proto/NewSession_pb2.pyi +40 -40
- streamlit/proto/NumberInput_pb2.pyi +3 -3
- streamlit/proto/PageConfig_pb2.pyi +7 -7
- streamlit/proto/PageInfo_pb2.pyi +7 -1
- streamlit/proto/PageLink_pb2.py +2 -2
- streamlit/proto/PageLink_pb2.pyi +11 -2
- streamlit/proto/PageNotFound_pb2.pyi +7 -1
- streamlit/proto/PageProfile_pb2.pyi +13 -7
- streamlit/proto/PagesChanged_pb2.pyi +7 -1
- streamlit/proto/ParentMessage_pb2.pyi +7 -1
- streamlit/proto/PlotlyChart_pb2.pyi +6 -6
- streamlit/proto/Progress_pb2.pyi +7 -1
- streamlit/proto/Radio_pb2.pyi +7 -1
- streamlit/proto/RootContainer_pb2.pyi +1 -1
- streamlit/proto/Selectbox_pb2.pyi +7 -1
- streamlit/proto/SessionEvent_pb2.pyi +7 -1
- streamlit/proto/SessionStatus_pb2.pyi +7 -1
- streamlit/proto/Skeleton_pb2.pyi +3 -3
- streamlit/proto/Slider_pb2.pyi +5 -5
- streamlit/proto/Snow_pb2.pyi +7 -1
- streamlit/proto/Space_pb2.pyi +7 -1
- streamlit/proto/Spinner_pb2.pyi +7 -1
- streamlit/proto/TextAlignmentConfig_pb2.py +29 -0
- streamlit/proto/TextAlignmentConfig_pb2.pyi +68 -0
- streamlit/proto/TextArea_pb2.pyi +7 -1
- streamlit/proto/TextInput_pb2.pyi +3 -3
- streamlit/proto/Text_pb2.pyi +7 -1
- streamlit/proto/TimeInput_pb2.pyi +7 -1
- streamlit/proto/Toast_pb2.pyi +7 -1
- streamlit/proto/VegaLiteChart_pb2.pyi +7 -1
- streamlit/proto/Video_pb2.pyi +6 -6
- streamlit/proto/WidgetStates_pb2.pyi +10 -4
- streamlit/proto/WidthConfig_pb2.pyi +7 -1
- streamlit/proto/openmetrics_data_model_pb2.pyi +52 -52
- streamlit/runtime/app_session.py +38 -1
- streamlit/runtime/caching/cache_data_api.py +1 -1
- streamlit/runtime/caching/cache_resource_api.py +2 -2
- streamlit/runtime/caching/cache_utils.py +1 -1
- streamlit/runtime/caching/hashing.py +1 -1
- streamlit/runtime/download_data_util.py +53 -0
- streamlit/runtime/forward_msg_queue.py +1 -0
- streamlit/runtime/media_file_manager.py +178 -2
- streamlit/runtime/metrics_util.py +87 -3
- streamlit/runtime/scriptrunner/script_runner.py +3 -1
- streamlit/runtime/state/query_params.py +80 -29
- streamlit/runtime/state/session_state.py +2 -2
- streamlit/static/index.html +1 -1
- streamlit/static/manifest.json +530 -229
- streamlit/static/static/js/{ErrorOutline.esm.YoJdlW1p.js → ErrorOutline.esm.sMJdFExW.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.Ddx8VEYy.js → FileDownload.esm.CV-WYqBn.js} +1 -1
- streamlit/static/static/js/{FileHelper.90EtOmj9.js → FileHelper.5nCh9KDY.js} +3 -3
- streamlit/static/static/js/{FormClearHelper.BB1Km6eP.js → FormClearHelper.-9RbsnV0.js} +1 -1
- streamlit/static/static/js/IFrameUtil.DefezniK.js +1 -0
- streamlit/static/static/js/InputInstructions.2R3tBtW9.js +1 -0
- streamlit/static/static/js/Particles.DDHoXFxh.js +1 -0
- streamlit/static/static/js/{ProgressBar.DLY8H6nE.js → ProgressBar.BxmfHxKu.js} +2 -2
- streamlit/static/static/js/StreamlitSyntaxHighlighter.BFWV0oqR.js +20 -0
- streamlit/static/static/js/{Toolbar.D8nHCkuz.js → Toolbar.DMgU0Vgw.js} +1 -1
- streamlit/static/static/js/_arrayIncludes.B19Iyn2B.js +1 -0
- streamlit/static/static/js/_baseIndexOf.BTknn6Gb.js +1 -0
- streamlit/static/static/js/{base-input.CJGiNqed.js → base-input.BXTqYbyG.js} +4 -4
- streamlit/static/static/js/{checkbox.Cpdd482O.js → checkbox.5xWaqPqm.js} +1 -1
- streamlit/static/static/js/{createSuper.CuQIogbW.js → createSuper.OIgV8wc-.js} +1 -1
- streamlit/static/static/js/data-grid-overlay-editor.B4RIu9cw.js +1 -0
- streamlit/static/static/js/{downloader.CN0K7xlu.js → downloader.DwCJck8O.js} +1 -1
- streamlit/static/static/js/embed.HKcgTiLB.js +195 -0
- streamlit/static/static/js/{es6.BJcsVXQ0.js → es6.4AP97RGk.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.XzUvQqcZ.js → iframeResizer.contentWindow.BZAsvL9q.js} +1 -1
- streamlit/static/static/js/index.-3selq_5.js +2 -0
- streamlit/static/static/js/index.1ylynMAS.js +7 -0
- streamlit/static/static/js/{index.CxIUUfab.js → index.6UunrySF.js} +53 -122
- streamlit/static/static/js/index.8HslT92O.js +14 -0
- streamlit/static/static/js/index.B0TPxAZ1.js +1 -0
- streamlit/static/static/js/index.B0yp3bM1.js +6 -0
- streamlit/static/static/js/index.BHWBaOWH.js +1 -0
- streamlit/static/static/js/index.BJas6XzW.js +1 -0
- streamlit/static/static/js/index.BKIlG7Ng.js +3 -0
- streamlit/static/static/js/index.BMU6zZRk.js +1 -0
- streamlit/static/static/js/index.BNMLO-0p.js +2 -0
- streamlit/static/static/js/index.BPmBNTel.js +1 -0
- streamlit/static/static/js/index.BVuohWM1.js +1 -0
- streamlit/static/static/js/index.B_AvdOKC.js +1 -0
- streamlit/static/static/js/index.BjQIH-3U.js +1 -0
- streamlit/static/static/js/index.BrqtKtSu.js +2 -0
- streamlit/static/static/js/index.Buc7XrOl.js +188 -0
- streamlit/static/static/js/index.CIC9pLsG.js +2 -0
- streamlit/static/static/js/index.CP2YZ73v.js +1 -0
- streamlit/static/static/js/index.CSbah0y4.js +27 -0
- streamlit/static/static/js/index.CbiYVMT1.js +1 -0
- streamlit/static/static/js/index.CbxllBj8.js +1 -0
- streamlit/static/static/js/index.Cd1D2eGF.js +263 -0
- streamlit/static/static/js/index.Ckcqwai8.js +2 -0
- streamlit/static/static/js/index.CqTPbV5Y.js +151 -0
- streamlit/static/static/js/index.CxXo5UKy.js +1 -0
- streamlit/static/static/js/index.CxbL5FgL.js +1 -0
- streamlit/static/static/js/index.D52dMvK5.js +1 -0
- streamlit/static/static/js/index.DBUdji-9.js +3 -0
- streamlit/static/static/js/index.DMU3coc2.js +1 -0
- streamlit/static/static/js/index.DN4sfQLP.js +1 -0
- streamlit/static/static/js/{index.DPUXkcQL.js → index.DRDE9rnx.js} +1 -1
- streamlit/static/static/js/{index.B_dWA3vd.js → index.DY9Ac89e.js} +2 -2
- streamlit/static/static/js/index.DYKCsDvl.js +1 -0
- streamlit/static/static/js/index.Da9gznCC.js +1 -0
- streamlit/static/static/js/index.DfIRibXG.js +1 -0
- streamlit/static/static/js/{index.D3GPA5k4.js → index.Dg5zbEp2.js} +9 -40
- streamlit/static/static/js/index.Di9I2cid.js +1 -0
- streamlit/static/static/js/index.DkpEv0uV.js +1 -0
- streamlit/static/static/js/index.DwJ9Vhsl.js +1 -0
- streamlit/static/static/js/index.L7erTnMm.js +1 -0
- streamlit/static/static/js/{index.DOFlg3dS.js → index.NaDyAN1s.js} +1 -1
- streamlit/static/static/js/index.RNTPpVde.js +1 -0
- streamlit/static/static/js/index.VFDFuf_7.js +1 -0
- streamlit/static/static/js/index.W-bl3NDo.js +1 -0
- streamlit/static/static/js/index.XYozEjwK.js +1 -0
- streamlit/static/static/js/index.oyLQ4pue.js +1 -0
- streamlit/static/static/js/index.q4fLUQtC.js +11 -0
- streamlit/static/static/js/index.q9puCQgK.js +2 -0
- streamlit/static/static/js/index.xZBTXGNC.js +1 -0
- streamlit/static/static/js/{input.D4MN_FzN.js → input.CcvrgErO.js} +2 -2
- streamlit/static/static/js/main.eVHOp4Th.js +13 -0
- streamlit/static/static/js/{memory.DrZjtdGT.js → memory.Ck_sLv5Y.js} +1 -1
- streamlit/static/static/js/moment.C3j7ZXd7.js +4 -0
- streamlit/static/static/js/number-overlay-editor.DgcLMWOy.js +9 -0
- streamlit/static/static/js/pandasStylerUtils.DqP0h70z.js +1 -0
- streamlit/static/static/js/{possibleConstructorReturn.exeeJQEP.js → possibleConstructorReturn.C_51n46K.js} +1 -1
- streamlit/static/static/js/{sandbox.ClO3IuUr.js → sandbox.Q-g3QIZJ.js} +1 -1
- streamlit/static/static/js/styled-components.e0V96rJw.js +1 -0
- streamlit/static/static/js/threshold.Q1mXg5rX.js +1 -0
- streamlit/static/static/js/throttle.D3b5WILl.js +1 -0
- streamlit/static/static/js/{timepicker.DAhu-vcF.js → timepicker.Bpn70xGc.js} +1 -1
- streamlit/static/static/js/timer.C2hYhUse.js +1 -0
- streamlit/static/static/js/{toConsumableArray.DNbljYEC.js → toConsumableArray.DIN_ys1J.js} +1 -1
- streamlit/static/static/js/uniqueId.B27POWT6.js +1 -0
- streamlit/static/static/js/urls.BwSlolu9.js +1 -0
- streamlit/static/static/js/{useBasicWidgetState.D6sOH6oI.js → useBasicWidgetState.DA3_qaXD.js} +1 -1
- streamlit/static/static/js/useIntlLocale.BSq6SANa.js +12 -0
- streamlit/static/static/js/{useTextInputAutoExpand.4u3_GcuN.js → useTextInputAutoExpand.ytEW5QmA.js} +1 -1
- streamlit/static/static/js/useUpdateUiValue.DOxWBNiI.js +1 -0
- streamlit/static/static/js/useWaveformController.BCmk6WLk.js +1 -0
- streamlit/static/static/js/value.B4vHRSi7.js +1 -0
- streamlit/static/static/js/withCalculatedWidth.ChdrMItN.js +1 -0
- streamlit/static/static/js/withFullScreenWrapper.7j_lzlaF.js +1 -0
- streamlit/string_util.py +8 -1
- streamlit/testing/v1/app_test.py +15 -0
- streamlit/testing/v1/element_tree.py +62 -0
- streamlit/web/bootstrap.py +24 -0
- streamlit/web/server/oauth_authlib_routes.py +5 -2
- streamlit/web/server/upload_file_request_handler.py +16 -0
- {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/METADATA +9 -5
- {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/RECORD +274 -239
- streamlit/static/static/js/InputInstructions.jhH15PqV.js +0 -1
- streamlit/static/static/js/Particles.DUsputn1.js +0 -1
- streamlit/static/static/js/data-grid-overlay-editor.2Ufgxc6y.js +0 -1
- streamlit/static/static/js/index.B1ZQh4P1.js +0 -1
- streamlit/static/static/js/index.BKstZk0M.js +0 -27
- streamlit/static/static/js/index.BMcFsUee.js +0 -1
- streamlit/static/static/js/index.BR-IdcTb.js +0 -2
- streamlit/static/static/js/index.BgnZEMVh.js +0 -1
- streamlit/static/static/js/index.BohqXifI.js +0 -1
- streamlit/static/static/js/index.Br5nxKNj.js +0 -2
- streamlit/static/static/js/index.BrIKVbNc.js +0 -3
- streamlit/static/static/js/index.BtWUPzle.js +0 -1
- streamlit/static/static/js/index.C0RLraek.js +0 -1
- streamlit/static/static/js/index.CAIjskgG.js +0 -1
- streamlit/static/static/js/index.CAj-7vWz.js +0 -949
- streamlit/static/static/js/index.CMtEit2O.js +0 -1
- streamlit/static/static/js/index.CkRlykEE.js +0 -12
- streamlit/static/static/js/index.CmN3FXfI.js +0 -1617
- streamlit/static/static/js/index.CwbFI1_-.js +0 -1
- streamlit/static/static/js/index.D2KPNy7e.js +0 -1
- streamlit/static/static/js/index.DGAh7DMq.js +0 -1
- streamlit/static/static/js/index.DKb_NvmG.js +0 -197
- streamlit/static/static/js/index.DMqgUYKq.js +0 -1
- streamlit/static/static/js/index.DX1xY89g.js +0 -1
- streamlit/static/static/js/index.DYATBCsq.js +0 -2
- streamlit/static/static/js/index.DaSmGJ76.js +0 -3
- streamlit/static/static/js/index.Dd7bMeLP.js +0 -1
- streamlit/static/static/js/index.DjmmgI5U.js +0 -1
- streamlit/static/static/js/index.Dq56CyM2.js +0 -1
- streamlit/static/static/js/index.DuiXaS5_.js +0 -7
- streamlit/static/static/js/index.DvFidMLe.js +0 -2
- streamlit/static/static/js/index.DwkhC5Pc.js +0 -1
- streamlit/static/static/js/index.Q-3sFn1v.js +0 -1
- streamlit/static/static/js/index.QJ5QO9sJ.js +0 -1
- streamlit/static/static/js/index.VwTaeety.js +0 -1
- streamlit/static/static/js/index.YOqQbeX8.js +0 -1
- streamlit/static/static/js/number-overlay-editor.DRwAw1In.js +0 -9
- streamlit/static/static/js/uniqueId.oG4Gvj1v.js +0 -1
- streamlit/static/static/js/useUpdateUiValue.F2R3eTeR.js +0 -1
- streamlit/static/static/js/withFullScreenWrapper.zothJIsI.js +0 -1
- {streamlit-1.51.0.data → streamlit-1.52.1.data}/scripts/streamlit.cmd +0 -0
- {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/WHEEL +0 -0
- {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/entry_points.txt +0 -0
- {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/top_level.txt +0 -0
streamlit/__init__.py
CHANGED
|
@@ -180,6 +180,7 @@ container = _main.container
|
|
|
180
180
|
dataframe = _main.dataframe
|
|
181
181
|
data_editor = _main.data_editor
|
|
182
182
|
date_input = _main.date_input
|
|
183
|
+
datetime_input = _main.datetime_input
|
|
183
184
|
divider = _main.divider
|
|
184
185
|
download_button = _main.download_button
|
|
185
186
|
expander = _main.expander
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
|
+
import time
|
|
19
|
+
from collections.abc import Iterable, Mapping
|
|
18
20
|
from itertools import dropwhile
|
|
19
21
|
from pathlib import Path
|
|
20
|
-
from typing import Literal, NoReturn
|
|
22
|
+
from typing import TYPE_CHECKING, Literal, NoReturn
|
|
21
23
|
|
|
22
24
|
import streamlit as st
|
|
23
25
|
from streamlit.errors import NoSessionContext, StreamlitAPIException
|
|
@@ -30,6 +32,9 @@ from streamlit.runtime.scriptrunner import (
|
|
|
30
32
|
get_script_run_ctx,
|
|
31
33
|
)
|
|
32
34
|
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from streamlit.runtime.state.query_params import QueryParams, QueryParamsInput
|
|
37
|
+
|
|
33
38
|
|
|
34
39
|
@gather_metrics("stop")
|
|
35
40
|
def stop() -> NoReturn: # type: ignore[misc]
|
|
@@ -99,6 +104,31 @@ def _new_fragment_id_queue(
|
|
|
99
104
|
return new_queue
|
|
100
105
|
|
|
101
106
|
|
|
107
|
+
def _set_query_params_for_switch(
|
|
108
|
+
query_params_state: QueryParams,
|
|
109
|
+
new_query_params: QueryParamsInput | None,
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Set query params for a switch page."""
|
|
112
|
+
|
|
113
|
+
if new_query_params is None:
|
|
114
|
+
query_params_state.clear()
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
if isinstance(new_query_params, Mapping) or (
|
|
118
|
+
isinstance(new_query_params, Iterable)
|
|
119
|
+
and not isinstance(
|
|
120
|
+
new_query_params, # type: ignore[unreachable]
|
|
121
|
+
(str, bytes),
|
|
122
|
+
)
|
|
123
|
+
):
|
|
124
|
+
query_params_state.from_dict(new_query_params)
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
raise StreamlitAPIException(
|
|
128
|
+
f"`query_params` must be a mapping or an iterable of (key, value) pairs not a `{type(new_query_params)}`."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
102
132
|
@gather_metrics("rerun")
|
|
103
133
|
def rerun( # type: ignore[misc]
|
|
104
134
|
*, # The scope argument can only be passed via keyword.
|
|
@@ -155,7 +185,11 @@ def rerun( # type: ignore[misc]
|
|
|
155
185
|
|
|
156
186
|
|
|
157
187
|
@gather_metrics("switch_page")
|
|
158
|
-
def switch_page(
|
|
188
|
+
def switch_page( # type: ignore[misc]
|
|
189
|
+
page: str | Path | StreamlitPage,
|
|
190
|
+
*,
|
|
191
|
+
query_params: QueryParamsInput | None = None,
|
|
192
|
+
) -> NoReturn: # ty: ignore[invalid-return-type]
|
|
159
193
|
"""Programmatically switch the current page in a multipage app.
|
|
160
194
|
|
|
161
195
|
When ``st.switch_page`` is called, the current page execution stops and
|
|
@@ -166,20 +200,31 @@ def switch_page(page: str | Path | StreamlitPage) -> NoReturn: # type: ignore[m
|
|
|
166
200
|
|
|
167
201
|
Parameters
|
|
168
202
|
----------
|
|
169
|
-
page: str, Path, or st.Page
|
|
203
|
+
page : str, Path, or st.Page
|
|
170
204
|
The file path (relative to the main script) or an st.Page indicating
|
|
171
205
|
the page to switch to.
|
|
172
206
|
|
|
207
|
+
query_params : dict, list of tuples, or None
|
|
208
|
+
Query parameters to apply when navigating to the target page.
|
|
209
|
+
This can be a dictionary or an iterable of key-value tuples. Values can
|
|
210
|
+
be strings or iterables of strings (for repeated keys). When this is
|
|
211
|
+
``None`` (default), all non-embed query parameters are cleared during
|
|
212
|
+
navigation.
|
|
173
213
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
214
|
+
Examples
|
|
215
|
+
--------
|
|
216
|
+
**Example 1: Basic usage**
|
|
217
|
+
|
|
218
|
+
The following example shows how to switch to a different page in a
|
|
219
|
+
multipage app that uses the ``pages/`` directory:
|
|
177
220
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
221
|
+
.. code-block:: text
|
|
222
|
+
|
|
223
|
+
your-repository/
|
|
224
|
+
├── pages/
|
|
225
|
+
│ ├── page_1.py
|
|
226
|
+
│ └── page_2.py
|
|
227
|
+
└── your_app.py
|
|
183
228
|
|
|
184
229
|
>>> import streamlit as st
|
|
185
230
|
>>>
|
|
@@ -190,10 +235,35 @@ def switch_page(page: str | Path | StreamlitPage) -> NoReturn: # type: ignore[m
|
|
|
190
235
|
>>> if st.button("Page 2"):
|
|
191
236
|
>>> st.switch_page("pages/page_2.py")
|
|
192
237
|
|
|
193
|
-
.. output
|
|
238
|
+
.. output::
|
|
194
239
|
https://doc-switch-page.streamlit.app/
|
|
195
240
|
height: 350px
|
|
196
241
|
|
|
242
|
+
**Example 2: Passing query parameters**
|
|
243
|
+
|
|
244
|
+
The following example shows how to pass query parameters when switching to a
|
|
245
|
+
different page. This example uses ``st.navigation`` to create a multipage app.
|
|
246
|
+
|
|
247
|
+
.. code-block:: text
|
|
248
|
+
|
|
249
|
+
your-repository/
|
|
250
|
+
├── page_2.py
|
|
251
|
+
└── your_app.py
|
|
252
|
+
|
|
253
|
+
>>> import streamlit as st
|
|
254
|
+
>>>
|
|
255
|
+
>>> def page_1():
|
|
256
|
+
>>> st.title("Page 1")
|
|
257
|
+
>>> if st.button("Switch to Page 2"):
|
|
258
|
+
>>> st.switch_page("page_2.py", query_params={"utm_source": "page_1"})
|
|
259
|
+
>>>
|
|
260
|
+
>>> pg = st.navigation([page_1, "page_2.py"])
|
|
261
|
+
>>> pg.run()
|
|
262
|
+
|
|
263
|
+
.. output::
|
|
264
|
+
https://doc-switch-page-query-params.streamlit.app/
|
|
265
|
+
height: 350px
|
|
266
|
+
|
|
197
267
|
"""
|
|
198
268
|
|
|
199
269
|
ctx = get_script_run_ctx()
|
|
@@ -227,9 +297,14 @@ def switch_page(page: str | Path | StreamlitPage) -> NoReturn: # type: ignore[m
|
|
|
227
297
|
|
|
228
298
|
page_script_hash = matched_pages[0]["page_script_hash"]
|
|
229
299
|
|
|
230
|
-
#
|
|
300
|
+
# Reset query params (with exception of embed) and optionally apply overrides.
|
|
231
301
|
with ctx.session_state.query_params() as qp:
|
|
232
|
-
qp
|
|
302
|
+
_set_query_params_for_switch(qp, query_params)
|
|
303
|
+
# Additional safeguard to ensure the query params
|
|
304
|
+
# are sent out to the frontend before the new rerun might clear
|
|
305
|
+
# outstanding messages. This uses the same time that is used as waiting
|
|
306
|
+
# in our event loop.
|
|
307
|
+
time.sleep(0.01)
|
|
233
308
|
|
|
234
309
|
ctx.script_requests.request_rerun(
|
|
235
310
|
RerunData(
|
|
@@ -24,7 +24,7 @@ from streamlit import dataframe_util
|
|
|
24
24
|
from streamlit.elements.lib import pandas_styler_utils
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
|
-
from pandas import DataFrame, Index
|
|
27
|
+
from pandas import DataFrame, Index
|
|
28
28
|
|
|
29
29
|
from streamlit.proto.Components_pb2 import ArrowTable as ArrowTableProto
|
|
30
30
|
|
|
@@ -57,7 +57,7 @@ def marshall(
|
|
|
57
57
|
_marshall_data(proto, df)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def _marshall_index(proto: ArrowTableProto, index: Index) -> None:
|
|
60
|
+
def _marshall_index(proto: ArrowTableProto, index: Index[Any]) -> None:
|
|
61
61
|
"""Marshall pandas.DataFrame index into an ArrowTable proto.
|
|
62
62
|
|
|
63
63
|
Parameters
|
|
@@ -72,12 +72,12 @@ def _marshall_index(proto: ArrowTableProto, index: Index) -> None:
|
|
|
72
72
|
"""
|
|
73
73
|
import pandas as pd
|
|
74
74
|
|
|
75
|
-
index_values = map(_maybe_tuple_to_list, index.values)
|
|
75
|
+
index_values = list(map(_maybe_tuple_to_list, index.values))
|
|
76
76
|
index_df = pd.DataFrame(index_values)
|
|
77
77
|
proto.index = dataframe_util.convert_pandas_df_to_arrow_bytes(index_df)
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def _marshall_columns(proto: ArrowTableProto, columns:
|
|
80
|
+
def _marshall_columns(proto: ArrowTableProto, columns: Index[Any]) -> None:
|
|
81
81
|
"""Marshall pandas.DataFrame columns into an ArrowTable proto.
|
|
82
82
|
|
|
83
83
|
Parameters
|
|
@@ -85,15 +85,15 @@ def _marshall_columns(proto: ArrowTableProto, columns: Series) -> None:
|
|
|
85
85
|
proto : proto.ArrowTable
|
|
86
86
|
Output. The protobuf for a Streamlit ArrowTable proto.
|
|
87
87
|
|
|
88
|
-
columns :
|
|
88
|
+
columns : Index
|
|
89
89
|
Column labels to use for resulting frame.
|
|
90
90
|
Will default to RangeIndex (0, 1, 2, ..., n) if no column labels are provided.
|
|
91
91
|
|
|
92
92
|
"""
|
|
93
93
|
import pandas as pd
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
columns_df = pd.DataFrame(
|
|
95
|
+
column_values = list(map(_maybe_tuple_to_list, columns.values))
|
|
96
|
+
columns_df = pd.DataFrame(column_values)
|
|
97
97
|
proto.columns = dataframe_util.convert_pandas_df_to_arrow_bytes(columns_df)
|
|
98
98
|
|
|
99
99
|
|
|
@@ -322,7 +322,7 @@ def component(
|
|
|
322
322
|
if result.clicked:
|
|
323
323
|
st.write(f"You clicked {result.clicked}!")
|
|
324
324
|
|
|
325
|
-
.. output
|
|
325
|
+
.. output::
|
|
326
326
|
https://doc-components-markdown-links.streamlit.app/
|
|
327
327
|
height: 250px
|
|
328
328
|
|
|
@@ -386,7 +386,7 @@ def component(
|
|
|
386
386
|
elif result.clicked == "link_2":
|
|
387
387
|
st.write("You clicked the second link!")
|
|
388
388
|
|
|
389
|
-
.. output
|
|
389
|
+
.. output::
|
|
390
390
|
https://doc-components-custom-anchors.streamlit.app/
|
|
391
391
|
height: 250px
|
|
392
392
|
|
|
@@ -445,10 +445,66 @@ def component(
|
|
|
445
445
|
result = my_component(on_clicked_change=lambda: None)
|
|
446
446
|
result
|
|
447
447
|
|
|
448
|
-
.. output
|
|
448
|
+
.. output::
|
|
449
449
|
https://doc-components-interactive-svg.streamlit.app/
|
|
450
450
|
height: 550px
|
|
451
451
|
|
|
452
|
+
**Example 4: Clean up your component's resources**
|
|
453
|
+
|
|
454
|
+
You can use the return value of the component's JavaScript function to
|
|
455
|
+
clean up any resources when the component is unmounted. For example, you
|
|
456
|
+
can disconnect a MutationObserver that was monitoring changes in the DOM.
|
|
457
|
+
|
|
458
|
+
.. code-block:: python
|
|
459
|
+
|
|
460
|
+
import streamlit as st
|
|
461
|
+
|
|
462
|
+
JS = """
|
|
463
|
+
export default function(component) {
|
|
464
|
+
const { setStateValue, parentElement } = component;
|
|
465
|
+
const sidebar = document.querySelector('section.stSidebar');
|
|
466
|
+
const initialState = sidebar.getAttribute('aria-expanded') === 'true';
|
|
467
|
+
|
|
468
|
+
// Create observer to watch for aria-expanded attribute changes
|
|
469
|
+
const observer = new MutationObserver((mutations) => {
|
|
470
|
+
mutations.forEach((mutation) => {
|
|
471
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'aria-expanded') {
|
|
472
|
+
const newIsExpanded = sidebar.getAttribute('aria-expanded') === 'true';
|
|
473
|
+
setStateValue('expanded', newIsExpanded);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Start observing
|
|
479
|
+
observer.observe(sidebar, {
|
|
480
|
+
attributes: true,
|
|
481
|
+
attributeFilter: ['aria-expanded']
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Set initial state
|
|
485
|
+
setStateValue('expanded', initialState);
|
|
486
|
+
|
|
487
|
+
// Cleanup function to remove the observer
|
|
488
|
+
return () => {
|
|
489
|
+
observer.disconnect();
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
};
|
|
493
|
+
"""
|
|
494
|
+
|
|
495
|
+
my_component = st.components.v2.component(
|
|
496
|
+
"sidebar_expansion_detector",
|
|
497
|
+
js=JS,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
st.sidebar.write("Sidebar content")
|
|
501
|
+
result = my_component(on_expanded_change=lambda: None)
|
|
502
|
+
result
|
|
503
|
+
|
|
504
|
+
.. output::
|
|
505
|
+
https://doc-components-cleanup-function.streamlit.app/
|
|
506
|
+
height: 250px
|
|
507
|
+
|
|
452
508
|
'''
|
|
453
509
|
return _create_component_callable(name, html=html, css=css, js=js)
|
|
454
510
|
|
|
@@ -55,9 +55,11 @@ from streamlit.errors import (
|
|
|
55
55
|
)
|
|
56
56
|
from streamlit.proto.ArrowData_pb2 import ArrowData as ArrowDataProto
|
|
57
57
|
from streamlit.proto.BidiComponent_pb2 import BidiComponent as BidiComponentProto
|
|
58
|
+
from streamlit.proto.BidiComponent_pb2 import MixedData as MixedDataProto
|
|
58
59
|
from streamlit.runtime.metrics_util import gather_metrics
|
|
59
60
|
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
|
60
61
|
from streamlit.runtime.state import register_widget
|
|
62
|
+
from streamlit.util import calc_md5
|
|
61
63
|
|
|
62
64
|
if TYPE_CHECKING:
|
|
63
65
|
from streamlit.components.v2.types import (
|
|
@@ -117,6 +119,144 @@ def _make_trigger_id(base: str, event: str) -> str:
|
|
|
117
119
|
class BidiComponentMixin:
|
|
118
120
|
"""Mixin class for the bidi_component DeltaGenerator method."""
|
|
119
121
|
|
|
122
|
+
def _canonicalize_json_for_identity(self, payload: str) -> str:
|
|
123
|
+
"""Return a deterministic JSON string for identity comparisons.
|
|
124
|
+
|
|
125
|
+
Payloads that cannot be parsed (or re-serialized) are returned as-is to
|
|
126
|
+
avoid mutating developer data.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
if not payload:
|
|
130
|
+
return payload
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
parsed = json.loads(payload)
|
|
134
|
+
except (TypeError, ValueError):
|
|
135
|
+
return payload
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
return json.dumps(parsed, sort_keys=True)
|
|
139
|
+
except (TypeError, ValueError):
|
|
140
|
+
return payload
|
|
141
|
+
|
|
142
|
+
def _canonical_json_digest_for_identity(self, payload: str) -> str:
|
|
143
|
+
"""Return the hash of the canonicalized JSON payload for identity use.
|
|
144
|
+
|
|
145
|
+
Hashing keeps the kwargs passed to ``compute_and_register_element_id``
|
|
146
|
+
small even when the JSON payload is very large, while still changing the
|
|
147
|
+
identity whenever the canonical JSON content changes.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
canonical = self._canonicalize_json_for_identity(payload)
|
|
151
|
+
return calc_md5(canonical)
|
|
152
|
+
|
|
153
|
+
def _build_bidi_identity_kwargs(
|
|
154
|
+
self,
|
|
155
|
+
*,
|
|
156
|
+
component_name: str,
|
|
157
|
+
isolate_styles: bool,
|
|
158
|
+
width: Width,
|
|
159
|
+
height: Height,
|
|
160
|
+
proto: BidiComponentProto,
|
|
161
|
+
data: BidiComponentData = None,
|
|
162
|
+
) -> dict[str, Any]:
|
|
163
|
+
"""Build deterministic identity kwargs for ID computation.
|
|
164
|
+
|
|
165
|
+
Construct a stable mapping of identity-relevant properties for
|
|
166
|
+
``compute_and_register_element_id``. This includes structural
|
|
167
|
+
properties (name, style isolation, layout) and an explicit, typed
|
|
168
|
+
handling of the ``BidiComponent`` ``oneof data`` field to ensure
|
|
169
|
+
unkeyed components change identity when their serialized payload
|
|
170
|
+
changes.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
component_name : str
|
|
175
|
+
The registered component name.
|
|
176
|
+
isolate_styles : bool
|
|
177
|
+
Whether the component styles are rendered in a Shadow DOM.
|
|
178
|
+
width : Width
|
|
179
|
+
Desired width configuration passed to the component.
|
|
180
|
+
height : Height
|
|
181
|
+
Desired height configuration passed to the component.
|
|
182
|
+
proto : BidiComponentProto
|
|
183
|
+
The populated component protobuf. Its ``data`` oneof determines
|
|
184
|
+
which serialized payload (JSON, Arrow, bytes, or Mixed) contributes
|
|
185
|
+
to identity.
|
|
186
|
+
data : BidiComponentData
|
|
187
|
+
The raw data passed to the component. Used to optimize identity
|
|
188
|
+
calculation for JSON payloads by avoiding a parse/serialize cycle.
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
dict[str, Any]
|
|
193
|
+
A mapping of deterministic values to be forwarded into
|
|
194
|
+
``compute_and_register_element_id``.
|
|
195
|
+
|
|
196
|
+
Raises
|
|
197
|
+
------
|
|
198
|
+
RuntimeError
|
|
199
|
+
If an unhandled ``oneof data`` variant is encountered (guards
|
|
200
|
+
against adding new fields without updating identity computation).
|
|
201
|
+
"""
|
|
202
|
+
identity: dict[str, Any] = {
|
|
203
|
+
"component_name": component_name,
|
|
204
|
+
"isolate_styles": isolate_styles,
|
|
205
|
+
"width": width,
|
|
206
|
+
"height": height,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
data_field = proto.WhichOneof("data")
|
|
210
|
+
if data_field is None:
|
|
211
|
+
return identity
|
|
212
|
+
|
|
213
|
+
if data_field == "json":
|
|
214
|
+
# Canonicalize only for identity so unkeyed widgets don't churn when
|
|
215
|
+
# dict insertion order changes.
|
|
216
|
+
#
|
|
217
|
+
# Optimization: Use raw `data` if available to avoid the overhead of
|
|
218
|
+
# parsing `proto.json` back into a dict.
|
|
219
|
+
canonical_digest = None
|
|
220
|
+
|
|
221
|
+
if data is not None:
|
|
222
|
+
try:
|
|
223
|
+
canonical = json.dumps(data, sort_keys=True)
|
|
224
|
+
canonical_digest = calc_md5(canonical)
|
|
225
|
+
except (TypeError, ValueError):
|
|
226
|
+
# Fallback to existing logic if direct dump fails
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
if canonical_digest is None:
|
|
230
|
+
canonical_digest = self._canonical_json_digest_for_identity(proto.json)
|
|
231
|
+
|
|
232
|
+
identity["json"] = canonical_digest
|
|
233
|
+
elif data_field == "arrow_data":
|
|
234
|
+
# Hash large payloads instead of shoving raw bytes through the ID
|
|
235
|
+
# hasher for performance.
|
|
236
|
+
identity["arrow_data"] = calc_md5(proto.arrow_data.data)
|
|
237
|
+
elif data_field == "bytes":
|
|
238
|
+
# Same story for arbitrary bytes payloads: content-address the data
|
|
239
|
+
# so identity changes track real mutations without re-hashing the
|
|
240
|
+
# whole blob every run.
|
|
241
|
+
identity["bytes"] = calc_md5(proto.bytes)
|
|
242
|
+
elif data_field == "mixed":
|
|
243
|
+
mixed: MixedDataProto = proto.mixed
|
|
244
|
+
# Add the JSON content of the MixedData to the identity.
|
|
245
|
+
identity["mixed_json"] = self._canonical_json_digest_for_identity(
|
|
246
|
+
mixed.json
|
|
247
|
+
)
|
|
248
|
+
# Add the sorted content-addressed ref IDs of the Arrow blobs to the identity.
|
|
249
|
+
# Unlike other data types where we include actual bytes, here we only include
|
|
250
|
+
# the blob keys. This is sufficient because keys are MD5 hashes of the blob
|
|
251
|
+
# content (content-addressed), so identical content produces identical keys.
|
|
252
|
+
identity["mixed_arrow_blobs"] = ",".join(sorted(mixed.arrow_blobs.keys()))
|
|
253
|
+
else:
|
|
254
|
+
raise RuntimeError(
|
|
255
|
+
f"Unhandled BidiComponent.data oneof field: {data_field}"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return identity
|
|
259
|
+
|
|
120
260
|
@gather_metrics("_bidi_component")
|
|
121
261
|
def _bidi_component(
|
|
122
262
|
self,
|
|
@@ -206,18 +346,6 @@ class BidiComponentMixin:
|
|
|
206
346
|
if not has_js and not has_html:
|
|
207
347
|
raise BidiComponentMissingContentError(component_name)
|
|
208
348
|
|
|
209
|
-
# Compute a unique ID for this component instance
|
|
210
|
-
computed_id = compute_and_register_element_id(
|
|
211
|
-
"bidi_component",
|
|
212
|
-
user_key=key,
|
|
213
|
-
component_name=component_name,
|
|
214
|
-
isolate_styles=isolate_styles,
|
|
215
|
-
width=width,
|
|
216
|
-
height=height,
|
|
217
|
-
dg=self.dg,
|
|
218
|
-
key_as_main_identity=True,
|
|
219
|
-
)
|
|
220
|
-
|
|
221
349
|
# ------------------------------------------------------------------
|
|
222
350
|
# 1. Parse user-supplied callbacks
|
|
223
351
|
# ------------------------------------------------------------------
|
|
@@ -253,7 +381,6 @@ class BidiComponentMixin:
|
|
|
253
381
|
|
|
254
382
|
# Set up the component proto
|
|
255
383
|
bidi_component_proto = BidiComponentProto()
|
|
256
|
-
bidi_component_proto.id = computed_id
|
|
257
384
|
bidi_component_proto.component_name = component_name
|
|
258
385
|
bidi_component_proto.isolate_styles = isolate_styles
|
|
259
386
|
bidi_component_proto.js_content = component_def.js_content or ""
|
|
@@ -298,6 +425,27 @@ class BidiComponentMixin:
|
|
|
298
425
|
raise BidiComponentUnserializableDataError()
|
|
299
426
|
bidi_component_proto.form_id = current_form_id(self.dg)
|
|
300
427
|
|
|
428
|
+
# Build identity kwargs for the component instance now that the proto is
|
|
429
|
+
# populated.
|
|
430
|
+
identity_kwargs = self._build_bidi_identity_kwargs(
|
|
431
|
+
component_name=component_name,
|
|
432
|
+
isolate_styles=isolate_styles,
|
|
433
|
+
width=width,
|
|
434
|
+
height=height,
|
|
435
|
+
proto=bidi_component_proto,
|
|
436
|
+
data=data,
|
|
437
|
+
)
|
|
438
|
+
# Compute a unique ID for this component instance now that the proto is
|
|
439
|
+
# populated.
|
|
440
|
+
computed_id = compute_and_register_element_id(
|
|
441
|
+
"bidi_component",
|
|
442
|
+
user_key=key,
|
|
443
|
+
key_as_main_identity=True,
|
|
444
|
+
dg=self.dg,
|
|
445
|
+
**identity_kwargs,
|
|
446
|
+
)
|
|
447
|
+
bidi_component_proto.id = computed_id
|
|
448
|
+
|
|
301
449
|
# Instantiate the Serde for this component instance
|
|
302
450
|
serde = BidiComponentSerde(default=default)
|
|
303
451
|
|
|
@@ -23,7 +23,7 @@ from streamlit.dataframe_util import convert_anything_to_arrow_bytes, is_datafra
|
|
|
23
23
|
from streamlit.logger import get_logger
|
|
24
24
|
from streamlit.proto.BidiComponent_pb2 import BidiComponent as BidiComponentProto
|
|
25
25
|
from streamlit.proto.BidiComponent_pb2 import MixedData as MixedDataProto
|
|
26
|
-
from streamlit.util import AttributeDictionary
|
|
26
|
+
from streamlit.util import AttributeDictionary, calc_md5
|
|
27
27
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
29
29
|
from streamlit.components.v2.bidi_component.state import BidiComponentState
|
|
@@ -56,8 +56,6 @@ def _extract_dataframes_from_dict(
|
|
|
56
56
|
dict[str, Any]
|
|
57
57
|
A new dictionary with dataframe-like objects replaced by placeholders.
|
|
58
58
|
"""
|
|
59
|
-
import uuid
|
|
60
|
-
|
|
61
59
|
if arrow_blobs is None:
|
|
62
60
|
arrow_blobs = {}
|
|
63
61
|
|
|
@@ -68,11 +66,20 @@ def _extract_dataframes_from_dict(
|
|
|
68
66
|
# This is a dataframe-like object, serialize it to Arrow
|
|
69
67
|
try:
|
|
70
68
|
arrow_bytes = convert_anything_to_arrow_bytes(value)
|
|
71
|
-
|
|
69
|
+
# Use deterministic, content-addressed ref IDs so placeholders
|
|
70
|
+
# are stable for identical content on each run. This also provides
|
|
71
|
+
# natural deduplication - identical DataFrames share a single blob.
|
|
72
|
+
ref_id = calc_md5(arrow_bytes)
|
|
72
73
|
arrow_blobs[ref_id] = arrow_bytes
|
|
73
74
|
processed_data[key] = {ARROW_REF_KEY: ref_id}
|
|
74
|
-
except Exception:
|
|
75
|
-
# If Arrow serialization fails, keep the original value for JSON
|
|
75
|
+
except Exception as e:
|
|
76
|
+
# If Arrow serialization fails, keep the original value for JSON
|
|
77
|
+
# serialization attempt downstream.
|
|
78
|
+
_LOGGER.debug(
|
|
79
|
+
"Arrow serialization failed for key %r, keeping original value: %s",
|
|
80
|
+
key,
|
|
81
|
+
e,
|
|
82
|
+
)
|
|
76
83
|
processed_data[key] = value
|
|
77
84
|
else:
|
|
78
85
|
# Not dataframe-like, keep as-is
|
|
@@ -304,11 +304,18 @@ class BidiComponentManager:
|
|
|
304
304
|
else:
|
|
305
305
|
_LOGGER.debug("File watching not started")
|
|
306
306
|
|
|
307
|
-
def discover_and_register_components(
|
|
307
|
+
def discover_and_register_components(
|
|
308
|
+
self, *, start_file_watching: bool = True
|
|
309
|
+
) -> None:
|
|
308
310
|
"""Discover installed v2 components and register them.
|
|
309
311
|
|
|
310
312
|
This scans installed distributions for manifests, registers all discovered
|
|
311
313
|
components, and starts file watching for development workflows.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
start_file_watching : bool
|
|
318
|
+
Whether to start file watching after components are registered.
|
|
312
319
|
"""
|
|
313
320
|
try:
|
|
314
321
|
from streamlit.components.v2.manifest_scanner import (
|
|
@@ -318,14 +325,15 @@ class BidiComponentManager:
|
|
|
318
325
|
manifests = scan_component_manifests()
|
|
319
326
|
for manifest, package_root in manifests:
|
|
320
327
|
self.register_from_manifest(manifest, package_root)
|
|
321
|
-
_LOGGER.
|
|
328
|
+
_LOGGER.debug(
|
|
322
329
|
"Registered components from pyproject.toml: %s v%s",
|
|
323
330
|
manifest.name,
|
|
324
331
|
manifest.version,
|
|
325
332
|
)
|
|
326
333
|
|
|
327
334
|
# Start file watching for development mode after all components are registered
|
|
328
|
-
|
|
335
|
+
if start_file_watching:
|
|
336
|
+
self.start_file_watching()
|
|
329
337
|
|
|
330
338
|
except Exception as e:
|
|
331
339
|
_LOGGER.warning("Failed to scan component manifests: %s", e)
|
|
@@ -164,6 +164,16 @@ class BidiComponentDefinition:
|
|
|
164
164
|
# If we get here, it's content, not a path
|
|
165
165
|
return False, None
|
|
166
166
|
|
|
167
|
+
@property
|
|
168
|
+
def is_placeholder(self) -> bool:
|
|
169
|
+
"""Return True if this definition is a placeholder (no content).
|
|
170
|
+
|
|
171
|
+
Placeholders are typically created during the manifest scanning phase
|
|
172
|
+
when we discover a component's existence but haven't yet loaded its
|
|
173
|
+
content via the public API.
|
|
174
|
+
"""
|
|
175
|
+
return self.html is None and self.css is None and self.js is None
|
|
176
|
+
|
|
167
177
|
@property
|
|
168
178
|
def css_url(self) -> str | None:
|
|
169
179
|
"""Return the asset-dir-relative URL path for CSS when file-backed.
|
|
@@ -329,7 +339,14 @@ class BidiComponentRegistry:
|
|
|
329
339
|
name = definition.name
|
|
330
340
|
if name in self._components:
|
|
331
341
|
existing_definition = self._components[name]
|
|
332
|
-
if
|
|
342
|
+
# Check if the existing definition is different and NOT a placeholder.
|
|
343
|
+
# We expect placeholders (from manifest scanning) to be overwritten
|
|
344
|
+
# by the actual definition from the script execution, so we silence
|
|
345
|
+
# the warning in that specific case.
|
|
346
|
+
if (
|
|
347
|
+
existing_definition != definition
|
|
348
|
+
and not existing_definition.is_placeholder
|
|
349
|
+
):
|
|
333
350
|
_LOGGER.warning(
|
|
334
351
|
"Component %s is already registered. Overwriting "
|
|
335
352
|
"previous definition. This may lead to unexpected behavior "
|
streamlit/components/v2/types.py
CHANGED
|
@@ -245,7 +245,7 @@ class BidiComponentCallable(Protocol):
|
|
|
245
245
|
st.write("Result:", result)
|
|
246
246
|
st.write("Session state:", st.session_state)
|
|
247
247
|
|
|
248
|
-
.. output
|
|
248
|
+
.. output::
|
|
249
249
|
https://doc-components-text-input.streamlit.app/
|
|
250
250
|
height: 600px
|
|
251
251
|
|
|
@@ -296,7 +296,7 @@ class BidiComponentCallable(Protocol):
|
|
|
296
296
|
)
|
|
297
297
|
result_2
|
|
298
298
|
|
|
299
|
-
.. output
|
|
299
|
+
.. output::
|
|
300
300
|
https://doc-components-tailwind-button.streamlit.app/
|
|
301
301
|
height: 350px
|
|
302
302
|
|
|
@@ -357,7 +357,7 @@ class SnowflakeConnection(BaseConnection["InternalSnowflakeConnection"]):
|
|
|
357
357
|
def _query(sql: str) -> DataFrame:
|
|
358
358
|
cur = self._instance.cursor()
|
|
359
359
|
cur.execute(sql, params=params, **kwargs)
|
|
360
|
-
return cur.fetch_pandas_all()
|
|
360
|
+
return cur.fetch_pandas_all() # type: ignore
|
|
361
361
|
|
|
362
362
|
# We modify our helper function's `__qualname__` here to work around default
|
|
363
363
|
# `@st.cache_data` behavior. Otherwise, `.query()` being called with different
|
|
@@ -146,7 +146,7 @@ class SnowparkConnection(BaseConnection["Session"]):
|
|
|
146
146
|
)
|
|
147
147
|
def _query(sql: str) -> DataFrame:
|
|
148
148
|
with self._lock:
|
|
149
|
-
return self._instance.sql(sql).to_pandas()
|
|
149
|
+
return self._instance.sql(sql).to_pandas() # type: ignore
|
|
150
150
|
|
|
151
151
|
# We modify our helper function's `__qualname__` here to work around default
|
|
152
152
|
# `@st.cache_data` behavior. Otherwise, `.query()` being called with different
|