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
streamlit/auth_util.py
ADDED
|
@@ -0,0 +1,583 @@
|
|
|
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 json
|
|
18
|
+
import re
|
|
19
|
+
from collections.abc import Callable, Mapping
|
|
20
|
+
from datetime import datetime, timedelta, timezone
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Final, TypedDict, cast
|
|
22
|
+
from urllib.parse import urlencode, urlparse
|
|
23
|
+
|
|
24
|
+
from streamlit import config
|
|
25
|
+
from streamlit.errors import StreamlitAuthError
|
|
26
|
+
from streamlit.logger import get_logger
|
|
27
|
+
from streamlit.runtime.secrets import AttrDict, secrets_singleton
|
|
28
|
+
|
|
29
|
+
_LOGGER: Final = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
|
|
33
|
+
class ProviderTokenPayload(TypedDict):
|
|
34
|
+
provider: str
|
|
35
|
+
exp: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
MAX_COOKIE_BYTES: Final = 4096
|
|
39
|
+
# Cookie attributes added by Tornado: "; Path=/; HttpOnly"
|
|
40
|
+
COOKIE_ATTRIBUTES: Final = "; Path=/; HttpOnly"
|
|
41
|
+
COOKIE_ATTR_SIZE: Final = len(COOKIE_ATTRIBUTES)
|
|
42
|
+
# Safety buffer for signing overhead to account for edge cases, rounding, and potential
|
|
43
|
+
# variations in signing implementations (e.g., longer timestamps after year 2286)
|
|
44
|
+
SIGNING_OVERHEAD_SAFETY_BUFFER: Final = 50
|
|
45
|
+
# Base64 encoding of 1 byte = 4 bytes, so overhead = total - 4
|
|
46
|
+
SINGLE_BYTE_BASE64_SIZE: Final = 4
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AuthCache:
|
|
50
|
+
"""Simple cache implementation for storing info required for Authlib."""
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
self.cache: dict[str, Any] = {}
|
|
54
|
+
|
|
55
|
+
def get(self, key: str) -> Any:
|
|
56
|
+
return self.cache.get(key)
|
|
57
|
+
|
|
58
|
+
# for set method, we are follow the same signature used in Authlib
|
|
59
|
+
# the expires_in is not used in our case
|
|
60
|
+
def set(self, key: str, value: Any, expires_in: int | None = None) -> None: # noqa: ARG002
|
|
61
|
+
self.cache[key] = value
|
|
62
|
+
|
|
63
|
+
def get_dict(self) -> dict[str, Any]:
|
|
64
|
+
return self.cache
|
|
65
|
+
|
|
66
|
+
def delete(self, key: str) -> None:
|
|
67
|
+
self.cache.pop(key, None)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_authlib_installed() -> bool:
|
|
71
|
+
"""Check if Authlib is installed."""
|
|
72
|
+
try:
|
|
73
|
+
import authlib
|
|
74
|
+
|
|
75
|
+
authlib_version = authlib.__version__
|
|
76
|
+
authlib_version_tuple = tuple(map(int, authlib_version.split(".")))
|
|
77
|
+
|
|
78
|
+
if authlib_version_tuple < (1, 3, 2):
|
|
79
|
+
return False
|
|
80
|
+
except (ImportError, ModuleNotFoundError):
|
|
81
|
+
return False
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_signing_secret() -> str:
|
|
86
|
+
"""Get the cookie signing secret from the configuration or secrets.toml."""
|
|
87
|
+
signing_secret: str = config.get_option("server.cookieSecret")
|
|
88
|
+
if secrets_singleton.load_if_toml_exists():
|
|
89
|
+
auth_section = secrets_singleton.get("auth")
|
|
90
|
+
if auth_section:
|
|
91
|
+
signing_secret = auth_section.get("cookie_secret", signing_secret)
|
|
92
|
+
return signing_secret
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_secrets_auth_section() -> AttrDict:
|
|
96
|
+
"""Get the 'auth' section of the secrets.toml."""
|
|
97
|
+
auth_section = AttrDict({})
|
|
98
|
+
if secrets_singleton.load_if_toml_exists():
|
|
99
|
+
auth_section = cast("AttrDict", secrets_singleton.get("auth", AttrDict({})))
|
|
100
|
+
|
|
101
|
+
return auth_section
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_expose_tokens_config() -> list[str]:
|
|
105
|
+
"""Get the expose_tokens configuration from secrets.toml.
|
|
106
|
+
|
|
107
|
+
Returns a list of token types to expose. Accepts both string and list formats:
|
|
108
|
+
- expose_tokens = "id" -> ["id"]
|
|
109
|
+
- expose_tokens = ["id", "access"] -> ["id", "access"]
|
|
110
|
+
"""
|
|
111
|
+
auth_section = get_secrets_auth_section()
|
|
112
|
+
expose_tokens = auth_section.get("expose_tokens")
|
|
113
|
+
|
|
114
|
+
if isinstance(expose_tokens, str):
|
|
115
|
+
res = [expose_tokens]
|
|
116
|
+
elif isinstance(expose_tokens, list):
|
|
117
|
+
res = [str(token) for token in expose_tokens]
|
|
118
|
+
else:
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
if set(res) - {"id", "access"}:
|
|
122
|
+
raise StreamlitAuthError(
|
|
123
|
+
"Invalid expose_tokens configuration. Only 'id' and 'access' are allowed."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return res
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_redirect_uri(auth_section: AttrDict) -> str | None:
|
|
130
|
+
"""Get the redirect_uri from auth_section - filling in port number if needed."""
|
|
131
|
+
|
|
132
|
+
if "redirect_uri" not in auth_section:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
redirect_uri: str = auth_section["redirect_uri"]
|
|
136
|
+
if "{port}" in redirect_uri:
|
|
137
|
+
redirect_uri = redirect_uri.replace(
|
|
138
|
+
"{port}", str(config.get_option("server.port"))
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
redirect_uri_parsed = urlparse(redirect_uri)
|
|
143
|
+
except ValueError:
|
|
144
|
+
raise StreamlitAuthError(
|
|
145
|
+
f"Invalid redirect_uri: {redirect_uri}. Please check your configuration."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return redirect_uri_parsed.geturl()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_validated_redirect_uri() -> str | None:
|
|
152
|
+
"""Get the redirect_uri from secrets, validating it ends with /oauth2callback.
|
|
153
|
+
|
|
154
|
+
This is used for logout flows where we need a validated redirect URI
|
|
155
|
+
that matches the OAuth callback path.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
str | None
|
|
160
|
+
The validated redirect URI, or None if not configured or invalid.
|
|
161
|
+
"""
|
|
162
|
+
auth_section = get_secrets_auth_section()
|
|
163
|
+
if not auth_section:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
redirect_uri = get_redirect_uri(auth_section)
|
|
167
|
+
if not redirect_uri:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
if not redirect_uri.endswith("/oauth2callback"):
|
|
171
|
+
_LOGGER.warning("Redirect URI does not end with /oauth2callback")
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
return redirect_uri
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_origin_from_redirect_uri() -> str | None:
|
|
178
|
+
"""Extract the origin (scheme + host) from the configured redirect_uri.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
str | None
|
|
183
|
+
The origin in format "scheme://host:port", or None if not configured.
|
|
184
|
+
"""
|
|
185
|
+
auth_section = get_secrets_auth_section()
|
|
186
|
+
if not auth_section:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
redirect_uri = get_redirect_uri(auth_section)
|
|
190
|
+
if not redirect_uri:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
redirect_uri_parsed = urlparse(redirect_uri)
|
|
194
|
+
return f"{redirect_uri_parsed.scheme}://{redirect_uri_parsed.netloc}"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def build_logout_url(
|
|
198
|
+
end_session_endpoint: str,
|
|
199
|
+
client_id: str,
|
|
200
|
+
post_logout_redirect_uri: str,
|
|
201
|
+
id_token: str | None = None,
|
|
202
|
+
) -> str:
|
|
203
|
+
"""Build an OIDC logout URL with the required parameters.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
end_session_endpoint
|
|
208
|
+
The OIDC provider's end_session_endpoint URL.
|
|
209
|
+
client_id
|
|
210
|
+
The OAuth client ID.
|
|
211
|
+
post_logout_redirect_uri
|
|
212
|
+
The URI to redirect to after logout.
|
|
213
|
+
id_token
|
|
214
|
+
Optional ID token to include as id_token_hint for the logout request.
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
str
|
|
219
|
+
The complete logout URL with query parameters.
|
|
220
|
+
"""
|
|
221
|
+
from urllib.parse import parse_qsl
|
|
222
|
+
|
|
223
|
+
logout_params: dict[str, str] = {
|
|
224
|
+
"client_id": client_id,
|
|
225
|
+
"post_logout_redirect_uri": post_logout_redirect_uri,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if id_token:
|
|
229
|
+
logout_params["id_token_hint"] = id_token
|
|
230
|
+
|
|
231
|
+
# Per OIDC spec, end_session_endpoint should be a clean URL without query params,
|
|
232
|
+
# but we handle existing params defensively for non-standard providers.
|
|
233
|
+
parsed = urlparse(end_session_endpoint)
|
|
234
|
+
existing_params = dict(parse_qsl(parsed.query))
|
|
235
|
+
merged_params = {**existing_params, **logout_params}
|
|
236
|
+
new_query = urlencode(merged_params)
|
|
237
|
+
return parsed._replace(query=new_query).geturl()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def encode_provider_token(provider: str) -> str:
|
|
241
|
+
"""Returns a signed JWT token with the provider and expiration time."""
|
|
242
|
+
try:
|
|
243
|
+
from authlib.jose import jwt
|
|
244
|
+
except ImportError:
|
|
245
|
+
raise StreamlitAuthError(
|
|
246
|
+
"""To use authentication features, you need to install Authlib>=1.3.2, e.g. via `pip install Authlib`."""
|
|
247
|
+
) from None
|
|
248
|
+
|
|
249
|
+
header = {"alg": "HS256"}
|
|
250
|
+
payload = {
|
|
251
|
+
"provider": provider,
|
|
252
|
+
"exp": datetime.now(timezone.utc) + timedelta(minutes=2),
|
|
253
|
+
}
|
|
254
|
+
provider_token: bytes = jwt.encode(header, payload, get_signing_secret())
|
|
255
|
+
# JWT token is a byte string, so we need to decode it to a URL compatible string
|
|
256
|
+
return provider_token.decode("latin-1")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def decode_provider_token(provider_token: str) -> ProviderTokenPayload:
|
|
260
|
+
"""Decode the JWT token and validate the claims."""
|
|
261
|
+
try:
|
|
262
|
+
from authlib.jose import JoseError, JWTClaims, jwt
|
|
263
|
+
except ImportError:
|
|
264
|
+
raise StreamlitAuthError(
|
|
265
|
+
"""To use authentication features, you need to install Authlib>=1.3.2, e.g. via `pip install Authlib`."""
|
|
266
|
+
) from None
|
|
267
|
+
|
|
268
|
+
# Our JWT token is short-lived (2 minutes), so we check here that it contains
|
|
269
|
+
# the 'exp' (and it is not expired), and 'provider' field exists.
|
|
270
|
+
claim_options = {"exp": {"essential": True}, "provider": {"essential": True}}
|
|
271
|
+
try:
|
|
272
|
+
payload: JWTClaims = jwt.decode(
|
|
273
|
+
provider_token, get_signing_secret(), claims_options=claim_options
|
|
274
|
+
)
|
|
275
|
+
payload.validate()
|
|
276
|
+
except JoseError as e:
|
|
277
|
+
raise StreamlitAuthError(f"Error decoding provider token: {e}") from None
|
|
278
|
+
|
|
279
|
+
return cast("ProviderTokenPayload", payload)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def generate_default_provider_section(auth_section: AttrDict) -> dict[str, Any]:
|
|
283
|
+
"""Generate a default provider section for the 'auth' section of secrets.toml."""
|
|
284
|
+
default_provider_section = {}
|
|
285
|
+
if auth_section.get("client_id"):
|
|
286
|
+
default_provider_section["client_id"] = auth_section.get("client_id")
|
|
287
|
+
if auth_section.get("client_secret"):
|
|
288
|
+
default_provider_section["client_secret"] = auth_section.get("client_secret")
|
|
289
|
+
if auth_section.get("server_metadata_url"):
|
|
290
|
+
default_provider_section["server_metadata_url"] = auth_section.get(
|
|
291
|
+
"server_metadata_url"
|
|
292
|
+
)
|
|
293
|
+
if auth_section.get("client_kwargs"):
|
|
294
|
+
default_provider_section["client_kwargs"] = cast(
|
|
295
|
+
"AttrDict", auth_section.get("client_kwargs", AttrDict({}))
|
|
296
|
+
).to_dict()
|
|
297
|
+
if auth_section.get("expose_tokens"):
|
|
298
|
+
default_provider_section["expose_tokens"] = auth_section.get("expose_tokens")
|
|
299
|
+
return default_provider_section
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def set_cookie_with_chunks(
|
|
303
|
+
set_single_cookie_fn: Callable[[str, str], None],
|
|
304
|
+
create_signed_value_fn: Callable[[str, str], bytes],
|
|
305
|
+
cookie_name: str,
|
|
306
|
+
value: dict[str, Any],
|
|
307
|
+
) -> None:
|
|
308
|
+
"""Set a cookie, splitting into multiple cookies if necessary.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
set_single_cookie_fn: Function to set a single cookie (cookie_name, value)
|
|
312
|
+
create_signed_value_fn: Function to create a signed cookie value (cookie_name, value)
|
|
313
|
+
cookie_name: Name of the cookie
|
|
314
|
+
value: Dictionary value to serialize and store
|
|
315
|
+
"""
|
|
316
|
+
serialized_cookie_value = json.dumps(value)
|
|
317
|
+
|
|
318
|
+
# Calculate actual cookie size using the provided signing function
|
|
319
|
+
signed_value = create_signed_value_fn(cookie_name, serialized_cookie_value)
|
|
320
|
+
|
|
321
|
+
# Cookie format: "name=value" + COOKIE_ATTRIBUTES
|
|
322
|
+
actual_cookie_size = len(cookie_name) + 1 + len(signed_value) + COOKIE_ATTR_SIZE
|
|
323
|
+
|
|
324
|
+
# Check if cookie needs to be split
|
|
325
|
+
if actual_cookie_size > MAX_COOKIE_BYTES:
|
|
326
|
+
_LOGGER.debug(
|
|
327
|
+
"Cookie size (%d bytes) exceeds browser limit. Splitting into multiple cookies.",
|
|
328
|
+
actual_cookie_size,
|
|
329
|
+
)
|
|
330
|
+
_set_split_cookie(
|
|
331
|
+
set_single_cookie_fn,
|
|
332
|
+
create_signed_value_fn,
|
|
333
|
+
cookie_name,
|
|
334
|
+
serialized_cookie_value,
|
|
335
|
+
)
|
|
336
|
+
else:
|
|
337
|
+
set_single_cookie_fn(cookie_name, serialized_cookie_value)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _calculate_signing_overhead(
|
|
341
|
+
create_signed_value_fn: Callable[[str, str], bytes],
|
|
342
|
+
cookie_name: str,
|
|
343
|
+
) -> int:
|
|
344
|
+
"""Calculate the server's signing overhead by measuring the size difference.
|
|
345
|
+
|
|
346
|
+
This empirically measures the overhead added by the signing function (e.g., Tornado's
|
|
347
|
+
create_signed_value) by signing a minimal test value and computing the difference.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
create_signed_value_fn: Function to create a signed cookie value
|
|
351
|
+
cookie_name: Name of the cookie (affects overhead due to length prefix)
|
|
352
|
+
|
|
353
|
+
Returns
|
|
354
|
+
-------
|
|
355
|
+
The number of bytes added by signing (excluding the base64-encoded value)
|
|
356
|
+
"""
|
|
357
|
+
test_value = "x" # Minimal test value (1 byte)
|
|
358
|
+
signed = create_signed_value_fn(cookie_name, test_value)
|
|
359
|
+
return len(signed) - SINGLE_BYTE_BASE64_SIZE
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _set_split_cookie(
|
|
363
|
+
set_single_cookie_fn: Callable[[str, str], None],
|
|
364
|
+
create_signed_value_fn: Callable[[str, str], bytes],
|
|
365
|
+
cookie_name: str,
|
|
366
|
+
value: str,
|
|
367
|
+
) -> None:
|
|
368
|
+
"""Split a large cookie value into multiple smaller cookies.
|
|
369
|
+
|
|
370
|
+
The main cookie always exists and either contains the whole value or the chunk count.
|
|
371
|
+
Additional chunks are stored as cookie_name_1, cookie_name_2, etc.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
set_single_cookie_fn: Function to set a single cookie (cookie_name, value)
|
|
375
|
+
create_signed_value_fn: Function to create a signed cookie value
|
|
376
|
+
cookie_name: Name of the cookie
|
|
377
|
+
value: Serialized string value to split and store
|
|
378
|
+
"""
|
|
379
|
+
# Calculate overhead empirically from the actual signing function, plus safety buffer
|
|
380
|
+
signing_overhead = (
|
|
381
|
+
_calculate_signing_overhead(create_signed_value_fn, cookie_name)
|
|
382
|
+
+ SIGNING_OVERHEAD_SAFETY_BUFFER
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# Available space for the signed value:
|
|
386
|
+
# MAX_COOKIE_BYTES - cookie_name - "=" (1 byte) - cookie attributes
|
|
387
|
+
available_for_signed_value = (
|
|
388
|
+
MAX_COOKIE_BYTES - len(cookie_name) - 1 - COOKIE_ATTR_SIZE
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Space available for the base64-encoded value (after subtracting signing overhead)
|
|
392
|
+
available_for_base64_value = available_for_signed_value - signing_overhead
|
|
393
|
+
|
|
394
|
+
# If there is not enough space for the base64-encoded value, raise an error.
|
|
395
|
+
# We need at least 4 bytes for a minimal base64-encoded value.
|
|
396
|
+
if available_for_base64_value < SINGLE_BYTE_BASE64_SIZE:
|
|
397
|
+
raise StreamlitAuthError("Not enough space available for the signed value.")
|
|
398
|
+
|
|
399
|
+
# Convert from base64 space to raw value space (base64 has 4/3 expansion ratio)
|
|
400
|
+
chunk_size = (available_for_base64_value * 3) // 4
|
|
401
|
+
chunks = []
|
|
402
|
+
for i in range(0, len(value), chunk_size):
|
|
403
|
+
chunk = value[i : i + chunk_size]
|
|
404
|
+
chunks.append(chunk)
|
|
405
|
+
|
|
406
|
+
if len(chunks) == 1:
|
|
407
|
+
set_single_cookie_fn(cookie_name, chunks[0])
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
# Store count in the main cookie
|
|
411
|
+
set_single_cookie_fn(cookie_name, f"chunks-{len(chunks)}")
|
|
412
|
+
|
|
413
|
+
# Store remaining chunks as cookie_name_1, cookie_name_2, etc.
|
|
414
|
+
for i in range(len(chunks)):
|
|
415
|
+
chunk_name = f"{cookie_name}_{i + 1}"
|
|
416
|
+
set_single_cookie_fn(chunk_name, chunks[i])
|
|
417
|
+
|
|
418
|
+
_LOGGER.info(
|
|
419
|
+
"Split cookie '%s' into %d chunks",
|
|
420
|
+
cookie_name,
|
|
421
|
+
len(chunks),
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
_chunks_regex = re.compile(rb"chunks-(\d+)")
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def get_cookie_with_chunks(
|
|
429
|
+
get_single_cookie_fn: Callable[[str], bytes | None],
|
|
430
|
+
cookie_name: str,
|
|
431
|
+
) -> bytes | None:
|
|
432
|
+
"""Get a cookie, reconstructing from chunks if it was split.
|
|
433
|
+
|
|
434
|
+
If a count cookie exists, the main cookie contains the first chunk,
|
|
435
|
+
and additional chunks are in cookie_name_1, cookie_name_2, etc.
|
|
436
|
+
If no count cookie exists, the main cookie contains the entire value.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
get_single_cookie_fn: Function to get a single cookie (cookie_name) -> bytes | None
|
|
440
|
+
cookie_name: Name of the cookie
|
|
441
|
+
|
|
442
|
+
Returns
|
|
443
|
+
-------
|
|
444
|
+
Cookie value as bytes, or None if not found
|
|
445
|
+
"""
|
|
446
|
+
cookie_value = get_single_cookie_fn(cookie_name)
|
|
447
|
+
if cookie_value is None:
|
|
448
|
+
return cookie_value
|
|
449
|
+
|
|
450
|
+
match = _chunks_regex.match(cookie_value)
|
|
451
|
+
if match is None:
|
|
452
|
+
return cookie_value
|
|
453
|
+
|
|
454
|
+
# Parse chunk count
|
|
455
|
+
try:
|
|
456
|
+
chunk_count = int(match.group(1))
|
|
457
|
+
except (ValueError, TypeError):
|
|
458
|
+
_LOGGER.exception("Invalid chunk count for cookie '%s'", cookie_name)
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
# Reconstruct the original value from chunks
|
|
462
|
+
chunks = []
|
|
463
|
+
|
|
464
|
+
for i in range(chunk_count):
|
|
465
|
+
chunk_name = f"{cookie_name}_{i + 1}"
|
|
466
|
+
chunk_value = get_single_cookie_fn(chunk_name)
|
|
467
|
+
if chunk_value is None:
|
|
468
|
+
_LOGGER.error("Missing chunk %d for cookie '%s'", i + 1, cookie_name)
|
|
469
|
+
return None
|
|
470
|
+
chunks.append(chunk_value)
|
|
471
|
+
|
|
472
|
+
reconstructed_value = b"".join(chunks)
|
|
473
|
+
return reconstructed_value
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def clear_cookie_and_chunks(
|
|
477
|
+
get_single_cookie_fn: Callable[[str], bytes | None],
|
|
478
|
+
clear_single_cookie_fn: Callable[[str], None],
|
|
479
|
+
cookie_name: str,
|
|
480
|
+
) -> None:
|
|
481
|
+
"""Clear a cookie and any associated chunk cookies.
|
|
482
|
+
|
|
483
|
+
The main cookie always exists. If there are chunks, also clear
|
|
484
|
+
cookie_name_1, cookie_name_2, etc., and the count cookie.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
get_single_cookie_fn: Function to get a single cookie (cookie_name) -> bytes | None
|
|
488
|
+
clear_single_cookie_fn: Function to clear a single cookie (cookie_name)
|
|
489
|
+
cookie_name: Name of the cookie
|
|
490
|
+
"""
|
|
491
|
+
cookie_value = get_single_cookie_fn(cookie_name)
|
|
492
|
+
clear_single_cookie_fn(cookie_name)
|
|
493
|
+
if cookie_value is None:
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
match = _chunks_regex.match(cookie_value)
|
|
497
|
+
if match is None:
|
|
498
|
+
return
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
chunk_count = int(match.group(1))
|
|
502
|
+
# Clear additional chunk cookies (starting from 1, since main cookie is chunk 0)
|
|
503
|
+
for i in range(1, chunk_count + 1):
|
|
504
|
+
clear_single_cookie_fn(f"{cookie_name}_{i}")
|
|
505
|
+
except (ValueError, TypeError):
|
|
506
|
+
# If count is invalid, but we already cleared the main cookie
|
|
507
|
+
# so we can ignore it
|
|
508
|
+
pass
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def validate_auth_credentials(provider: str) -> None:
|
|
512
|
+
"""Validate the general auth credentials and auth credentials for the given
|
|
513
|
+
provider.
|
|
514
|
+
"""
|
|
515
|
+
if not secrets_singleton.load_if_toml_exists():
|
|
516
|
+
raise StreamlitAuthError(
|
|
517
|
+
"""To use authentication features you need to configure credentials for at
|
|
518
|
+
least one authentication provider in `.streamlit/secrets.toml`."""
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
auth_section = secrets_singleton.get("auth")
|
|
522
|
+
if auth_section is None:
|
|
523
|
+
raise StreamlitAuthError(
|
|
524
|
+
"""To use authentication features you need to configure credentials for at
|
|
525
|
+
least one authentication provider in `.streamlit/secrets.toml`."""
|
|
526
|
+
)
|
|
527
|
+
if "redirect_uri" not in auth_section:
|
|
528
|
+
raise StreamlitAuthError(
|
|
529
|
+
"""Authentication credentials in `.streamlit/secrets.toml` are missing the
|
|
530
|
+
"redirect_uri" key. Please check your configuration."""
|
|
531
|
+
)
|
|
532
|
+
if "cookie_secret" not in auth_section:
|
|
533
|
+
raise StreamlitAuthError(
|
|
534
|
+
"""Authentication credentials in `.streamlit/secrets.toml` are missing the
|
|
535
|
+
"cookie_secret" key. Please check your configuration."""
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
provider_section = auth_section.get(provider)
|
|
539
|
+
|
|
540
|
+
# TODO(kajarenc): Revisit this check later when investigating the ability
|
|
541
|
+
# TODO(kajarenc): to add "_" to the provider name.
|
|
542
|
+
if "_" in provider:
|
|
543
|
+
raise StreamlitAuthError(
|
|
544
|
+
f'Auth provider name "{provider}" contains an underscore. '
|
|
545
|
+
f"Please use a provider name without underscores."
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
if provider_section is None and provider == "default":
|
|
549
|
+
provider_section = generate_default_provider_section(auth_section)
|
|
550
|
+
|
|
551
|
+
if provider_section is None:
|
|
552
|
+
if provider == "default":
|
|
553
|
+
raise StreamlitAuthError(
|
|
554
|
+
"""Authentication credentials in `.streamlit/secrets.toml` are missing for
|
|
555
|
+
the default authentication provider. Please check your configuration."""
|
|
556
|
+
)
|
|
557
|
+
raise StreamlitAuthError(
|
|
558
|
+
f"Authentication credentials in `.streamlit/secrets.toml` are missing for "
|
|
559
|
+
f'the authentication provider "{provider}". Please check your '
|
|
560
|
+
f"configuration."
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
if not isinstance(provider_section, Mapping):
|
|
564
|
+
raise StreamlitAuthError(
|
|
565
|
+
f"Authentication credentials in `.streamlit/secrets.toml` for the "
|
|
566
|
+
f'authentication provider "{provider}" must be valid TOML. Please check '
|
|
567
|
+
f"your configuration."
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
required_keys = ["client_id", "client_secret", "server_metadata_url"]
|
|
571
|
+
missing_keys = [key for key in required_keys if key not in provider_section]
|
|
572
|
+
if missing_keys:
|
|
573
|
+
if provider == "default":
|
|
574
|
+
raise StreamlitAuthError(
|
|
575
|
+
"Authentication credentials in `.streamlit/secrets.toml` for the "
|
|
576
|
+
f"default authentication provider are missing the following keys: "
|
|
577
|
+
f"{missing_keys}. Please check your configuration."
|
|
578
|
+
)
|
|
579
|
+
raise StreamlitAuthError(
|
|
580
|
+
"Authentication credentials in `.streamlit/secrets.toml` for the "
|
|
581
|
+
f'authentication provider "{provider}" are missing the following keys: '
|
|
582
|
+
f"{missing_keys}. Please check your configuration."
|
|
583
|
+
)
|
streamlit/cli_util.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
"""Utilities related to the CLI."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from streamlit import env_util, errors
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def print_to_cli(message: str, **kwargs: Any) -> None:
|
|
26
|
+
"""Print a message to the terminal using click if available, else print
|
|
27
|
+
using the built-in print function.
|
|
28
|
+
|
|
29
|
+
You can provide any keyword arguments that click.secho supports.
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
import click
|
|
33
|
+
|
|
34
|
+
click.secho(message, **kwargs)
|
|
35
|
+
except ImportError:
|
|
36
|
+
print(message, flush=True) # noqa: T201
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def style_for_cli(message: str, **kwargs: Any) -> str:
|
|
40
|
+
"""Style a message using click if available, else return the message
|
|
41
|
+
unchanged.
|
|
42
|
+
|
|
43
|
+
You can provide any keyword arguments that click.style supports.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
import click
|
|
48
|
+
|
|
49
|
+
return click.style(message, **kwargs)
|
|
50
|
+
except ImportError:
|
|
51
|
+
return message
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _open_browser_with_webbrowser(url: str) -> None:
|
|
55
|
+
import webbrowser
|
|
56
|
+
|
|
57
|
+
webbrowser.open(url)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _open_browser_with_command(command: str, url: str) -> None:
|
|
61
|
+
cmd_line = [command, url]
|
|
62
|
+
with open(os.devnull, "w", encoding="utf-8") as devnull:
|
|
63
|
+
import subprocess # noqa: S404
|
|
64
|
+
|
|
65
|
+
subprocess.Popen(cmd_line, stdout=devnull, stderr=subprocess.STDOUT) # noqa: S603
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def open_browser(url: str) -> None:
|
|
69
|
+
"""Open a web browser pointing to a given URL.
|
|
70
|
+
|
|
71
|
+
We use this function instead of Python's `webbrowser` module because this
|
|
72
|
+
way we can capture stdout/stderr to avoid polluting the terminal with the
|
|
73
|
+
browser's messages. For example, Chrome always prints things like "Created
|
|
74
|
+
new window in existing browser session", and those get on the user's way.
|
|
75
|
+
|
|
76
|
+
url : str
|
|
77
|
+
The URL. Must include the protocol.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
# Treat Windows separately because:
|
|
81
|
+
# 1. /dev/null doesn't exist.
|
|
82
|
+
# 2. subprocess.Popen(['start', url]) doesn't actually pop up the
|
|
83
|
+
# browser even though 'start url' works from the command prompt.
|
|
84
|
+
# Fun!
|
|
85
|
+
# Also, use webbrowser if we are on Linux and xdg-open is not installed.
|
|
86
|
+
#
|
|
87
|
+
# We don't use the webbrowser module on Linux and Mac because some browsers
|
|
88
|
+
# (ahem... Chrome) always print "Opening in existing browser session" to
|
|
89
|
+
# the terminal, which is spammy and annoying. So instead we start the
|
|
90
|
+
# browser ourselves and send all its output to /dev/null.
|
|
91
|
+
|
|
92
|
+
if env_util.IS_WINDOWS:
|
|
93
|
+
_open_browser_with_webbrowser(url)
|
|
94
|
+
return
|
|
95
|
+
if env_util.IS_LINUX_OR_BSD:
|
|
96
|
+
if env_util.is_executable_in_path("xdg-open"):
|
|
97
|
+
_open_browser_with_command("xdg-open", url)
|
|
98
|
+
return
|
|
99
|
+
_open_browser_with_webbrowser(url)
|
|
100
|
+
return
|
|
101
|
+
if env_util.IS_DARWIN:
|
|
102
|
+
_open_browser_with_command("open", url)
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
import platform
|
|
106
|
+
|
|
107
|
+
raise errors.Error(f'Cannot open browser in platform "{platform.system()}"') # ty: ignore[unresolved-attribute]
|