streamlit 1.45.1__py3-none-any.whl → 1.46.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 +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/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 +53 -29
- 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.DU9IrB3M.js → ErrorOutline.esm.DitPpe1Y.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.P9rKwKo8.js → FileDownload.esm.AI3watX9.js} +1 -1
- streamlit/static/static/js/{FileHelper.D7RMkx0e.js → FileHelper.kt7mhnu8.js} +5 -5
- streamlit/static/static/js/{FormClearHelper.B67tgll0.js → FormClearHelper.D1M9GM_c.js} +1 -1
- streamlit/static/static/js/{Hooks.ncTJktu9.js → Hooks.BGwHKeUc.js} +1 -1
- streamlit/static/static/js/{InputInstructions.D-Y8geDN.js → InputInstructions.DaZ89mzH.js} +1 -1
- streamlit/static/static/js/{ProgressBar.B-kexwwD.js → ProgressBar.C0zPMe-p.js} +2 -2
- streamlit/static/static/js/{RenderInPortalIfExists.BgaoZgep.js → RenderInPortalIfExists.Ox8gQvdz.js} +1 -1
- streamlit/static/static/js/Toolbar.KhlcEc0K.js +1 -0
- streamlit/static/static/js/UploadFileInfo.0DCkpDDf.js +6 -0
- streamlit/static/static/js/{base-input.BoAa1U94.js → base-input.BJ4qsfSq.js} +4 -4
- streamlit/static/static/js/{checkbox.Z6iSfe5F.js → checkbox.DSDh78Xz.js} +2 -2
- streamlit/static/static/js/{createSuper.B4oGDYRm.js → createSuper.wQ9SIXEJ.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.msYws2Ou.js → data-grid-overlay-editor.DvbdPJ15.js} +1 -1
- streamlit/static/static/js/{downloader.kc14n2Hv.js → downloader.CD9rzih5.js} +1 -1
- streamlit/static/static/js/{es6.CxQz807-.js → es6.48Q9Qjgb.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.B19u0ONI.js → iframeResizer.contentWindow.CKdem3Bn.js} +1 -1
- streamlit/static/static/js/{index.LaIasviC.js → index.6md5Qhod.js} +1 -1
- streamlit/static/static/js/index.7hy6AeJ1.js +1 -0
- streamlit/static/static/js/index.B4CGJiBW.js +1 -0
- streamlit/static/static/js/index.B8oW0ZTD.js +1 -0
- streamlit/static/static/js/index.BU6RnlHI.js +73 -0
- streamlit/static/static/js/index.BUq9Wcf8.js +197 -0
- streamlit/static/static/js/{index.BFz9U2y0.js → index.BXXo-Yoj.js} +1 -1
- streamlit/static/static/js/index.Bae9H0OS.js +1 -0
- streamlit/static/static/js/{index.-5ruC9At.js → index.BhTl2Uyb.js} +1 -1
- streamlit/static/static/js/{index.BpILzHf_.js → index.BiSaCB1o.js} +20 -20
- streamlit/static/static/js/{index.xNQq3Ei5.js → index.BulSAJ9z.js} +1 -1
- streamlit/static/static/js/{index.9V1KdxfP.js → index.Bv-EuTKR.js} +1 -1
- streamlit/static/static/js/index.BvMLYCHi.js +1 -0
- streamlit/static/static/js/index.C1NIn1Y2.js +783 -0
- streamlit/static/static/js/index.CP-fthOJ.js +2 -0
- streamlit/static/static/js/{index.BoigZiu7.js → index.CS9guO3p.js} +1 -1
- streamlit/static/static/js/index.CYTBHth8.js +1 -0
- streamlit/static/static/js/{index.CmTAF0dM.js → index.CcJufcuD.js} +1 -1
- streamlit/static/static/js/index.CnENU1yn.js +1 -0
- streamlit/static/static/js/index.Cns13qBb.js +1 -0
- streamlit/static/static/js/index.Ct_xXq7w.js +1 -0
- streamlit/static/static/js/{index.BqfdT8-Q.js → index.CxGSemHL.js} +1 -1
- streamlit/static/static/js/index.D5S0ldVb.js +1 -0
- streamlit/static/static/js/index.D72B_ksb.js +2 -0
- streamlit/static/static/js/index.DI4yZ27M.js +1 -0
- streamlit/static/static/js/index.DN51vLxR.js +1 -0
- streamlit/static/static/js/index.DRtq5dka.js +1 -0
- streamlit/static/static/js/{index.BHXxWdde.js → index.DX-oiXlb.js} +1 -1
- streamlit/static/static/js/index.DlFE4_Aq.js +12 -0
- streamlit/static/static/js/{index.BHGGDa8K.js → index.J7BJwXOi.js} +2 -2
- streamlit/static/static/js/index.Jg38kJPP.js +1 -0
- streamlit/static/static/js/index.JhIO6abf.js +3 -0
- streamlit/static/static/js/{index.DeB9iKFW.js → index.NkRcWwc5.js} +255 -255
- streamlit/static/static/js/{index.BGga-hcS.js → index.prekPLrm.js} +25 -25
- streamlit/static/static/js/{index.BRXmLIsC.js → index.wyzngKUE.js} +1 -1
- streamlit/static/static/js/index.xW7mVdI8.js +1 -0
- streamlit/static/static/js/index.yk07dYGx.js +1 -0
- streamlit/static/static/js/{input.DsCfafm0.js → input.CxKZ5Wrc.js} +2 -2
- streamlit/static/static/js/{memory.nY_lMTtu.js → memory.DeZ9VUvl.js} +1 -1
- streamlit/static/static/js/{mergeWith.B_7zmsM4.js → mergeWith.CVkhrWUb.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.CSeVhHRU.js → number-overlay-editor.Bpkm3nTq.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.nNhsvgRd.js → possibleConstructorReturn.CIDCId52.js} +1 -1
- streamlit/static/static/js/{sandbox.Cgm3iuL6.js → sandbox.TrkMaokR.js} +1 -1
- streamlit/static/static/js/{textarea.BR8rlyih.js → textarea.QKjxR64N.js} +2 -2
- streamlit/static/static/js/{timepicker.w4XhAenH.js → timepicker.DJYmE1dK.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.CgkEPBwD.js → toConsumableArray.BZoworE-.js} +1 -1
- streamlit/static/static/js/{uniqueId.j-1rlNNH.js → uniqueId.O0UbJ2Bu.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.zXY9CjFS.js → useBasicWidgetState.Ci89jaH5.js} +1 -1
- streamlit/static/static/js/useOnInputChange.Cxh6ExEn.js +1 -0
- streamlit/static/static/js/{withFullScreenWrapper.Ov13692o.js → withFullScreenWrapper.iW37lS8Z.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 +21 -2
- 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 +13 -24
- streamlit/web/server/server_util.py +43 -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.1.dist-info → streamlit-1.46.1.dist-info}/METADATA +4 -4
- streamlit-1.46.1.dist-info/RECORD +559 -0
- {streamlit-1.45.1.dist-info → streamlit-1.46.1.dist-info}/WHEEL +1 -1
- streamlit/elements/lib/event_utils.py +0 -39
- streamlit/static/static/js/Toolbar.D9RUZv9G.js +0 -1
- streamlit/static/static/js/UploadFileInfo.C-jY39rj.js +0 -1
- streamlit/static/static/js/index.8jhZBWF2.js +0 -3
- streamlit/static/static/js/index.BCx3C6e_.js +0 -1
- streamlit/static/static/js/index.BRuTz_S4.js +0 -1
- streamlit/static/static/js/index.Bcru_ti-.js +0 -1
- streamlit/static/static/js/index.Bl1FMJRd.js +0 -1
- streamlit/static/static/js/index.C1z8KpLA.js +0 -779
- streamlit/static/static/js/index.C32I2PUe.js +0 -2
- streamlit/static/static/js/index.C5GnDRB7.js +0 -1
- streamlit/static/static/js/index.CG4qPaaW.js +0 -2
- streamlit/static/static/js/index.C_msmT1u.js +0 -1
- streamlit/static/static/js/index.CbeNTdd6.js +0 -1
- streamlit/static/static/js/index.CnGQVJcw.js +0 -12
- streamlit/static/static/js/index.CopVVq4l.js +0 -1
- streamlit/static/static/js/index.CtXupx4d.js +0 -197
- streamlit/static/static/js/index.DGmCchO7.js +0 -1
- streamlit/static/static/js/index.DH6zBk0e.js +0 -1
- streamlit/static/static/js/index.DHVlVWsm.js +0 -1
- streamlit/static/static/js/index.DRKIVBoi.js +0 -1
- streamlit/static/static/js/index.DUd-lFXx.js +0 -73
- streamlit/static/static/js/index.D_uRBA4B.js +0 -1
- streamlit/static/static/js/index.QHNfgPJd.js +0 -1
- streamlit/static/static/js/index.a-RJocYL.js +0 -1
- streamlit/static/static/js/index.cvz4B1gy.js +0 -1
- streamlit/static/static/js/index.t--hEgTQ.js +0 -6
- streamlit/static/static/js/useOnInputChange.z04u96A8.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.1.dist-info/RECORD +0 -568
- {streamlit-1.45.1.data → streamlit-1.46.1.data}/scripts/streamlit.cmd +0 -0
- {streamlit-1.45.1.dist-info → streamlit-1.46.1.dist-info}/entry_points.txt +0 -0
- {streamlit-1.45.1.dist-info → streamlit-1.46.1.dist-info}/top_level.txt +0 -0
|
@@ -43,12 +43,13 @@ class LocalScriptRunner(ScriptRunner):
|
|
|
43
43
|
script_path: str,
|
|
44
44
|
session_state: SafeSessionState,
|
|
45
45
|
pages_manager: PagesManager,
|
|
46
|
-
args=None,
|
|
47
|
-
kwargs=None,
|
|
48
|
-
):
|
|
46
|
+
args: Any = None,
|
|
47
|
+
kwargs: Any = None,
|
|
48
|
+
) -> None:
|
|
49
49
|
"""Initializes the ScriptRunner for the given script_path."""
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
if not os.path.isfile(script_path):
|
|
52
|
+
raise FileNotFoundError(f"File not found at {script_path}")
|
|
52
53
|
|
|
53
54
|
self.forward_msg_queue = ForwardMsgQueue()
|
|
54
55
|
self.script_path = script_path
|
|
@@ -73,13 +74,12 @@ class LocalScriptRunner(ScriptRunner):
|
|
|
73
74
|
self.event_data: list[Any] = []
|
|
74
75
|
|
|
75
76
|
def record_event(
|
|
76
|
-
sender: ScriptRunner | None, event: ScriptRunnerEvent, **kwargs
|
|
77
|
+
sender: ScriptRunner | None, event: ScriptRunnerEvent, **kwargs: Any
|
|
77
78
|
) -> None:
|
|
78
79
|
# Assert that we're not getting unexpected `sender` params
|
|
79
80
|
# from ScriptRunner.on_event
|
|
80
|
-
|
|
81
|
-
"Unexpected ScriptRunnerEvent sender!"
|
|
82
|
-
)
|
|
81
|
+
if sender is not None and sender != self:
|
|
82
|
+
raise RuntimeError("Unexpected ScriptRunnerEvent sender!")
|
|
83
83
|
|
|
84
84
|
self.events.append(event)
|
|
85
85
|
self.event_data.append(kwargs)
|
|
@@ -103,7 +103,7 @@ class LocalScriptRunner(ScriptRunner):
|
|
|
103
103
|
def run(
|
|
104
104
|
self,
|
|
105
105
|
widget_state: WidgetStates | None = None,
|
|
106
|
-
query_params=None,
|
|
106
|
+
query_params: dict[str, Any] | None = None,
|
|
107
107
|
timeout: float = 3,
|
|
108
108
|
page_hash: str = "",
|
|
109
109
|
) -> ElementTree:
|
|
@@ -127,14 +127,10 @@ class LocalScriptRunner(ScriptRunner):
|
|
|
127
127
|
self.start()
|
|
128
128
|
require_widgets_deltas(self, timeout)
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
return tree
|
|
130
|
+
return parse_tree_from_messages(self.forward_msgs())
|
|
132
131
|
|
|
133
132
|
def script_stopped(self) -> bool:
|
|
134
|
-
for e in self.events
|
|
135
|
-
if e == ScriptRunnerEvent.SHUTDOWN:
|
|
136
|
-
return True
|
|
137
|
-
return False
|
|
133
|
+
return any(e == ScriptRunnerEvent.SHUTDOWN for e in self.events)
|
|
138
134
|
|
|
139
135
|
def _on_script_finished(
|
|
140
136
|
self, ctx: ScriptRunContext, event: ScriptRunnerEvent, premature_stop: bool
|
streamlit/testing/v1/util.py
CHANGED
|
@@ -15,13 +15,18 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
from contextlib import contextmanager
|
|
18
|
-
from typing import Any
|
|
18
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
19
19
|
|
|
20
20
|
from streamlit import config
|
|
21
21
|
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Generator
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
@contextmanager
|
|
24
|
-
def patch_config_options(
|
|
27
|
+
def patch_config_options(
|
|
28
|
+
config_overrides: dict[str, Any],
|
|
29
|
+
) -> Generator[None, None, None]:
|
|
25
30
|
"""A context manager that overrides config options. It can
|
|
26
31
|
also be used as a function decorator.
|
|
27
32
|
|
|
@@ -43,10 +48,12 @@ def patch_config_options(config_overrides: dict[str, Any]):
|
|
|
43
48
|
yield
|
|
44
49
|
|
|
45
50
|
|
|
46
|
-
def build_mock_config_get_option(
|
|
51
|
+
def build_mock_config_get_option(
|
|
52
|
+
overrides_dict: dict[str, Any],
|
|
53
|
+
) -> Callable[[str], Any]:
|
|
47
54
|
orig_get_option = config.get_option
|
|
48
55
|
|
|
49
|
-
def mock_config_get_option(name):
|
|
56
|
+
def mock_config_get_option(name: str) -> Any:
|
|
50
57
|
if name in overrides_dict:
|
|
51
58
|
return overrides_dict[name]
|
|
52
59
|
return orig_get_option(name)
|
streamlit/type_util.py
CHANGED
|
@@ -49,7 +49,7 @@ from streamlit.errors import StreamlitAPIException
|
|
|
49
49
|
|
|
50
50
|
if TYPE_CHECKING:
|
|
51
51
|
import graphviz
|
|
52
|
-
import sympy
|
|
52
|
+
import sympy
|
|
53
53
|
from plotly.graph_objs import Figure
|
|
54
54
|
from pydeck import Deck
|
|
55
55
|
|
|
@@ -119,8 +119,7 @@ def is_type(obj: object, fqn_type_pattern: str | re.Pattern[str]) -> bool:
|
|
|
119
119
|
fqn_type = get_fqn_type(obj)
|
|
120
120
|
if isinstance(fqn_type_pattern, str):
|
|
121
121
|
return fqn_type_pattern == fqn_type
|
|
122
|
-
|
|
123
|
-
return fqn_type_pattern.match(fqn_type) is not None
|
|
122
|
+
return fqn_type_pattern.match(fqn_type) is not None
|
|
124
123
|
|
|
125
124
|
|
|
126
125
|
def _is_type_instance(obj: object, type_to_check: str) -> bool:
|
|
@@ -161,7 +160,7 @@ def to_bytes(obj: BytesLike) -> bytes:
|
|
|
161
160
|
"""
|
|
162
161
|
if isinstance(obj, bytearray):
|
|
163
162
|
return bytes(obj)
|
|
164
|
-
|
|
163
|
+
if isinstance(obj, bytes):
|
|
165
164
|
return obj
|
|
166
165
|
|
|
167
166
|
raise RuntimeError(f"{obj} is not convertible to bytes")
|
|
@@ -232,10 +231,10 @@ def is_graphviz_chart(
|
|
|
232
231
|
) -> TypeGuard[graphviz.Graph | graphviz.Digraph]:
|
|
233
232
|
"""True if input looks like a GraphViz chart."""
|
|
234
233
|
return (
|
|
235
|
-
# GraphViz < 0.18
|
|
234
|
+
# In GraphViz < 0.18
|
|
236
235
|
is_type(obj, "graphviz.dot.Graph")
|
|
237
236
|
or is_type(obj, "graphviz.dot.Digraph")
|
|
238
|
-
# GraphViz >= 0.18
|
|
237
|
+
# In GraphViz >= 0.18
|
|
239
238
|
or is_type(obj, "graphviz.graphs.Graph")
|
|
240
239
|
or is_type(obj, "graphviz.graphs.Digraph")
|
|
241
240
|
or is_type(obj, "graphviz.sources.Source")
|
|
@@ -263,16 +262,13 @@ def _is_probably_plotly_dict(obj: object) -> TypeGuard[dict[str, Any]]:
|
|
|
263
262
|
if len(obj.keys()) == 0:
|
|
264
263
|
return False
|
|
265
264
|
|
|
266
|
-
if any(k not in ["config", "data", "frames", "layout"] for k in obj
|
|
265
|
+
if any(k not in ["config", "data", "frames", "layout"] for k in obj):
|
|
267
266
|
return False
|
|
268
267
|
|
|
269
268
|
if any(_is_plotly_obj(v) for v in obj.values()):
|
|
270
269
|
return True
|
|
271
270
|
|
|
272
|
-
|
|
273
|
-
return True
|
|
274
|
-
|
|
275
|
-
return False
|
|
271
|
+
return bool(any(_is_list_of_plotly_objs(v) for v in obj.values()))
|
|
276
272
|
|
|
277
273
|
|
|
278
274
|
def is_delta_generator(obj: object) -> TypeGuard[DeltaGenerator]:
|
|
@@ -317,7 +313,7 @@ def is_pydeck(obj: object) -> TypeGuard[Deck]:
|
|
|
317
313
|
return is_type(obj, "pydeck.bindings.deck.Deck")
|
|
318
314
|
|
|
319
315
|
|
|
320
|
-
def is_pydantic_model(obj) -> bool:
|
|
316
|
+
def is_pydantic_model(obj: object) -> bool:
|
|
321
317
|
"""True if input looks like a Pydantic model instance."""
|
|
322
318
|
|
|
323
319
|
if isinstance(obj, type):
|
streamlit/url_util.py
CHANGED
streamlit/user_info.py
CHANGED
|
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
from collections.abc import Iterator, Mapping
|
|
18
18
|
from typing import (
|
|
19
19
|
TYPE_CHECKING,
|
|
20
|
+
Any,
|
|
20
21
|
Final,
|
|
21
22
|
NoReturn,
|
|
22
23
|
Union,
|
|
@@ -358,7 +359,7 @@ def _get_user_info() -> UserInfo:
|
|
|
358
359
|
ctx = _get_script_run_ctx()
|
|
359
360
|
if ctx is None:
|
|
360
361
|
_LOGGER.warning(
|
|
361
|
-
"No script run context available. st.
|
|
362
|
+
"No script run context available. st.user will return an empty dictionary."
|
|
362
363
|
)
|
|
363
364
|
return {}
|
|
364
365
|
|
|
@@ -486,13 +487,13 @@ class UserInfoProxy(Mapping[str, Union[str, bool, None]]):
|
|
|
486
487
|
try:
|
|
487
488
|
return _get_user_info()[key]
|
|
488
489
|
except KeyError:
|
|
489
|
-
raise KeyError(f'st.
|
|
490
|
+
raise KeyError(f'st.user has no key "{key}".')
|
|
490
491
|
|
|
491
492
|
def __getattr__(self, key: str) -> str | bool | None:
|
|
492
493
|
try:
|
|
493
494
|
return _get_user_info()[key]
|
|
494
495
|
except KeyError:
|
|
495
|
-
raise AttributeError(f'st.
|
|
496
|
+
raise AttributeError(f'st.user has no attribute "{key}".')
|
|
496
497
|
|
|
497
498
|
def __setattr__(self, name: str, value: str | None) -> NoReturn:
|
|
498
499
|
raise StreamlitAPIException("st.user cannot be modified")
|
|
@@ -548,10 +549,10 @@ class DeprecatedUserInfoProxy(UserInfoProxy):
|
|
|
548
549
|
Streamlit.
|
|
549
550
|
"""
|
|
550
551
|
|
|
551
|
-
def __getattribute__(self, name: str):
|
|
552
|
+
def __getattribute__(self, name: str) -> Any:
|
|
552
553
|
maybe_show_deprecated_user_warning()
|
|
553
554
|
return super().__getattribute__(name)
|
|
554
555
|
|
|
555
|
-
def __getitem__(self, key: str):
|
|
556
|
+
def __getitem__(self, key: str) -> Any:
|
|
556
557
|
maybe_show_deprecated_user_warning()
|
|
557
558
|
return super().__getitem__(key)
|
streamlit/util.py
CHANGED
|
@@ -27,7 +27,7 @@ def memoize(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
27
27
|
result: list[Any] = []
|
|
28
28
|
|
|
29
29
|
@functools.wraps(func)
|
|
30
|
-
def wrapped_func():
|
|
30
|
+
def wrapped_func() -> Any:
|
|
31
31
|
if not result:
|
|
32
32
|
result.append(func())
|
|
33
33
|
return result[0]
|
|
@@ -70,3 +70,27 @@ def calc_md5(s: bytes | str) -> str:
|
|
|
70
70
|
|
|
71
71
|
h.update(b)
|
|
72
72
|
return h.hexdigest()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AttributeDictionary(dict[Any, Any]):
|
|
76
|
+
"""
|
|
77
|
+
A dictionary subclass that supports attribute-style access.
|
|
78
|
+
|
|
79
|
+
This class extends the functionality of a standard dictionary to allow items
|
|
80
|
+
to be accessed via attribute-style dot notation in addition to the traditional
|
|
81
|
+
key-based access. If a dictionary item is accessed and is itself a dictionary,
|
|
82
|
+
it is automatically wrapped in another `AttributeDictionary`, enabling recursive
|
|
83
|
+
attribute-style access.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __getattr__(self, key: str) -> Any:
|
|
87
|
+
try:
|
|
88
|
+
item = self.__getitem__(key)
|
|
89
|
+
return AttributeDictionary(item) if isinstance(item, dict) else item
|
|
90
|
+
except KeyError as err:
|
|
91
|
+
raise AttributeError(
|
|
92
|
+
f"'{type(self).__name__}' object has no attribute '{key}'"
|
|
93
|
+
) from err
|
|
94
|
+
|
|
95
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
96
|
+
self[name] = value
|
|
@@ -172,6 +172,7 @@ details.
|
|
|
172
172
|
|
|
173
173
|
import sys
|
|
174
174
|
import types as Types
|
|
175
|
+
from typing import Any
|
|
175
176
|
import warnings
|
|
176
177
|
import weakref as Weakref
|
|
177
178
|
from inspect import isbuiltin, isclass, iscode, isframe, isfunction, ismethod, ismodule
|
|
@@ -192,7 +193,7 @@ _ignored_modules = {
|
|
|
192
193
|
"types",
|
|
193
194
|
Exception.__module__, # 'weakref'
|
|
194
195
|
__name__,
|
|
195
|
-
} #
|
|
196
|
+
} # including this very module
|
|
196
197
|
|
|
197
198
|
# Sizes of some primitive C types
|
|
198
199
|
# XXX len(pack(T, 0)) == Struct(T).size == calcsize(T)
|
|
@@ -2586,7 +2587,7 @@ def asized(*objs, **opts):
|
|
|
2586
2587
|
return t
|
|
2587
2588
|
|
|
2588
2589
|
|
|
2589
|
-
def asizeof(*objs, **opts):
|
|
2590
|
+
def asizeof(*objs: Any, **opts: Any) -> int:
|
|
2590
2591
|
"""Return the combined size (in bytes) of all objects passed
|
|
2591
2592
|
as positional arguments.
|
|
2592
2593
|
|
|
@@ -243,7 +243,7 @@ class WatchedPath:
|
|
|
243
243
|
*, # keyword-only arguments:
|
|
244
244
|
glob_pattern: str | None = None,
|
|
245
245
|
allow_nonexistent: bool = False,
|
|
246
|
-
):
|
|
246
|
+
) -> None:
|
|
247
247
|
self.md5 = md5
|
|
248
248
|
self.modification_time = modification_time
|
|
249
249
|
|
|
@@ -359,7 +359,26 @@ class _FolderEventHandler(events.FileSystemEventHandler):
|
|
|
359
359
|
|
|
360
360
|
abs_changed_path = os.path.abspath(changed_path)
|
|
361
361
|
|
|
362
|
-
|
|
362
|
+
# To prevent a race condition, we hold a lock while accessing
|
|
363
|
+
# _watched_paths.
|
|
364
|
+
with self._lock:
|
|
365
|
+
# First check if the exact path is being watched
|
|
366
|
+
changed_path_info = self._watched_paths.get(abs_changed_path, None)
|
|
367
|
+
|
|
368
|
+
# If the exact path isn't found, check if it's inside any watched
|
|
369
|
+
# directories. This is necessary for the folder watching feature to
|
|
370
|
+
# detect changes to files within watched directories, not just the
|
|
371
|
+
# directories themselves.
|
|
372
|
+
if changed_path_info is None:
|
|
373
|
+
for path, info in self._watched_paths.items():
|
|
374
|
+
if (
|
|
375
|
+
os.path.isdir(path)
|
|
376
|
+
and os.path.commonpath([path, abs_changed_path]) == path
|
|
377
|
+
):
|
|
378
|
+
changed_path_info = info
|
|
379
|
+
break
|
|
380
|
+
|
|
381
|
+
# If we still haven't found a matching path, ignore this event
|
|
363
382
|
if changed_path_info is None:
|
|
364
383
|
_LOGGER.debug(
|
|
365
384
|
"Ignoring changed path %s.\nWatched_paths: %s",
|
|
@@ -46,7 +46,7 @@ class FolderBlackList:
|
|
|
46
46
|
|
|
47
47
|
"""
|
|
48
48
|
|
|
49
|
-
def __init__(self, folder_blacklist):
|
|
49
|
+
def __init__(self, folder_blacklist: list[str]) -> None:
|
|
50
50
|
"""Constructor.
|
|
51
51
|
|
|
52
52
|
Parameters
|
|
@@ -67,7 +67,7 @@ class FolderBlackList:
|
|
|
67
67
|
def __repr__(self) -> str:
|
|
68
68
|
return util.repr_(self)
|
|
69
69
|
|
|
70
|
-
def is_blacklisted(self, filepath):
|
|
70
|
+
def is_blacklisted(self, filepath: str) -> bool:
|
|
71
71
|
"""Test if filepath is in the blacklist.
|
|
72
72
|
|
|
73
73
|
Parameters
|
|
@@ -46,11 +46,12 @@ PathWatcher = None
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
class LocalSourcesWatcher:
|
|
49
|
-
def __init__(self, pages_manager: PagesManager):
|
|
49
|
+
def __init__(self, pages_manager: PagesManager) -> None:
|
|
50
50
|
self._pages_manager = pages_manager
|
|
51
51
|
self._main_script_path = os.path.abspath(self._pages_manager.main_script_path)
|
|
52
|
+
self._watch_folders = config.get_option("server.folderWatchList")
|
|
52
53
|
self._script_folder = os.path.dirname(self._main_script_path)
|
|
53
|
-
self.
|
|
54
|
+
self._on_path_changed: list[Callable[[str], None]] = []
|
|
54
55
|
self._is_closed = False
|
|
55
56
|
self._cached_sys_modules: set[str] = set()
|
|
56
57
|
|
|
@@ -79,6 +80,21 @@ class LocalSourcesWatcher:
|
|
|
79
80
|
module_name=None,
|
|
80
81
|
)
|
|
81
82
|
|
|
83
|
+
# Add custom watch path if it exists
|
|
84
|
+
|
|
85
|
+
for watch_folder in self._watch_folders:
|
|
86
|
+
# check if it is folder
|
|
87
|
+
if not os.path.isdir(watch_folder):
|
|
88
|
+
_LOGGER.warning("Watch folder is not a directory: %s", watch_folder)
|
|
89
|
+
continue
|
|
90
|
+
_LOGGER.debug("Registering watch folder: %s", watch_folder)
|
|
91
|
+
if watch_folder not in self._watched_pages:
|
|
92
|
+
self._register_watcher(
|
|
93
|
+
watch_folder,
|
|
94
|
+
module_name=None,
|
|
95
|
+
is_directory=True,
|
|
96
|
+
)
|
|
97
|
+
|
|
82
98
|
for old_page_path in old_page_paths:
|
|
83
99
|
# Only remove pages that are no longer valid files
|
|
84
100
|
if old_page_path not in new_pages_paths and not os.path.isfile(
|
|
@@ -90,11 +106,22 @@ class LocalSourcesWatcher:
|
|
|
90
106
|
self._watched_pages = self._watched_pages.union(new_pages_paths)
|
|
91
107
|
|
|
92
108
|
def register_file_change_callback(self, cb: Callable[[str], None]) -> None:
|
|
93
|
-
self.
|
|
109
|
+
self._on_path_changed.append(cb)
|
|
94
110
|
|
|
95
|
-
def
|
|
111
|
+
def on_path_changed(self, filepath: str) -> None:
|
|
112
|
+
_LOGGER.debug("Path changed: %s", filepath)
|
|
96
113
|
if filepath not in self._watched_modules:
|
|
97
|
-
|
|
114
|
+
# Check if this is a file in a watched directory
|
|
115
|
+
for watched_dir in self._watched_modules:
|
|
116
|
+
if (
|
|
117
|
+
os.path.isdir(watched_dir)
|
|
118
|
+
and os.path.commonpath([watched_dir, filepath]) == watched_dir
|
|
119
|
+
):
|
|
120
|
+
_LOGGER.info("File changed in watched directory: %s", filepath)
|
|
121
|
+
for cb in self._on_path_changed:
|
|
122
|
+
cb(filepath)
|
|
123
|
+
return
|
|
124
|
+
_LOGGER.error("Received event for non-watched path: %s", filepath)
|
|
98
125
|
return
|
|
99
126
|
|
|
100
127
|
# Workaround:
|
|
@@ -113,17 +140,19 @@ class LocalSourcesWatcher:
|
|
|
113
140
|
if wm.module_name is not None and wm.module_name in sys.modules:
|
|
114
141
|
del sys.modules[wm.module_name]
|
|
115
142
|
|
|
116
|
-
for cb in self.
|
|
143
|
+
for cb in self._on_path_changed:
|
|
117
144
|
cb(filepath)
|
|
118
145
|
|
|
119
|
-
def close(self):
|
|
146
|
+
def close(self) -> None:
|
|
120
147
|
for wm in self._watched_modules.values():
|
|
121
148
|
wm.watcher.close()
|
|
122
149
|
self._watched_modules = {}
|
|
123
150
|
self._watched_pages = set()
|
|
124
151
|
self._is_closed = True
|
|
125
152
|
|
|
126
|
-
def _register_watcher(
|
|
153
|
+
def _register_watcher(
|
|
154
|
+
self, filepath: str, module_name: str | None, is_directory: bool = False
|
|
155
|
+
) -> None:
|
|
127
156
|
global PathWatcher # noqa: PLW0603
|
|
128
157
|
if PathWatcher is None:
|
|
129
158
|
PathWatcher = get_default_path_watcher_class()
|
|
@@ -132,10 +161,19 @@ class LocalSourcesWatcher:
|
|
|
132
161
|
return
|
|
133
162
|
|
|
134
163
|
try:
|
|
164
|
+
# Instead of using **kwargs, explicitly pass the named parameters
|
|
165
|
+
glob_pattern = "**/*" if is_directory else None
|
|
166
|
+
|
|
135
167
|
wm = WatchedModule(
|
|
136
|
-
watcher=PathWatcher(
|
|
168
|
+
watcher=PathWatcher(
|
|
169
|
+
filepath,
|
|
170
|
+
self.on_path_changed,
|
|
171
|
+
glob_pattern=glob_pattern, # Pass as named parameter
|
|
172
|
+
allow_nonexistent=False,
|
|
173
|
+
),
|
|
137
174
|
module_name=module_name,
|
|
138
175
|
)
|
|
176
|
+
self._watched_modules[filepath] = wm
|
|
139
177
|
except PermissionError:
|
|
140
178
|
# If you don't have permission to read this file, don't even add it
|
|
141
179
|
# to watchers.
|
|
@@ -143,7 +181,7 @@ class LocalSourcesWatcher:
|
|
|
143
181
|
|
|
144
182
|
self._watched_modules[filepath] = wm
|
|
145
183
|
|
|
146
|
-
def _deregister_watcher(self, filepath):
|
|
184
|
+
def _deregister_watcher(self, filepath: str) -> None:
|
|
147
185
|
if filepath not in self._watched_modules:
|
|
148
186
|
return
|
|
149
187
|
|
|
@@ -154,17 +192,17 @@ class LocalSourcesWatcher:
|
|
|
154
192
|
wm.watcher.close()
|
|
155
193
|
del self._watched_modules[filepath]
|
|
156
194
|
|
|
157
|
-
def _file_is_new(self, filepath):
|
|
195
|
+
def _file_is_new(self, filepath: str) -> bool:
|
|
158
196
|
return filepath not in self._watched_modules
|
|
159
197
|
|
|
160
|
-
def _file_should_be_watched(self, filepath):
|
|
198
|
+
def _file_should_be_watched(self, filepath: str) -> bool:
|
|
161
199
|
# Using short circuiting for performance.
|
|
162
200
|
return self._file_is_new(filepath) and (
|
|
163
201
|
file_util.file_is_in_folder_glob(filepath, self._script_folder)
|
|
164
202
|
or file_util.file_in_pythonpath(filepath)
|
|
165
203
|
)
|
|
166
204
|
|
|
167
|
-
def update_watched_modules(self):
|
|
205
|
+
def update_watched_modules(self) -> None:
|
|
168
206
|
if self._is_closed:
|
|
169
207
|
return
|
|
170
208
|
|
|
@@ -187,12 +225,12 @@ class LocalSourcesWatcher:
|
|
|
187
225
|
|
|
188
226
|
|
|
189
227
|
def get_module_paths(module: ModuleType) -> set[str]:
|
|
190
|
-
paths_extractors = [
|
|
228
|
+
paths_extractors: list[Callable[[ModuleType], list[str | None]]] = [
|
|
191
229
|
# https://docs.python.org/3/reference/datamodel.html
|
|
192
230
|
# __file__ is the pathname of the file from which the module was loaded
|
|
193
231
|
# if it was loaded from a file.
|
|
194
232
|
# The __file__ attribute may be missing for certain types of modules
|
|
195
|
-
lambda m: [m.__file__],
|
|
233
|
+
lambda m: [m.__file__] if hasattr(m, "__file__") else [],
|
|
196
234
|
# https://docs.python.org/3/reference/import.html#__spec__
|
|
197
235
|
# The __spec__ attribute is set to the module spec that was used
|
|
198
236
|
# when importing the module. one exception is __main__,
|
|
@@ -202,12 +240,20 @@ def get_module_paths(module: ModuleType) -> set[str]:
|
|
|
202
240
|
# (or resource within a system) from which a module originates
|
|
203
241
|
# ... It is up to the loader to decide on how to interpret
|
|
204
242
|
# and use a module's origin, if at all.
|
|
205
|
-
lambda m: [m.__spec__.origin]
|
|
243
|
+
lambda m: [m.__spec__.origin]
|
|
244
|
+
if hasattr(m, "__spec__") and m.__spec__ is not None
|
|
245
|
+
else [],
|
|
206
246
|
# https://www.python.org/dev/peps/pep-0420/
|
|
207
247
|
# Handling of "namespace packages" in which the __path__ attribute
|
|
208
248
|
# is a _NamespacePath object with a _path attribute containing
|
|
209
249
|
# the various paths of the package.
|
|
210
|
-
lambda m: list(m.__path__._path)
|
|
250
|
+
lambda m: list(m.__path__._path)
|
|
251
|
+
if hasattr(m, "__path__")
|
|
252
|
+
# This check prevents issues with torch classes:
|
|
253
|
+
# https://github.com/streamlit/streamlit/issues/10992
|
|
254
|
+
and type(m.__path__).__name__ == "_NamespacePath"
|
|
255
|
+
and hasattr(m.__path__, "_path")
|
|
256
|
+
else [],
|
|
211
257
|
]
|
|
212
258
|
|
|
213
259
|
all_paths = set()
|
|
@@ -220,7 +266,7 @@ def get_module_paths(module: ModuleType) -> set[str]:
|
|
|
220
266
|
pass
|
|
221
267
|
except Exception:
|
|
222
268
|
_LOGGER.warning(
|
|
223
|
-
|
|
269
|
+
"Examining the path of %s raised:", module.__name__, exc_info=True
|
|
224
270
|
)
|
|
225
271
|
|
|
226
272
|
all_paths.update(
|
|
@@ -38,7 +38,7 @@ class NoOpPathWatcher:
|
|
|
38
38
|
*, # keyword-only arguments:
|
|
39
39
|
glob_pattern: str | None = None,
|
|
40
40
|
allow_nonexistent: bool = False,
|
|
41
|
-
):
|
|
41
|
+
) -> None:
|
|
42
42
|
pass
|
|
43
43
|
|
|
44
44
|
|
|
@@ -62,7 +62,7 @@ def _is_watchdog_available() -> bool:
|
|
|
62
62
|
return False
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def report_watchdog_availability():
|
|
65
|
+
def report_watchdog_availability() -> None:
|
|
66
66
|
if (
|
|
67
67
|
config.get_option("server.fileWatcherType") not in ["poll", "none"]
|
|
68
68
|
and not _is_watchdog_available()
|
|
@@ -70,15 +70,14 @@ def report_watchdog_availability():
|
|
|
70
70
|
msg = "\n $ xcode-select --install" if env_util.IS_DARWIN else ""
|
|
71
71
|
|
|
72
72
|
cli_util.print_to_cli(
|
|
73
|
-
"
|
|
73
|
+
" For better performance, install the Watchdog module:",
|
|
74
74
|
fg="blue",
|
|
75
75
|
bold=True,
|
|
76
76
|
)
|
|
77
77
|
cli_util.print_to_cli(
|
|
78
|
-
"""
|
|
78
|
+
f"""{msg}
|
|
79
79
|
$ pip install watchdog
|
|
80
80
|
"""
|
|
81
|
-
% msg
|
|
82
81
|
)
|
|
83
82
|
|
|
84
83
|
|
|
@@ -177,9 +176,6 @@ def get_path_watcher_class(watcher_type: str) -> PathWatcherType:
|
|
|
177
176
|
from streamlit.watcher.event_based_path_watcher import EventBasedPathWatcher
|
|
178
177
|
|
|
179
178
|
return EventBasedPathWatcher
|
|
180
|
-
|
|
181
|
-
return PollingPathWatcher
|
|
182
|
-
elif watcher_type == "poll":
|
|
179
|
+
if watcher_type in {"auto", "poll"}:
|
|
183
180
|
return PollingPathWatcher
|
|
184
|
-
|
|
185
|
-
return NoOpPathWatcher
|
|
181
|
+
return NoOpPathWatcher
|
|
@@ -18,6 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
import time
|
|
20
20
|
from concurrent.futures import ThreadPoolExecutor
|
|
21
|
+
from pathlib import Path
|
|
21
22
|
from typing import Callable, Final
|
|
22
23
|
|
|
23
24
|
from streamlit.logger import get_logger
|
|
@@ -59,7 +60,7 @@ class PollingPathWatcher:
|
|
|
59
60
|
retains references to all active instances.)
|
|
60
61
|
"""
|
|
61
62
|
# TODO(vdonato): Modernize this by switching to pathlib.
|
|
62
|
-
self._path = path
|
|
63
|
+
self._path = Path(path) # Changed to pathlib.Path
|
|
63
64
|
self._on_changed = on_changed
|
|
64
65
|
|
|
65
66
|
self._glob_pattern = glob_pattern
|
|
@@ -68,10 +69,10 @@ class PollingPathWatcher:
|
|
|
68
69
|
self._active = True
|
|
69
70
|
|
|
70
71
|
self._modification_time = util.path_modification_time(
|
|
71
|
-
self._path, self._allow_nonexistent
|
|
72
|
+
str(self._path), self._allow_nonexistent
|
|
72
73
|
)
|
|
73
74
|
self._md5 = util.calc_md5_with_blocking_retries(
|
|
74
|
-
self._path,
|
|
75
|
+
str(self._path),
|
|
75
76
|
glob_pattern=self._glob_pattern,
|
|
76
77
|
allow_nonexistent=self._allow_nonexistent,
|
|
77
78
|
)
|
|
@@ -81,7 +82,7 @@ class PollingPathWatcher:
|
|
|
81
82
|
return repr_(self)
|
|
82
83
|
|
|
83
84
|
def _schedule(self) -> None:
|
|
84
|
-
def task():
|
|
85
|
+
def task() -> None:
|
|
85
86
|
time.sleep(_POLLING_PERIOD_SECS)
|
|
86
87
|
self._check_if_path_changed()
|
|
87
88
|
|
|
@@ -93,7 +94,7 @@ class PollingPathWatcher:
|
|
|
93
94
|
return
|
|
94
95
|
|
|
95
96
|
modification_time = util.path_modification_time(
|
|
96
|
-
self._path, self._allow_nonexistent
|
|
97
|
+
str(self._path), self._allow_nonexistent
|
|
97
98
|
)
|
|
98
99
|
# We add modification_time != 0.0 check since on some file systems (s3fs/fuse)
|
|
99
100
|
# modification_time is always 0.0 because of file system limitations.
|
|
@@ -104,7 +105,7 @@ class PollingPathWatcher:
|
|
|
104
105
|
self._modification_time = modification_time
|
|
105
106
|
|
|
106
107
|
md5 = util.calc_md5_with_blocking_retries(
|
|
107
|
-
self._path,
|
|
108
|
+
str(self._path),
|
|
108
109
|
glob_pattern=self._glob_pattern,
|
|
109
110
|
allow_nonexistent=self._allow_nonexistent,
|
|
110
111
|
)
|
|
@@ -115,7 +116,7 @@ class PollingPathWatcher:
|
|
|
115
116
|
self._md5 = md5
|
|
116
117
|
|
|
117
118
|
_LOGGER.debug("Change detected: %s", self._path)
|
|
118
|
-
self._on_changed(self._path)
|
|
119
|
+
self._on_changed(str(self._path))
|
|
119
120
|
|
|
120
121
|
self._schedule()
|
|
121
122
|
|
streamlit/watcher/util.py
CHANGED
|
@@ -23,11 +23,14 @@ from __future__ import annotations
|
|
|
23
23
|
import os
|
|
24
24
|
import time
|
|
25
25
|
from pathlib import Path
|
|
26
|
-
from typing import Callable, TypeVar
|
|
26
|
+
from typing import TYPE_CHECKING, Callable, TypeVar
|
|
27
27
|
|
|
28
28
|
from streamlit.errors import Error
|
|
29
29
|
from streamlit.util import calc_md5
|
|
30
30
|
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import Generator
|
|
33
|
+
|
|
31
34
|
# How many times to try to grab the MD5 hash.
|
|
32
35
|
_MAX_RETRIES = 5
|
|
33
36
|
|
|
@@ -170,17 +173,15 @@ def _do_with_retries(
|
|
|
170
173
|
for i in _retry_dance():
|
|
171
174
|
try:
|
|
172
175
|
return orig_fn()
|
|
173
|
-
except exceptions:
|
|
176
|
+
except exceptions: # noqa: PERF203
|
|
174
177
|
if i >= _MAX_RETRIES - 1:
|
|
175
178
|
raise
|
|
176
|
-
|
|
177
|
-
# Continue with loop to either retry or raise MaxRetriesError.
|
|
178
|
-
pass
|
|
179
|
+
# Continue with loop to either retry or raise MaxRetriesError.
|
|
179
180
|
|
|
180
181
|
raise MaxRetriesError(f"Unable to access file or folder: {path}")
|
|
181
182
|
|
|
182
183
|
|
|
183
|
-
def _retry_dance():
|
|
184
|
+
def _retry_dance() -> Generator[int, None, None]:
|
|
184
185
|
"""Helper for writing a retry loop.
|
|
185
186
|
|
|
186
187
|
This is useful to make sure all our retry loops work the same way. For example,
|