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
|
@@ -18,14 +18,33 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
import collections
|
|
20
20
|
import threading
|
|
21
|
-
|
|
21
|
+
import uuid
|
|
22
|
+
from typing import TYPE_CHECKING, BinaryIO, Final, TextIO, TypedDict, cast
|
|
22
23
|
|
|
23
24
|
from streamlit.logger import get_logger
|
|
24
|
-
from streamlit.runtime.
|
|
25
|
+
from streamlit.runtime.download_data_util import convert_data_to_bytes_and_infer_mime
|
|
26
|
+
from streamlit.runtime.media_file_storage import (
|
|
27
|
+
MediaFileKind,
|
|
28
|
+
MediaFileStorage,
|
|
29
|
+
MediaFileStorageError,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
import io
|
|
34
|
+
from collections.abc import Callable
|
|
25
35
|
|
|
26
36
|
_LOGGER: Final = get_logger(__name__)
|
|
27
37
|
|
|
28
38
|
|
|
39
|
+
class DeferredCallableEntry(TypedDict):
|
|
40
|
+
"""Typed metadata for deferred download callables."""
|
|
41
|
+
|
|
42
|
+
callable: Callable[[], bytes | str | BinaryIO | TextIO | io.RawIOBase]
|
|
43
|
+
mimetype: str | None
|
|
44
|
+
filename: str | None
|
|
45
|
+
coordinates: str
|
|
46
|
+
|
|
47
|
+
|
|
29
48
|
def _get_session_id() -> str:
|
|
30
49
|
"""Get the active AppSession's session_id."""
|
|
31
50
|
from streamlit.runtime.scriptrunner_utils.script_run_context import (
|
|
@@ -90,6 +109,10 @@ class MediaFileManager:
|
|
|
90
109
|
collections.defaultdict(dict)
|
|
91
110
|
)
|
|
92
111
|
|
|
112
|
+
# Dict of [file_id -> deferred callable metadata]
|
|
113
|
+
# Used for deferred download button execution
|
|
114
|
+
self._deferred_callables: dict[str, DeferredCallableEntry] = {}
|
|
115
|
+
|
|
93
116
|
# MediaFileManager is used from multiple threads, so all operations
|
|
94
117
|
# need to be protected with a Lock. (This is not an RLock, which
|
|
95
118
|
# means taking it multiple times from the same thread will deadlock.)
|
|
@@ -129,6 +152,31 @@ class MediaFileManager:
|
|
|
129
152
|
else:
|
|
130
153
|
file.mark_for_delete()
|
|
131
154
|
|
|
155
|
+
# Clean up orphaned deferred callables
|
|
156
|
+
self._remove_orphaned_deferred_callables()
|
|
157
|
+
|
|
158
|
+
def _remove_orphaned_deferred_callables(self) -> None:
|
|
159
|
+
"""Remove deferred callables that are not referenced by any active session.
|
|
160
|
+
|
|
161
|
+
Thread safety: callers must hold `self._lock`.
|
|
162
|
+
"""
|
|
163
|
+
_LOGGER.debug("Removing orphaned deferred callables...")
|
|
164
|
+
|
|
165
|
+
# Get all file_ids currently referenced by any session
|
|
166
|
+
active_file_ids = set[str]()
|
|
167
|
+
for session_file_ids_by_coord in self._files_by_session_and_coord.values():
|
|
168
|
+
active_file_ids.update(session_file_ids_by_coord.values())
|
|
169
|
+
|
|
170
|
+
# Remove deferred callables that are no longer referenced
|
|
171
|
+
deferred_ids_to_remove = [
|
|
172
|
+
file_id
|
|
173
|
+
for file_id in self._deferred_callables
|
|
174
|
+
if file_id not in active_file_ids
|
|
175
|
+
]
|
|
176
|
+
for file_id in deferred_ids_to_remove:
|
|
177
|
+
_LOGGER.debug("Removing deferred callable: %s", file_id)
|
|
178
|
+
del self._deferred_callables[file_id]
|
|
179
|
+
|
|
132
180
|
def _delete_file(self, file_id: str) -> None:
|
|
133
181
|
"""Delete the given file from storage, and remove its metadata from
|
|
134
182
|
self._files_by_id.
|
|
@@ -158,6 +206,10 @@ class MediaFileManager:
|
|
|
158
206
|
if session_id in self._files_by_session_and_coord:
|
|
159
207
|
del self._files_by_session_and_coord[session_id]
|
|
160
208
|
|
|
209
|
+
# Don't immediately delete deferred callables here to avoid race conditions.
|
|
210
|
+
# They will be cleaned up by remove_orphaned_deferred_callables() which
|
|
211
|
+
# only removes callables that are not referenced by ANY session.
|
|
212
|
+
|
|
161
213
|
_LOGGER.debug(
|
|
162
214
|
"Sessions still active: %r", self._files_by_session_and_coord.keys()
|
|
163
215
|
)
|
|
@@ -231,3 +283,127 @@ class MediaFileManager:
|
|
|
231
283
|
self._files_by_session_and_coord[session_id][coordinates] = file_id
|
|
232
284
|
|
|
233
285
|
return self._storage.get_url(file_id)
|
|
286
|
+
|
|
287
|
+
def add_deferred(
|
|
288
|
+
self,
|
|
289
|
+
data_callable: Callable[[], bytes | str | BinaryIO | TextIO | io.RawIOBase],
|
|
290
|
+
mimetype: str | None,
|
|
291
|
+
coordinates: str,
|
|
292
|
+
file_name: str | None = None,
|
|
293
|
+
) -> str:
|
|
294
|
+
"""Register a callable for deferred execution. Returns placeholder file_id.
|
|
295
|
+
|
|
296
|
+
The callable will be executed later when execute_deferred() is called,
|
|
297
|
+
typically when the user clicks a download button.
|
|
298
|
+
|
|
299
|
+
Safe to call from any thread.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
data_callable : Callable[[], bytes | str | BinaryIO | TextIO | io.RawIOBase]
|
|
304
|
+
A callable that returns the file data when invoked.
|
|
305
|
+
mimetype : str or None
|
|
306
|
+
The mime type for the file. E.g. "text/csv".
|
|
307
|
+
If None, the mimetype will be inferred from the data type when
|
|
308
|
+
execute_deferred() is called.
|
|
309
|
+
coordinates : str
|
|
310
|
+
Unique string identifying an element's location.
|
|
311
|
+
file_name : str or None
|
|
312
|
+
Optional file_name. Used to set the filename in the response header.
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
str
|
|
317
|
+
A placeholder file_id that can be used to execute the callable later.
|
|
318
|
+
"""
|
|
319
|
+
session_id = _get_session_id()
|
|
320
|
+
|
|
321
|
+
with self._lock:
|
|
322
|
+
# Generate a unique placeholder ID for this deferred callable
|
|
323
|
+
# Expected: a new placeholder ID is created on every script rerun.
|
|
324
|
+
file_id = uuid.uuid4().hex
|
|
325
|
+
|
|
326
|
+
# Store the callable with its metadata
|
|
327
|
+
self._deferred_callables[file_id] = cast(
|
|
328
|
+
"DeferredCallableEntry",
|
|
329
|
+
{
|
|
330
|
+
"callable": data_callable,
|
|
331
|
+
"mimetype": mimetype,
|
|
332
|
+
"filename": file_name,
|
|
333
|
+
"coordinates": coordinates,
|
|
334
|
+
},
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Track this deferred file by session and coordinate
|
|
338
|
+
self._files_by_session_and_coord[session_id][coordinates] = file_id
|
|
339
|
+
|
|
340
|
+
return file_id
|
|
341
|
+
|
|
342
|
+
def execute_deferred(self, file_id: str) -> str:
|
|
343
|
+
"""Execute a deferred callable and return the URL to the generated file.
|
|
344
|
+
|
|
345
|
+
This method retrieves the callable registered with add_deferred(),
|
|
346
|
+
executes it, stores the result, and returns a URL to access it.
|
|
347
|
+
|
|
348
|
+
Safe to call from any thread.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
file_id : str
|
|
353
|
+
The placeholder file_id returned by add_deferred().
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
str
|
|
358
|
+
The URL that can be used to download the generated file.
|
|
359
|
+
|
|
360
|
+
Raises
|
|
361
|
+
------
|
|
362
|
+
MediaFileStorageError
|
|
363
|
+
If the file_id is not found or if the callable execution fails.
|
|
364
|
+
"""
|
|
365
|
+
# Retrieve deferred callable metadata while holding lock
|
|
366
|
+
with self._lock:
|
|
367
|
+
if file_id not in self._deferred_callables:
|
|
368
|
+
raise MediaFileStorageError(f"Deferred file {file_id} not found")
|
|
369
|
+
|
|
370
|
+
deferred = self._deferred_callables[file_id]
|
|
371
|
+
|
|
372
|
+
# Execute callable outside lock to avoid blocking other operations
|
|
373
|
+
try:
|
|
374
|
+
data = deferred["callable"]()
|
|
375
|
+
except Exception as e:
|
|
376
|
+
raise MediaFileStorageError(f"Callable execution failed: {e}") from e
|
|
377
|
+
|
|
378
|
+
# Convert data to bytes and infer mimetype if needed
|
|
379
|
+
data_as_bytes, inferred_mime_type = convert_data_to_bytes_and_infer_mime(
|
|
380
|
+
data,
|
|
381
|
+
unsupported_error=MediaFileStorageError(
|
|
382
|
+
f"Callable returned unsupported type: {type(data)}"
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Use provided mimetype if available, otherwise use inferred mimetype
|
|
387
|
+
mime_type: str = deferred["mimetype"] or inferred_mime_type
|
|
388
|
+
|
|
389
|
+
# Store the generated data and get the actual file_id
|
|
390
|
+
with self._lock:
|
|
391
|
+
actual_file_id = self._storage.load_and_get_id(
|
|
392
|
+
data_as_bytes,
|
|
393
|
+
mime_type,
|
|
394
|
+
MediaFileKind.DOWNLOADABLE,
|
|
395
|
+
deferred["filename"],
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Create metadata for the actual file
|
|
399
|
+
metadata = MediaFileMetadata(kind=MediaFileKind.DOWNLOADABLE)
|
|
400
|
+
self._file_metadata[actual_file_id] = metadata
|
|
401
|
+
|
|
402
|
+
# Keep the deferred callable so users can download multiple times
|
|
403
|
+
# It will be cleaned up when clear_session_refs() is called on rerun
|
|
404
|
+
|
|
405
|
+
# We leave actual_file_id unmapped so repeat clicks rerun the callable.
|
|
406
|
+
# Cleanup prunes the stored file once no session references it.
|
|
407
|
+
|
|
408
|
+
# Return the URL to access the file
|
|
409
|
+
return self._storage.get_url(actual_file_id)
|
|
@@ -78,6 +78,7 @@ _ATTRIBUTIONS_TO_CHECK: Final = [
|
|
|
78
78
|
"duckdb",
|
|
79
79
|
"opensearchpy",
|
|
80
80
|
"supabase",
|
|
81
|
+
"databricks",
|
|
81
82
|
# Dataframe Libraries:
|
|
82
83
|
"polars",
|
|
83
84
|
"dask",
|
|
@@ -92,6 +93,7 @@ _ATTRIBUTIONS_TO_CHECK: Final = [
|
|
|
92
93
|
"tables",
|
|
93
94
|
"zarr",
|
|
94
95
|
"datasets",
|
|
96
|
+
"daft",
|
|
95
97
|
# ML & LLM Tools:
|
|
96
98
|
"mistralai",
|
|
97
99
|
"openai",
|
|
@@ -131,11 +133,62 @@ _ATTRIBUTIONS_TO_CHECK: Final = [
|
|
|
131
133
|
"lightgbm",
|
|
132
134
|
"catboost",
|
|
133
135
|
"sklearn",
|
|
136
|
+
"pydantic_ai",
|
|
137
|
+
"datachain",
|
|
138
|
+
"docling",
|
|
139
|
+
"litserve",
|
|
140
|
+
"crawl4ai",
|
|
141
|
+
"baml_client",
|
|
142
|
+
"browser_use",
|
|
143
|
+
"crewai",
|
|
144
|
+
"unsloth",
|
|
145
|
+
"langgraph",
|
|
146
|
+
"dspy",
|
|
147
|
+
"ultralytics",
|
|
148
|
+
"instructor",
|
|
149
|
+
"ragas",
|
|
150
|
+
"swarm",
|
|
151
|
+
"faster_whisper",
|
|
152
|
+
"memori",
|
|
153
|
+
"autogen_agentchat",
|
|
154
|
+
"xai_sdk",
|
|
155
|
+
"agno",
|
|
156
|
+
"langfuse",
|
|
157
|
+
"smolagents",
|
|
158
|
+
"ollama",
|
|
159
|
+
"groq",
|
|
160
|
+
"together",
|
|
161
|
+
"ai21",
|
|
162
|
+
"marvin",
|
|
163
|
+
"outlines",
|
|
164
|
+
"guardrails",
|
|
165
|
+
"promptflow",
|
|
166
|
+
"semantic_router",
|
|
167
|
+
"mem0",
|
|
168
|
+
"aisuite",
|
|
169
|
+
"mlflow",
|
|
170
|
+
"optuna",
|
|
171
|
+
"keras",
|
|
172
|
+
"jax",
|
|
173
|
+
"shap",
|
|
174
|
+
"evidently",
|
|
175
|
+
"great_expectations",
|
|
176
|
+
"bentoml",
|
|
177
|
+
"modal",
|
|
178
|
+
"sagemaker",
|
|
179
|
+
"vertexai",
|
|
180
|
+
"tiktoken",
|
|
181
|
+
"sentence_transformers",
|
|
182
|
+
"spacy",
|
|
183
|
+
"nltk",
|
|
184
|
+
"onnxruntime",
|
|
185
|
+
"llama_api_client",
|
|
134
186
|
# Workflow Tools:
|
|
135
187
|
"prefect",
|
|
136
188
|
"luigi",
|
|
137
189
|
"airflow",
|
|
138
190
|
"dagster",
|
|
191
|
+
"celery",
|
|
139
192
|
# Vector Stores:
|
|
140
193
|
"pgvector",
|
|
141
194
|
"faiss",
|
|
@@ -148,11 +201,40 @@ _ATTRIBUTIONS_TO_CHECK: Final = [
|
|
|
148
201
|
"lancedb",
|
|
149
202
|
# Others:
|
|
150
203
|
"snowflake",
|
|
204
|
+
"pydantic",
|
|
205
|
+
"fastapi",
|
|
206
|
+
"starlette",
|
|
207
|
+
"playwright",
|
|
208
|
+
"folium",
|
|
209
|
+
"geopandas",
|
|
210
|
+
"httpx",
|
|
211
|
+
"pyecharts",
|
|
212
|
+
"fastplotlib",
|
|
213
|
+
"pygfx",
|
|
214
|
+
"highcharts_core",
|
|
215
|
+
# Optional streamlit dependencies:
|
|
216
|
+
"seaborn",
|
|
217
|
+
"graphviz",
|
|
218
|
+
"matplotlib",
|
|
219
|
+
"uvloop",
|
|
220
|
+
"orjson",
|
|
221
|
+
"rich",
|
|
151
222
|
"streamlit_extras",
|
|
152
223
|
"streamlit_pydantic",
|
|
153
|
-
"
|
|
224
|
+
"pygwalker",
|
|
225
|
+
"plotly",
|
|
226
|
+
"bokeh",
|
|
154
227
|
"plost",
|
|
155
228
|
"authlib",
|
|
229
|
+
# Document Processing:
|
|
230
|
+
"pypdf",
|
|
231
|
+
"pdfplumber",
|
|
232
|
+
"docx",
|
|
233
|
+
"openpyxl",
|
|
234
|
+
"xlsxwriter",
|
|
235
|
+
# Image/Vision:
|
|
236
|
+
"cv2",
|
|
237
|
+
"mediapipe",
|
|
156
238
|
]
|
|
157
239
|
|
|
158
240
|
_ETC_MACHINE_ID_PATH = "/etc/machine-id"
|
|
@@ -331,8 +413,10 @@ def _get_command_telemetry(
|
|
|
331
413
|
):
|
|
332
414
|
name = f"component:{self_arg.name}"
|
|
333
415
|
|
|
334
|
-
if name == "_bidi_component" and len(args) >
|
|
335
|
-
|
|
416
|
+
if name == "_bidi_component" and len(args) > 1 and isinstance(args[1], str):
|
|
417
|
+
# Bound DeltaGenerator methods always receive `self` as args[0], so args[1]
|
|
418
|
+
# is the user-supplied component name.
|
|
419
|
+
component_name = args[1]
|
|
336
420
|
name = f"component_v2:{component_name}"
|
|
337
421
|
|
|
338
422
|
return Command(name=name, args=arguments)
|
|
@@ -529,7 +529,9 @@ class ScriptRunner:
|
|
|
529
529
|
widget_ids = {w.id for w in rerun_data.widget_states.widgets}
|
|
530
530
|
self._session_state.on_script_finished(widget_ids)
|
|
531
531
|
|
|
532
|
-
fragment_ids_this_run =
|
|
532
|
+
fragment_ids_this_run: list[str] | None = (
|
|
533
|
+
rerun_data.fragment_id_queue or None
|
|
534
|
+
)
|
|
533
535
|
|
|
534
536
|
ctx.reset(
|
|
535
537
|
query_string=rerun_data.query_string,
|
|
@@ -14,18 +14,21 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from collections.abc import Iterable, Iterator, MutableMapping
|
|
17
|
+
from collections.abc import Iterable, Iterator, Mapping, MutableMapping
|
|
18
18
|
from dataclasses import dataclass, field
|
|
19
19
|
from typing import TYPE_CHECKING, Final, cast
|
|
20
20
|
from urllib import parse
|
|
21
21
|
|
|
22
|
-
from streamlit.errors import StreamlitAPIException
|
|
22
|
+
from streamlit.errors import StreamlitAPIException, StreamlitQueryParamDictValueError
|
|
23
23
|
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
|
24
24
|
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from _typeshed import SupportsKeysAndGetItem
|
|
28
28
|
|
|
29
|
+
QueryParamValue = str | Iterable[str]
|
|
30
|
+
QueryParamsInput = Mapping[str, QueryParamValue] | Iterable[tuple[str, QueryParamValue]]
|
|
31
|
+
|
|
29
32
|
|
|
30
33
|
EMBED_QUERY_PARAM: Final[str] = "embed"
|
|
31
34
|
EMBED_OPTIONS_QUERY_PARAM: Final[str] = "embed_options"
|
|
@@ -47,7 +50,9 @@ class QueryParams(MutableMapping[str, str]):
|
|
|
47
50
|
self._ensure_single_query_api_used()
|
|
48
51
|
|
|
49
52
|
return iter(
|
|
50
|
-
key
|
|
53
|
+
key
|
|
54
|
+
for key in self._query_params
|
|
55
|
+
if key.lower() not in EMBED_QUERY_PARAMS_KEYS
|
|
51
56
|
)
|
|
52
57
|
|
|
53
58
|
def __getitem__(self, key: str) -> str:
|
|
@@ -56,7 +61,7 @@ class QueryParams(MutableMapping[str, str]):
|
|
|
56
61
|
If the key is not present, raise KeyError.
|
|
57
62
|
"""
|
|
58
63
|
self._ensure_single_query_api_used()
|
|
59
|
-
if key in EMBED_QUERY_PARAMS_KEYS:
|
|
64
|
+
if key.lower() in EMBED_QUERY_PARAMS_KEYS:
|
|
60
65
|
raise KeyError(missing_key_error_message(key))
|
|
61
66
|
|
|
62
67
|
try:
|
|
@@ -73,29 +78,15 @@ class QueryParams(MutableMapping[str, str]):
|
|
|
73
78
|
|
|
74
79
|
def __setitem__(self, key: str, value: str | Iterable[str]) -> None:
|
|
75
80
|
self._ensure_single_query_api_used()
|
|
76
|
-
self.
|
|
81
|
+
self._set_item_internal(key, value)
|
|
77
82
|
self._send_query_param_msg()
|
|
78
83
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
raise StreamlitAPIException(
|
|
82
|
-
f"You cannot set a query params key `{key}` to a dictionary."
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
if key in EMBED_QUERY_PARAMS_KEYS:
|
|
86
|
-
raise StreamlitAPIException(
|
|
87
|
-
"Query param embed and embed_options (case-insensitive) cannot be set programmatically."
|
|
88
|
-
)
|
|
89
|
-
# Type checking users should handle the string serialization themselves
|
|
90
|
-
# We will accept any type for the list and serialize to str just in case
|
|
91
|
-
if isinstance(value, Iterable) and not isinstance(value, str):
|
|
92
|
-
self._query_params[key] = [str(item) for item in value]
|
|
93
|
-
else:
|
|
94
|
-
self._query_params[key] = str(value)
|
|
84
|
+
def _set_item_internal(self, key: str, value: str | Iterable[str]) -> None:
|
|
85
|
+
_set_item_in_dict(self._query_params, key, value)
|
|
95
86
|
|
|
96
87
|
def __delitem__(self, key: str) -> None:
|
|
97
88
|
self._ensure_single_query_api_used()
|
|
98
|
-
if key in EMBED_QUERY_PARAMS_KEYS:
|
|
89
|
+
if key.lower() in EMBED_QUERY_PARAMS_KEYS:
|
|
99
90
|
raise KeyError(missing_key_error_message(key))
|
|
100
91
|
try:
|
|
101
92
|
del self._query_params[key]
|
|
@@ -116,17 +107,17 @@ class QueryParams(MutableMapping[str, str]):
|
|
|
116
107
|
if hasattr(other, "keys") and hasattr(other, "__getitem__"):
|
|
117
108
|
other = cast("SupportsKeysAndGetItem[str, str | Iterable[str]]", other)
|
|
118
109
|
for key in other.keys(): # noqa: SIM118
|
|
119
|
-
self.
|
|
110
|
+
self._set_item_internal(key, other[key])
|
|
120
111
|
else:
|
|
121
112
|
for key, value in other:
|
|
122
|
-
self.
|
|
113
|
+
self._set_item_internal(key, value)
|
|
123
114
|
for key, value in kwds.items():
|
|
124
|
-
self.
|
|
115
|
+
self._set_item_internal(key, value)
|
|
125
116
|
self._send_query_param_msg()
|
|
126
117
|
|
|
127
118
|
def get_all(self, key: str) -> list[str]:
|
|
128
119
|
self._ensure_single_query_api_used()
|
|
129
|
-
if key not in self._query_params or key in EMBED_QUERY_PARAMS_KEYS:
|
|
120
|
+
if key not in self._query_params or key.lower() in EMBED_QUERY_PARAMS_KEYS:
|
|
130
121
|
return []
|
|
131
122
|
value = self._query_params[key]
|
|
132
123
|
return value if isinstance(value, list) else [value]
|
|
@@ -134,7 +125,11 @@ class QueryParams(MutableMapping[str, str]):
|
|
|
134
125
|
def __len__(self) -> int:
|
|
135
126
|
self._ensure_single_query_api_used()
|
|
136
127
|
return len(
|
|
137
|
-
{
|
|
128
|
+
{
|
|
129
|
+
key
|
|
130
|
+
for key in self._query_params
|
|
131
|
+
if key.lower() not in EMBED_QUERY_PARAMS_KEYS
|
|
132
|
+
}
|
|
138
133
|
)
|
|
139
134
|
|
|
140
135
|
def __str__(self) -> str:
|
|
@@ -165,7 +160,7 @@ class QueryParams(MutableMapping[str, str]):
|
|
|
165
160
|
return {
|
|
166
161
|
key: self[key]
|
|
167
162
|
for key in self._query_params
|
|
168
|
-
if key not in EMBED_QUERY_PARAMS_KEYS
|
|
163
|
+
if key.lower() not in EMBED_QUERY_PARAMS_KEYS
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
def from_dict(
|
|
@@ -190,7 +185,7 @@ class QueryParams(MutableMapping[str, str]):
|
|
|
190
185
|
self._query_params = {
|
|
191
186
|
key: value
|
|
192
187
|
for key, value in self._query_params.items()
|
|
193
|
-
if key in EMBED_QUERY_PARAMS_KEYS and preserve_embed
|
|
188
|
+
if key.lower() in EMBED_QUERY_PARAMS_KEYS and preserve_embed
|
|
194
189
|
}
|
|
195
190
|
|
|
196
191
|
def _ensure_single_query_api_used(self) -> None:
|
|
@@ -202,3 +197,59 @@ class QueryParams(MutableMapping[str, str]):
|
|
|
202
197
|
|
|
203
198
|
def missing_key_error_message(key: str) -> str:
|
|
204
199
|
return f'st.query_params has no key "{key}".'
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _set_item_in_dict(
|
|
203
|
+
target_dict: dict[str, list[str] | str], key: str, value: str | Iterable[str]
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Set an item in a dictionary."""
|
|
206
|
+
if isinstance(value, dict):
|
|
207
|
+
raise StreamlitQueryParamDictValueError(key)
|
|
208
|
+
|
|
209
|
+
if key.lower() in EMBED_QUERY_PARAMS_KEYS:
|
|
210
|
+
raise StreamlitAPIException(
|
|
211
|
+
"Query param embed and embed_options (case-insensitive) cannot be set programmatically."
|
|
212
|
+
)
|
|
213
|
+
# Type checking users should handle the string serialization themselves
|
|
214
|
+
# We will accept any type for the list and serialize to str just in case
|
|
215
|
+
if isinstance(value, Iterable) and not isinstance(value, str):
|
|
216
|
+
target_dict[key] = [str(item) for item in value]
|
|
217
|
+
else:
|
|
218
|
+
target_dict[key] = str(value)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def process_query_params(
|
|
222
|
+
query_params: Iterable[tuple[str, str | Iterable[str]]]
|
|
223
|
+
| SupportsKeysAndGetItem[str, str | Iterable[str]],
|
|
224
|
+
) -> str:
|
|
225
|
+
"""Convert query params into a URL-encoded query string."""
|
|
226
|
+
processed_params: dict[str, list[str] | str] = {}
|
|
227
|
+
|
|
228
|
+
if hasattr(query_params, "keys") and hasattr(query_params, "__getitem__"):
|
|
229
|
+
query_params = cast(
|
|
230
|
+
"SupportsKeysAndGetItem[str, str | Iterable[str]]", query_params
|
|
231
|
+
)
|
|
232
|
+
for key in query_params.keys(): # noqa: SIM118
|
|
233
|
+
value = query_params[key]
|
|
234
|
+
_set_item_in_dict(processed_params, key, value)
|
|
235
|
+
else:
|
|
236
|
+
for key, value in query_params:
|
|
237
|
+
if key in processed_params:
|
|
238
|
+
# If the key already exists, we need to accumulate the values.
|
|
239
|
+
if isinstance(value, dict):
|
|
240
|
+
raise StreamlitQueryParamDictValueError(key)
|
|
241
|
+
|
|
242
|
+
current_val = processed_params[key]
|
|
243
|
+
if not isinstance(current_val, list):
|
|
244
|
+
current_val = [current_val]
|
|
245
|
+
|
|
246
|
+
if isinstance(value, Iterable) and not isinstance(value, str):
|
|
247
|
+
current_val.extend([str(item) for item in value])
|
|
248
|
+
else:
|
|
249
|
+
current_val.append(str(value))
|
|
250
|
+
|
|
251
|
+
processed_params[key] = current_val
|
|
252
|
+
else:
|
|
253
|
+
_set_item_in_dict(processed_params, key, value)
|
|
254
|
+
|
|
255
|
+
return parse.urlencode(processed_params, doseq=True)
|
|
@@ -16,7 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import pickle
|
|
19
|
-
from collections.abc import Iterator, KeysView, MutableMapping
|
|
19
|
+
from collections.abc import Iterator, KeysView, Mapping, MutableMapping
|
|
20
20
|
from copy import deepcopy
|
|
21
21
|
from dataclasses import dataclass, field, replace
|
|
22
22
|
from typing import (
|
|
@@ -726,7 +726,7 @@ class SessionState:
|
|
|
726
726
|
|
|
727
727
|
for payload in payloads:
|
|
728
728
|
if isinstance(payload, dict):
|
|
729
|
-
event_name = payload.get("event")
|
|
729
|
+
event_name = cast("Mapping[str, object]", payload).get("event")
|
|
730
730
|
if isinstance(event_name, str) and metadata.callbacks:
|
|
731
731
|
cb = metadata.callbacks.get(event_name)
|
|
732
732
|
if cb is not None:
|
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.CqTPbV5Y.js"></script>
|
|
41
41
|
<link rel="stylesheet" crossorigin href="./static/css/index.BpABIXK9.css">
|
|
42
42
|
</head>
|
|
43
43
|
<body>
|