streamlit 1.45.0__py3-none-any.whl → 1.46.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 +5 -1
- streamlit/auth_util.py +12 -12
- streamlit/cli_util.py +4 -3
- streamlit/column_config.py +11 -9
- streamlit/commands/echo.py +6 -4
- streamlit/commands/execution_control.py +33 -32
- streamlit/commands/experimental_query_params.py +2 -2
- streamlit/commands/logo.py +9 -4
- streamlit/commands/navigation.py +61 -18
- streamlit/commands/page_config.py +57 -47
- streamlit/components/types/base_custom_component.py +7 -7
- streamlit/components/v1/component_registry.py +7 -3
- streamlit/components/v1/components.py +1 -1
- streamlit/components/v1/custom_component.py +8 -8
- streamlit/config.py +289 -144
- streamlit/config_option.py +19 -15
- streamlit/config_util.py +29 -23
- streamlit/connections/__init__.py +2 -2
- streamlit/connections/base_connection.py +5 -5
- streamlit/connections/snowflake_connection.py +13 -11
- streamlit/connections/snowpark_connection.py +3 -3
- streamlit/connections/sql_connection.py +20 -18
- streamlit/connections/util.py +2 -2
- streamlit/cursor.py +6 -6
- streamlit/dataframe_util.py +52 -52
- streamlit/delta_generator.py +46 -48
- streamlit/delta_generator_singletons.py +3 -3
- streamlit/deprecation_util.py +6 -6
- streamlit/elements/alert.py +37 -29
- streamlit/elements/arrow.py +40 -22
- streamlit/elements/code.py +46 -13
- streamlit/elements/deck_gl_json_chart.py +38 -27
- streamlit/elements/dialog_decorator.py +3 -4
- streamlit/elements/doc_string.py +64 -58
- streamlit/elements/exception.py +23 -27
- streamlit/elements/form.py +41 -0
- streamlit/elements/graphviz_chart.py +1 -1
- streamlit/elements/heading.py +60 -9
- streamlit/elements/html.py +3 -4
- streamlit/elements/image.py +8 -9
- streamlit/elements/json.py +21 -2
- streamlit/elements/layouts.py +120 -31
- streamlit/elements/lib/built_in_chart_utils.py +96 -73
- streamlit/elements/lib/color_util.py +3 -3
- streamlit/elements/lib/column_config_utils.py +2 -4
- streamlit/elements/lib/column_types.py +14 -8
- streamlit/elements/lib/dialog.py +9 -5
- streamlit/elements/lib/file_uploader_utils.py +7 -2
- streamlit/elements/lib/image_utils.py +39 -40
- streamlit/elements/lib/js_number.py +4 -4
- streamlit/elements/lib/layout_utils.py +65 -1
- streamlit/elements/lib/mutable_status_container.py +14 -3
- streamlit/elements/lib/options_selector_utils.py +22 -12
- streamlit/elements/lib/pandas_styler_utils.py +25 -21
- streamlit/elements/lib/policies.py +6 -5
- streamlit/elements/lib/streamlit_plotly_theme.py +54 -53
- streamlit/elements/lib/subtitle_utils.py +6 -9
- streamlit/elements/lib/utils.py +20 -5
- streamlit/elements/map.py +32 -56
- streamlit/elements/markdown.py +101 -12
- streamlit/elements/media.py +78 -21
- streamlit/elements/metric.py +32 -16
- streamlit/elements/plotly_chart.py +15 -15
- streamlit/elements/progress.py +33 -15
- streamlit/elements/spinner.py +31 -6
- streamlit/elements/text.py +21 -1
- streamlit/elements/toast.py +1 -2
- streamlit/elements/vega_charts.py +54 -23
- streamlit/elements/widgets/audio_input.py +24 -7
- streamlit/elements/widgets/button.py +26 -19
- streamlit/elements/widgets/button_group.py +10 -15
- streamlit/elements/widgets/camera_input.py +27 -7
- streamlit/elements/widgets/chat.py +91 -38
- streamlit/elements/widgets/checkbox.py +45 -4
- streamlit/elements/widgets/color_picker.py +40 -17
- streamlit/elements/widgets/data_editor.py +76 -37
- streamlit/elements/widgets/file_uploader.py +42 -13
- streamlit/elements/widgets/multiselect.py +7 -10
- streamlit/elements/widgets/number_input.py +123 -47
- streamlit/elements/widgets/radio.py +59 -13
- streamlit/elements/widgets/select_slider.py +35 -30
- streamlit/elements/widgets/selectbox.py +56 -9
- streamlit/elements/widgets/slider.py +190 -99
- streamlit/elements/widgets/text_widgets.py +54 -8
- streamlit/elements/widgets/time_widgets.py +53 -14
- streamlit/elements/write.py +5 -8
- streamlit/env_util.py +2 -7
- streamlit/error_util.py +16 -9
- streamlit/errors.py +69 -48
- streamlit/external/langchain/streamlit_callback_handler.py +10 -5
- streamlit/file_util.py +27 -10
- streamlit/git_util.py +29 -24
- streamlit/hello/animation_demo.py +9 -9
- streamlit/hello/dataframe_demo.py +5 -5
- streamlit/hello/hello.py +1 -0
- streamlit/hello/mapping_demo.py +7 -8
- streamlit/hello/plotting_demo.py +3 -3
- streamlit/hello/streamlit_app.py +28 -26
- streamlit/hello/utils.py +2 -1
- streamlit/logger.py +10 -11
- streamlit/navigation/page.py +11 -8
- streamlit/proto/Audio_pb2.py +4 -3
- streamlit/proto/Audio_pb2.pyi +8 -1
- streamlit/proto/Block_pb2.py +38 -29
- streamlit/proto/Block_pb2.pyi +72 -4
- streamlit/proto/ClientState_pb2.py +4 -4
- streamlit/proto/ClientState_pb2.pyi +7 -2
- streamlit/proto/Code_pb2.py +4 -2
- streamlit/proto/Code_pb2.pyi +1 -0
- streamlit/proto/DataFrame_pb2.pyi +1 -1
- streamlit/proto/DeckGlJsonChart_pb2.pyi +1 -1
- streamlit/proto/Element_pb2.py +5 -3
- streamlit/proto/Element_pb2.pyi +20 -3
- streamlit/proto/GapSize_pb2.py +29 -0
- streamlit/proto/GapSize_pb2.pyi +70 -0
- streamlit/proto/HeightConfig_pb2.py +27 -0
- streamlit/proto/HeightConfig_pb2.pyi +48 -0
- streamlit/proto/NamedDataSet_pb2.pyi +1 -1
- streamlit/proto/Navigation_pb2.py +3 -3
- streamlit/proto/Navigation_pb2.pyi +4 -0
- streamlit/proto/NewSession_pb2.py +18 -16
- streamlit/proto/NewSession_pb2.pyi +29 -3
- streamlit/proto/PageConfig_pb2.py +7 -7
- streamlit/proto/PageConfig_pb2.pyi +21 -1
- streamlit/proto/Video_pb2.py +8 -7
- streamlit/proto/Video_pb2.pyi +8 -1
- streamlit/proto/WidthConfig_pb2.py +2 -2
- streamlit/proto/WidthConfig_pb2.pyi +15 -1
- streamlit/runtime/__init__.py +1 -1
- streamlit/runtime/app_session.py +53 -40
- streamlit/runtime/caching/__init__.py +9 -9
- streamlit/runtime/caching/cache_data_api.py +36 -30
- streamlit/runtime/caching/cache_errors.py +4 -4
- streamlit/runtime/caching/cache_resource_api.py +8 -8
- streamlit/runtime/caching/cache_utils.py +15 -14
- streamlit/runtime/caching/cached_message_replay.py +14 -8
- streamlit/runtime/caching/hashing.py +91 -97
- streamlit/runtime/caching/legacy_cache_api.py +2 -2
- streamlit/runtime/caching/storage/cache_storage_protocol.py +1 -1
- streamlit/runtime/caching/storage/dummy_cache_storage.py +1 -1
- streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py +12 -14
- streamlit/runtime/caching/storage/local_disk_cache_storage.py +6 -6
- streamlit/runtime/connection_factory.py +36 -36
- streamlit/runtime/context.py +58 -9
- streamlit/runtime/credentials.py +29 -40
- streamlit/runtime/forward_msg_queue.py +11 -11
- streamlit/runtime/fragment.py +7 -7
- streamlit/runtime/media_file_manager.py +3 -4
- streamlit/runtime/memory_media_file_storage.py +6 -5
- streamlit/runtime/memory_uploaded_file_manager.py +2 -2
- streamlit/runtime/metrics_util.py +11 -12
- streamlit/runtime/pages_manager.py +4 -6
- streamlit/runtime/runtime.py +8 -6
- streamlit/runtime/runtime_util.py +7 -6
- streamlit/runtime/scriptrunner/__init__.py +4 -4
- streamlit/runtime/scriptrunner/exec_code.py +12 -5
- streamlit/runtime/scriptrunner/magic.py +16 -12
- streamlit/runtime/scriptrunner/script_cache.py +1 -1
- streamlit/runtime/scriptrunner/script_runner.py +55 -34
- streamlit/runtime/scriptrunner_utils/exceptions.py +1 -1
- streamlit/runtime/scriptrunner_utils/script_requests.py +7 -4
- streamlit/runtime/scriptrunner_utils/script_run_context.py +10 -23
- streamlit/runtime/secrets.py +40 -35
- streamlit/runtime/session_manager.py +2 -1
- streamlit/runtime/state/__init__.py +5 -5
- streamlit/runtime/state/common.py +2 -2
- streamlit/runtime/state/query_params.py +13 -15
- streamlit/runtime/state/query_params_proxy.py +17 -13
- streamlit/runtime/state/safe_session_state.py +2 -2
- streamlit/runtime/state/session_state.py +52 -34
- streamlit/runtime/stats.py +2 -2
- streamlit/runtime/uploaded_file_manager.py +1 -1
- streamlit/runtime/websocket_session_manager.py +10 -6
- streamlit/source_util.py +8 -6
- streamlit/static/index.html +3 -17
- streamlit/static/manifest.json +1180 -0
- streamlit/static/static/css/{index.DqDwtg6_.css → index.CJVRHjQZ.css} +1 -1
- streamlit/static/static/js/{ErrorOutline.esm.DyIfDYvY.js → ErrorOutline.esm.6PVAQvlT.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.kF1FCxeJ.js → FileDownload.esm.BZQHC61b.js} +1 -1
- streamlit/static/static/js/{FileHelper.DKt6tIeO.js → FileHelper.Bn1VShMJ.js} +5 -5
- streamlit/static/static/js/{FormClearHelper.DpJR9YCu.js → FormClearHelper.CsFEiTNN.js} +1 -1
- streamlit/static/static/js/{Hooks.BT6PF2Zi.js → Hooks.DguOHQL1.js} +1 -1
- streamlit/static/static/js/{InputInstructions.BmnD4oa3.js → InputInstructions.CTYn2BJQ.js} +1 -1
- streamlit/static/static/js/{ProgressBar.Ch7VNdkM.js → ProgressBar.CPOGBKCi.js} +2 -2
- streamlit/static/static/js/{RenderInPortalIfExists.43tDswzK.js → RenderInPortalIfExists.BYu_CZaF.js} +1 -1
- streamlit/static/static/js/Toolbar.gXKw7ANv.js +1 -0
- streamlit/static/static/js/UploadFileInfo.0DCkpDDf.js +6 -0
- streamlit/static/static/js/{base-input.BjeC3XFX.js → base-input.DBYPj91R.js} +4 -4
- streamlit/static/static/js/{checkbox.DIVN0GOS.js → checkbox.BUm2vnNv.js} +2 -2
- streamlit/static/static/js/{createSuper.CKyBiJe0.js → createSuper.KD4RuZ-W.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.CXpaXRk5.js → data-grid-overlay-editor.CUwpDfvI.js} +1 -1
- streamlit/static/static/js/{downloader.B-uWAyLB.js → downloader.CkDtclup.js} +1 -1
- streamlit/static/static/js/{es6._eGNfJ2i.js → es6.Dlcvh_r0.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.CebQfV1Q.js → iframeResizer.contentWindow.DOXlFfve.js} +1 -1
- streamlit/static/static/js/{index.EbMzSayc.js → index.B0cuGMAB.js} +25 -25
- streamlit/static/static/js/index.BCWTclSV.js +73 -0
- streamlit/static/static/js/index.BJY_fap7.js +1 -0
- streamlit/static/static/js/index.BL3l6dnk.js +1 -0
- streamlit/static/static/js/{index.CpV1hnf8.js → index.BMZzRZjB.js} +1 -1
- streamlit/static/static/js/{index.l6QfBDTC.js → index.BOzUTGDe.js} +1 -1
- streamlit/static/static/js/index.BYI5iO-o.js +1 -0
- streamlit/static/static/js/index.BYo0ywlm.js +783 -0
- streamlit/static/static/js/{index.CuUJHsRK.js → index.BYz9btsY.js} +1 -1
- streamlit/static/static/js/{index.C_nMqHLH.js → index.CCVzQz0Z.js} +2 -2
- streamlit/static/static/js/index.CD6FydK9.js +1 -0
- streamlit/static/static/js/index.CDYEqgC8.js +2 -0
- streamlit/static/static/js/{index.BAdBHmJD.js → index.CMP9c4xA.js} +1 -1
- streamlit/static/static/js/index.CN30QAPD.js +1 -0
- streamlit/static/static/js/{index.CtTgot1Z.js → index.CNqWQkTe.js} +1 -1
- streamlit/static/static/js/index.CaxS67Xz.js +1 -0
- streamlit/static/static/js/{index.HsXxdgGd.js → index.CbsT4sGW.js} +1 -1
- streamlit/static/static/js/index.ChAVlxpQ.js +1 -0
- streamlit/static/static/js/{index.BaYSBSaz.js → index.ClLMMmDd.js} +1 -1
- streamlit/static/static/js/{index.CxxktCLw.js → index.D-O9rQmV.js} +1 -1
- streamlit/static/static/js/{index.Coc8OVG7.js → index.D4k7VZZL.js} +1 -1
- streamlit/static/static/js/index.DLBi0Ar1.js +1 -0
- streamlit/static/static/js/index.DVq5XmJo.js +197 -0
- streamlit/static/static/js/{index.ClX0ambk.js → index.DZKmKXWw.js} +1 -1
- streamlit/static/static/js/index.DkaVx80F.js +1 -0
- streamlit/static/static/js/index.Dr968Klx.js +1 -0
- streamlit/static/static/js/{index.BaozEIL-.js → index.DtUYLn9j.js} +20 -20
- streamlit/static/static/js/index.DwjYSyhs.js +1 -0
- streamlit/static/static/js/index.DzrImxu4.js +1 -0
- streamlit/static/static/js/index.HyGsn4VM.js +1 -0
- streamlit/static/static/js/index.OwxC65od.js +12 -0
- streamlit/static/static/js/index.PZs7VZkC.js +1 -0
- streamlit/static/static/js/index.Voiqpj4q.js +1 -0
- streamlit/static/static/js/index.bSROvR-J.js +3 -0
- streamlit/static/static/js/index.oT9GD3l4.js +1 -0
- streamlit/static/static/js/{index.schmj9D9.js → index.qb-yAPH6.js} +255 -255
- streamlit/static/static/js/index.rJFy_Ygy.js +2 -0
- streamlit/static/static/js/{input.VQEe_bZy.js → input.CwQtEnFN.js} +2 -2
- streamlit/static/static/js/{memory.DGVHab07.js → memory.C5XaFIjR.js} +1 -1
- streamlit/static/static/js/{mergeWith.-RIuUGoA.js → mergeWith.DzwwH6AG.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.wzFLIbEE.js → number-overlay-editor.Dx0XqCkD.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.GIObu6rf.js → possibleConstructorReturn.CVfSu9Ws.js} +1 -1
- streamlit/static/static/js/{sandbox.DpjSeqe2.js → sandbox.BT0gdMXk.js} +1 -1
- streamlit/static/static/js/{textarea.Dz0J9LZe.js → textarea.DNCbrtbM.js} +2 -2
- streamlit/static/static/js/{timepicker.BLgJZnzX.js → timepicker.4UYJD9Ts.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.0OZlxz7U.js → toConsumableArray.DUmnaVWV.js} +1 -1
- streamlit/static/static/js/{uniqueId.DbzplC8D.js → uniqueId.DUvh-GL8.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.COJZng1S.js → useBasicWidgetState.Cwd7-jJa.js} +1 -1
- streamlit/static/static/js/useOnInputChange.DvemQrOM.js +1 -0
- streamlit/static/static/js/{withFullScreenWrapper.CzWvNbvi.js → withFullScreenWrapper.CiQ10ByU.js} +1 -1
- streamlit/static/static/media/SourceCodeVF-Italic.ttf.Ba1oaZG1.woff2 +0 -0
- streamlit/static/static/media/SourceCodeVF-Upright.ttf.BjWn63N-.woff2 +0 -0
- streamlit/static/static/media/SourceSansVF-Italic.ttf.Bt9VkdQ3.woff2 +0 -0
- streamlit/static/static/media/SourceSansVF-Upright.ttf.BsWL4Kly.woff2 +0 -0
- streamlit/static/static/media/SourceSerifVariable-Italic.ttf.CVdzAtxO.woff2 +0 -0
- streamlit/static/static/media/SourceSerifVariable-Roman.ttf.mdpVL9bi.woff2 +0 -0
- streamlit/string_util.py +14 -19
- streamlit/temporary_directory.py +13 -4
- streamlit/testing/v1/app_test.py +15 -10
- streamlit/testing/v1/element_tree.py +157 -178
- streamlit/testing/v1/local_script_runner.py +11 -15
- streamlit/testing/v1/util.py +11 -4
- streamlit/type_util.py +8 -12
- streamlit/url_util.py +1 -1
- streamlit/user_info.py +6 -5
- streamlit/util.py +25 -1
- streamlit/vendor/pympler/asizeof.py +3 -2
- streamlit/watcher/event_based_path_watcher.py +15 -1
- streamlit/watcher/folder_black_list.py +2 -2
- streamlit/watcher/local_sources_watcher.py +64 -18
- streamlit/watcher/path_watcher.py +6 -10
- streamlit/watcher/polling_path_watcher.py +8 -7
- streamlit/watcher/util.py +7 -6
- streamlit/web/bootstrap.py +16 -14
- streamlit/web/cli.py +52 -45
- streamlit/web/server/__init__.py +7 -3
- streamlit/web/server/app_static_file_handler.py +1 -1
- streamlit/web/server/authlib_tornado_integration.py +9 -4
- streamlit/web/server/browser_websocket_handler.py +8 -2
- streamlit/web/server/component_request_handler.py +14 -10
- streamlit/web/server/media_file_handler.py +14 -7
- streamlit/web/server/oauth_authlib_routes.py +41 -9
- streamlit/web/server/oidc_mixin.py +35 -17
- streamlit/web/server/routes.py +32 -22
- streamlit/web/server/server.py +6 -18
- streamlit/web/server/server_util.py +19 -9
- streamlit/web/server/stats_request_handler.py +7 -5
- streamlit/web/server/upload_file_request_handler.py +22 -19
- streamlit/web/server/websocket_headers.py +1 -1
- {streamlit-1.45.0.dist-info → streamlit-1.46.0.dist-info}/METADATA +4 -4
- streamlit-1.46.0.dist-info/RECORD +559 -0
- {streamlit-1.45.0.dist-info → streamlit-1.46.0.dist-info}/WHEEL +1 -1
- streamlit/elements/lib/event_utils.py +0 -39
- streamlit/static/static/js/Toolbar.HTGsjfCP.js +0 -1
- streamlit/static/static/js/UploadFileInfo.C-jY39rj.js +0 -1
- streamlit/static/static/js/index.B5TWFN5r.js +0 -1
- streamlit/static/static/js/index.BI9-p_-s.js +0 -3
- streamlit/static/static/js/index.BYHnDZYn.js +0 -1
- streamlit/static/static/js/index.B_M97aPz.js +0 -1
- streamlit/static/static/js/index.BdN5swP0.js +0 -1
- streamlit/static/static/js/index.BfCAp_Sj.js +0 -1
- streamlit/static/static/js/index.BqDl3eRM.js +0 -779
- streamlit/static/static/js/index.C0VtYb3T.js +0 -2
- streamlit/static/static/js/index.C1qW_Owy.js +0 -1
- streamlit/static/static/js/index.C4AcBARa.js +0 -1
- streamlit/static/static/js/index.CCOiJRk1.js +0 -1
- streamlit/static/static/js/index.COvpza5W.js +0 -1
- streamlit/static/static/js/index.C_9qGjbK.js +0 -2
- streamlit/static/static/js/index.CqISBfsc.js +0 -197
- streamlit/static/static/js/index.D1WOs2Ce.js +0 -1
- streamlit/static/static/js/index.DQljs-9e.js +0 -1
- streamlit/static/static/js/index.DZqX4P_2.js +0 -1
- streamlit/static/static/js/index.DajIfBOb.js +0 -1
- streamlit/static/static/js/index.DuOXre0H.js +0 -1
- streamlit/static/static/js/index.EWD98YhP.js +0 -1
- streamlit/static/static/js/index.LaZloCTl.js +0 -73
- streamlit/static/static/js/index.S_1klBoy.js +0 -1
- streamlit/static/static/js/index.b0Gf958T.js +0 -12
- streamlit/static/static/js/index.t--hEgTQ.js +0 -6
- streamlit/static/static/js/useOnInputChange.CgOwAHyw.js +0 -1
- streamlit/static/static/media/SourceCodePro-Bold.CFEfr7-q.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-BoldItalic.C-LkFXxa.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-Italic.CxFOx7N-.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-Regular.CBOlD63d.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-SemiBold.CFHwW3Wd.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-SemiBoldItalic.Cg2yRu82.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-Bold.-6c9oR8J.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-BoldItalic.DmM_grLY.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-Italic.I1ipWe7Q.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-Regular.DZLUzqI4.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-SemiBold.sKQIyTMz.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-SemiBoldItalic.C0wP0icr.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-Bold.8TUnKj4x.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-BoldItalic.CBVO7Ve7.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-Italic.DkFgL2HZ.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-Regular.CNJNET2S.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-SemiBold.CHyh9GC5.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-SemiBoldItalic.CBtz8sWN.woff2 +0 -0
- streamlit-1.45.0.dist-info/RECORD +0 -568
- {streamlit-1.45.0.data → streamlit-1.46.0.data}/scripts/streamlit.cmd +0 -0
- {streamlit-1.45.0.dist-info → streamlit-1.46.0.dist-info}/entry_points.txt +0 -0
- {streamlit-1.45.0.dist-info → streamlit-1.46.0.dist-info}/top_level.txt +0 -0
|
@@ -62,6 +62,8 @@ from streamlit.runtime.state import (
|
|
|
62
62
|
from streamlit.source_util import page_sort_key
|
|
63
63
|
|
|
64
64
|
if TYPE_CHECKING:
|
|
65
|
+
from collections.abc import Generator
|
|
66
|
+
|
|
65
67
|
from streamlit.runtime.fragment import FragmentStorage
|
|
66
68
|
from streamlit.runtime.scriptrunner.script_cache import ScriptCache
|
|
67
69
|
from streamlit.runtime.uploaded_file_manager import UploadedFileManager
|
|
@@ -121,36 +123,35 @@ it in the future.
|
|
|
121
123
|
# is designed to leverage our original v1 version of multi-page apps. This
|
|
122
124
|
# function will be called to run the script in lieu of the main script. This
|
|
123
125
|
# function simulates the v1 setup using the modern v2 commands (st.navigation)
|
|
124
|
-
def _mpa_v1(main_script_path: str):
|
|
126
|
+
def _mpa_v1(main_script_path: str) -> None:
|
|
125
127
|
from pathlib import Path
|
|
126
128
|
|
|
127
129
|
from streamlit.commands.navigation import PageType, _navigation
|
|
128
130
|
from streamlit.navigation.page import StreamlitPage
|
|
129
131
|
|
|
130
132
|
# Select the folder that should be used for the pages:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
resolved_main_script_path: Final = Path(main_script_path).resolve()
|
|
134
|
+
pages_folder: Final = resolved_main_script_path.parent / "pages"
|
|
133
135
|
|
|
134
136
|
# Read out the my_pages folder and create a page for every script:
|
|
135
|
-
pages = PAGES_FOLDER.glob("*.py")
|
|
136
137
|
pages = sorted(
|
|
137
138
|
[
|
|
138
139
|
page
|
|
139
|
-
for page in
|
|
140
|
+
for page in pages_folder.glob("*.py")
|
|
140
141
|
if page.name.endswith(".py")
|
|
141
142
|
and not page.name.startswith(".")
|
|
142
|
-
and
|
|
143
|
+
and page.name != "__init__.py"
|
|
143
144
|
],
|
|
144
145
|
key=page_sort_key,
|
|
145
146
|
)
|
|
146
147
|
|
|
147
148
|
# Use this script as the main page and
|
|
148
|
-
main_page = StreamlitPage(
|
|
149
|
+
main_page = StreamlitPage(resolved_main_script_path, default=True)
|
|
149
150
|
all_pages = [main_page] + [
|
|
150
|
-
StreamlitPage(
|
|
151
|
+
StreamlitPage(pages_folder / page.name) for page in pages
|
|
151
152
|
]
|
|
152
153
|
# Initialize the navigation with all the pages:
|
|
153
|
-
position: Literal["sidebar", "hidden"] = (
|
|
154
|
+
position: Literal["sidebar", "hidden", "top"] = (
|
|
154
155
|
"hidden"
|
|
155
156
|
if config.get_option("client.showSidebarNavigation") is False
|
|
156
157
|
else "sidebar"
|
|
@@ -161,11 +162,7 @@ def _mpa_v1(main_script_path: str):
|
|
|
161
162
|
expanded=False,
|
|
162
163
|
)
|
|
163
164
|
|
|
164
|
-
|
|
165
|
-
# Only run the page if it is not pointing to this script:
|
|
166
|
-
page.run()
|
|
167
|
-
# Finish the script execution here to only run the selected page
|
|
168
|
-
raise StopException()
|
|
165
|
+
page.run()
|
|
169
166
|
|
|
170
167
|
|
|
171
168
|
class ScriptRunner:
|
|
@@ -180,7 +177,7 @@ class ScriptRunner:
|
|
|
180
177
|
user_info: dict[str, str | bool | None],
|
|
181
178
|
fragment_storage: FragmentStorage,
|
|
182
179
|
pages_manager: PagesManager,
|
|
183
|
-
):
|
|
180
|
+
) -> None:
|
|
184
181
|
"""Initialize the ScriptRunner.
|
|
185
182
|
|
|
186
183
|
(The ScriptRunner won't start executing until start() is called.)
|
|
@@ -265,7 +262,7 @@ class ScriptRunner:
|
|
|
265
262
|
# _maybe_handle_execution_control_request.
|
|
266
263
|
self._execing = False
|
|
267
264
|
|
|
268
|
-
# This is initialized in start()
|
|
265
|
+
# This is initialized in the start() method
|
|
269
266
|
self._script_thread: threading.Thread | None = None
|
|
270
267
|
|
|
271
268
|
def __repr__(self) -> str:
|
|
@@ -301,7 +298,7 @@ class ScriptRunner:
|
|
|
301
298
|
|
|
302
299
|
"""
|
|
303
300
|
if self._script_thread is not None:
|
|
304
|
-
raise
|
|
301
|
+
raise RuntimeError("ScriptRunner was already started")
|
|
305
302
|
|
|
306
303
|
self._script_thread = threading.Thread(
|
|
307
304
|
target=self._run_script_thread,
|
|
@@ -325,7 +322,10 @@ class ScriptRunner:
|
|
|
325
322
|
If there is no ScriptRunContext for the current thread.
|
|
326
323
|
|
|
327
324
|
"""
|
|
328
|
-
|
|
325
|
+
if not self._is_in_script_thread():
|
|
326
|
+
raise RuntimeError(
|
|
327
|
+
"ScriptRunner._get_script_run_ctx must be called from the script thread."
|
|
328
|
+
)
|
|
329
329
|
|
|
330
330
|
ctx = get_script_run_ctx()
|
|
331
331
|
if ctx is None:
|
|
@@ -345,7 +345,10 @@ class ScriptRunner:
|
|
|
345
345
|
When the ScriptRequestQueue is empty, or when a SHUTDOWN request is
|
|
346
346
|
dequeued, this function will exit and its thread will terminate.
|
|
347
347
|
"""
|
|
348
|
-
|
|
348
|
+
if not self._is_in_script_thread():
|
|
349
|
+
raise RuntimeError(
|
|
350
|
+
"ScriptRunner._run_script_thread must be called from the script thread."
|
|
351
|
+
)
|
|
349
352
|
|
|
350
353
|
_LOGGER.debug("Beginning script thread")
|
|
351
354
|
|
|
@@ -375,13 +378,18 @@ class ScriptRunner:
|
|
|
375
378
|
self._run_script(request.rerun_data)
|
|
376
379
|
request = self._requests.on_scriptrunner_ready()
|
|
377
380
|
|
|
378
|
-
|
|
381
|
+
if request.type != ScriptRequestType.STOP:
|
|
382
|
+
raise RuntimeError(
|
|
383
|
+
f"Unrecognized ScriptRequestType: {request.type}. This should never happen."
|
|
384
|
+
)
|
|
379
385
|
|
|
380
386
|
# Send a SHUTDOWN event before exiting, so some state can be saved
|
|
381
387
|
# for use in a future script run when not triggered by the client.
|
|
382
388
|
client_state = ClientState()
|
|
383
389
|
client_state.query_string = ctx.query_string
|
|
384
390
|
client_state.page_script_hash = ctx.page_script_hash
|
|
391
|
+
if ctx.context_info:
|
|
392
|
+
client_state.context_info.CopyFrom(ctx.context_info)
|
|
385
393
|
self.on_event.send(
|
|
386
394
|
self, event=ScriptRunnerEvent.SHUTDOWN, client_state=client_state
|
|
387
395
|
)
|
|
@@ -437,11 +445,14 @@ class ScriptRunner:
|
|
|
437
445
|
if request.type == ScriptRequestType.RERUN:
|
|
438
446
|
raise RerunException(request.rerun_data)
|
|
439
447
|
|
|
440
|
-
|
|
448
|
+
if request.type != ScriptRequestType.STOP:
|
|
449
|
+
raise RuntimeError(
|
|
450
|
+
f"Unrecognized ScriptRequestType: {request.type}. This should never happen."
|
|
451
|
+
)
|
|
441
452
|
raise StopException()
|
|
442
453
|
|
|
443
454
|
@contextmanager
|
|
444
|
-
def _set_execing_flag(self):
|
|
455
|
+
def _set_execing_flag(self) -> Generator[None, None, None]:
|
|
445
456
|
"""A context for setting the ScriptRunner._execing flag.
|
|
446
457
|
|
|
447
458
|
Used by _maybe_handle_execution_control_request to ensure that
|
|
@@ -465,7 +476,10 @@ class ScriptRunner:
|
|
|
465
476
|
|
|
466
477
|
"""
|
|
467
478
|
|
|
468
|
-
|
|
479
|
+
if not self._is_in_script_thread():
|
|
480
|
+
raise RuntimeError(
|
|
481
|
+
"ScriptRunner._run_script must be called from the script thread."
|
|
482
|
+
)
|
|
469
483
|
|
|
470
484
|
# An explicit loop instead of recursion to avoid stack overflows
|
|
471
485
|
while True:
|
|
@@ -484,7 +498,7 @@ class ScriptRunner:
|
|
|
484
498
|
rerun_data.page_script_hash, rerun_data.page_name
|
|
485
499
|
)
|
|
486
500
|
active_script = self._pages_manager.get_initial_active_script(
|
|
487
|
-
rerun_data.page_script_hash
|
|
501
|
+
rerun_data.page_script_hash
|
|
488
502
|
)
|
|
489
503
|
main_page_info = self._pages_manager.get_main_page()
|
|
490
504
|
|
|
@@ -589,7 +603,12 @@ class ScriptRunner:
|
|
|
589
603
|
# assume is the main script directory.
|
|
590
604
|
module.__dict__["__file__"] = script_path
|
|
591
605
|
|
|
592
|
-
def code_to_exec(
|
|
606
|
+
def code_to_exec(
|
|
607
|
+
code: str = code,
|
|
608
|
+
module: types.ModuleType = module,
|
|
609
|
+
ctx: ScriptRunContext = ctx,
|
|
610
|
+
rerun_data: RerunData = rerun_data,
|
|
611
|
+
) -> None:
|
|
593
612
|
with (
|
|
594
613
|
modified_sys_path(self._main_script_path),
|
|
595
614
|
self._set_execing_flag(),
|
|
@@ -610,7 +629,7 @@ class ScriptRunner:
|
|
|
610
629
|
)
|
|
611
630
|
wrapped_fragment()
|
|
612
631
|
|
|
613
|
-
except FragmentStorageKeyError:
|
|
632
|
+
except FragmentStorageKeyError: # noqa: PERF203
|
|
614
633
|
# This can happen if the fragment_id is removed from the
|
|
615
634
|
# storage before the script runner gets to it. In this
|
|
616
635
|
# case, the fragment is simply skipped.
|
|
@@ -622,20 +641,21 @@ class ScriptRunner:
|
|
|
622
641
|
# (see https://github.com/streamlit/streamlit/issues/9080).
|
|
623
642
|
if not rerun_data.is_auto_rerun:
|
|
624
643
|
_LOGGER.warning(
|
|
625
|
-
|
|
644
|
+
"Couldn't find fragment with id %s."
|
|
626
645
|
" This can happen if the fragment does not"
|
|
627
646
|
" exist anymore when this request is processed,"
|
|
628
647
|
" for example because a full app rerun happened"
|
|
629
648
|
" that did not register the fragment."
|
|
630
649
|
" Usually this doesn't happen or no action is"
|
|
631
|
-
" required, so its mainly for debugging."
|
|
650
|
+
" required, so its mainly for debugging.",
|
|
651
|
+
fragment_id,
|
|
632
652
|
)
|
|
633
|
-
except (RerunException, StopException)
|
|
653
|
+
except (RerunException, StopException):
|
|
634
654
|
# The wrapped_fragment function is executed
|
|
635
655
|
# inside of a exec_func_with_error_handling call, so
|
|
636
656
|
# there is a correct handler for these exceptions.
|
|
637
|
-
raise
|
|
638
|
-
except Exception:
|
|
657
|
+
raise
|
|
658
|
+
except Exception: # noqa: S110
|
|
639
659
|
# Ignore exceptions raised by fragments here as we don't
|
|
640
660
|
# want to stop the execution of other fragments. The
|
|
641
661
|
# error itself is already rendered within the wrapped
|
|
@@ -645,7 +665,8 @@ class ScriptRunner:
|
|
|
645
665
|
else:
|
|
646
666
|
if PagesManager.uses_pages_directory:
|
|
647
667
|
_mpa_v1(self._main_script_path)
|
|
648
|
-
|
|
668
|
+
else:
|
|
669
|
+
exec(code, module.__dict__) # noqa: S102
|
|
649
670
|
self._fragment_storage.clear(
|
|
650
671
|
new_fragment_ids=ctx.new_fragment_ids
|
|
651
672
|
)
|
|
@@ -742,7 +763,7 @@ def _clean_problem_modules() -> None:
|
|
|
742
763
|
try:
|
|
743
764
|
keras = sys.modules["keras"]
|
|
744
765
|
keras.backend.clear_session()
|
|
745
|
-
except Exception:
|
|
766
|
+
except Exception: # noqa: S110
|
|
746
767
|
# We don't want to crash the app if we can't clear the Keras session.
|
|
747
768
|
pass
|
|
748
769
|
|
|
@@ -750,7 +771,7 @@ def _clean_problem_modules() -> None:
|
|
|
750
771
|
try:
|
|
751
772
|
plt = sys.modules["matplotlib.pyplot"]
|
|
752
773
|
plt.close("all")
|
|
753
|
-
except Exception:
|
|
774
|
+
except Exception: # noqa: S110
|
|
754
775
|
# We don't want to crash the app if we can't close matplotlib
|
|
755
776
|
pass
|
|
756
777
|
|
|
@@ -34,7 +34,7 @@ class StopException(ScriptControlException):
|
|
|
34
34
|
class RerunException(ScriptControlException):
|
|
35
35
|
"""Silently stop and rerun the user's script."""
|
|
36
36
|
|
|
37
|
-
def __init__(self, rerun_data: RerunData):
|
|
37
|
+
def __init__(self, rerun_data: RerunData) -> None:
|
|
38
38
|
"""Construct a RerunException.
|
|
39
39
|
|
|
40
40
|
Parameters
|
|
@@ -111,9 +111,9 @@ def _coalesce_widget_states(
|
|
|
111
111
|
"""
|
|
112
112
|
if not old_states and not new_states:
|
|
113
113
|
return None
|
|
114
|
-
|
|
114
|
+
if not old_states:
|
|
115
115
|
return new_states
|
|
116
|
-
|
|
116
|
+
if not new_states:
|
|
117
117
|
return old_states
|
|
118
118
|
|
|
119
119
|
states_by_id: dict[str, WidgetState] = {
|
|
@@ -161,7 +161,7 @@ class ScriptRequests:
|
|
|
161
161
|
ScriptRunner handles those requests.
|
|
162
162
|
"""
|
|
163
163
|
|
|
164
|
-
def __init__(self):
|
|
164
|
+
def __init__(self) -> None:
|
|
165
165
|
self._lock = threading.Lock()
|
|
166
166
|
self._state = ScriptRequestType.CONTINUE
|
|
167
167
|
self._rerun_data = RerunData()
|
|
@@ -284,7 +284,10 @@ class ScriptRequests:
|
|
|
284
284
|
self._state = ScriptRequestType.CONTINUE
|
|
285
285
|
return ScriptRequest(ScriptRequestType.RERUN, self._rerun_data)
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
if self._state != ScriptRequestType.STOP:
|
|
288
|
+
raise RuntimeError(
|
|
289
|
+
f"Unrecognized ScriptRunnerState: {self._state}. This should never happen."
|
|
290
|
+
)
|
|
288
291
|
return ScriptRequest(ScriptRequestType.STOP)
|
|
289
292
|
|
|
290
293
|
def on_scriptrunner_ready(self) -> ScriptRequest:
|
|
@@ -33,7 +33,6 @@ from typing_extensions import TypeAlias
|
|
|
33
33
|
from streamlit.errors import (
|
|
34
34
|
NoSessionContext,
|
|
35
35
|
StreamlitAPIException,
|
|
36
|
-
StreamlitSetPageConfigMustBeFirstCommandError,
|
|
37
36
|
)
|
|
38
37
|
from streamlit.logger import get_logger
|
|
39
38
|
from streamlit.runtime.forward_msg_cache import (
|
|
@@ -42,6 +41,7 @@ from streamlit.runtime.forward_msg_cache import (
|
|
|
42
41
|
)
|
|
43
42
|
|
|
44
43
|
if TYPE_CHECKING:
|
|
44
|
+
from collections.abc import Generator
|
|
45
45
|
from pathlib import Path
|
|
46
46
|
|
|
47
47
|
from streamlit.cursor import RunningCursor
|
|
@@ -97,7 +97,6 @@ class ScriptRunContext:
|
|
|
97
97
|
command_tracking_deactivated: bool = False
|
|
98
98
|
tracked_commands: list[Command] = field(default_factory=list)
|
|
99
99
|
tracked_commands_counter: Counter[str] = field(default_factory=collections.Counter)
|
|
100
|
-
_set_page_config_allowed: bool = True
|
|
101
100
|
_has_script_started: bool = False
|
|
102
101
|
widget_ids_this_run: set[str] = field(default_factory=set)
|
|
103
102
|
widget_user_keys_this_run: set[str] = field(default_factory=set)
|
|
@@ -107,6 +106,7 @@ class ScriptRunContext:
|
|
|
107
106
|
current_fragment_id: str | None = None
|
|
108
107
|
fragment_ids_this_run: list[str] | None = None
|
|
109
108
|
new_fragment_ids: set[str] = field(default_factory=set)
|
|
109
|
+
in_fragment_callback: bool = False
|
|
110
110
|
_active_script_hash: str = ""
|
|
111
111
|
# we allow only one dialog to be open at the same time
|
|
112
112
|
has_dialog_opened: bool = False
|
|
@@ -116,11 +116,11 @@ class ScriptRunContext:
|
|
|
116
116
|
_production_query_params_used = False
|
|
117
117
|
|
|
118
118
|
@property
|
|
119
|
-
def page_script_hash(self):
|
|
119
|
+
def page_script_hash(self) -> str:
|
|
120
120
|
return self.pages_manager.current_page_script_hash
|
|
121
121
|
|
|
122
122
|
@property
|
|
123
|
-
def active_script_hash(self):
|
|
123
|
+
def active_script_hash(self) -> str:
|
|
124
124
|
return self._active_script_hash
|
|
125
125
|
|
|
126
126
|
@property
|
|
@@ -128,7 +128,7 @@ class ScriptRunContext:
|
|
|
128
128
|
return self.pages_manager.main_script_parent
|
|
129
129
|
|
|
130
130
|
@contextlib.contextmanager
|
|
131
|
-
def run_with_active_hash(self, page_hash: str):
|
|
131
|
+
def run_with_active_hash(self, page_hash: str) -> Generator[None, None, None]:
|
|
132
132
|
original_page_hash = self._active_script_hash
|
|
133
133
|
self._active_script_hash = page_hash
|
|
134
134
|
try:
|
|
@@ -137,7 +137,7 @@ class ScriptRunContext:
|
|
|
137
137
|
# in the event of any exception, ensure we set the active hash back
|
|
138
138
|
self._active_script_hash = original_page_hash
|
|
139
139
|
|
|
140
|
-
def set_mpa_v2_page(self, page_script_hash: str):
|
|
140
|
+
def set_mpa_v2_page(self, page_script_hash: str) -> None:
|
|
141
141
|
self._active_script_hash = self.pages_manager.main_script_hash
|
|
142
142
|
self.pages_manager.set_current_page_script_hash(page_script_hash)
|
|
143
143
|
|
|
@@ -157,8 +157,6 @@ class ScriptRunContext:
|
|
|
157
157
|
self.context_info = context_info
|
|
158
158
|
self.pages_manager.set_current_page_script_hash(page_script_hash)
|
|
159
159
|
self._active_script_hash = self.pages_manager.main_script_hash
|
|
160
|
-
# Permit set_page_config when the ScriptRunContext is reused on a rerun
|
|
161
|
-
self._set_page_config_allowed = True
|
|
162
160
|
self._has_script_started = False
|
|
163
161
|
self.command_tracking_deactivated: bool = False
|
|
164
162
|
self.tracked_commands = []
|
|
@@ -188,17 +186,6 @@ class ScriptRunContext:
|
|
|
188
186
|
|
|
189
187
|
def enqueue(self, msg: ForwardMsg) -> None:
|
|
190
188
|
"""Enqueue a ForwardMsg for this context's session."""
|
|
191
|
-
if msg.HasField("page_config_changed") and not self._set_page_config_allowed:
|
|
192
|
-
raise StreamlitSetPageConfigMustBeFirstCommandError()
|
|
193
|
-
|
|
194
|
-
# We want to disallow set_page config if one of the following occurs:
|
|
195
|
-
# - set_page_config was called on this message
|
|
196
|
-
# - The script has already started and a different st call occurs (a delta)
|
|
197
|
-
if msg.HasField("page_config_changed") or (
|
|
198
|
-
msg.HasField("delta") and self._has_script_started
|
|
199
|
-
):
|
|
200
|
-
self._set_page_config_allowed = False
|
|
201
|
-
|
|
202
189
|
msg.metadata.active_script_hash = self.active_script_hash
|
|
203
190
|
|
|
204
191
|
# We populate the hash and cacheable field for all messages.
|
|
@@ -217,7 +204,7 @@ class ScriptRunContext:
|
|
|
217
204
|
# Pass the message up to our associated ScriptRunner.
|
|
218
205
|
self._enqueue(msg_to_send)
|
|
219
206
|
|
|
220
|
-
def ensure_single_query_api_used(self):
|
|
207
|
+
def ensure_single_query_api_used(self) -> None:
|
|
221
208
|
if self._experimental_query_params_used and self._production_query_params_used:
|
|
222
209
|
raise StreamlitAPIException(
|
|
223
210
|
"Using `st.query_params` together with either `st.experimental_get_query_params` "
|
|
@@ -225,11 +212,11 @@ class ScriptRunContext:
|
|
|
225
212
|
" convert your app to only use `st.query_params`"
|
|
226
213
|
)
|
|
227
214
|
|
|
228
|
-
def mark_experimental_query_params_used(self):
|
|
215
|
+
def mark_experimental_query_params_used(self) -> None:
|
|
229
216
|
self._experimental_query_params_used = True
|
|
230
217
|
self.ensure_single_query_api_used()
|
|
231
218
|
|
|
232
|
-
def mark_production_query_params_used(self):
|
|
219
|
+
def mark_production_query_params_used(self) -> None:
|
|
233
220
|
self._production_query_params_used = True
|
|
234
221
|
self.ensure_single_query_api_used()
|
|
235
222
|
|
|
@@ -239,7 +226,7 @@ SCRIPT_RUN_CONTEXT_ATTR_NAME: Final = "streamlit_script_run_ctx"
|
|
|
239
226
|
|
|
240
227
|
def add_script_run_ctx(
|
|
241
228
|
thread: threading.Thread | None = None, ctx: ScriptRunContext | None = None
|
|
242
|
-
):
|
|
229
|
+
) -> threading.Thread:
|
|
243
230
|
"""Adds the current ScriptRunContext to a newly-created thread.
|
|
244
231
|
|
|
245
232
|
This should be called from this thread's parent thread,
|
streamlit/runtime/secrets.py
CHANGED
|
@@ -27,9 +27,8 @@ from typing import (
|
|
|
27
27
|
|
|
28
28
|
from blinker import Signal
|
|
29
29
|
|
|
30
|
-
import streamlit as st
|
|
31
30
|
import streamlit.watcher.path_watcher
|
|
32
|
-
from streamlit import runtime
|
|
31
|
+
from streamlit import config, runtime
|
|
33
32
|
from streamlit.errors import StreamlitSecretNotFoundError
|
|
34
33
|
from streamlit.logger import get_logger
|
|
35
34
|
|
|
@@ -37,34 +36,40 @@ _LOGGER: Final = get_logger(__name__)
|
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
class SecretErrorMessages:
|
|
40
|
-
"""SecretErrorMessages stores all error messages we use for secrets to allow customization
|
|
39
|
+
"""SecretErrorMessages stores all error messages we use for secrets to allow customization
|
|
40
|
+
for different environments.
|
|
41
|
+
|
|
41
42
|
For example Streamlit Cloud can customize the message to be different than the open source.
|
|
42
43
|
|
|
43
44
|
For internal use, may change in future releases without notice.
|
|
44
45
|
"""
|
|
45
46
|
|
|
46
|
-
def __init__(self):
|
|
47
|
-
self.missing_attr_message = lambda attr_name: (
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
self.missing_attr_message: Callable[[str], str] = lambda attr_name: (
|
|
48
49
|
f'st.secrets has no attribute "{attr_name}". '
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
"Did you forget to add it to secrets.toml, mount it to secret directory, or the app settings "
|
|
51
|
+
"on Streamlit Cloud? More info: "
|
|
52
|
+
"https://docs.streamlit.io/deploy/streamlit-community-cloud/deploy-your-app/secrets-management"
|
|
51
53
|
)
|
|
52
|
-
self.missing_key_message = lambda key: (
|
|
54
|
+
self.missing_key_message: Callable[[str], str] = lambda key: (
|
|
53
55
|
f'st.secrets has no key "{key}". '
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
"Did you forget to add it to secrets.toml, mount it to secret directory, or the app settings "
|
|
57
|
+
"on Streamlit Cloud? More info: "
|
|
58
|
+
"https://docs.streamlit.io/deploy/streamlit-community-cloud/deploy-your-app/secrets-management"
|
|
56
59
|
)
|
|
57
|
-
self.no_secrets_found = lambda file_paths: (
|
|
60
|
+
self.no_secrets_found: Callable[[list[str]], str] = lambda file_paths: (
|
|
58
61
|
f"No secrets found. Valid paths for a secrets.toml file or secret directories are: {', '.join(file_paths)}"
|
|
59
62
|
)
|
|
60
|
-
self.error_parsing_file_at_path = (
|
|
63
|
+
self.error_parsing_file_at_path: Callable[[str, Exception], str] = (
|
|
61
64
|
lambda path, ex: f"Error parsing secrets file at {path}: {ex}"
|
|
62
65
|
)
|
|
63
|
-
self.subfolder_path_is_not_a_folder
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
self.subfolder_path_is_not_a_folder: Callable[[str], str] = (
|
|
67
|
+
lambda sub_folder_path: (
|
|
68
|
+
f"{sub_folder_path} is not a folder. "
|
|
69
|
+
"To use directory based secrets, mount every secret in a subfolder under the secret directory"
|
|
70
|
+
)
|
|
66
71
|
)
|
|
67
|
-
self.invalid_secret_path = lambda path: (
|
|
72
|
+
self.invalid_secret_path: Callable[[str], str] = lambda path: (
|
|
68
73
|
f"Invalid secrets path: {path}: path is not a .toml file or a directory"
|
|
69
74
|
)
|
|
70
75
|
|
|
@@ -144,15 +149,14 @@ class AttrDict(Mapping[str, Any]):
|
|
|
144
149
|
to provide dot access to nested secrets.
|
|
145
150
|
"""
|
|
146
151
|
|
|
147
|
-
def __init__(self, value):
|
|
152
|
+
def __init__(self, value: Mapping[str, Any]) -> None:
|
|
148
153
|
self.__dict__["__nested_secrets__"] = dict(value)
|
|
149
154
|
|
|
150
155
|
@staticmethod
|
|
151
|
-
def _maybe_wrap_in_attr_dict(value) -> Any:
|
|
156
|
+
def _maybe_wrap_in_attr_dict(value: Any) -> Any:
|
|
152
157
|
if not isinstance(value, Mapping):
|
|
153
158
|
return value
|
|
154
|
-
|
|
155
|
-
return AttrDict(value)
|
|
159
|
+
return AttrDict(value)
|
|
156
160
|
|
|
157
161
|
def __len__(self) -> int:
|
|
158
162
|
return len(self.__nested_secrets__)
|
|
@@ -174,13 +178,13 @@ class AttrDict(Mapping[str, Any]):
|
|
|
174
178
|
except KeyError:
|
|
175
179
|
raise AttributeError(_missing_attr_error_message(attr_name))
|
|
176
180
|
|
|
177
|
-
def __repr__(self):
|
|
181
|
+
def __repr__(self) -> str:
|
|
178
182
|
return repr(self.__nested_secrets__)
|
|
179
183
|
|
|
180
|
-
def __setitem__(self, key, value) -> NoReturn:
|
|
184
|
+
def __setitem__(self, key: str, value: Any) -> NoReturn:
|
|
181
185
|
raise TypeError("Secrets does not support item assignment.")
|
|
182
186
|
|
|
183
|
-
def __setattr__(self, key, value) -> NoReturn:
|
|
187
|
+
def __setattr__(self, key: str, value: Any) -> NoReturn:
|
|
184
188
|
raise TypeError("Secrets does not support attribute assignment.")
|
|
185
189
|
|
|
186
190
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -194,7 +198,7 @@ class Secrets(Mapping[str, Any]):
|
|
|
194
198
|
Safe to use from multiple threads.
|
|
195
199
|
"""
|
|
196
200
|
|
|
197
|
-
def __init__(self):
|
|
201
|
+
def __init__(self) -> None:
|
|
198
202
|
# Our secrets dict.
|
|
199
203
|
self._secrets: Mapping[str, Any] | None = None
|
|
200
204
|
self._lock = threading.RLock()
|
|
@@ -272,7 +276,8 @@ class Secrets(Mapping[str, Any]):
|
|
|
272
276
|
return secrets, found_secrets_file
|
|
273
277
|
|
|
274
278
|
def _parse_directory(self, path: str) -> tuple[Mapping[str, Any], bool]:
|
|
275
|
-
"""Parse a directory for secrets. Directory style can be used to support Kubernetes secrets that are
|
|
279
|
+
"""Parse a directory for secrets. Directory style can be used to support Kubernetes secrets that are
|
|
280
|
+
mounted to folders.
|
|
276
281
|
|
|
277
282
|
Example structure:
|
|
278
283
|
- top_level_secret_folder
|
|
@@ -310,7 +315,7 @@ class Secrets(Mapping[str, Any]):
|
|
|
310
315
|
|
|
311
316
|
if len(sub_secrets) == 1:
|
|
312
317
|
# if there's just one file, collapse it so it's directly under `dirname`
|
|
313
|
-
secrets[dirname] = sub_secrets[
|
|
318
|
+
secrets[dirname] = sub_secrets[next(iter(sub_secrets.keys()))]
|
|
314
319
|
else:
|
|
315
320
|
secrets[dirname] = sub_secrets
|
|
316
321
|
|
|
@@ -356,7 +361,7 @@ class Secrets(Mapping[str, Any]):
|
|
|
356
361
|
|
|
357
362
|
secrets = {}
|
|
358
363
|
|
|
359
|
-
file_paths =
|
|
364
|
+
file_paths = config.get_option("secrets.files")
|
|
360
365
|
found_secrets_file = False
|
|
361
366
|
for path in file_paths:
|
|
362
367
|
path_secrets, found_secrets_file_in_path = self._parse_file_path(path)
|
|
@@ -380,7 +385,9 @@ class Secrets(Mapping[str, Any]):
|
|
|
380
385
|
return self._secrets
|
|
381
386
|
|
|
382
387
|
def to_dict(self) -> dict[str, Any]:
|
|
383
|
-
"""Converts the secrets store into a nested dictionary, where nested AttrDict objects are
|
|
388
|
+
"""Converts the secrets store into a nested dictionary, where nested AttrDict objects are
|
|
389
|
+
also converted into dictionaries.
|
|
390
|
+
"""
|
|
384
391
|
secrets = self._parse()
|
|
385
392
|
return _convert_to_dict(secrets)
|
|
386
393
|
|
|
@@ -407,7 +414,7 @@ class Secrets(Mapping[str, Any]):
|
|
|
407
414
|
if self._file_watchers_installed:
|
|
408
415
|
return
|
|
409
416
|
|
|
410
|
-
file_paths =
|
|
417
|
+
file_paths = config.get_option("secrets.files")
|
|
411
418
|
for path in file_paths:
|
|
412
419
|
try:
|
|
413
420
|
if path.endswith(".toml"):
|
|
@@ -422,7 +429,7 @@ class Secrets(Mapping[str, Any]):
|
|
|
422
429
|
self._on_secrets_changed,
|
|
423
430
|
watcher_type="poll",
|
|
424
431
|
)
|
|
425
|
-
except FileNotFoundError:
|
|
432
|
+
except FileNotFoundError: # noqa: PERF203
|
|
426
433
|
# A user may only have one secrets.toml file defined, so we'd expect
|
|
427
434
|
# FileNotFoundErrors to be raised when attempting to install a
|
|
428
435
|
# watcher on the nonexistent ones.
|
|
@@ -432,7 +439,7 @@ class Secrets(Mapping[str, Any]):
|
|
|
432
439
|
# failed to avoid repeatedly trying to install it.
|
|
433
440
|
self._file_watchers_installed = True
|
|
434
441
|
|
|
435
|
-
def _on_secrets_changed(self, changed_file_path) -> None:
|
|
442
|
+
def _on_secrets_changed(self, changed_file_path: str) -> None:
|
|
436
443
|
with self._lock:
|
|
437
444
|
_LOGGER.debug("Secret path %s changed, reloading", changed_file_path)
|
|
438
445
|
self._reset()
|
|
@@ -452,8 +459,7 @@ class Secrets(Mapping[str, Any]):
|
|
|
452
459
|
value = self._parse()[key]
|
|
453
460
|
if not isinstance(value, Mapping):
|
|
454
461
|
return value
|
|
455
|
-
|
|
456
|
-
return AttrDict(value)
|
|
462
|
+
return AttrDict(value)
|
|
457
463
|
# We add FileNotFoundError since __getattr__ is expected to only raise
|
|
458
464
|
# AttributeError. Without handling FileNotFoundError, unittests.mocks
|
|
459
465
|
# fails during mock creation on Python3.9
|
|
@@ -470,8 +476,7 @@ class Secrets(Mapping[str, Any]):
|
|
|
470
476
|
value = self._parse()[key]
|
|
471
477
|
if not isinstance(value, Mapping):
|
|
472
478
|
return value
|
|
473
|
-
|
|
474
|
-
return AttrDict(value)
|
|
479
|
+
return AttrDict(value)
|
|
475
480
|
except KeyError:
|
|
476
481
|
raise KeyError(_missing_key_error_message(key))
|
|
477
482
|
|
|
@@ -72,7 +72,8 @@ class SessionInfo:
|
|
|
72
72
|
return self.client is not None
|
|
73
73
|
|
|
74
74
|
def to_active(self) -> ActiveSessionInfo:
|
|
75
|
-
|
|
75
|
+
if not self.is_active():
|
|
76
|
+
raise RuntimeError("A SessionInfo with no client cannot be active!")
|
|
76
77
|
|
|
77
78
|
# NOTE: The cast here (rather than copying this SessionInfo's fields into a new
|
|
78
79
|
# ActiveSessionInfo) is important as the Runtime expects to be able to mutate
|
|
@@ -27,15 +27,15 @@ from streamlit.runtime.state.session_state_proxy import (
|
|
|
27
27
|
from streamlit.runtime.state.widgets import register_widget
|
|
28
28
|
|
|
29
29
|
__all__ = [
|
|
30
|
-
"
|
|
31
|
-
"WidgetCallback",
|
|
32
|
-
"WidgetKwargs",
|
|
30
|
+
"SCRIPT_RUN_WITHOUT_ERRORS_KEY",
|
|
33
31
|
"QueryParamsProxy",
|
|
34
32
|
"SafeSessionState",
|
|
35
|
-
"SCRIPT_RUN_WITHOUT_ERRORS_KEY",
|
|
36
33
|
"SessionState",
|
|
37
|
-
"SessionStateStatProvider",
|
|
38
34
|
"SessionStateProxy",
|
|
35
|
+
"SessionStateStatProvider",
|
|
36
|
+
"WidgetArgs",
|
|
37
|
+
"WidgetCallback",
|
|
38
|
+
"WidgetKwargs",
|
|
39
39
|
"get_session_state",
|
|
40
40
|
"register_widget",
|
|
41
41
|
]
|
|
@@ -51,7 +51,7 @@ WidgetCallback: TypeAlias = Callable[..., None]
|
|
|
51
51
|
# WidgetState proto, and returns a regular python value. A serializer
|
|
52
52
|
# receives a regular python value, and returns something suitable for
|
|
53
53
|
# a value field on WidgetState proto. They should be inverses.
|
|
54
|
-
WidgetDeserializer: TypeAlias = Callable[[Any
|
|
54
|
+
WidgetDeserializer: TypeAlias = Callable[[Any], T]
|
|
55
55
|
WidgetSerializer: TypeAlias = Callable[[T], Any]
|
|
56
56
|
|
|
57
57
|
# The array value field names are part of the larger set of possible value
|
|
@@ -156,7 +156,7 @@ class RegisterWidgetResult(Generic[T_co]):
|
|
|
156
156
|
"""The canonical way to construct a RegisterWidgetResult in cases
|
|
157
157
|
where the true widget value could not be determined.
|
|
158
158
|
"""
|
|
159
|
-
return cls(value=deserializer(None
|
|
159
|
+
return cls(value=deserializer(None), value_changed=False)
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
def user_key_from_element_id(element_id: str) -> str | None:
|