streamlit-nightly 1.41.2.dev20241227__py2.py3-none-any.whl → 1.41.2.dev20250124__py2.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 +11 -2
- streamlit/__main__.py +1 -1
- streamlit/auth_util.py +209 -0
- streamlit/cli_util.py +1 -1
- streamlit/column_config.py +1 -1
- streamlit/commands/__init__.py +1 -1
- streamlit/commands/echo.py +1 -1
- streamlit/commands/execution_control.py +1 -1
- streamlit/commands/experimental_query_params.py +1 -1
- streamlit/commands/logo.py +1 -1
- streamlit/commands/navigation.py +16 -15
- streamlit/commands/page_config.py +1 -1
- streamlit/components/__init__.py +1 -1
- streamlit/components/lib/__init__.py +1 -1
- streamlit/components/lib/local_component_registry.py +1 -1
- streamlit/components/types/__init__.py +1 -1
- streamlit/components/types/base_component_registry.py +1 -1
- streamlit/components/types/base_custom_component.py +1 -1
- streamlit/components/v1/__init__.py +1 -1
- streamlit/components/v1/component_arrow.py +1 -1
- streamlit/components/v1/component_registry.py +1 -1
- streamlit/components/v1/components.py +1 -1
- streamlit/components/v1/custom_component.py +1 -1
- streamlit/config.py +18 -3
- streamlit/config_option.py +10 -19
- streamlit/config_util.py +1 -1
- streamlit/connections/__init__.py +1 -1
- streamlit/connections/base_connection.py +1 -1
- streamlit/connections/snowflake_connection.py +31 -50
- streamlit/connections/snowpark_connection.py +1 -1
- streamlit/connections/sql_connection.py +1 -1
- streamlit/connections/util.py +1 -1
- streamlit/cursor.py +1 -1
- streamlit/dataframe_util.py +3 -3
- streamlit/delta_generator.py +7 -3
- streamlit/delta_generator_singletons.py +1 -1
- streamlit/deprecation_util.py +1 -1
- streamlit/development.py +1 -1
- streamlit/elements/__init__.py +1 -1
- streamlit/elements/alert.py +1 -1
- streamlit/elements/arrow.py +25 -3
- streamlit/elements/balloons.py +1 -1
- streamlit/elements/bokeh_chart.py +1 -1
- streamlit/elements/code.py +9 -1
- streamlit/elements/deck_gl_json_chart.py +1 -1
- streamlit/elements/dialog_decorator.py +1 -1
- streamlit/elements/doc_string.py +1 -1
- streamlit/elements/empty.py +1 -1
- streamlit/elements/exception.py +8 -10
- streamlit/elements/form.py +1 -1
- streamlit/elements/graphviz_chart.py +1 -1
- streamlit/elements/heading.py +1 -1
- streamlit/elements/html.py +1 -1
- streamlit/elements/iframe.py +1 -1
- streamlit/elements/image.py +1 -1
- streamlit/elements/json.py +1 -1
- streamlit/elements/layouts.py +1 -1
- streamlit/elements/lib/__init__.py +1 -1
- streamlit/elements/lib/built_in_chart_utils.py +1 -1
- streamlit/elements/lib/color_util.py +1 -1
- streamlit/elements/lib/column_config_utils.py +1 -1
- streamlit/elements/lib/column_types.py +1 -1
- streamlit/elements/lib/dialog.py +1 -1
- streamlit/elements/lib/dicttools.py +1 -1
- streamlit/elements/lib/event_utils.py +1 -1
- streamlit/elements/lib/file_uploader_utils.py +1 -1
- streamlit/elements/lib/form_utils.py +1 -1
- streamlit/elements/lib/image_utils.py +1 -1
- streamlit/elements/lib/js_number.py +1 -1
- streamlit/elements/lib/mutable_status_container.py +1 -1
- streamlit/elements/lib/options_selector_utils.py +1 -1
- streamlit/elements/lib/pandas_styler_utils.py +1 -1
- streamlit/elements/lib/policies.py +1 -1
- streamlit/elements/lib/streamlit_plotly_theme.py +1 -1
- streamlit/elements/lib/subtitle_utils.py +1 -1
- streamlit/elements/lib/utils.py +1 -1
- streamlit/elements/map.py +1 -1
- streamlit/elements/markdown.py +3 -3
- streamlit/elements/media.py +1 -1
- streamlit/elements/metric.py +1 -1
- streamlit/elements/plotly_chart.py +1 -1
- streamlit/elements/progress.py +1 -1
- streamlit/elements/pyplot.py +1 -1
- streamlit/elements/snow.py +1 -1
- streamlit/elements/spinner.py +14 -7
- streamlit/elements/text.py +1 -1
- streamlit/elements/toast.py +1 -1
- streamlit/elements/vega_charts.py +20 -6
- streamlit/elements/widgets/__init__.py +1 -1
- streamlit/elements/widgets/audio_input.py +1 -1
- streamlit/elements/widgets/button.py +9 -8
- streamlit/elements/widgets/button_group.py +6 -4
- streamlit/elements/widgets/camera_input.py +1 -1
- streamlit/elements/widgets/chat.py +1 -1
- streamlit/elements/widgets/checkbox.py +1 -1
- streamlit/elements/widgets/color_picker.py +1 -1
- streamlit/elements/widgets/data_editor.py +6 -5
- streamlit/elements/widgets/file_uploader.py +1 -1
- streamlit/elements/widgets/multiselect.py +1 -1
- streamlit/elements/widgets/number_input.py +1 -1
- streamlit/elements/widgets/radio.py +1 -1
- streamlit/elements/widgets/select_slider.py +1 -1
- streamlit/elements/widgets/selectbox.py +1 -1
- streamlit/elements/widgets/slider.py +1 -1
- streamlit/elements/widgets/text_widgets.py +1 -1
- streamlit/elements/widgets/time_widgets.py +63 -3
- streamlit/elements/write.py +2 -2
- streamlit/emojis.py +2 -2
- streamlit/env_util.py +1 -1
- streamlit/error_util.py +9 -3
- streamlit/errors.py +5 -1
- streamlit/external/__init__.py +1 -1
- streamlit/external/langchain/__init__.py +1 -1
- streamlit/external/langchain/streamlit_callback_handler.py +1 -1
- streamlit/file_util.py +1 -1
- streamlit/git_util.py +1 -1
- streamlit/hello/__init__.py +1 -1
- streamlit/hello/animation_demo.py +1 -1
- streamlit/hello/dataframe_demo.py +1 -1
- streamlit/hello/hello.py +1 -1
- streamlit/hello/mapping_demo.py +1 -1
- streamlit/hello/plotting_demo.py +1 -1
- streamlit/hello/streamlit_app.py +1 -1
- streamlit/hello/utils.py +1 -1
- streamlit/logger.py +1 -1
- streamlit/material_icon_names.py +2 -2
- streamlit/navigation/__init__.py +1 -1
- streamlit/navigation/page.py +7 -4
- streamlit/net_util.py +1 -1
- streamlit/platform.py +1 -1
- streamlit/proto/Alert_pb2.pyi +1 -1
- streamlit/proto/AppPage_pb2.pyi +1 -1
- streamlit/proto/ArrowNamedDataSet_pb2.pyi +1 -1
- streamlit/proto/ArrowVegaLiteChart_pb2.pyi +1 -1
- streamlit/proto/Arrow_pb2.pyi +1 -1
- streamlit/proto/AudioInput_pb2.pyi +1 -1
- streamlit/proto/Audio_pb2.pyi +1 -1
- streamlit/proto/AuthRedirect_pb2.py +27 -0
- streamlit/proto/AuthRedirect_pb2.pyi +41 -0
- streamlit/proto/AutoRerun_pb2.pyi +1 -1
- streamlit/proto/BackMsg_pb2.pyi +1 -1
- streamlit/proto/Balloons_pb2.pyi +1 -1
- streamlit/proto/Block_pb2.pyi +1 -1
- streamlit/proto/BokehChart_pb2.pyi +1 -1
- streamlit/proto/ButtonGroup_pb2.pyi +1 -1
- streamlit/proto/Button_pb2.pyi +1 -1
- streamlit/proto/CameraInput_pb2.pyi +1 -1
- streamlit/proto/ChatInput_pb2.pyi +1 -1
- streamlit/proto/Checkbox_pb2.pyi +1 -1
- streamlit/proto/ClientState_pb2.pyi +1 -1
- streamlit/proto/Code_pb2.py +2 -2
- streamlit/proto/Code_pb2.pyi +5 -2
- streamlit/proto/ColorPicker_pb2.pyi +1 -1
- streamlit/proto/Common_pb2.pyi +1 -1
- streamlit/proto/Components_pb2.pyi +1 -1
- streamlit/proto/DataFrame_pb2.pyi +1 -1
- streamlit/proto/DateInput_pb2.pyi +1 -1
- streamlit/proto/DeckGlJsonChart_pb2.pyi +1 -1
- streamlit/proto/Delta_pb2.pyi +1 -1
- streamlit/proto/DocString_pb2.pyi +1 -1
- streamlit/proto/DownloadButton_pb2.pyi +1 -1
- streamlit/proto/Element_pb2.pyi +1 -1
- streamlit/proto/Empty_pb2.pyi +1 -1
- streamlit/proto/Exception_pb2.pyi +1 -1
- streamlit/proto/Favicon_pb2.pyi +1 -1
- streamlit/proto/FileUploader_pb2.pyi +1 -1
- streamlit/proto/ForwardMsg_pb2.py +12 -9
- streamlit/proto/ForwardMsg_pb2.pyi +34 -4
- streamlit/proto/GitInfo_pb2.pyi +1 -1
- streamlit/proto/GraphVizChart_pb2.pyi +1 -1
- streamlit/proto/Heading_pb2.pyi +1 -1
- streamlit/proto/Html_pb2.pyi +1 -1
- streamlit/proto/IFrame_pb2.pyi +1 -1
- streamlit/proto/Image_pb2.pyi +1 -1
- streamlit/proto/Json_pb2.pyi +1 -1
- streamlit/proto/LabelVisibilityMessage_pb2.pyi +1 -1
- streamlit/proto/LinkButton_pb2.pyi +1 -1
- streamlit/proto/Logo_pb2.pyi +1 -1
- streamlit/proto/Markdown_pb2.pyi +1 -1
- streamlit/proto/Metric_pb2.pyi +1 -1
- streamlit/proto/MetricsEvent_pb2.pyi +1 -1
- streamlit/proto/MultiSelect_pb2.pyi +1 -1
- streamlit/proto/NamedDataSet_pb2.pyi +1 -1
- streamlit/proto/Navigation_pb2.pyi +1 -1
- streamlit/proto/NewSession_pb2.pyi +1 -1
- streamlit/proto/NumberInput_pb2.pyi +1 -1
- streamlit/proto/PageConfig_pb2.pyi +1 -1
- streamlit/proto/PageInfo_pb2.pyi +1 -1
- streamlit/proto/PageLink_pb2.pyi +1 -1
- streamlit/proto/PageNotFound_pb2.pyi +1 -1
- streamlit/proto/PageProfile_pb2.pyi +1 -1
- streamlit/proto/PagesChanged_pb2.pyi +1 -1
- streamlit/proto/ParentMessage_pb2.pyi +1 -1
- streamlit/proto/PlotlyChart_pb2.pyi +1 -1
- streamlit/proto/Progress_pb2.pyi +1 -1
- streamlit/proto/Radio_pb2.pyi +1 -1
- streamlit/proto/RootContainer_pb2.pyi +1 -1
- streamlit/proto/Selectbox_pb2.pyi +1 -1
- streamlit/proto/SessionEvent_pb2.pyi +1 -1
- streamlit/proto/SessionStatus_pb2.pyi +1 -1
- streamlit/proto/Skeleton_pb2.pyi +1 -1
- streamlit/proto/Slider_pb2.pyi +1 -1
- streamlit/proto/Snow_pb2.pyi +1 -1
- streamlit/proto/Spinner_pb2.py +2 -2
- streamlit/proto/Spinner_pb2.pyi +6 -2
- streamlit/proto/TextArea_pb2.pyi +1 -1
- streamlit/proto/TextInput_pb2.pyi +1 -1
- streamlit/proto/Text_pb2.pyi +1 -1
- streamlit/proto/TimeInput_pb2.pyi +1 -1
- streamlit/proto/Toast_pb2.pyi +1 -1
- streamlit/proto/VegaLiteChart_pb2.pyi +1 -1
- streamlit/proto/Video_pb2.pyi +1 -1
- streamlit/proto/WidgetStates_pb2.pyi +1 -1
- streamlit/proto/__init__.py +1 -1
- streamlit/runtime/__init__.py +1 -1
- streamlit/runtime/app_session.py +29 -5
- streamlit/runtime/caching/__init__.py +1 -1
- streamlit/runtime/caching/cache_data_api.py +3 -3
- streamlit/runtime/caching/cache_errors.py +1 -1
- streamlit/runtime/caching/cache_resource_api.py +1 -1
- streamlit/runtime/caching/cache_type.py +1 -1
- streamlit/runtime/caching/cache_utils.py +5 -4
- streamlit/runtime/caching/cached_message_replay.py +1 -1
- streamlit/runtime/caching/hashing.py +1 -1
- streamlit/runtime/caching/legacy_cache_api.py +1 -1
- streamlit/runtime/caching/storage/__init__.py +1 -1
- 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 +1 -1
- streamlit/runtime/caching/storage/local_disk_cache_storage.py +5 -5
- streamlit/runtime/connection_factory.py +1 -1
- streamlit/runtime/context.py +1 -1
- streamlit/runtime/credentials.py +5 -5
- streamlit/runtime/forward_msg_cache.py +4 -2
- streamlit/runtime/forward_msg_queue.py +33 -5
- streamlit/runtime/fragment.py +2 -2
- streamlit/runtime/media_file_manager.py +1 -1
- streamlit/runtime/media_file_storage.py +1 -1
- streamlit/runtime/memory_media_file_storage.py +1 -1
- streamlit/runtime/memory_session_storage.py +1 -1
- streamlit/runtime/memory_uploaded_file_manager.py +1 -1
- streamlit/runtime/metrics_util.py +2 -1
- streamlit/runtime/pages_manager.py +4 -2
- streamlit/runtime/runtime.py +22 -6
- streamlit/runtime/runtime_util.py +1 -1
- streamlit/runtime/script_data.py +1 -1
- streamlit/runtime/scriptrunner/__init__.py +1 -1
- streamlit/runtime/scriptrunner/exec_code.py +34 -1
- streamlit/runtime/scriptrunner/magic.py +1 -1
- streamlit/runtime/scriptrunner/magic_funcs.py +1 -1
- streamlit/runtime/scriptrunner/script_cache.py +1 -1
- streamlit/runtime/scriptrunner/script_runner.py +23 -12
- streamlit/runtime/scriptrunner_utils/__init__.py +1 -1
- streamlit/runtime/scriptrunner_utils/exceptions.py +1 -1
- streamlit/runtime/scriptrunner_utils/script_requests.py +2 -1
- streamlit/runtime/scriptrunner_utils/script_run_context.py +2 -2
- streamlit/runtime/secrets.py +1 -1
- streamlit/runtime/session_manager.py +2 -2
- streamlit/runtime/state/__init__.py +1 -1
- streamlit/runtime/state/common.py +1 -1
- streamlit/runtime/state/query_params.py +2 -2
- streamlit/runtime/state/query_params_proxy.py +15 -5
- streamlit/runtime/state/safe_session_state.py +1 -1
- streamlit/runtime/state/session_state.py +1 -1
- streamlit/runtime/state/session_state_proxy.py +1 -1
- streamlit/runtime/state/widgets.py +1 -1
- streamlit/runtime/stats.py +1 -1
- streamlit/runtime/uploaded_file_manager.py +1 -1
- streamlit/runtime/websocket_session_manager.py +2 -2
- streamlit/source_util.py +1 -1
- streamlit/static/index.html +3 -3
- streamlit/static/static/css/index.mUTQuMqR.css +1 -0
- streamlit/static/static/js/{FileDownload.esm.WslOojMp.js → FileDownload.esm.C1QvS8Zm.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.DSgVZ-NK.js → FormClearHelper.mT4-5rFn.js} +1 -1
- streamlit/static/static/js/{Hooks.wzeYp0oD.js → Hooks.CVNEuaGG.js} +1 -1
- streamlit/static/static/js/InputInstructions.BCvAGBeC.js +1 -0
- streamlit/static/static/js/{ProgressBar.D1he2Gib.js → ProgressBar.CcQcYD9u.js} +2 -2
- streamlit/static/static/js/RenderInPortalIfExists.DHDG9PHn.js +1 -0
- streamlit/static/static/js/Toolbar.yTE_O6B7.js +1 -0
- streamlit/static/static/js/{base-input.gDrmzpbY.js → base-input.CQPY9_oN.js} +4 -4
- streamlit/static/static/js/createSuper.DE47Tkz4.js +1 -0
- streamlit/static/static/js/data-grid-overlay-editor.5JEQwfzp.js +1 -0
- streamlit/static/static/js/{downloader.B-SkHjg2.js → downloader.CX7_KkfB.js} +1 -1
- streamlit/static/static/js/{es6.DoVDVSd_.js → es6.Be2vwxoI.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.Exq_q4jU.js → iframeResizer.contentWindow.CFHofqC-.js} +1 -1
- streamlit/static/static/js/index.0OeiPvr3.js +1 -0
- streamlit/static/static/js/index.1GBg_Cdb.js +1 -0
- streamlit/static/static/js/index.4nNgvhbk.js +4 -0
- streamlit/static/static/js/index.5F9_LJ-9.js +1 -0
- streamlit/static/static/js/index.6v5ZgUAy.js +1 -0
- streamlit/static/static/js/index.8tVXPrLX.js +1 -0
- streamlit/static/static/js/index.AExANgn1.js +1 -0
- streamlit/static/static/js/index.AXOgS1NM.js +1 -0
- streamlit/static/static/js/index.B-xzXPZE.js +3 -0
- streamlit/static/static/js/index.B0pfFJGU.js +1 -0
- streamlit/static/static/js/index.BBjf7kH1.js +1 -0
- streamlit/static/static/js/index.BGICDmL0.js +201 -0
- streamlit/static/static/js/{index.rDDlhQFp.js → index.BOeLQ18h.js} +1 -1
- streamlit/static/static/js/index.BUgJRfqp.js +3 -0
- streamlit/static/static/js/index.BpeIAypN.js +1 -0
- streamlit/static/static/js/{index.BjQJsYG6.js → index.C0ZNaKGF.js} +1 -1
- streamlit/static/static/js/index.C3eR9AQ5.js +1 -0
- streamlit/static/static/js/index.CCmrDm8Z.js +2 -0
- streamlit/static/static/js/index.CHuo89tL.js +1 -0
- streamlit/static/static/js/index.Cbi_d9ak.js +197 -0
- streamlit/static/static/js/{index.CyRDf6c5.js → index.Cg7T4wJt.js} +3 -3
- streamlit/static/static/js/index.Ci3iebpl.js +1 -0
- streamlit/static/static/js/index.CjsaBedq.js +1 -0
- streamlit/static/static/js/{index.CLHVvMN0.js → index.CvqaNor_.js} +139 -128
- streamlit/static/static/js/index.D0lxtOCO.js +2 -0
- streamlit/static/static/js/index.D1AD4NsW.js +1 -0
- streamlit/static/static/js/{index.4lqQPLdu.js → index.DLjk_wlB.js} +1 -1
- streamlit/static/static/js/{index.DfwGDOWW.js → index.DNDhYAPI.js} +2 -2
- streamlit/static/static/js/index.DY6666R7.js +1 -0
- streamlit/static/static/js/{index.DaTKmBKa.js → index.Dkr_c_9x.js} +100 -100
- streamlit/static/static/js/{index.Bcz23DKe.js → index.Hhv6gSq2.js} +12 -12
- streamlit/static/static/js/{index.Bm2n8u7I.js → index.OM83ZHKg.js} +2 -2
- streamlit/static/static/js/index.SLleoa9s.js +1 -0
- streamlit/static/static/js/{index.2Rr5kbVL.js → index.XCwDes79.js} +1 -1
- streamlit/static/static/js/{index.B_RzfO7U.js → index.a6pesEXT.js} +1 -1
- streamlit/static/static/js/{index.CAkYXoVB.js → index.er3Zfn3e.js} +2 -2
- streamlit/static/static/js/{index.CZ-znj8N.js → index.mtSGfsND.js} +1 -1
- streamlit/static/static/js/{input.28wXD4uV.js → input.B07wSbwn.js} +2 -2
- streamlit/static/static/js/{memory.jbWyoc5k.js → memory.CC_p93jh.js} +1 -1
- streamlit/static/static/js/mergeWith.ivc75cD1.js +1 -0
- streamlit/static/static/js/number-overlay-editor.Db2Be6nq.js +9 -0
- streamlit/static/static/js/possibleConstructorReturn.BNprLGNF.js +1 -0
- streamlit/static/static/js/{sandbox.CfK9-cKf.js → sandbox.BlG3HhHL.js} +1 -1
- streamlit/static/static/js/{textarea.C5OZk1uL.js → textarea.Bz6Z-kmO.js} +2 -2
- streamlit/static/static/js/threshold.B8r8f5kt.js +1 -0
- streamlit/static/static/js/{timepicker.DEpQEk7W.js → timepicker.BMSb5NlP.js} +3 -3
- streamlit/static/static/js/timer.RueuYoQV.js +1 -0
- streamlit/static/static/js/toConsumableArray.uYLXBIlD.js +3 -0
- streamlit/static/static/js/uniqueId.BdisvZXg.js +1 -0
- streamlit/static/static/js/{useBasicWidgetState.C2DsWpWi.js → useBasicWidgetState.BJFGEQDL.js} +1 -1
- streamlit/static/static/js/{useOnInputChange.Bw6YkmJD.js → useOnInputChange.BmSN_ACz.js} +1 -1
- streamlit/static/static/js/value.iufjd77T.js +1 -0
- streamlit/static/static/js/withFullScreenWrapper.CbOOMX5j.js +1 -0
- streamlit/static/static/media/MaterialSymbols-Rounded.DzyB5T7Y.woff2 +0 -0
- streamlit/string_util.py +1 -1
- streamlit/temporary_directory.py +1 -1
- streamlit/testing/__init__.py +1 -1
- streamlit/testing/v1/__init__.py +1 -1
- streamlit/testing/v1/app_test.py +1 -1
- streamlit/testing/v1/element_tree.py +1 -1
- streamlit/testing/v1/local_script_runner.py +1 -1
- streamlit/testing/v1/util.py +1 -1
- streamlit/time_util.py +1 -1
- streamlit/type_util.py +2 -2
- streamlit/url_util.py +24 -1
- streamlit/user_info.py +445 -25
- streamlit/util.py +1 -1
- streamlit/version.py +1 -1
- streamlit/watcher/__init__.py +1 -1
- streamlit/watcher/event_based_path_watcher.py +1 -1
- streamlit/watcher/folder_black_list.py +1 -1
- streamlit/watcher/local_sources_watcher.py +5 -3
- streamlit/watcher/path_watcher.py +1 -1
- streamlit/watcher/polling_path_watcher.py +1 -1
- streamlit/watcher/util.py +87 -21
- streamlit/web/__init__.py +1 -1
- streamlit/web/bootstrap.py +22 -2
- streamlit/web/cache_storage_manager_config.py +1 -1
- streamlit/web/cli.py +1 -1
- streamlit/web/server/__init__.py +1 -1
- streamlit/web/server/app_static_file_handler.py +1 -1
- streamlit/web/server/authlib_tornado_integration.py +58 -0
- streamlit/web/server/browser_websocket_handler.py +73 -7
- streamlit/web/server/component_request_handler.py +1 -1
- streamlit/web/server/media_file_handler.py +1 -1
- streamlit/web/server/oauth_authlib_routes.py +176 -0
- streamlit/web/server/oidc_mixin.py +108 -0
- streamlit/web/server/routes.py +6 -4
- streamlit/web/server/server.py +41 -4
- streamlit/web/server/server_util.py +25 -1
- streamlit/web/server/stats_request_handler.py +1 -1
- streamlit/web/server/upload_file_request_handler.py +3 -2
- streamlit/web/server/websocket_headers.py +1 -1
- {streamlit_nightly-1.41.2.dev20241227.data → streamlit_nightly-1.41.2.dev20250124.data}/scripts/streamlit.cmd +1 -1
- {streamlit_nightly-1.41.2.dev20241227.dist-info → streamlit_nightly-1.41.2.dev20250124.dist-info}/METADATA +16 -4
- streamlit_nightly-1.41.2.dev20250124.dist-info/RECORD +560 -0
- {streamlit_nightly-1.41.2.dev20241227.dist-info → streamlit_nightly-1.41.2.dev20250124.dist-info}/WHEEL +1 -1
- streamlit/static/static/css/index.ZIJkhegp.css +0 -1
- streamlit/static/static/js/InputInstructions.DpTmOmkP.js +0 -1
- streamlit/static/static/js/RenderInPortalIfExists.CphMKHZ_.js +0 -1
- streamlit/static/static/js/Toolbar.YZjXpW2P.js +0 -1
- streamlit/static/static/js/_commonjs-dynamic-modules.TDtrdbi3.js +0 -1
- streamlit/static/static/js/createSuper.Cj3WfXha.js +0 -1
- streamlit/static/static/js/data-grid-overlay-editor.CXW3Vrt9.js +0 -1
- streamlit/static/static/js/getPrototypeOf.BZAK2f3t.js +0 -1
- streamlit/static/static/js/index.1auHKWgw.js +0 -3
- streamlit/static/static/js/index.2XFKVtzu.js +0 -1
- streamlit/static/static/js/index.B9O1l5Yf.js +0 -32
- streamlit/static/static/js/index.BjtDBCFh.js +0 -2
- streamlit/static/static/js/index.Bp-uIPRI.js +0 -1
- streamlit/static/static/js/index.BrGYP4fV.js +0 -1
- streamlit/static/static/js/index.BwUmNKlx.js +0 -1
- streamlit/static/static/js/index.CCVCD0op.js +0 -1
- streamlit/static/static/js/index.CP7C0jmi.js +0 -1
- streamlit/static/static/js/index.CVNaQiWI.js +0 -4
- streamlit/static/static/js/index.CX7qwffH.js +0 -1
- streamlit/static/static/js/index.CaCnIXu_.js +0 -1
- streamlit/static/static/js/index.Ck6XWYeb.js +0 -1
- streamlit/static/static/js/index.CmD2DSvp.js +0 -201
- streamlit/static/static/js/index.CnX4MRBV.js +0 -1
- streamlit/static/static/js/index.DR8k91Kp.js +0 -1
- streamlit/static/static/js/index.Dc0EFNHf.js +0 -197
- streamlit/static/static/js/index.M9USxdKN.js +0 -1
- streamlit/static/static/js/index.MCDV8ab_.js +0 -1
- streamlit/static/static/js/index.T4c5nSGV.js +0 -2
- streamlit/static/static/js/index.brVZtr01.js +0 -1
- streamlit/static/static/js/index.fm1fEqFi.js +0 -1
- streamlit/static/static/js/index.vm3Bds7I.js +0 -1
- streamlit/static/static/js/index.y_EIxzAg.js +0 -1
- streamlit/static/static/js/number-overlay-editor.D5dgP2YW.js +0 -9
- streamlit/static/static/js/slicedToArray.DVgs1NsC.js +0 -2
- streamlit/static/static/js/string.Bl9OLDCw.js +0 -1
- streamlit/static/static/js/threshold.skajmqVB.js +0 -1
- streamlit/static/static/js/timer.DwZfkapc.js +0 -1
- streamlit/static/static/js/uniqueId.OJw6SDpp.js +0 -1
- streamlit/static/static/js/withFullScreenWrapper.BjS0eA06.js +0 -1
- streamlit/static/static/media/MaterialSymbols-Rounded.MYSe4dsi.woff2 +0 -0
- streamlit/vendor/ipython/__init__.py +0 -0
- streamlit/vendor/ipython/modified_sys_path.py +0 -67
- streamlit_nightly-1.41.2.dev20241227.dist-info/RECORD +0 -556
- /streamlit/static/static/css/{index.B26BQfSF.css → index.Bmkmz40k.css} +0 -0
- /streamlit/static/static/css/{index.CG16XVnL.css → index.DzuxGC_t.css} +0 -0
- {streamlit_nightly-1.41.2.dev20241227.dist-info → streamlit_nightly-1.41.2.dev20250124.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.41.2.dev20241227.dist-info → streamlit_nightly-1.41.2.dev20250124.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -218,8 +218,10 @@ def get_module_paths(module: ModuleType) -> set[str]:
|
|
218
218
|
except AttributeError:
|
219
219
|
# Some modules might not have __file__ or __spec__ attributes.
|
220
220
|
pass
|
221
|
-
except Exception
|
222
|
-
_LOGGER.warning(
|
221
|
+
except Exception:
|
222
|
+
_LOGGER.warning(
|
223
|
+
f"Examining the path of {module.__name__} raised:", exc_info=True
|
224
|
+
)
|
223
225
|
|
224
226
|
all_paths.update(
|
225
227
|
[os.path.abspath(str(p)) for p in potential_paths if _is_valid_path(p)]
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
streamlit/watcher/util.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -24,7 +24,9 @@ import hashlib
|
|
24
24
|
import os
|
25
25
|
import time
|
26
26
|
from pathlib import Path
|
27
|
+
from typing import Callable, TypeVar
|
27
28
|
|
29
|
+
from streamlit.errors import Error
|
28
30
|
from streamlit.util import HASHLIB_KWARGS
|
29
31
|
|
30
32
|
# How many times to try to grab the MD5 hash.
|
@@ -56,7 +58,14 @@ def calc_md5_with_blocking_retries(
|
|
56
58
|
glob_pattern = glob_pattern or "*"
|
57
59
|
content = _stable_dir_identifier(path, glob_pattern).encode("UTF-8")
|
58
60
|
else:
|
59
|
-
|
61
|
+
# There's a race condition where sometimes file_path no longer exists when
|
62
|
+
# we try to read it (since the file is in the process of being written).
|
63
|
+
# So here we retry a few times using this loop. See issue #186.
|
64
|
+
content = _do_with_retries(
|
65
|
+
lambda: _get_file_content(path),
|
66
|
+
FileNotFoundError,
|
67
|
+
path,
|
68
|
+
)
|
60
69
|
|
61
70
|
md5 = hashlib.md5(**HASHLIB_KWARGS)
|
62
71
|
md5.update(content)
|
@@ -81,24 +90,19 @@ def path_modification_time(path: str, allow_nonexistent: bool = False) -> float:
|
|
81
90
|
"""
|
82
91
|
if allow_nonexistent and not os.path.exists(path):
|
83
92
|
return 0.0
|
84
|
-
return os.stat(path).st_mtime
|
85
93
|
|
94
|
+
# Use retries to avoid race condition where file may be in the process of being
|
95
|
+
# modified.
|
96
|
+
return _do_with_retries(
|
97
|
+
lambda: os.stat(path).st_mtime,
|
98
|
+
FileNotFoundError,
|
99
|
+
path,
|
100
|
+
)
|
86
101
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
# So here we retry a few times using this loop. See issue #186.
|
92
|
-
for i in range(_MAX_RETRIES):
|
93
|
-
try:
|
94
|
-
with open(file_path, "rb") as f:
|
95
|
-
content = f.read()
|
96
|
-
break
|
97
|
-
except FileNotFoundError as e:
|
98
|
-
if i >= _MAX_RETRIES - 1:
|
99
|
-
raise e
|
100
|
-
time.sleep(_RETRY_WAIT_SECS)
|
101
|
-
return content
|
102
|
+
|
103
|
+
def _get_file_content(file_path: str) -> bytes:
|
104
|
+
with open(file_path, "rb") as f:
|
105
|
+
return f.read()
|
102
106
|
|
103
107
|
|
104
108
|
def _dirfiles(dir_path: str, glob_pattern: str) -> str:
|
@@ -134,9 +138,7 @@ def _stable_dir_identifier(dir_path: str, glob_pattern: str) -> str:
|
|
134
138
|
"""
|
135
139
|
dirfiles = _dirfiles(dir_path, glob_pattern)
|
136
140
|
|
137
|
-
for _ in
|
138
|
-
time.sleep(_RETRY_WAIT_SECS)
|
139
|
-
|
141
|
+
for _ in _retry_dance():
|
140
142
|
new_dirfiles = _dirfiles(dir_path, glob_pattern)
|
141
143
|
if dirfiles == new_dirfiles:
|
142
144
|
break
|
@@ -144,3 +146,67 @@ def _stable_dir_identifier(dir_path: str, glob_pattern: str) -> str:
|
|
144
146
|
dirfiles = new_dirfiles
|
145
147
|
|
146
148
|
return f"{dir_path}+{dirfiles}"
|
149
|
+
|
150
|
+
|
151
|
+
T = TypeVar("T")
|
152
|
+
|
153
|
+
|
154
|
+
def _do_with_retries(
|
155
|
+
orig_fn: Callable[[], T],
|
156
|
+
exception: type[Exception],
|
157
|
+
path: str | Path,
|
158
|
+
) -> T:
|
159
|
+
"""Helper for retrying a function.
|
160
|
+
|
161
|
+
Calls `orig_fn`. If `exception` is raised, retry.
|
162
|
+
|
163
|
+
To use this, just replace things like this...
|
164
|
+
|
165
|
+
result = thing_to_do(file_path, a, b, c)
|
166
|
+
|
167
|
+
...with this:
|
168
|
+
|
169
|
+
result = _do_with_retries(
|
170
|
+
lambda: thing_to_do(file_path, a, b, c),
|
171
|
+
exception: ExceptionThatWillCauseARetry,
|
172
|
+
file_path, # For pretty error message.
|
173
|
+
)
|
174
|
+
"""
|
175
|
+
for i in _retry_dance():
|
176
|
+
try:
|
177
|
+
return orig_fn()
|
178
|
+
except exception:
|
179
|
+
if i >= _MAX_RETRIES - 1:
|
180
|
+
raise
|
181
|
+
else:
|
182
|
+
# Continue with loop to either retry or raise MaxRetriesError.
|
183
|
+
pass
|
184
|
+
|
185
|
+
raise MaxRetriesError(f"Unable to access file or folder: {path}")
|
186
|
+
|
187
|
+
|
188
|
+
def _retry_dance():
|
189
|
+
"""Helper for writing a retry loop.
|
190
|
+
|
191
|
+
This is useful to make sure all our retry loops work the same way. For example,
|
192
|
+
prior to this helper, some loops had time.sleep() *before the first try*, which just
|
193
|
+
slowed things down for no reason.
|
194
|
+
|
195
|
+
Usage:
|
196
|
+
|
197
|
+
for i in _retry_dance():
|
198
|
+
# Do the thing you want to retry automatically.
|
199
|
+
the_thing_worked = do_thing()
|
200
|
+
|
201
|
+
# Don't forget to include a break/return when the thing you're trying to do
|
202
|
+
# works.
|
203
|
+
if the_thing_worked:
|
204
|
+
break
|
205
|
+
"""
|
206
|
+
for i in range(_MAX_RETRIES):
|
207
|
+
yield i
|
208
|
+
time.sleep(_RETRY_WAIT_SECS)
|
209
|
+
|
210
|
+
|
211
|
+
class MaxRetriesError(Error):
|
212
|
+
pass
|
streamlit/web/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
streamlit/web/bootstrap.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -298,6 +298,8 @@ def run(
|
|
298
298
|
is_hello: bool,
|
299
299
|
args: list[str],
|
300
300
|
flag_options: dict[str, Any],
|
301
|
+
*,
|
302
|
+
stop_immediately_for_testing: bool = False,
|
301
303
|
) -> None:
|
302
304
|
"""Run a script in a separate thread and start a server for the app.
|
303
305
|
|
@@ -321,9 +323,27 @@ def run(
|
|
321
323
|
# and close all our threads
|
322
324
|
_set_up_signal_handler(server)
|
323
325
|
|
326
|
+
# return immediately if we're testing the server start
|
327
|
+
if stop_immediately_for_testing:
|
328
|
+
_LOGGER.debug("Stopping server immediately for testing")
|
329
|
+
server.stop()
|
330
|
+
|
324
331
|
# Wait until `Server.stop` is called, either by our signal handler, or
|
325
332
|
# by a debug websocket session.
|
326
333
|
await server.stopped
|
327
334
|
|
328
335
|
# Run the server. This function will not return until the server is shut down.
|
329
|
-
asyncio.run(
|
336
|
+
# FIX RuntimeError: asyncio.run() cannot be called from a running event loop on Python 3.10.16
|
337
|
+
# asyncio.run(run_server())
|
338
|
+
|
339
|
+
# Define a main function to handle the event loop logic
|
340
|
+
async def main():
|
341
|
+
await run_server()
|
342
|
+
|
343
|
+
# Check if we're already in an event loop
|
344
|
+
if asyncio.get_event_loop().is_running():
|
345
|
+
# Use `asyncio.create_task` if we're in an async context
|
346
|
+
asyncio.create_task(main())
|
347
|
+
else:
|
348
|
+
# Otherwise, use `asyncio.run`
|
349
|
+
asyncio.run(main())
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
streamlit/web/cli.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
streamlit/web/server/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from __future__ import annotations
|
16
|
+
|
17
|
+
from typing import TYPE_CHECKING, Any, Sequence
|
18
|
+
|
19
|
+
from authlib.integrations.base_client import ( # type: ignore[import-untyped]
|
20
|
+
FrameworkIntegration,
|
21
|
+
)
|
22
|
+
|
23
|
+
from streamlit.runtime.secrets import AttrDict
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from streamlit.web.server.oidc_mixin import TornadoOAuth
|
27
|
+
|
28
|
+
|
29
|
+
class TornadoIntegration(FrameworkIntegration): # type: ignore[misc]
|
30
|
+
def update_token(self, token, refresh_token=None, access_token=None):
|
31
|
+
"""We do not support access token refresh, since we obtain and operate only on
|
32
|
+
identity tokens. We override this method explicitly to implement all abstract
|
33
|
+
methods of base class.
|
34
|
+
"""
|
35
|
+
|
36
|
+
@staticmethod
|
37
|
+
def load_config(
|
38
|
+
oauth: TornadoOAuth, name: str, params: Sequence[str]
|
39
|
+
) -> dict[str, Any]:
|
40
|
+
"""Configure Authlib integration with provider parameters
|
41
|
+
specified in secrets.toml
|
42
|
+
"""
|
43
|
+
|
44
|
+
# oauth.config here is an auth section from secrets.toml
|
45
|
+
# We parse it here to transform nested AttrDict (for client_kwargs value)
|
46
|
+
# to dict so Authlib can work with it under the hood.
|
47
|
+
if not oauth.config:
|
48
|
+
return {}
|
49
|
+
|
50
|
+
prepared_config = {}
|
51
|
+
for key in params:
|
52
|
+
value = oauth.config.get(name, {}).get(key, None)
|
53
|
+
if isinstance(value, AttrDict):
|
54
|
+
# We want to modify client_kwargs further after loading server metadata
|
55
|
+
value = value.to_dict()
|
56
|
+
if value is not None:
|
57
|
+
prepared_config[key] = value
|
58
|
+
return prepared_config
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -16,14 +16,17 @@ from __future__ import annotations
|
|
16
16
|
|
17
17
|
import base64
|
18
18
|
import binascii
|
19
|
+
import hmac
|
19
20
|
import json
|
20
21
|
from typing import TYPE_CHECKING, Any, Awaitable, Final
|
22
|
+
from urllib.parse import urlparse
|
21
23
|
|
22
24
|
import tornado.concurrent
|
23
25
|
import tornado.locks
|
24
26
|
import tornado.netutil
|
25
27
|
import tornado.web
|
26
28
|
import tornado.websocket
|
29
|
+
from tornado.escape import utf8
|
27
30
|
from tornado.websocket import WebSocketHandler
|
28
31
|
|
29
32
|
from streamlit import config
|
@@ -31,7 +34,11 @@ from streamlit.logger import get_logger
|
|
31
34
|
from streamlit.proto.BackMsg_pb2 import BackMsg
|
32
35
|
from streamlit.runtime import Runtime, SessionClient, SessionClientDisconnectedError
|
33
36
|
from streamlit.runtime.runtime_util import serialize_forward_msg
|
34
|
-
from streamlit.web.server.server_util import
|
37
|
+
from streamlit.web.server.server_util import (
|
38
|
+
AUTH_COOKIE_NAME,
|
39
|
+
is_url_from_allowed_origins,
|
40
|
+
is_xsrf_enabled,
|
41
|
+
)
|
35
42
|
|
36
43
|
if TYPE_CHECKING:
|
37
44
|
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
@@ -50,13 +57,63 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
50
57
|
# need to read the self.xsrf_token manually to set the cookie as a side
|
51
58
|
# effect. See https://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection
|
52
59
|
# for more details.
|
53
|
-
if
|
60
|
+
if is_xsrf_enabled():
|
54
61
|
_ = self.xsrf_token
|
55
62
|
|
63
|
+
def get_signed_cookie(
|
64
|
+
self,
|
65
|
+
name: str,
|
66
|
+
value: str | None = None,
|
67
|
+
max_age_days: float = 31,
|
68
|
+
min_version: int | None = None,
|
69
|
+
) -> bytes | None:
|
70
|
+
"""Get a signed cookie from the request. Added for compatibility with
|
71
|
+
Tornado < 6.3.0.
|
72
|
+
See release notes: https://www.tornadoweb.org/en/stable/releases/v6.3.0.html#deprecation-notices
|
73
|
+
"""
|
74
|
+
try:
|
75
|
+
return super().get_signed_cookie(name, value, max_age_days, min_version)
|
76
|
+
except AttributeError:
|
77
|
+
return super().get_secure_cookie(name, value, max_age_days, min_version)
|
78
|
+
|
56
79
|
def check_origin(self, origin: str) -> bool:
|
57
80
|
"""Set up CORS."""
|
58
81
|
return super().check_origin(origin) or is_url_from_allowed_origins(origin)
|
59
82
|
|
83
|
+
def _validate_xsrf_token(self, supplied_token: str) -> bool:
|
84
|
+
"""Inspired by tornado.web.RequestHandler.check_xsrf_cookie method,
|
85
|
+
to check the XSRF token passed in Websocket connection header.
|
86
|
+
"""
|
87
|
+
_, token, _ = self._decode_xsrf_token(supplied_token)
|
88
|
+
_, expected_token, _ = self._get_raw_xsrf_token()
|
89
|
+
|
90
|
+
decoded_token = utf8(token)
|
91
|
+
decoded_expected_token = utf8(expected_token)
|
92
|
+
|
93
|
+
if not decoded_token or not decoded_expected_token:
|
94
|
+
return False
|
95
|
+
return hmac.compare_digest(decoded_token, decoded_expected_token)
|
96
|
+
|
97
|
+
def _parse_user_cookie(self, raw_cookie_value: bytes, email: str) -> dict[str, Any]:
|
98
|
+
"""Process the user cookie and extract the user info after
|
99
|
+
validating the origin. Origin is validated for security reasons.
|
100
|
+
"""
|
101
|
+
cookie_value = json.loads(raw_cookie_value)
|
102
|
+
user_info = {}
|
103
|
+
|
104
|
+
cookie_value_origin = cookie_value.get("origin", None)
|
105
|
+
parsed_origin_from_header = urlparse(self.request.headers["Origin"])
|
106
|
+
expected_origin_value = (
|
107
|
+
parsed_origin_from_header.scheme + "://" + parsed_origin_from_header.netloc
|
108
|
+
)
|
109
|
+
if cookie_value_origin == expected_origin_value:
|
110
|
+
user_info["is_logged_in"] = cookie_value.get("is_logged_in", False)
|
111
|
+
del cookie_value["origin"]
|
112
|
+
del cookie_value["is_logged_in"]
|
113
|
+
user_info.update(cookie_value)
|
114
|
+
|
115
|
+
return user_info
|
116
|
+
|
60
117
|
def write_forward_msg(self, msg: ForwardMsg) -> None:
|
61
118
|
"""Send a ForwardMsg to the browser."""
|
62
119
|
try:
|
@@ -102,11 +159,13 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
102
159
|
is_public_cloud_app = user_obj["isPublicCloudApp"]
|
103
160
|
except (KeyError, binascii.Error, json.decoder.JSONDecodeError):
|
104
161
|
email = "test@example.com"
|
105
|
-
|
106
|
-
|
107
|
-
"email": None if is_public_cloud_app else email
|
162
|
+
user_info: dict[str, str | bool | None] = {
|
163
|
+
"email": None if is_public_cloud_app else email,
|
108
164
|
}
|
109
165
|
|
166
|
+
if is_public_cloud_app or email == "test@example.com":
|
167
|
+
user_info.pop("email", None)
|
168
|
+
|
110
169
|
existing_session_id = None
|
111
170
|
try:
|
112
171
|
ws_protocols = [
|
@@ -114,6 +173,13 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
114
173
|
for p in self.request.headers["Sec-Websocket-Protocol"].split(",")
|
115
174
|
]
|
116
175
|
|
176
|
+
raw_cookie_value = self.get_signed_cookie(AUTH_COOKIE_NAME)
|
177
|
+
if is_xsrf_enabled() and raw_cookie_value:
|
178
|
+
csrf_protocol_value = ws_protocols[1]
|
179
|
+
|
180
|
+
if self._validate_xsrf_token(csrf_protocol_value):
|
181
|
+
user_info.update(self._parse_user_cookie(raw_cookie_value, email))
|
182
|
+
|
117
183
|
if len(ws_protocols) >= 3:
|
118
184
|
# See the NOTE in the docstring of the `select_subprotocol` method above
|
119
185
|
# for a detailed explanation of why this is done.
|
@@ -166,7 +232,7 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
166
232
|
_LOGGER.debug("Received the following back message:\n%s", msg)
|
167
233
|
|
168
234
|
except Exception as ex:
|
169
|
-
_LOGGER.
|
235
|
+
_LOGGER.exception("Error deserializing back message")
|
170
236
|
self._runtime.handle_backmsg_deserialization_exception(self._session_id, ex)
|
171
237
|
return
|
172
238
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
from __future__ import annotations
|
15
|
+
|
16
|
+
import json
|
17
|
+
from typing import Any
|
18
|
+
from urllib.parse import urlparse
|
19
|
+
|
20
|
+
import tornado.web
|
21
|
+
|
22
|
+
from streamlit.auth_util import (
|
23
|
+
AuthCache,
|
24
|
+
decode_provider_token,
|
25
|
+
generate_default_provider_section,
|
26
|
+
get_secrets_auth_section,
|
27
|
+
)
|
28
|
+
from streamlit.errors import StreamlitAuthError
|
29
|
+
from streamlit.url_util import make_url_path
|
30
|
+
from streamlit.web.server.oidc_mixin import TornadoOAuth, TornadoOAuth2App
|
31
|
+
from streamlit.web.server.server_util import AUTH_COOKIE_NAME
|
32
|
+
|
33
|
+
auth_cache = AuthCache()
|
34
|
+
|
35
|
+
|
36
|
+
def create_oauth_client(provider: str) -> tuple[TornadoOAuth2App, str]:
|
37
|
+
"""Create an OAuth client for the given provider based on secrets.toml configuration."""
|
38
|
+
auth_section = get_secrets_auth_section()
|
39
|
+
if auth_section:
|
40
|
+
redirect_uri = auth_section.get("redirect_uri", None)
|
41
|
+
config = auth_section.to_dict()
|
42
|
+
else:
|
43
|
+
config = {}
|
44
|
+
redirect_uri = "/"
|
45
|
+
|
46
|
+
provider_section = config.setdefault(provider, {})
|
47
|
+
|
48
|
+
if not provider_section and provider == "default":
|
49
|
+
provider_section = generate_default_provider_section(auth_section)
|
50
|
+
config["default"] = provider_section
|
51
|
+
|
52
|
+
provider_client_kwargs = provider_section.setdefault("client_kwargs", {})
|
53
|
+
if "scope" not in provider_client_kwargs:
|
54
|
+
provider_client_kwargs["scope"] = "openid email profile"
|
55
|
+
if "prompt" not in provider_client_kwargs:
|
56
|
+
provider_client_kwargs["prompt"] = "select_account"
|
57
|
+
|
58
|
+
oauth = TornadoOAuth(config, cache=auth_cache)
|
59
|
+
oauth.register(provider)
|
60
|
+
return oauth.create_client(provider), redirect_uri
|
61
|
+
|
62
|
+
|
63
|
+
class AuthHandlerMixin(tornado.web.RequestHandler):
|
64
|
+
"""Mixin for handling auth cookies. Added for compatibility with Tornado < 6.3.0."""
|
65
|
+
|
66
|
+
def initialize(self, base_url: str) -> None:
|
67
|
+
self.base_url = base_url
|
68
|
+
|
69
|
+
def redirect_to_base(self) -> None:
|
70
|
+
self.redirect(make_url_path(self.base_url, "/"))
|
71
|
+
|
72
|
+
def set_auth_cookie(self, user_info: dict[str, Any]) -> None:
|
73
|
+
serialized_cookie_value = json.dumps(user_info)
|
74
|
+
try:
|
75
|
+
# We don't specify Tornado secure flag here because it leads to missing cookie on Safari.
|
76
|
+
# The OIDC flow should work only on secure context anyway (localhost or HTTPS),
|
77
|
+
# so specifying the secure flag here will not add anything in terms of security.
|
78
|
+
self.set_signed_cookie(
|
79
|
+
AUTH_COOKIE_NAME,
|
80
|
+
serialized_cookie_value,
|
81
|
+
httpOnly=True,
|
82
|
+
)
|
83
|
+
except AttributeError:
|
84
|
+
self.set_secure_cookie(
|
85
|
+
AUTH_COOKIE_NAME,
|
86
|
+
serialized_cookie_value,
|
87
|
+
httponly=True,
|
88
|
+
)
|
89
|
+
|
90
|
+
def clear_auth_cookie(self) -> None:
|
91
|
+
self.clear_cookie(AUTH_COOKIE_NAME)
|
92
|
+
|
93
|
+
|
94
|
+
class AuthLoginHandler(AuthHandlerMixin, tornado.web.RequestHandler):
|
95
|
+
async def get(self):
|
96
|
+
"""Redirect to the OAuth provider login page."""
|
97
|
+
provider = self._parse_provider_token()
|
98
|
+
if provider is None:
|
99
|
+
self.redirect_to_base()
|
100
|
+
return
|
101
|
+
|
102
|
+
client, redirect_uri = create_oauth_client(provider)
|
103
|
+
try:
|
104
|
+
client.authorize_redirect(self, redirect_uri)
|
105
|
+
except Exception as e:
|
106
|
+
self.send_error(400, reason=str(e))
|
107
|
+
|
108
|
+
def _parse_provider_token(self) -> str | None:
|
109
|
+
provider_token = self.get_argument("provider", None)
|
110
|
+
try:
|
111
|
+
if provider_token is None:
|
112
|
+
raise StreamlitAuthError("Missing provider token")
|
113
|
+
payload = decode_provider_token(provider_token)
|
114
|
+
except StreamlitAuthError:
|
115
|
+
return None
|
116
|
+
|
117
|
+
return payload["provider"]
|
118
|
+
|
119
|
+
|
120
|
+
class AuthLogoutHandler(AuthHandlerMixin, tornado.web.RequestHandler):
|
121
|
+
def get(self):
|
122
|
+
self.clear_auth_cookie()
|
123
|
+
self.redirect_to_base()
|
124
|
+
|
125
|
+
|
126
|
+
class AuthCallbackHandler(AuthHandlerMixin, tornado.web.RequestHandler):
|
127
|
+
async def get(self):
|
128
|
+
provider = self._get_provider_by_state()
|
129
|
+
origin = self._get_origin_from_secrets()
|
130
|
+
if origin is None:
|
131
|
+
self.redirect_to_base()
|
132
|
+
return
|
133
|
+
|
134
|
+
error = self.get_argument("error", None)
|
135
|
+
if error:
|
136
|
+
self.redirect_to_base()
|
137
|
+
return
|
138
|
+
|
139
|
+
if provider is None:
|
140
|
+
self.redirect_to_base()
|
141
|
+
return
|
142
|
+
|
143
|
+
client, _ = create_oauth_client(provider)
|
144
|
+
token = client.authorize_access_token(self)
|
145
|
+
user = token.get("userinfo")
|
146
|
+
|
147
|
+
cookie_value = dict(user, origin=origin, is_logged_in=True)
|
148
|
+
if user:
|
149
|
+
self.set_auth_cookie(cookie_value)
|
150
|
+
self.redirect_to_base()
|
151
|
+
|
152
|
+
def _get_provider_by_state(self) -> str | None:
|
153
|
+
state_code_from_url = self.get_argument("state")
|
154
|
+
current_cache_keys = list(auth_cache.get_dict().keys())
|
155
|
+
state_provider_mapping = {}
|
156
|
+
for key in current_cache_keys:
|
157
|
+
_, _, recorded_provider, code = key.split("_")
|
158
|
+
state_provider_mapping[code] = recorded_provider
|
159
|
+
|
160
|
+
provider: str | None = state_provider_mapping.get(state_code_from_url, None)
|
161
|
+
return provider
|
162
|
+
|
163
|
+
def _get_origin_from_secrets(self) -> str | None:
|
164
|
+
redirect_uri = None
|
165
|
+
auth_section = get_secrets_auth_section()
|
166
|
+
if auth_section:
|
167
|
+
redirect_uri = auth_section.get("redirect_uri", None)
|
168
|
+
|
169
|
+
if not redirect_uri:
|
170
|
+
return None
|
171
|
+
|
172
|
+
redirect_uri_parsed = urlparse(redirect_uri)
|
173
|
+
origin_from_redirect_uri: str = (
|
174
|
+
redirect_uri_parsed.scheme + "://" + redirect_uri_parsed.netloc
|
175
|
+
)
|
176
|
+
return origin_from_redirect_uri
|