streamlit-data-editor-plus 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit/__init__.py +292 -0
- streamlit/__main__.py +20 -0
- streamlit/auth_util.py +583 -0
- streamlit/cli_util.py +107 -0
- streamlit/column_config.py +60 -0
- streamlit/commands/__init__.py +13 -0
- streamlit/commands/echo.py +128 -0
- streamlit/commands/execution_control.py +318 -0
- streamlit/commands/logo.py +250 -0
- streamlit/commands/navigation.py +430 -0
- streamlit/commands/page_config.py +335 -0
- streamlit/components/__init__.py +13 -0
- streamlit/components/lib/__init__.py +13 -0
- streamlit/components/lib/local_component_registry.py +84 -0
- streamlit/components/types/__init__.py +13 -0
- streamlit/components/types/base_component_registry.py +99 -0
- streamlit/components/types/base_custom_component.py +158 -0
- streamlit/components/v1/__init__.py +29 -0
- streamlit/components/v1/component_arrow.py +141 -0
- streamlit/components/v1/component_registry.py +147 -0
- streamlit/components/v1/components.py +38 -0
- streamlit/components/v1/custom_component.py +239 -0
- streamlit/components/v2/__init__.py +531 -0
- streamlit/components/v2/bidi_component/__init__.py +20 -0
- streamlit/components/v2/bidi_component/constants.py +29 -0
- streamlit/components/v2/bidi_component/main.py +537 -0
- streamlit/components/v2/bidi_component/serialization.py +272 -0
- streamlit/components/v2/bidi_component/state.py +92 -0
- streamlit/components/v2/component_definition_resolver.py +143 -0
- streamlit/components/v2/component_file_watcher.py +403 -0
- streamlit/components/v2/component_manager.py +439 -0
- streamlit/components/v2/component_manifest_handler.py +122 -0
- streamlit/components/v2/component_path_utils.py +233 -0
- streamlit/components/v2/component_registry.py +426 -0
- streamlit/components/v2/get_bidi_component_manager.py +51 -0
- streamlit/components/v2/manifest_scanner.py +620 -0
- streamlit/components/v2/presentation.py +198 -0
- streamlit/components/v2/types.py +317 -0
- streamlit/config.py +2977 -0
- streamlit/config_option.py +319 -0
- streamlit/config_util.py +887 -0
- streamlit/connections/__init__.py +32 -0
- streamlit/connections/base_connection.py +215 -0
- streamlit/connections/snowflake_connection.py +765 -0
- streamlit/connections/snowpark_connection.py +213 -0
- streamlit/connections/sql_connection.py +427 -0
- streamlit/connections/util.py +97 -0
- streamlit/cursor.py +314 -0
- streamlit/dataframe_util.py +1434 -0
- streamlit/delta_generator.py +696 -0
- streamlit/delta_generator_singletons.py +243 -0
- streamlit/deprecation_util.py +253 -0
- streamlit/development.py +21 -0
- streamlit/elements/__init__.py +13 -0
- streamlit/elements/alert.py +341 -0
- streamlit/elements/arrow.py +1065 -0
- streamlit/elements/balloons.py +47 -0
- streamlit/elements/bokeh_chart.py +75 -0
- streamlit/elements/code.py +154 -0
- streamlit/elements/deck_gl_json_chart.py +627 -0
- streamlit/elements/dialog_decorator.py +320 -0
- streamlit/elements/empty.py +130 -0
- streamlit/elements/exception.py +370 -0
- streamlit/elements/form.py +481 -0
- streamlit/elements/graphviz_chart.py +226 -0
- streamlit/elements/heading.py +407 -0
- streamlit/elements/help.py +565 -0
- streamlit/elements/html.py +189 -0
- streamlit/elements/iframe.py +245 -0
- streamlit/elements/image.py +221 -0
- streamlit/elements/json.py +171 -0
- streamlit/elements/layouts.py +1534 -0
- streamlit/elements/lib/__init__.py +13 -0
- streamlit/elements/lib/built_in_chart_utils.py +1316 -0
- streamlit/elements/lib/color_util.py +272 -0
- streamlit/elements/lib/column_config_utils.py +555 -0
- streamlit/elements/lib/column_types.py +2699 -0
- streamlit/elements/lib/dialog.py +212 -0
- streamlit/elements/lib/dicttools.py +152 -0
- streamlit/elements/lib/file_uploader_utils.py +91 -0
- streamlit/elements/lib/form_utils.py +77 -0
- streamlit/elements/lib/image_utils.py +447 -0
- streamlit/elements/lib/js_number.py +105 -0
- streamlit/elements/lib/layout_utils.py +325 -0
- streamlit/elements/lib/mutable_expander_container.py +73 -0
- streamlit/elements/lib/mutable_popover_container.py +73 -0
- streamlit/elements/lib/mutable_status_container.py +194 -0
- streamlit/elements/lib/mutable_tab_container.py +73 -0
- streamlit/elements/lib/options_selector_utils.py +480 -0
- streamlit/elements/lib/pandas_styler_utils.py +302 -0
- streamlit/elements/lib/policies.py +198 -0
- streamlit/elements/lib/shortcut_utils.py +152 -0
- streamlit/elements/lib/streamlit_plotly_theme.py +206 -0
- streamlit/elements/lib/subtitle_utils.py +175 -0
- streamlit/elements/lib/utils.py +274 -0
- streamlit/elements/map.py +526 -0
- streamlit/elements/markdown.py +529 -0
- streamlit/elements/media.py +843 -0
- streamlit/elements/metric.py +529 -0
- streamlit/elements/pdf.py +190 -0
- streamlit/elements/plotly_chart.py +779 -0
- streamlit/elements/progress.py +172 -0
- streamlit/elements/pyplot.py +240 -0
- streamlit/elements/snow.py +47 -0
- streamlit/elements/space.py +121 -0
- streamlit/elements/spinner.py +152 -0
- streamlit/elements/table.py +240 -0
- streamlit/elements/text.py +113 -0
- streamlit/elements/toast.py +175 -0
- streamlit/elements/vega_charts.py +2510 -0
- streamlit/elements/widgets/__init__.py +13 -0
- streamlit/elements/widgets/audio_input.py +334 -0
- streamlit/elements/widgets/button.py +1559 -0
- streamlit/elements/widgets/button_group.py +1061 -0
- streamlit/elements/widgets/camera_input.py +281 -0
- streamlit/elements/widgets/chat.py +1028 -0
- streamlit/elements/widgets/checkbox.py +420 -0
- streamlit/elements/widgets/color_picker.py +329 -0
- streamlit/elements/widgets/data_editor.py +1168 -0
- streamlit/elements/widgets/data_editor_plus.py +902 -0
- streamlit/elements/widgets/feedback.py +322 -0
- streamlit/elements/widgets/file_uploader.py +592 -0
- streamlit/elements/widgets/multiselect.py +654 -0
- streamlit/elements/widgets/number_input.py +719 -0
- streamlit/elements/widgets/radio.py +551 -0
- streamlit/elements/widgets/select_slider.py +568 -0
- streamlit/elements/widgets/selectbox.py +680 -0
- streamlit/elements/widgets/slider.py +1093 -0
- streamlit/elements/widgets/text_widgets.py +779 -0
- streamlit/elements/widgets/time_widgets.py +1712 -0
- streamlit/elements/write.py +585 -0
- streamlit/emojis.py +34 -0
- streamlit/env_util.py +56 -0
- streamlit/error_util.py +112 -0
- streamlit/errors.py +680 -0
- streamlit/external/__init__.py +13 -0
- streamlit/external/langchain/__init__.py +23 -0
- streamlit/external/langchain/streamlit_callback_handler.py +410 -0
- streamlit/file_util.py +266 -0
- streamlit/git_util.py +200 -0
- streamlit/hello/__init__.py +13 -0
- streamlit/hello/__pycache__/__init__.cpython-312.pyc +0 -0
- streamlit/hello/__pycache__/streamlit_app.cpython-312.pyc +0 -0
- streamlit/hello/animation_demo.py +82 -0
- streamlit/hello/dataframe_demo.py +71 -0
- streamlit/hello/hello.py +46 -0
- streamlit/hello/mapping_demo.py +113 -0
- streamlit/hello/plotting_demo.py +62 -0
- streamlit/hello/streamlit_app.py +57 -0
- streamlit/hello/utils.py +30 -0
- streamlit/logger.py +129 -0
- streamlit/material_icon_names.py +25 -0
- streamlit/navigation/__init__.py +13 -0
- streamlit/navigation/page.py +365 -0
- streamlit/net_util.py +125 -0
- streamlit/path_security.py +98 -0
- streamlit/platform.py +31 -0
- streamlit/proto/Alert_pb2.py +28 -0
- streamlit/proto/Alert_pb2.pyi +100 -0
- streamlit/proto/AppPage_pb2.py +25 -0
- streamlit/proto/AppPage_pb2.pyi +75 -0
- streamlit/proto/ArrowData_pb2.py +27 -0
- streamlit/proto/ArrowData_pb2.pyi +103 -0
- streamlit/proto/ArrowNamedDataSet_pb2.py +26 -0
- streamlit/proto/ArrowNamedDataSet_pb2.pyi +65 -0
- streamlit/proto/AudioInput_pb2.py +26 -0
- streamlit/proto/AudioInput_pb2.pyi +72 -0
- streamlit/proto/Audio_pb2.py +26 -0
- streamlit/proto/Audio_pb2.pyi +75 -0
- streamlit/proto/AuthRedirect_pb2.py +25 -0
- streamlit/proto/AuthRedirect_pb2.pyi +48 -0
- streamlit/proto/AutoRerun_pb2.py +25 -0
- streamlit/proto/AutoRerun_pb2.pyi +52 -0
- streamlit/proto/BackMsg_pb2.py +29 -0
- streamlit/proto/BackMsg_pb2.pyi +132 -0
- streamlit/proto/Balloons_pb2.py +25 -0
- streamlit/proto/Balloons_pb2.pyi +48 -0
- streamlit/proto/BidiComponent_pb2.py +32 -0
- streamlit/proto/BidiComponent_pb2.pyi +176 -0
- streamlit/proto/Block_pb2.py +62 -0
- streamlit/proto/Block_pb2.pyi +518 -0
- streamlit/proto/ButtonGroup_pb2.py +32 -0
- streamlit/proto/ButtonGroup_pb2.pyi +157 -0
- streamlit/proto/ButtonLikeIconPosition_pb2.py +25 -0
- streamlit/proto/ButtonLikeIconPosition_pb2.pyi +47 -0
- streamlit/proto/Button_pb2.py +26 -0
- streamlit/proto/Button_pb2.pyi +82 -0
- streamlit/proto/CameraInput_pb2.py +26 -0
- streamlit/proto/CameraInput_pb2.pyi +66 -0
- streamlit/proto/ChatInput_pb2.py +27 -0
- streamlit/proto/ChatInput_pb2.pyi +113 -0
- streamlit/proto/Checkbox_pb2.py +28 -0
- streamlit/proto/Checkbox_pb2.pyi +99 -0
- streamlit/proto/ClientState_pb2.py +28 -0
- streamlit/proto/ClientState_pb2.pyi +137 -0
- streamlit/proto/Code_pb2.py +25 -0
- streamlit/proto/Code_pb2.pyi +59 -0
- streamlit/proto/ColorPicker_pb2.py +26 -0
- streamlit/proto/ColorPicker_pb2.pyi +82 -0
- streamlit/proto/Common_pb2.py +49 -0
- streamlit/proto/Common_pb2.pyi +325 -0
- streamlit/proto/Components_pb2.py +33 -0
- streamlit/proto/Components_pb2.pyi +196 -0
- streamlit/proto/Dataframe_pb2.py +30 -0
- streamlit/proto/Dataframe_pb2.pyi +172 -0
- streamlit/proto/DateInput_pb2.py +26 -0
- streamlit/proto/DateInput_pb2.pyi +91 -0
- streamlit/proto/DateTimeInput_pb2.py +26 -0
- streamlit/proto/DateTimeInput_pb2.pyi +94 -0
- streamlit/proto/DeckGlJsonChart_pb2.py +27 -0
- streamlit/proto/DeckGlJsonChart_pb2.pyi +91 -0
- streamlit/proto/Delta_pb2.py +29 -0
- streamlit/proto/Delta_pb2.pyi +82 -0
- streamlit/proto/DownloadButton_pb2.py +26 -0
- streamlit/proto/DownloadButton_pb2.pyi +92 -0
- streamlit/proto/Element_pb2.py +81 -0
- streamlit/proto/Element_pb2.pyi +348 -0
- streamlit/proto/Empty_pb2.py +25 -0
- streamlit/proto/Empty_pb2.pyi +42 -0
- streamlit/proto/Exception_pb2.py +26 -0
- streamlit/proto/Exception_pb2.pyi +88 -0
- streamlit/proto/Favicon_pb2.py +25 -0
- streamlit/proto/Favicon_pb2.pyi +47 -0
- streamlit/proto/Feedback_pb2.py +27 -0
- streamlit/proto/Feedback_pb2.pyi +93 -0
- streamlit/proto/FileUploader_pb2.py +26 -0
- streamlit/proto/FileUploader_pb2.pyi +90 -0
- streamlit/proto/ForwardMsg_pb2.py +48 -0
- streamlit/proto/ForwardMsg_pb2.pyi +296 -0
- streamlit/proto/GapSize_pb2.py +27 -0
- streamlit/proto/GapSize_pb2.pyi +82 -0
- streamlit/proto/GitInfo_pb2.py +27 -0
- streamlit/proto/GitInfo_pb2.pyi +84 -0
- streamlit/proto/GraphVizChart_pb2.py +25 -0
- streamlit/proto/GraphVizChart_pb2.pyi +56 -0
- streamlit/proto/Heading_pb2.py +25 -0
- streamlit/proto/Heading_pb2.pyi +63 -0
- streamlit/proto/HeightConfig_pb2.py +25 -0
- streamlit/proto/HeightConfig_pb2.pyi +61 -0
- streamlit/proto/Help_pb2.py +27 -0
- streamlit/proto/Help_pb2.pyi +104 -0
- streamlit/proto/Html_pb2.py +25 -0
- streamlit/proto/Html_pb2.pyi +52 -0
- streamlit/proto/IFrame_pb2.py +25 -0
- streamlit/proto/IFrame_pb2.pyi +68 -0
- streamlit/proto/Image_pb2.py +27 -0
- streamlit/proto/Image_pb2.pyi +73 -0
- streamlit/proto/Json_pb2.py +25 -0
- streamlit/proto/Json_pb2.pyi +63 -0
- streamlit/proto/LabelVisibility_pb2.py +27 -0
- streamlit/proto/LabelVisibility_pb2.pyi +69 -0
- streamlit/proto/LinkButton_pb2.py +26 -0
- streamlit/proto/LinkButton_pb2.pyi +76 -0
- streamlit/proto/Logo_pb2.py +27 -0
- streamlit/proto/Logo_pb2.pyi +82 -0
- streamlit/proto/Markdown_pb2.py +27 -0
- streamlit/proto/Markdown_pb2.pyi +83 -0
- streamlit/proto/Metric_pb2.py +32 -0
- streamlit/proto/Metric_pb2.pyi +145 -0
- streamlit/proto/MetricsEvent_pb2.py +28 -0
- streamlit/proto/MetricsEvent_pb2.pyi +224 -0
- streamlit/proto/MultiSelect_pb2.py +26 -0
- streamlit/proto/MultiSelect_pb2.pyi +108 -0
- streamlit/proto/Navigation_pb2.py +28 -0
- streamlit/proto/Navigation_pb2.pyi +89 -0
- streamlit/proto/NewSession_pb2.py +47 -0
- streamlit/proto/NewSession_pb2.pyi +753 -0
- streamlit/proto/NumberInput_pb2.py +28 -0
- streamlit/proto/NumberInput_pb2.pyi +138 -0
- streamlit/proto/PageConfig_pb2.py +33 -0
- streamlit/proto/PageConfig_pb2.pyi +179 -0
- streamlit/proto/PageInfo_pb2.py +25 -0
- streamlit/proto/PageInfo_pb2.pyi +50 -0
- streamlit/proto/PageLink_pb2.py +26 -0
- streamlit/proto/PageLink_pb2.pyi +80 -0
- streamlit/proto/PageNotFound_pb2.py +25 -0
- streamlit/proto/PageNotFound_pb2.pyi +49 -0
- streamlit/proto/PageProfile_pb2.py +29 -0
- streamlit/proto/PageProfile_pb2.pyi +146 -0
- streamlit/proto/ParentMessage_pb2.py +25 -0
- streamlit/proto/ParentMessage_pb2.pyi +53 -0
- streamlit/proto/PlotlyChart_pb2.py +27 -0
- streamlit/proto/PlotlyChart_pb2.pyi +96 -0
- streamlit/proto/Progress_pb2.py +25 -0
- streamlit/proto/Progress_pb2.pyi +50 -0
- streamlit/proto/Radio_pb2.py +26 -0
- streamlit/proto/Radio_pb2.pyi +104 -0
- streamlit/proto/RootContainer_pb2.py +25 -0
- streamlit/proto/RootContainer_pb2.pyi +56 -0
- streamlit/proto/Selectbox_pb2.py +26 -0
- streamlit/proto/Selectbox_pb2.pyi +107 -0
- streamlit/proto/SessionEvent_pb2.py +26 -0
- streamlit/proto/SessionEvent_pb2.pyi +72 -0
- streamlit/proto/SessionStatus_pb2.py +25 -0
- streamlit/proto/SessionStatus_pb2.pyi +64 -0
- streamlit/proto/Skeleton_pb2.py +27 -0
- streamlit/proto/Skeleton_pb2.pyi +75 -0
- streamlit/proto/Slider_pb2.py +30 -0
- streamlit/proto/Slider_pb2.pyi +161 -0
- streamlit/proto/Snow_pb2.py +25 -0
- streamlit/proto/Snow_pb2.pyi +48 -0
- streamlit/proto/Space_pb2.py +25 -0
- streamlit/proto/Space_pb2.pyi +48 -0
- streamlit/proto/Spinner_pb2.py +25 -0
- streamlit/proto/Spinner_pb2.pyi +56 -0
- streamlit/proto/Table_pb2.py +28 -0
- streamlit/proto/Table_pb2.pyi +83 -0
- streamlit/proto/TextAlignmentConfig_pb2.py +27 -0
- streamlit/proto/TextAlignmentConfig_pb2.pyi +69 -0
- streamlit/proto/TextArea_pb2.py +26 -0
- streamlit/proto/TextArea_pb2.pyi +97 -0
- streamlit/proto/TextInput_pb2.py +28 -0
- streamlit/proto/TextInput_pb2.pyi +124 -0
- streamlit/proto/Text_pb2.py +25 -0
- streamlit/proto/Text_pb2.pyi +53 -0
- streamlit/proto/TimeInput_pb2.py +26 -0
- streamlit/proto/TimeInput_pb2.pyi +86 -0
- streamlit/proto/Toast_pb2.py +25 -0
- streamlit/proto/Toast_pb2.pyi +64 -0
- streamlit/proto/Transient_pb2.py +26 -0
- streamlit/proto/Transient_pb2.pyi +53 -0
- streamlit/proto/VegaLiteChart_pb2.py +27 -0
- streamlit/proto/VegaLiteChart_pb2.pyi +92 -0
- streamlit/proto/Video_pb2.py +30 -0
- streamlit/proto/Video_pb2.pyi +129 -0
- streamlit/proto/WidgetStates_pb2.py +31 -0
- streamlit/proto/WidgetStates_pb2.pyi +157 -0
- streamlit/proto/WidthConfig_pb2.py +25 -0
- streamlit/proto/WidthConfig_pb2.pyi +61 -0
- streamlit/proto/__init__.py +15 -0
- streamlit/proto/openmetrics_data_model_pb2.py +58 -0
- streamlit/proto/openmetrics_data_model_pb2.pyi +558 -0
- streamlit/py.typed +0 -0
- streamlit/runtime/__init__.py +50 -0
- streamlit/runtime/app_session.py +1302 -0
- streamlit/runtime/caching/__init__.py +118 -0
- streamlit/runtime/caching/cache_data_api.py +775 -0
- streamlit/runtime/caching/cache_errors.py +143 -0
- streamlit/runtime/caching/cache_resource_api.py +756 -0
- streamlit/runtime/caching/cache_type.py +33 -0
- streamlit/runtime/caching/cache_utils.py +601 -0
- streamlit/runtime/caching/cached_message_replay.py +290 -0
- streamlit/runtime/caching/hashing.py +655 -0
- streamlit/runtime/caching/legacy_cache_api.py +170 -0
- streamlit/runtime/caching/storage/__init__.py +29 -0
- streamlit/runtime/caching/storage/cache_storage_protocol.py +236 -0
- streamlit/runtime/caching/storage/dummy_cache_storage.py +60 -0
- streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py +159 -0
- streamlit/runtime/caching/storage/local_disk_cache_storage.py +223 -0
- streamlit/runtime/caching/ttl_cleanup_cache.py +85 -0
- streamlit/runtime/connection_factory.py +498 -0
- streamlit/runtime/context.py +449 -0
- streamlit/runtime/context_util.py +49 -0
- streamlit/runtime/credentials.py +355 -0
- streamlit/runtime/download_data_util.py +53 -0
- streamlit/runtime/forward_msg_cache.py +101 -0
- streamlit/runtime/forward_msg_queue.py +263 -0
- streamlit/runtime/fragment.py +441 -0
- streamlit/runtime/media_file_manager.py +409 -0
- streamlit/runtime/media_file_storage.py +143 -0
- streamlit/runtime/memory_media_file_storage.py +201 -0
- streamlit/runtime/memory_session_storage.py +77 -0
- streamlit/runtime/memory_uploaded_file_manager.py +149 -0
- streamlit/runtime/metrics_util.py +612 -0
- streamlit/runtime/pages_manager.py +160 -0
- streamlit/runtime/runtime.py +775 -0
- streamlit/runtime/runtime_util.py +108 -0
- streamlit/runtime/script_data.py +46 -0
- streamlit/runtime/scriptrunner/__init__.py +38 -0
- streamlit/runtime/scriptrunner/exec_code.py +167 -0
- streamlit/runtime/scriptrunner/magic.py +277 -0
- streamlit/runtime/scriptrunner/magic_funcs.py +32 -0
- streamlit/runtime/scriptrunner/script_cache.py +89 -0
- streamlit/runtime/scriptrunner/script_runner.py +805 -0
- streamlit/runtime/scriptrunner_utils/__init__.py +19 -0
- streamlit/runtime/scriptrunner_utils/exceptions.py +44 -0
- streamlit/runtime/scriptrunner_utils/script_requests.py +311 -0
- streamlit/runtime/scriptrunner_utils/script_run_context.py +277 -0
- streamlit/runtime/secrets.py +533 -0
- streamlit/runtime/session_manager.py +430 -0
- streamlit/runtime/state/__init__.py +47 -0
- streamlit/runtime/state/common.py +256 -0
- streamlit/runtime/state/presentation.py +85 -0
- streamlit/runtime/state/query_params.py +767 -0
- streamlit/runtime/state/query_params_proxy.py +222 -0
- streamlit/runtime/state/safe_session_state.py +146 -0
- streamlit/runtime/state/session_state.py +1299 -0
- streamlit/runtime/state/session_state_proxy.py +153 -0
- streamlit/runtime/state/widgets.py +197 -0
- streamlit/runtime/stats.py +356 -0
- streamlit/runtime/theme_util.py +148 -0
- streamlit/runtime/uploaded_file_manager.py +152 -0
- streamlit/runtime/websocket_session_manager.py +301 -0
- streamlit/source_util.py +97 -0
- streamlit/starlette.py +34 -0
- streamlit/string_util.py +264 -0
- streamlit/temporary_directory.py +67 -0
- streamlit/testing/__init__.py +13 -0
- streamlit/testing/v1/__init__.py +17 -0
- streamlit/testing/v1/app_test.py +1089 -0
- streamlit/testing/v1/element_tree.py +2272 -0
- streamlit/testing/v1/local_script_runner.py +176 -0
- streamlit/testing/v1/util.py +61 -0
- streamlit/time_util.py +73 -0
- streamlit/type_util.py +480 -0
- streamlit/url_util.py +120 -0
- streamlit/user_info.py +698 -0
- streamlit/util.py +108 -0
- streamlit/vendor/__init__.py +0 -0
- streamlit/vendor/pympler/__init__.py +0 -0
- streamlit/vendor/pympler/asizeof.py +2870 -0
- streamlit/version.py +18 -0
- streamlit/watcher/__init__.py +28 -0
- streamlit/watcher/event_based_path_watcher.py +500 -0
- streamlit/watcher/folder_black_list.py +82 -0
- streamlit/watcher/local_sources_watcher.py +297 -0
- streamlit/watcher/path_watcher.py +244 -0
- streamlit/watcher/polling_path_watcher.py +138 -0
- streamlit/watcher/util.py +223 -0
- streamlit/web/__init__.py +13 -0
- streamlit/web/bootstrap.py +463 -0
- streamlit/web/cache_storage_manager_config.py +38 -0
- streamlit/web/cli.py +465 -0
- streamlit/web/server/__init__.py +30 -0
- streamlit/web/server/app_discovery.py +422 -0
- streamlit/web/server/app_static_file_handler.py +102 -0
- streamlit/web/server/authlib_tornado_integration.py +125 -0
- streamlit/web/server/bidi_component_request_handler.py +193 -0
- streamlit/web/server/browser_websocket_handler.py +341 -0
- streamlit/web/server/component_file_utils.py +105 -0
- streamlit/web/server/component_request_handler.py +107 -0
- streamlit/web/server/media_file_handler.py +148 -0
- streamlit/web/server/oauth_authlib_routes.py +314 -0
- streamlit/web/server/oidc_mixin.py +138 -0
- streamlit/web/server/routes.py +257 -0
- streamlit/web/server/server.py +555 -0
- streamlit/web/server/server_util.py +235 -0
- streamlit/web/server/starlette/__init__.py +23 -0
- streamlit/web/server/starlette/starlette_app.py +541 -0
- streamlit/web/server/starlette/starlette_app_utils.py +306 -0
- streamlit/web/server/starlette/starlette_auth_routes.py +584 -0
- streamlit/web/server/starlette/starlette_gzip_middleware.py +121 -0
- streamlit/web/server/starlette/starlette_path_security_middleware.py +97 -0
- streamlit/web/server/starlette/starlette_routes.py +884 -0
- streamlit/web/server/starlette/starlette_server.py +496 -0
- streamlit/web/server/starlette/starlette_server_config.py +55 -0
- streamlit/web/server/starlette/starlette_static_routes.py +181 -0
- streamlit/web/server/starlette/starlette_websocket.py +560 -0
- streamlit/web/server/stats_request_handler.py +116 -0
- streamlit/web/server/upload_file_request_handler.py +158 -0
- streamlit/web/server/websocket_headers.py +56 -0
- streamlit_data_editor_plus-0.1.0.dist-info/METADATA +76 -0
- streamlit_data_editor_plus-0.1.0.dist-info/RECORD +456 -0
- streamlit_data_editor_plus-0.1.0.dist-info/WHEEL +5 -0
- streamlit_data_editor_plus-0.1.0.dist-info/entry_points.txt +2 -0
- streamlit_data_editor_plus-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026)
|
|
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
|
+
"""Serve static assets for Custom Components v2.
|
|
16
|
+
|
|
17
|
+
This module defines a Tornado ``RequestHandler`` that serves static files for
|
|
18
|
+
Custom Components v2 from their registered component directories. Requests are
|
|
19
|
+
resolved safely within the component's root to avoid directory traversal and are
|
|
20
|
+
served with appropriate content type and cache headers. CORS headers are applied
|
|
21
|
+
based on the server configuration.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
from typing import TYPE_CHECKING, Final, cast
|
|
28
|
+
|
|
29
|
+
import tornado.web
|
|
30
|
+
|
|
31
|
+
import streamlit.web.server.routes
|
|
32
|
+
from streamlit.logger import get_logger
|
|
33
|
+
from streamlit.web.server.component_file_utils import (
|
|
34
|
+
build_safe_abspath,
|
|
35
|
+
guess_content_type,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from streamlit.components.v2.component_manager import BidiComponentManager
|
|
40
|
+
|
|
41
|
+
_LOGGER: Final = get_logger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BidiComponentRequestHandler(tornado.web.RequestHandler):
|
|
45
|
+
"""Request handler for serving Custom Components v2 static assets.
|
|
46
|
+
|
|
47
|
+
The handler resolves a requested path to a registered component's asset
|
|
48
|
+
within its component root, writes the file contents to the response, and
|
|
49
|
+
sets appropriate ``Content-Type`` and cache headers. If the component or
|
|
50
|
+
asset cannot be found, a suitable HTTP status is returned.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def initialize(self, component_manager: BidiComponentManager) -> None:
|
|
54
|
+
"""Initialize the handler with the given component manager.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
component_manager : BidiComponentManager
|
|
59
|
+
Manager used to look up registered components and their root paths.
|
|
60
|
+
"""
|
|
61
|
+
self._component_manager = component_manager
|
|
62
|
+
|
|
63
|
+
def get(self, path: str) -> None:
|
|
64
|
+
"""Serve a component asset for the given URL path.
|
|
65
|
+
|
|
66
|
+
The first path segment is interpreted as the component name. The rest
|
|
67
|
+
of the path is resolved to a file within that component's root
|
|
68
|
+
directory. If the file exists and is readable, its bytes are written to
|
|
69
|
+
the response and the ``Content-Type`` header is set based on the file
|
|
70
|
+
type.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
path : str
|
|
75
|
+
Request path in the form ``"<component_name>/<relative_file>"``.
|
|
76
|
+
|
|
77
|
+
Notes
|
|
78
|
+
-----
|
|
79
|
+
This method writes directly to the response and sets appropriate HTTP
|
|
80
|
+
status codes on error (``404`` for missing components/files, ``400`` for
|
|
81
|
+
unsafe paths).
|
|
82
|
+
"""
|
|
83
|
+
parts = path.split("/")
|
|
84
|
+
component_name = parts[0]
|
|
85
|
+
component_def = self._component_manager.get(component_name)
|
|
86
|
+
if component_def is None:
|
|
87
|
+
self.write("not found")
|
|
88
|
+
self.set_status(404)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Get the component path from the component manager
|
|
92
|
+
component_path = self._component_manager.get_component_path(component_name)
|
|
93
|
+
if component_path is None:
|
|
94
|
+
self.write("not found")
|
|
95
|
+
self.set_status(404)
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
# Build a safe absolute path within the component root
|
|
99
|
+
filename = "/".join(parts[1:])
|
|
100
|
+
# If no file segment is provided (e.g. only component name or trailing slash),
|
|
101
|
+
# treat as not found rather than attempting to open a directory.
|
|
102
|
+
if not filename or filename.endswith("/"):
|
|
103
|
+
self.write("not found")
|
|
104
|
+
self.set_status(404)
|
|
105
|
+
return
|
|
106
|
+
abspath = build_safe_abspath(component_path, filename)
|
|
107
|
+
if abspath is None:
|
|
108
|
+
self.write("Bad Request")
|
|
109
|
+
self.set_status(400)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# If the resolved path is a directory, return 404 not found.
|
|
113
|
+
if os.path.isdir(abspath):
|
|
114
|
+
self.write("not found")
|
|
115
|
+
self.set_status(404)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
with open(abspath, "rb") as file:
|
|
120
|
+
contents = file.read()
|
|
121
|
+
except OSError:
|
|
122
|
+
sanitized_abspath = abspath.replace("\n", "").replace("\r", "")
|
|
123
|
+
_LOGGER.exception(
|
|
124
|
+
"BidiComponentRequestHandler: GET %s read error", sanitized_abspath
|
|
125
|
+
)
|
|
126
|
+
self.write("read error")
|
|
127
|
+
self.set_status(404)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
self.write(contents)
|
|
131
|
+
self.set_header("Content-Type", guess_content_type(abspath))
|
|
132
|
+
|
|
133
|
+
self.set_extra_headers(path)
|
|
134
|
+
|
|
135
|
+
def set_extra_headers(self, path: str) -> None:
|
|
136
|
+
"""Disable cache for HTML files.
|
|
137
|
+
|
|
138
|
+
We assume other assets like JS and CSS are suffixed with their hash, so
|
|
139
|
+
they can be cached indefinitely.
|
|
140
|
+
"""
|
|
141
|
+
if path.endswith(".html"):
|
|
142
|
+
self.set_header("Cache-Control", "no-cache")
|
|
143
|
+
else:
|
|
144
|
+
self.set_header("Cache-Control", "public")
|
|
145
|
+
|
|
146
|
+
def set_default_headers(self) -> None:
|
|
147
|
+
"""Set default CORS headers based on server configuration.
|
|
148
|
+
|
|
149
|
+
If cross-origin requests are fully allowed, ``Access-Control-Allow-
|
|
150
|
+
Origin`` is set to ``"*"``. Otherwise, if the request ``Origin`` header
|
|
151
|
+
is an allowed origin, the header is echoed back.
|
|
152
|
+
"""
|
|
153
|
+
if streamlit.web.server.routes.allow_all_cross_origin_requests():
|
|
154
|
+
self.set_header("Access-Control-Allow-Origin", "*")
|
|
155
|
+
elif streamlit.web.server.routes.is_allowed_origin(
|
|
156
|
+
origin := self.request.headers.get("Origin")
|
|
157
|
+
):
|
|
158
|
+
self.set_header("Access-Control-Allow-Origin", cast("str", origin))
|
|
159
|
+
|
|
160
|
+
def options(self) -> None:
|
|
161
|
+
"""Handle preflight CORS requests.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
None
|
|
166
|
+
Responds with HTTP ``204 No Content`` to indicate that the actual
|
|
167
|
+
request can proceed.
|
|
168
|
+
"""
|
|
169
|
+
self.set_status(204)
|
|
170
|
+
self.finish()
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def get_url(file_id: str) -> str:
|
|
174
|
+
"""Return the URL for a component asset identified by ``file_id``.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
file_id : str
|
|
179
|
+
Component file identifier (typically a relative path or hashed
|
|
180
|
+
filename).
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
str
|
|
185
|
+
Relative URL path for the resource, to be joined with the server
|
|
186
|
+
base URL.
|
|
187
|
+
|
|
188
|
+
Examples
|
|
189
|
+
--------
|
|
190
|
+
>>> BidiComponentRequestHandler.get_url("my_component/main.js")
|
|
191
|
+
'_stcore/bidi-components/my_component/main.js'
|
|
192
|
+
"""
|
|
193
|
+
return f"_stcore/bidi-components/{file_id}"
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026)
|
|
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
|
+
import hmac
|
|
18
|
+
import json
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Final
|
|
20
|
+
from urllib.parse import urlparse
|
|
21
|
+
|
|
22
|
+
import tornado.websocket
|
|
23
|
+
from tornado.escape import utf8
|
|
24
|
+
from tornado.websocket import WebSocketHandler
|
|
25
|
+
|
|
26
|
+
from streamlit import config
|
|
27
|
+
from streamlit.auth_util import get_cookie_with_chunks, get_expose_tokens_config
|
|
28
|
+
from streamlit.logger import get_logger
|
|
29
|
+
from streamlit.proto.BackMsg_pb2 import BackMsg
|
|
30
|
+
from streamlit.runtime import Runtime, SessionClient, SessionClientDisconnectedError
|
|
31
|
+
from streamlit.runtime.runtime_util import serialize_forward_msg
|
|
32
|
+
from streamlit.runtime.session_manager import ClientContext
|
|
33
|
+
from streamlit.web.server.server_util import (
|
|
34
|
+
AUTH_COOKIE_NAME,
|
|
35
|
+
TOKENS_COOKIE_NAME,
|
|
36
|
+
is_url_from_allowed_origins,
|
|
37
|
+
is_xsrf_enabled,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from collections.abc import Awaitable, Iterable, Mapping
|
|
42
|
+
|
|
43
|
+
from tornado.httputil import HTTPServerRequest
|
|
44
|
+
|
|
45
|
+
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
|
46
|
+
|
|
47
|
+
_LOGGER: Final = get_logger(__name__)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TornadoClientContext(ClientContext):
|
|
51
|
+
"""Tornado-specific implementation of ClientContext.
|
|
52
|
+
|
|
53
|
+
Captures headers, cookies, and client info from the initial WebSocket handshake.
|
|
54
|
+
Values are cached at construction time since they represent the initial request
|
|
55
|
+
context and should not change during the connection lifetime.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, tornado_request: HTTPServerRequest) -> None:
|
|
59
|
+
self._headers: list[tuple[str, str]] = list(tornado_request.headers.get_all())
|
|
60
|
+
self._cookies: dict[str, str] = {
|
|
61
|
+
k: m.value for k, m in tornado_request.cookies.items()
|
|
62
|
+
}
|
|
63
|
+
self._remote_ip: str | None = tornado_request.remote_ip
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def headers(self) -> Iterable[tuple[str, str]]:
|
|
67
|
+
"""All headers as (name, value) tuples."""
|
|
68
|
+
return self._headers
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def cookies(self) -> Mapping[str, str]:
|
|
72
|
+
"""Cookies as a name-to-value mapping."""
|
|
73
|
+
return self._cookies
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def remote_ip(self) -> str | None:
|
|
77
|
+
"""The client's remote IP address."""
|
|
78
|
+
return self._remote_ip
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
82
|
+
"""Handles a WebSocket connection from the browser."""
|
|
83
|
+
|
|
84
|
+
def initialize(self, runtime: Runtime) -> None:
|
|
85
|
+
self._runtime = runtime
|
|
86
|
+
self._session_id: str | None = None
|
|
87
|
+
# The XSRF cookie is normally set when xsrf_form_html is used, but in a
|
|
88
|
+
# pure-Javascript application that does not use any regular forms we just
|
|
89
|
+
# need to read the self.xsrf_token manually to set the cookie as a side
|
|
90
|
+
# effect. See https://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection
|
|
91
|
+
# for more details.
|
|
92
|
+
if is_xsrf_enabled():
|
|
93
|
+
_ = self.xsrf_token
|
|
94
|
+
|
|
95
|
+
# Do this once instead of on every open
|
|
96
|
+
self.expose_tokens = get_expose_tokens_config()
|
|
97
|
+
|
|
98
|
+
def get_signed_cookie(
|
|
99
|
+
self,
|
|
100
|
+
name: str,
|
|
101
|
+
value: str | None = None,
|
|
102
|
+
max_age_days: float = 31,
|
|
103
|
+
min_version: int | None = None,
|
|
104
|
+
) -> bytes | None:
|
|
105
|
+
"""Get a signed cookie from the request, reconstructing from chunks if needed.
|
|
106
|
+
|
|
107
|
+
Added for compatibility with Tornado < 6.3.0. Also handles chunked cookies
|
|
108
|
+
automatically.
|
|
109
|
+
|
|
110
|
+
See release notes: https://www.tornadoweb.org/en/stable/releases/v6.3.0.html#deprecation-notices
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def get_single_cookie(cookie_name: str) -> bytes | None:
|
|
114
|
+
try:
|
|
115
|
+
return super(BrowserWebSocketHandler, self).get_signed_cookie(
|
|
116
|
+
cookie_name, value, max_age_days, min_version
|
|
117
|
+
)
|
|
118
|
+
except AttributeError:
|
|
119
|
+
return super(BrowserWebSocketHandler, self).get_secure_cookie(
|
|
120
|
+
cookie_name, value, max_age_days, min_version
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return get_cookie_with_chunks(get_single_cookie, name)
|
|
124
|
+
|
|
125
|
+
def check_origin(self, origin: str) -> bool:
|
|
126
|
+
"""Set up CORS."""
|
|
127
|
+
return super().check_origin(origin) or is_url_from_allowed_origins(origin)
|
|
128
|
+
|
|
129
|
+
def _validate_xsrf_token(self, supplied_token: str) -> bool:
|
|
130
|
+
"""Inspired by tornado.web.RequestHandler.check_xsrf_cookie method,
|
|
131
|
+
to check the XSRF token passed in Websocket connection header.
|
|
132
|
+
"""
|
|
133
|
+
_, token, _ = self._decode_xsrf_token(supplied_token)
|
|
134
|
+
_, expected_token, _ = self._get_raw_xsrf_token()
|
|
135
|
+
|
|
136
|
+
decoded_token = utf8(token)
|
|
137
|
+
decoded_expected_token = utf8(expected_token)
|
|
138
|
+
|
|
139
|
+
if not decoded_token or not decoded_expected_token:
|
|
140
|
+
return False
|
|
141
|
+
return hmac.compare_digest(decoded_token, decoded_expected_token)
|
|
142
|
+
|
|
143
|
+
def _parse_user_cookie(self, raw_cookie_value: bytes) -> dict[str, Any]:
|
|
144
|
+
"""Process the user cookie and extract the user info after
|
|
145
|
+
validating the origin. Origin is validated for security reasons.
|
|
146
|
+
"""
|
|
147
|
+
cookie_value = json.loads(raw_cookie_value)
|
|
148
|
+
user_info = {}
|
|
149
|
+
|
|
150
|
+
cookie_value_origin = cookie_value.get("origin", None)
|
|
151
|
+
parsed_origin_from_header = urlparse(self.request.headers["Origin"])
|
|
152
|
+
expected_origin_value = (
|
|
153
|
+
parsed_origin_from_header.scheme + "://" + parsed_origin_from_header.netloc
|
|
154
|
+
)
|
|
155
|
+
if cookie_value_origin == expected_origin_value:
|
|
156
|
+
user_info["is_logged_in"] = cookie_value.get("is_logged_in", False)
|
|
157
|
+
del cookie_value["origin"]
|
|
158
|
+
del cookie_value["is_logged_in"]
|
|
159
|
+
user_info.update(cookie_value)
|
|
160
|
+
|
|
161
|
+
else:
|
|
162
|
+
_LOGGER.error(
|
|
163
|
+
"Origin mismatch, the origin of websocket request is not the "
|
|
164
|
+
"same origin of redirect_uri in secrets.toml",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return user_info
|
|
168
|
+
|
|
169
|
+
def write_forward_msg(self, msg: ForwardMsg) -> None:
|
|
170
|
+
"""Send a ForwardMsg to the browser."""
|
|
171
|
+
try:
|
|
172
|
+
self.write_message(serialize_forward_msg(msg), binary=True)
|
|
173
|
+
except tornado.websocket.WebSocketClosedError as e:
|
|
174
|
+
raise SessionClientDisconnectedError from e
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def client_context(self) -> ClientContext:
|
|
178
|
+
"""Return the client's connection context.
|
|
179
|
+
|
|
180
|
+
The context is cached on first access to avoid repeatedly
|
|
181
|
+
constructing a new TornadoClientContext instance.
|
|
182
|
+
"""
|
|
183
|
+
context = getattr(self, "_client_context", None)
|
|
184
|
+
if context is None:
|
|
185
|
+
context = TornadoClientContext(self.request)
|
|
186
|
+
self._client_context = context
|
|
187
|
+
return context
|
|
188
|
+
|
|
189
|
+
def select_subprotocol(self, subprotocols: list[str]) -> str | None:
|
|
190
|
+
"""Return the first subprotocol in the given list.
|
|
191
|
+
|
|
192
|
+
This method is used by Tornado to select a protocol when the
|
|
193
|
+
Sec-WebSocket-Protocol header is set in an HTTP Upgrade request.
|
|
194
|
+
|
|
195
|
+
NOTE: We repurpose the Sec-WebSocket-Protocol header here in a slightly
|
|
196
|
+
unfortunate (but necessary) way. The browser WebSocket API doesn't allow us to
|
|
197
|
+
set arbitrary HTTP headers, and this header is the only one where we have the
|
|
198
|
+
ability to set it to arbitrary values, so we use it to pass tokens (in this
|
|
199
|
+
case, the previous session ID to allow us to reconnect to it) from client to
|
|
200
|
+
server as the *third* value in the list.
|
|
201
|
+
|
|
202
|
+
The reason why the auth token is set as the third value is that:
|
|
203
|
+
- when Sec-WebSocket-Protocol is set, many clients expect the server to
|
|
204
|
+
respond with a selected subprotocol to use. We don't want that reply to be
|
|
205
|
+
the session token, so we by convention have the client always set the first
|
|
206
|
+
protocol to "streamlit" and select that.
|
|
207
|
+
- the second protocol in the list is reserved in some deployment environments
|
|
208
|
+
for an auth token that we currently don't use
|
|
209
|
+
"""
|
|
210
|
+
if subprotocols:
|
|
211
|
+
return subprotocols[0]
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def open(self, *args: Any, **kwargs: Any) -> Awaitable[None] | None:
|
|
216
|
+
user_info: dict[str, dict[str, str] | str | bool | None] = {}
|
|
217
|
+
|
|
218
|
+
existing_session_id = None
|
|
219
|
+
try:
|
|
220
|
+
ws_protocols = [
|
|
221
|
+
p.strip()
|
|
222
|
+
for p in self.request.headers["Sec-Websocket-Protocol"].split(",")
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
raw_cookie_value = self.get_signed_cookie(AUTH_COOKIE_NAME)
|
|
226
|
+
if is_xsrf_enabled() and raw_cookie_value:
|
|
227
|
+
csrf_protocol_value = ws_protocols[1]
|
|
228
|
+
|
|
229
|
+
if self._validate_xsrf_token(csrf_protocol_value):
|
|
230
|
+
user_info.update(self._parse_user_cookie(raw_cookie_value))
|
|
231
|
+
|
|
232
|
+
# Also read in tokens if token cookie exists
|
|
233
|
+
raw_token_cookie_value = self.get_signed_cookie(TOKENS_COOKIE_NAME)
|
|
234
|
+
if raw_token_cookie_value:
|
|
235
|
+
all_tokens = json.loads(raw_token_cookie_value)
|
|
236
|
+
|
|
237
|
+
# Filter tokens based on expose_tokens configuration
|
|
238
|
+
filtered_tokens: dict[str, str] = {}
|
|
239
|
+
for token_type in self.expose_tokens:
|
|
240
|
+
token_key = f"{token_type}_token"
|
|
241
|
+
if token_key in all_tokens:
|
|
242
|
+
filtered_tokens[token_type] = all_tokens[token_key]
|
|
243
|
+
|
|
244
|
+
user_info["tokens"] = filtered_tokens
|
|
245
|
+
|
|
246
|
+
if len(ws_protocols) >= 3:
|
|
247
|
+
# See the NOTE in the docstring of the `select_subprotocol` method above
|
|
248
|
+
# for a detailed explanation of why this is done.
|
|
249
|
+
existing_session_id = ws_protocols[2]
|
|
250
|
+
except (KeyError, json.JSONDecodeError):
|
|
251
|
+
# Just let existing_session_id=None if we run into any error while trying to
|
|
252
|
+
# extract it from the Sec-Websocket-Protocol header or parsing cookie JSON.
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
# Map in any user-configured headers. Note that these override anything coming
|
|
256
|
+
# from the auth cookie.
|
|
257
|
+
mapping_config = config.get_option("server.trustedUserHeaders")
|
|
258
|
+
for header_name, user_info_key in mapping_config.items():
|
|
259
|
+
header_values = self.request.headers.get_list(header_name)
|
|
260
|
+
if header_values:
|
|
261
|
+
# If there's at least one value, use the first value.
|
|
262
|
+
# NOTE: Tornado doesn't document the order of these values, so it's
|
|
263
|
+
# possible this won't be the first value that was received by the
|
|
264
|
+
# server.
|
|
265
|
+
user_info[user_info_key] = header_values[0]
|
|
266
|
+
else:
|
|
267
|
+
# Default to explicit None.
|
|
268
|
+
user_info[user_info_key] = None
|
|
269
|
+
|
|
270
|
+
self._session_id = self._runtime.connect_session(
|
|
271
|
+
client=self,
|
|
272
|
+
user_info=user_info,
|
|
273
|
+
existing_session_id=existing_session_id,
|
|
274
|
+
)
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
def on_close(self) -> None:
|
|
278
|
+
if not self._session_id:
|
|
279
|
+
return
|
|
280
|
+
self._runtime.disconnect_session(self._session_id)
|
|
281
|
+
self._session_id = None
|
|
282
|
+
|
|
283
|
+
def get_compression_options(self) -> dict[Any, Any] | None:
|
|
284
|
+
"""Enable WebSocket compression.
|
|
285
|
+
|
|
286
|
+
Returning an empty dict enables websocket compression. Returning
|
|
287
|
+
None disables it.
|
|
288
|
+
|
|
289
|
+
(See the docstring in the parent class.)
|
|
290
|
+
"""
|
|
291
|
+
if config.get_option("server.enableWebsocketCompression"):
|
|
292
|
+
return {}
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
def on_message(self, message: str | bytes) -> None:
|
|
296
|
+
if not self._session_id:
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
if isinstance(message, str):
|
|
301
|
+
# Sanity check. (The frontend should only be sending us bytes;
|
|
302
|
+
# Protobuf.ParseFromString does not accept str input.)
|
|
303
|
+
raise TypeError( # noqa: TRY301
|
|
304
|
+
"WebSocket received an unexpected `str` message. "
|
|
305
|
+
"(We expect `bytes` only.)"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
msg = BackMsg()
|
|
309
|
+
msg.ParseFromString(message)
|
|
310
|
+
_LOGGER.debug("Received the following back message:\n%s", msg)
|
|
311
|
+
|
|
312
|
+
except Exception as ex:
|
|
313
|
+
_LOGGER.exception("Error deserializing back message")
|
|
314
|
+
self._runtime.handle_backmsg_deserialization_exception(self._session_id, ex)
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
# "debug_disconnect_websocket" and "debug_shutdown_runtime" are special
|
|
318
|
+
# developmentMode-only messages used in e2e tests to test reconnect handling and
|
|
319
|
+
# disabling widgets.
|
|
320
|
+
if msg.WhichOneof("type") == "debug_disconnect_websocket":
|
|
321
|
+
if config.get_option("global.developmentMode") or config.get_option(
|
|
322
|
+
"global.e2eTest"
|
|
323
|
+
):
|
|
324
|
+
self.close()
|
|
325
|
+
else:
|
|
326
|
+
_LOGGER.warning(
|
|
327
|
+
"Client tried to disconnect websocket when not in development mode or e2e testing."
|
|
328
|
+
)
|
|
329
|
+
elif msg.WhichOneof("type") == "debug_shutdown_runtime":
|
|
330
|
+
if config.get_option("global.developmentMode") or config.get_option(
|
|
331
|
+
"global.e2eTest"
|
|
332
|
+
):
|
|
333
|
+
self._runtime.stop()
|
|
334
|
+
else:
|
|
335
|
+
_LOGGER.warning(
|
|
336
|
+
"Client tried to shut down runtime when not in development mode or e2e testing."
|
|
337
|
+
)
|
|
338
|
+
else:
|
|
339
|
+
# AppSession handles all other BackMsg types.
|
|
340
|
+
self._runtime.handle_backmsg(self._session_id, msg)
|
|
341
|
+
return
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026)
|
|
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
|
+
|
|
16
|
+
"""Utilities for safely resolving component asset paths and content types.
|
|
17
|
+
|
|
18
|
+
These helpers are used by server handlers to construct safe absolute paths for
|
|
19
|
+
component files and to determine the appropriate ``Content-Type`` header for
|
|
20
|
+
responses. Path resolution prevents directory traversal by normalizing and
|
|
21
|
+
checking real paths against a component's root directory.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import mimetypes
|
|
27
|
+
import os
|
|
28
|
+
from typing import Final
|
|
29
|
+
|
|
30
|
+
from streamlit.path_security import is_unsafe_path_pattern
|
|
31
|
+
|
|
32
|
+
_OCTET_STREAM: Final[str] = "application/octet-stream"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_safe_abspath(component_root: str, relative_url_path: str) -> str | None:
|
|
36
|
+
r"""Build an absolute path inside ``component_root`` if safe.
|
|
37
|
+
|
|
38
|
+
The function first validates that ``relative_url_path`` does not contain
|
|
39
|
+
dangerous patterns using :func:`~streamlit.path_security.is_unsafe_path_pattern`,
|
|
40
|
+
then joins it with ``component_root`` and resolves symlinks.
|
|
41
|
+
Returns ``None`` if the path is rejected by security checks or escapes the root.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
component_root : str
|
|
46
|
+
Absolute path to the component's root directory.
|
|
47
|
+
relative_url_path : str
|
|
48
|
+
Relative URL path from the component root to the requested file.
|
|
49
|
+
Must be a simple relative path without dangerous patterns.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
str or None
|
|
54
|
+
The resolved absolute path if it passes all validation and stays
|
|
55
|
+
within ``component_root``; otherwise ``None``.
|
|
56
|
+
"""
|
|
57
|
+
# See is_unsafe_path_pattern() for security details.
|
|
58
|
+
if is_unsafe_path_pattern(relative_url_path):
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
root_real = os.path.realpath(component_root)
|
|
62
|
+
candidate = os.path.normpath(os.path.join(root_real, relative_url_path))
|
|
63
|
+
candidate_real = os.path.realpath(candidate)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Ensure the candidate stays within the real component root
|
|
67
|
+
if os.path.commonpath([root_real, candidate_real]) != root_real:
|
|
68
|
+
return None
|
|
69
|
+
except ValueError:
|
|
70
|
+
# On some platforms, commonpath can raise if drives differ; treat as forbidden.
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
return candidate_real
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def guess_content_type(abspath: str) -> str:
|
|
77
|
+
"""Guess the HTTP ``Content-Type`` for a file path.
|
|
78
|
+
|
|
79
|
+
This logic mirrors Tornado's ``StaticFileHandler`` by respecting encoding
|
|
80
|
+
metadata from ``mimetypes.guess_type`` and falling back to
|
|
81
|
+
``application/octet-stream`` when no specific type can be determined.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
abspath : str
|
|
86
|
+
Absolute file path used for type detection (only the suffix matters).
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
str
|
|
91
|
+
Guessed content type string suitable for the ``Content-Type`` header.
|
|
92
|
+
"""
|
|
93
|
+
mime_type, encoding = mimetypes.guess_type(abspath)
|
|
94
|
+
# per RFC 6713, use the appropriate type for a gzip compressed file
|
|
95
|
+
if encoding == "gzip":
|
|
96
|
+
return "application/gzip"
|
|
97
|
+
# As of 2015-07-21 there is no bzip2 encoding defined at
|
|
98
|
+
# http://www.iana.org/assignments/media-types/media-types.xhtml
|
|
99
|
+
# So for that (and any other encoding), use octet-stream.
|
|
100
|
+
if encoding is not None:
|
|
101
|
+
return _OCTET_STREAM
|
|
102
|
+
if mime_type is not None:
|
|
103
|
+
return mime_type
|
|
104
|
+
# if mime_type not detected, use application/octet-stream
|
|
105
|
+
return _OCTET_STREAM
|