streamlit 1.49.1__py3-none-any.whl → 1.51.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 +4 -1
- streamlit/column_config.py +2 -0
- streamlit/commands/navigation.py +7 -7
- streamlit/commands/page_config.py +4 -6
- streamlit/components/v1/custom_component.py +17 -42
- streamlit/components/v2/__init__.py +458 -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 +386 -0
- streamlit/components/v2/bidi_component/serialization.py +265 -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 +431 -0
- streamlit/components/v2/component_manifest_handler.py +122 -0
- streamlit/components/v2/component_path_utils.py +245 -0
- streamlit/components/v2/component_registry.py +409 -0
- streamlit/components/v2/get_bidi_component_manager.py +51 -0
- streamlit/components/v2/manifest_scanner.py +615 -0
- streamlit/components/v2/presentation.py +198 -0
- streamlit/components/v2/types.py +324 -0
- streamlit/config.py +741 -32
- streamlit/config_option.py +4 -1
- streamlit/config_util.py +650 -1
- streamlit/connections/base_connection.py +4 -2
- streamlit/dataframe_util.py +18 -10
- streamlit/delta_generator.py +8 -7
- streamlit/delta_generator_singletons.py +3 -1
- streamlit/deprecation_util.py +17 -6
- streamlit/elements/arrow.py +90 -42
- streamlit/elements/deck_gl_json_chart.py +98 -39
- streamlit/elements/dialog_decorator.py +2 -1
- streamlit/elements/exception.py +3 -1
- streamlit/elements/form.py +6 -6
- streamlit/elements/graphviz_chart.py +24 -9
- streamlit/elements/heading.py +3 -5
- streamlit/elements/iframe.py +0 -2
- streamlit/elements/image.py +12 -13
- streamlit/elements/layouts.py +89 -22
- streamlit/elements/lib/built_in_chart_utils.py +95 -31
- streamlit/elements/lib/color_util.py +8 -18
- streamlit/elements/lib/column_config_utils.py +9 -8
- streamlit/elements/lib/column_types.py +595 -148
- streamlit/elements/lib/dialog.py +3 -2
- streamlit/elements/lib/image_utils.py +3 -5
- streamlit/elements/lib/layout_utils.py +50 -13
- streamlit/elements/lib/mutable_status_container.py +2 -2
- streamlit/elements/lib/options_selector_utils.py +2 -2
- streamlit/elements/lib/pandas_styler_utils.py +30 -14
- streamlit/elements/lib/utils.py +21 -9
- streamlit/elements/map.py +81 -40
- streamlit/elements/media.py +7 -7
- streamlit/elements/metric.py +11 -35
- streamlit/elements/pdf.py +2 -4
- streamlit/elements/plotly_chart.py +142 -26
- streamlit/elements/progress.py +2 -4
- streamlit/elements/pyplot.py +6 -6
- streamlit/elements/space.py +113 -0
- streamlit/elements/vega_charts.py +400 -143
- streamlit/elements/widgets/audio_input.py +52 -4
- streamlit/elements/widgets/button.py +29 -29
- streamlit/elements/widgets/button_group.py +33 -6
- streamlit/elements/widgets/camera_input.py +3 -4
- streamlit/elements/widgets/chat.py +7 -0
- streamlit/elements/widgets/checkbox.py +1 -0
- streamlit/elements/widgets/color_picker.py +1 -0
- streamlit/elements/widgets/data_editor.py +34 -29
- streamlit/elements/widgets/file_uploader.py +6 -10
- streamlit/elements/widgets/multiselect.py +14 -3
- streamlit/elements/widgets/number_input.py +5 -4
- streamlit/elements/widgets/radio.py +10 -2
- streamlit/elements/widgets/select_slider.py +8 -4
- streamlit/elements/widgets/selectbox.py +9 -2
- streamlit/elements/widgets/slider.py +38 -41
- streamlit/elements/widgets/text_widgets.py +6 -0
- streamlit/elements/widgets/time_widgets.py +15 -12
- streamlit/elements/write.py +28 -23
- streamlit/emojis.py +1 -1
- streamlit/errors.py +115 -0
- streamlit/git_util.py +65 -43
- streamlit/hello/hello.py +8 -0
- streamlit/hello/utils.py +2 -1
- streamlit/material_icon_names.py +1 -1
- streamlit/navigation/page.py +4 -1
- streamlit/proto/ArrowData_pb2.py +27 -0
- streamlit/proto/ArrowData_pb2.pyi +46 -0
- streamlit/proto/Arrow_pb2.py +10 -8
- streamlit/proto/Arrow_pb2.pyi +31 -2
- streamlit/proto/AudioInput_pb2.py +2 -2
- streamlit/proto/AudioInput_pb2.pyi +6 -2
- streamlit/proto/BidiComponent_pb2.py +34 -0
- streamlit/proto/BidiComponent_pb2.pyi +153 -0
- streamlit/proto/Block_pb2.py +11 -11
- streamlit/proto/Block_pb2.pyi +9 -1
- streamlit/proto/DeckGlJsonChart_pb2.py +10 -4
- streamlit/proto/DeckGlJsonChart_pb2.pyi +9 -3
- streamlit/proto/Element_pb2.py +5 -3
- streamlit/proto/Element_pb2.pyi +14 -4
- streamlit/proto/HeightConfig_pb2.py +2 -2
- streamlit/proto/HeightConfig_pb2.pyi +6 -3
- streamlit/proto/NewSession_pb2.py +18 -16
- streamlit/proto/NewSession_pb2.pyi +158 -6
- streamlit/proto/PlotlyChart_pb2.py +8 -6
- streamlit/proto/PlotlyChart_pb2.pyi +3 -1
- streamlit/proto/Space_pb2.py +27 -0
- streamlit/proto/Space_pb2.pyi +42 -0
- streamlit/proto/WidgetStates_pb2.py +2 -2
- streamlit/proto/WidgetStates_pb2.pyi +13 -3
- streamlit/proto/WidthConfig_pb2.py +2 -2
- streamlit/proto/WidthConfig_pb2.pyi +6 -3
- streamlit/runtime/app_session.py +45 -6
- streamlit/runtime/caching/cache_data_api.py +4 -4
- streamlit/runtime/caching/cache_errors.py +4 -1
- streamlit/runtime/caching/cache_resource_api.py +3 -2
- streamlit/runtime/caching/cache_utils.py +2 -1
- streamlit/runtime/caching/cached_message_replay.py +3 -3
- streamlit/runtime/caching/hashing.py +3 -4
- streamlit/runtime/caching/legacy_cache_api.py +2 -1
- streamlit/runtime/connection_factory.py +1 -3
- streamlit/runtime/forward_msg_queue.py +4 -1
- streamlit/runtime/fragment.py +2 -1
- streamlit/runtime/memory_media_file_storage.py +1 -1
- streamlit/runtime/metrics_util.py +6 -2
- streamlit/runtime/runtime.py +14 -0
- streamlit/runtime/scriptrunner/exec_code.py +2 -1
- streamlit/runtime/scriptrunner/script_runner.py +2 -2
- streamlit/runtime/scriptrunner_utils/script_run_context.py +3 -6
- streamlit/runtime/secrets.py +2 -4
- streamlit/runtime/session_manager.py +3 -1
- streamlit/runtime/state/common.py +30 -5
- streamlit/runtime/state/presentation.py +85 -0
- streamlit/runtime/state/safe_session_state.py +2 -2
- streamlit/runtime/state/session_state.py +220 -16
- streamlit/runtime/state/widgets.py +19 -3
- streamlit/runtime/theme_util.py +148 -0
- streamlit/runtime/websocket_session_manager.py +3 -1
- streamlit/source_util.py +2 -2
- streamlit/static/index.html +2 -2
- streamlit/static/manifest.json +244 -227
- streamlit/static/static/css/{index.C8X8rNzw.css → index.BpABIXK9.css} +1 -1
- streamlit/static/static/css/index.DgR7E2CV.css +1 -0
- streamlit/static/static/js/{ErrorOutline.esm.DcGrhbBP.js → ErrorOutline.esm.YoJdlW1p.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.DgBvV6Pq.js → FileDownload.esm.Ddx8VEYy.js} +1 -1
- streamlit/static/static/js/{FileHelper.M6AAaeuA.js → FileHelper.90EtOmj9.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.DHh1GFzm.js → FormClearHelper.BB1Km6eP.js} +1 -1
- streamlit/static/static/js/InputInstructions.jhH15PqV.js +1 -0
- streamlit/static/static/js/{Particles.DDVT-6Qc.js → Particles.DUsputn1.js} +1 -1
- streamlit/static/static/js/{ProgressBar.BEY0cXXV.js → ProgressBar.DLY8H6nE.js} +2 -2
- streamlit/static/static/js/Toolbar.D8nHCkuz.js +1 -0
- streamlit/static/static/js/{base-input.CK3UVGp1.js → base-input.CJGiNqed.js} +3 -3
- streamlit/static/static/js/{checkbox.D8W881TL.js → checkbox.Cpdd482O.js} +1 -1
- streamlit/static/static/js/{createSuper.B6W-Dh9S.js → createSuper.CuQIogbW.js} +1 -1
- streamlit/static/static/js/data-grid-overlay-editor.2Ufgxc6y.js +1 -0
- streamlit/static/static/js/{downloader.DiKpuU_S.js → downloader.CN0K7xlu.js} +1 -1
- streamlit/static/static/js/{es6.B8zRNPZ-.js → es6.BJcsVXQ0.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.DIewJmmh.js → iframeResizer.contentWindow.XzUvQqcZ.js} +1 -1
- streamlit/static/static/js/index.B1ZQh4P1.js +1 -0
- streamlit/static/static/js/index.BKstZk0M.js +27 -0
- streamlit/static/static/js/{index.Bte_9Lyq.js → index.BMcFsUee.js} +1 -1
- streamlit/static/static/js/{index.qhs54UAB.js → index.BR-IdcTb.js} +1 -1
- streamlit/static/static/js/{index.CejBxbg1.js → index.B_dWA3vd.js} +1 -1
- streamlit/static/static/js/{index.D5naqx-J.js → index.BgnZEMVh.js} +1 -1
- streamlit/static/static/js/{index.C7fRKRs4.js → index.BohqXifI.js} +1 -1
- streamlit/static/static/js/{index.cnnXF7xQ.js → index.Br5nxKNj.js} +1 -1
- streamlit/static/static/js/index.BrIKVbNc.js +3 -0
- streamlit/static/static/js/index.BtWUPzle.js +1 -0
- streamlit/static/static/js/index.C0RLraek.js +1 -0
- streamlit/static/static/js/{index.CP5TD2z1.js → index.CAIjskgG.js} +1 -1
- streamlit/static/static/js/{index.CD8HuT3N.js → index.CAj-7vWz.js} +135 -162
- streamlit/static/static/js/{index.DtYN2x4k.js → index.CMtEit2O.js} +1 -1
- streamlit/static/static/js/index.CkRlykEE.js +12 -0
- streamlit/static/static/js/{index.Ts_0SdB9.js → index.CmN3FXfI.js} +2 -2
- streamlit/static/static/js/{index.BnEpvLEz.js → index.CwbFI1_-.js} +1 -1
- streamlit/static/static/js/{index.CcJf6BCU.js → index.CxIUUfab.js} +27 -27
- streamlit/static/static/js/index.D2KPNy7e.js +1 -0
- streamlit/static/static/js/{index.Ch7MBCx0.js → index.D3GPA5k4.js} +47 -47
- streamlit/static/static/js/{index.ho6NIXGl.js → index.DGAh7DMq.js} +1 -1
- streamlit/static/static/js/index.DKb_NvmG.js +197 -0
- streamlit/static/static/js/{index.CvYYtxD_.js → index.DMqgUYKq.js} +1 -1
- streamlit/static/static/js/{index.zecpGxtj.js → index.DOFlg3dS.js} +1 -1
- streamlit/static/static/js/{index.B9mjBcgE.js → index.DPUXkcQL.js} +1 -1
- streamlit/static/static/js/index.DX1xY89g.js +1 -0
- streamlit/static/static/js/index.DYATBCsq.js +2 -0
- streamlit/static/static/js/{index.D2-atlaQ.js → index.DaSmGJ76.js} +3 -3
- streamlit/static/static/js/index.Dd7bMeLP.js +1 -0
- streamlit/static/static/js/{index.4eF4NxG2.js → index.DjmmgI5U.js} +1 -1
- streamlit/static/static/js/index.Dq56CyM2.js +1 -0
- streamlit/static/static/js/index.DuiXaS5_.js +7 -0
- streamlit/static/static/js/index.DvFidMLe.js +2 -0
- streamlit/static/static/js/{index.452cqrrL.js → index.DwkhC5Pc.js} +1 -1
- streamlit/static/static/js/{index.Dk4C7X3i.js → index.Q-3sFn1v.js} +1 -1
- streamlit/static/static/js/{index.CjXWwH-y.js → index.QJ5QO9sJ.js} +1 -1
- streamlit/static/static/js/{index.B6U8LQo3.js → index.VwTaeety.js} +1 -1
- streamlit/static/static/js/index.YOqQbeX8.js +1 -0
- streamlit/static/static/js/{input.nzVJphXi.js → input.D4MN_FzN.js} +1 -1
- streamlit/static/static/js/{memory.CjCgTQz3.js → memory.DrZjtdGT.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.DaRFzZEO.js → number-overlay-editor.DRwAw1In.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.DgiPnZ9N.js → possibleConstructorReturn.exeeJQEP.js} +1 -1
- streamlit/static/static/js/record.B-tDciZb.js +1 -0
- streamlit/static/static/js/{sandbox.mithfq7Z.js → sandbox.ClO3IuUr.js} +1 -1
- streamlit/static/static/js/{timepicker.Dbl5KFh6.js → timepicker.DAhu-vcF.js} +4 -4
- streamlit/static/static/js/{toConsumableArray.D-Dx88BQ.js → toConsumableArray.DNbljYEC.js} +1 -1
- streamlit/static/static/js/{uniqueId.Bh26R_3S.js → uniqueId.oG4Gvj1v.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.DeK-QJpD.js → useBasicWidgetState.D6sOH6oI.js} +1 -1
- streamlit/static/static/js/{useTextInputAutoExpand.4iAdLWD-.js → useTextInputAutoExpand.4u3_GcuN.js} +2 -2
- streamlit/static/static/js/{useUpdateUiValue.CmT7_nJN.js → useUpdateUiValue.F2R3eTeR.js} +1 -1
- streamlit/static/static/js/wavesurfer.esm.vI8Eid4k.js +73 -0
- streamlit/static/static/js/withFullScreenWrapper.zothJIsI.js +1 -0
- streamlit/static/static/media/MaterialSymbols-Rounded.C7IFxh57.woff2 +0 -0
- streamlit/string_util.py +56 -1
- streamlit/testing/v1/app_test.py +2 -2
- streamlit/testing/v1/element_tree.py +23 -9
- streamlit/testing/v1/util.py +2 -2
- streamlit/type_util.py +3 -4
- streamlit/url_util.py +1 -3
- streamlit/user_info.py +1 -2
- streamlit/util.py +3 -1
- streamlit/watcher/event_based_path_watcher.py +23 -12
- streamlit/watcher/local_sources_watcher.py +11 -1
- streamlit/watcher/path_watcher.py +9 -6
- streamlit/watcher/polling_path_watcher.py +4 -1
- streamlit/watcher/util.py +2 -2
- streamlit/web/bootstrap.py +0 -31
- streamlit/web/cli.py +51 -22
- streamlit/web/server/bidi_component_request_handler.py +193 -0
- streamlit/web/server/component_file_utils.py +97 -0
- streamlit/web/server/component_request_handler.py +8 -21
- streamlit/web/server/oidc_mixin.py +3 -1
- streamlit/web/server/routes.py +18 -5
- streamlit/web/server/server.py +10 -0
- streamlit/web/server/server_util.py +3 -1
- streamlit/web/server/upload_file_request_handler.py +3 -1
- {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/METADATA +4 -5
- {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/RECORD +238 -209
- streamlit/static/static/css/index.COe1010n.css +0 -1
- streamlit/static/static/js/Hooks.DGu1od_L.js +0 -1
- streamlit/static/static/js/InputInstructions.z6sVgyYt.js +0 -1
- streamlit/static/static/js/Toolbar.DSnK1fUh.js +0 -1
- streamlit/static/static/js/data-grid-overlay-editor.DRTHOydk.js +0 -1
- streamlit/static/static/js/index.BXYmrqnf.js +0 -1
- streamlit/static/static/js/index.B_8AnktO.js +0 -1
- streamlit/static/static/js/index.Bl7zGQSh.js +0 -7
- streamlit/static/static/js/index.BnJIOYn9.js +0 -73
- streamlit/static/static/js/index.C1HcTl5K.js +0 -1
- streamlit/static/static/js/index.C7lSmSOP.js +0 -1
- streamlit/static/static/js/index.C_tmcx4B.js +0 -1
- streamlit/static/static/js/index.D3K5nOu9.js +0 -197
- streamlit/static/static/js/index.DkKT3LUI.js +0 -1
- streamlit/static/static/js/index.MTPPBDHk.js +0 -2
- streamlit/static/static/js/index.pqW9AMJD.js +0 -3
- streamlit/static/static/js/index.urHgTgMQ.js +0 -12
- streamlit/static/static/js/index.wzkv_11M.js +0 -1
- streamlit/static/static/js/index.yF5AncHY.js +0 -1
- streamlit/static/static/js/withFullScreenWrapper.DLp1ENGm.js +0 -1
- streamlit/static/static/media/MaterialSymbols-Rounded.CBxVaFdk.woff2 +0 -0
- {streamlit-1.49.1.data → streamlit-1.51.0.data}/scripts/streamlit.cmd +0 -0
- {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/WHEEL +0 -0
- {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/entry_points.txt +0 -0
- {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Custom Components v2 manager and supporting orchestration.
|
|
16
|
+
|
|
17
|
+
This module composes the registry, manifest handling, and file watching
|
|
18
|
+
capabilities for Streamlit's Custom Components v2. It provides a unified
|
|
19
|
+
interface to register components from manifests or individual definitions, query
|
|
20
|
+
component metadata and asset paths, and react to on-disk changes by re-resolving
|
|
21
|
+
component definitions.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import threading
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from typing import TYPE_CHECKING, Final
|
|
29
|
+
|
|
30
|
+
from streamlit.components.v2.component_definition_resolver import (
|
|
31
|
+
build_definition_with_validation,
|
|
32
|
+
)
|
|
33
|
+
from streamlit.components.v2.component_file_watcher import ComponentFileWatcher
|
|
34
|
+
from streamlit.components.v2.component_manifest_handler import ComponentManifestHandler
|
|
35
|
+
from streamlit.components.v2.component_registry import (
|
|
36
|
+
BidiComponentDefinition,
|
|
37
|
+
BidiComponentRegistry,
|
|
38
|
+
)
|
|
39
|
+
from streamlit.logger import get_logger
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
from streamlit.components.v2.manifest_scanner import ComponentManifest
|
|
45
|
+
|
|
46
|
+
_LOGGER: Final = get_logger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class _ApiInputs:
|
|
51
|
+
"""Inputs provided via the Python API to resolve a component definition.
|
|
52
|
+
|
|
53
|
+
Attributes
|
|
54
|
+
----------
|
|
55
|
+
css : str | None
|
|
56
|
+
Inline CSS content or a path/glob to a CSS asset within ``asset_dir``.
|
|
57
|
+
js : str | None
|
|
58
|
+
Inline JS content or a path/glob to a JS asset within ``asset_dir``.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
css: str | None
|
|
62
|
+
js: str | None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BidiComponentManager:
|
|
66
|
+
"""Manager class that composes component registry, manifest handler, and
|
|
67
|
+
file watcher.
|
|
68
|
+
|
|
69
|
+
This class provides a unified interface for working with bidirectional
|
|
70
|
+
components while maintaining clean separation of concerns through
|
|
71
|
+
composition. It handles the coordination and lifecycle management of all
|
|
72
|
+
component-related functionality.
|
|
73
|
+
|
|
74
|
+
Component Lifecycle
|
|
75
|
+
-------------------
|
|
76
|
+
The lifecycle of a component managed by this class involves four key stages:
|
|
77
|
+
|
|
78
|
+
1. **Discovery**: On startup, ``discover_and_register_components`` scans
|
|
79
|
+
for installed packages with component manifests (``pyproject.toml``).
|
|
80
|
+
For each component found, a placeholder definition containing only its
|
|
81
|
+
name and ``asset_dir`` is registered. This makes the system aware of all
|
|
82
|
+
available installed components from the outset.
|
|
83
|
+
|
|
84
|
+
2. **Definition & Validation**: When a user's script calls the public API
|
|
85
|
+
(e.g., ``st.components.v2.component(...)``), the manager invokes
|
|
86
|
+
``build_definition_with_validation``. This function is the single,
|
|
87
|
+
centralized point for all validation. It resolves file paths, performs
|
|
88
|
+
security checks against the component's ``asset_dir``, and produces a
|
|
89
|
+
complete, validated ``BidiComponentDefinition``.
|
|
90
|
+
|
|
91
|
+
3. **Registration**: The validated definition is then passed to the
|
|
92
|
+
registry's ``register`` method. This adds the complete definition,
|
|
93
|
+
overwriting the placeholder if one existed from the discovery phase.
|
|
94
|
+
|
|
95
|
+
4. **Updating**: The ``ComponentFileWatcher`` monitors the ``asset_dir``
|
|
96
|
+
for changes. On a change, it triggers a re-computation of the definition
|
|
97
|
+
using the original API inputs, runs it through the same validation
|
|
98
|
+
logic, and updates the registry with the new definition via the stricter
|
|
99
|
+
``update_component`` method.
|
|
100
|
+
|
|
101
|
+
Notes
|
|
102
|
+
-----
|
|
103
|
+
This manager intentionally favors composition over inheritance and delegates
|
|
104
|
+
specialized responsibilities to ``BidiComponentRegistry``,
|
|
105
|
+
``ComponentManifestHandler``, and ``ComponentFileWatcher``.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
registry: BidiComponentRegistry | None = None,
|
|
111
|
+
manifest_handler: ComponentManifestHandler | None = None,
|
|
112
|
+
file_watcher: ComponentFileWatcher | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Initialize the component manager.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
registry : BidiComponentRegistry, optional
|
|
119
|
+
Component registry instance. If not provided, a new one will be created.
|
|
120
|
+
manifest_handler : ComponentManifestHandler, optional
|
|
121
|
+
Manifest handler instance. If not provided, a new one will be created.
|
|
122
|
+
file_watcher : ComponentFileWatcher, optional
|
|
123
|
+
File watcher instance. If not provided, a new one will be created.
|
|
124
|
+
"""
|
|
125
|
+
# Create dependencies
|
|
126
|
+
self._registry = registry or BidiComponentRegistry()
|
|
127
|
+
self._manifest_handler = manifest_handler or ComponentManifestHandler()
|
|
128
|
+
# Store API inputs for re-resolution on change events
|
|
129
|
+
self._api_inputs: dict[str, _ApiInputs] = {}
|
|
130
|
+
self._api_inputs_lock = threading.Lock()
|
|
131
|
+
self._file_watcher = file_watcher or ComponentFileWatcher(
|
|
132
|
+
self._on_components_changed
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def record_api_inputs(
|
|
136
|
+
self, component_key: str, css: str | None, js: str | None
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Record original API inputs for later re-resolution on file changes.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
component_key : str
|
|
143
|
+
Fully-qualified component name.
|
|
144
|
+
css : str | None
|
|
145
|
+
Inline CSS or a path/glob to a CSS file within the component's
|
|
146
|
+
``asset_dir``.
|
|
147
|
+
js : str | None
|
|
148
|
+
Inline JavaScript or a path/glob to a JS file within the component's
|
|
149
|
+
``asset_dir``.
|
|
150
|
+
"""
|
|
151
|
+
with self._api_inputs_lock:
|
|
152
|
+
self._api_inputs[component_key] = _ApiInputs(css=css, js=js)
|
|
153
|
+
|
|
154
|
+
def register_from_manifest(
|
|
155
|
+
self, manifest: ComponentManifest, package_root: Path
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Register components from a manifest file.
|
|
158
|
+
|
|
159
|
+
This is a high-level method that processes the manifest and registers
|
|
160
|
+
all components found within it.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
manifest : ComponentManifest
|
|
165
|
+
The component manifest to process.
|
|
166
|
+
package_root : Path
|
|
167
|
+
Root path of the package containing the components.
|
|
168
|
+
"""
|
|
169
|
+
# First process the manifest
|
|
170
|
+
component_definitions = self._manifest_handler.process_manifest(
|
|
171
|
+
manifest, package_root
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Register all component definitions
|
|
175
|
+
self._registry.register_components_from_definitions(component_definitions)
|
|
176
|
+
|
|
177
|
+
_LOGGER.debug(
|
|
178
|
+
"Registered %d components from manifest", len(component_definitions)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def register(self, definition: BidiComponentDefinition) -> None:
|
|
182
|
+
"""Register a single component definition.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
definition : BidiComponentDefinition
|
|
187
|
+
The component definition to register.
|
|
188
|
+
"""
|
|
189
|
+
self._registry.register(definition)
|
|
190
|
+
|
|
191
|
+
def get(self, name: str) -> BidiComponentDefinition | None:
|
|
192
|
+
"""Get a component definition by name.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
name : str
|
|
197
|
+
The name of the component to retrieve.
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
BidiComponentDefinition or None
|
|
202
|
+
The component definition if found; otherwise ``None``.
|
|
203
|
+
"""
|
|
204
|
+
return self._registry.get(name)
|
|
205
|
+
|
|
206
|
+
def build_definition_with_validation(
|
|
207
|
+
self,
|
|
208
|
+
*,
|
|
209
|
+
component_key: str,
|
|
210
|
+
html: str | None,
|
|
211
|
+
css: str | None,
|
|
212
|
+
js: str | None,
|
|
213
|
+
) -> BidiComponentDefinition:
|
|
214
|
+
"""Build a validated component definition for the given inputs.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
component_key : str
|
|
219
|
+
Fully-qualified component name the definition is for.
|
|
220
|
+
html : str | None
|
|
221
|
+
Inline HTML content to include in the definition.
|
|
222
|
+
css : str | None
|
|
223
|
+
Inline CSS content or a path/glob under the component's asset_dir.
|
|
224
|
+
js : str | None
|
|
225
|
+
Inline JS content or a path/glob under the component's asset_dir.
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
BidiComponentDefinition
|
|
230
|
+
The fully validated component definition.
|
|
231
|
+
"""
|
|
232
|
+
return build_definition_with_validation(
|
|
233
|
+
manager=self,
|
|
234
|
+
component_key=component_key,
|
|
235
|
+
html=html,
|
|
236
|
+
css=css,
|
|
237
|
+
js=js,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def get_component_asset_root(self, name: str) -> Path | None:
|
|
241
|
+
"""Get the asset root for a manifest-backed component.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
name : str
|
|
246
|
+
The name of the component to get the asset root for.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
Path or None
|
|
251
|
+
The component's ``asset_root`` directory if found; otherwise
|
|
252
|
+
``None``.
|
|
253
|
+
"""
|
|
254
|
+
return self._manifest_handler.get_asset_root(name)
|
|
255
|
+
|
|
256
|
+
def unregister(self, name: str) -> None:
|
|
257
|
+
"""Unregister a component by name.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
name : str
|
|
262
|
+
The name of the component to unregister.
|
|
263
|
+
"""
|
|
264
|
+
self._registry.unregister(name)
|
|
265
|
+
|
|
266
|
+
def clear(self) -> None:
|
|
267
|
+
"""Clear all registered components."""
|
|
268
|
+
self._registry.clear()
|
|
269
|
+
|
|
270
|
+
def get_component_path(self, name: str) -> str | None:
|
|
271
|
+
"""Get the filesystem path for a manifest-backed component.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
name : str
|
|
276
|
+
The name of the component.
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
str or None
|
|
281
|
+
The component's ``asset_dir`` directory if found; otherwise
|
|
282
|
+
``None``.
|
|
283
|
+
"""
|
|
284
|
+
asset_root = self._manifest_handler.get_asset_root(name)
|
|
285
|
+
if asset_root is not None:
|
|
286
|
+
return str(asset_root)
|
|
287
|
+
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
def start_file_watching(self) -> None:
|
|
291
|
+
"""Start file watching for component changes."""
|
|
292
|
+
if self._file_watcher.is_watching_active:
|
|
293
|
+
_LOGGER.warning("File watching is already started")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
# Get asset watch roots from manifest handler
|
|
297
|
+
asset_roots = self._manifest_handler.get_asset_watch_roots()
|
|
298
|
+
|
|
299
|
+
# Start file watching
|
|
300
|
+
self._file_watcher.start_file_watching(asset_roots)
|
|
301
|
+
|
|
302
|
+
if self._file_watcher.is_watching_active:
|
|
303
|
+
_LOGGER.debug("Started file watching for component changes") # type: ignore[unreachable]
|
|
304
|
+
else:
|
|
305
|
+
_LOGGER.debug("File watching not started")
|
|
306
|
+
|
|
307
|
+
def discover_and_register_components(self) -> None:
|
|
308
|
+
"""Discover installed v2 components and register them.
|
|
309
|
+
|
|
310
|
+
This scans installed distributions for manifests, registers all discovered
|
|
311
|
+
components, and starts file watching for development workflows.
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
from streamlit.components.v2.manifest_scanner import (
|
|
315
|
+
scan_component_manifests,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
manifests = scan_component_manifests()
|
|
319
|
+
for manifest, package_root in manifests:
|
|
320
|
+
self.register_from_manifest(manifest, package_root)
|
|
321
|
+
_LOGGER.info(
|
|
322
|
+
"Registered components from pyproject.toml: %s v%s",
|
|
323
|
+
manifest.name,
|
|
324
|
+
manifest.version,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Start file watching for development mode after all components are registered
|
|
328
|
+
self.start_file_watching()
|
|
329
|
+
|
|
330
|
+
except Exception as e:
|
|
331
|
+
_LOGGER.warning("Failed to scan component manifests: %s", e)
|
|
332
|
+
|
|
333
|
+
def stop_file_watching(self) -> None:
|
|
334
|
+
"""Stop file watching."""
|
|
335
|
+
if not self._file_watcher.is_watching_active:
|
|
336
|
+
_LOGGER.warning("File watching is not started")
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
self._file_watcher.stop_file_watching()
|
|
340
|
+
|
|
341
|
+
_LOGGER.debug("Stopped file watching")
|
|
342
|
+
|
|
343
|
+
def get_metadata(self, component_name: str) -> ComponentManifest | None:
|
|
344
|
+
"""Get metadata for a component.
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
component_name : str
|
|
349
|
+
The name of the component to get metadata for.
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
ComponentManifest or None
|
|
354
|
+
The component metadata if found; otherwise ``None``.
|
|
355
|
+
"""
|
|
356
|
+
return self._manifest_handler.get_metadata(component_name)
|
|
357
|
+
|
|
358
|
+
def _on_components_changed(self, component_names: list[str]) -> None:
|
|
359
|
+
"""Handle change events for components' asset roots.
|
|
360
|
+
|
|
361
|
+
For each component, re-resolve from stored API inputs and update the
|
|
362
|
+
registry with the new definition if resolution succeeds.
|
|
363
|
+
|
|
364
|
+
Parameters
|
|
365
|
+
----------
|
|
366
|
+
component_names : list[str]
|
|
367
|
+
Fully-qualified component names whose watched files changed.
|
|
368
|
+
"""
|
|
369
|
+
for name in component_names:
|
|
370
|
+
try:
|
|
371
|
+
updated_def = self._recompute_definition_from_api(name)
|
|
372
|
+
if updated_def is not None:
|
|
373
|
+
self._registry.update_component(updated_def)
|
|
374
|
+
except Exception: # noqa: PERF203
|
|
375
|
+
_LOGGER.exception("Failed to update component after change: %s", name)
|
|
376
|
+
|
|
377
|
+
def _recompute_definition_from_api(
|
|
378
|
+
self, component_name: str
|
|
379
|
+
) -> BidiComponentDefinition | None:
|
|
380
|
+
"""Recompute a component's definition using previously recorded API inputs.
|
|
381
|
+
|
|
382
|
+
Parameters
|
|
383
|
+
----------
|
|
384
|
+
component_name : str
|
|
385
|
+
Fully-qualified component name to recompute.
|
|
386
|
+
|
|
387
|
+
Returns
|
|
388
|
+
-------
|
|
389
|
+
BidiComponentDefinition | None
|
|
390
|
+
A fully validated component definition suitable for replacing the
|
|
391
|
+
stored entry in the registry, or ``None`` if recomputation failed
|
|
392
|
+
or no API inputs were previously recorded.
|
|
393
|
+
"""
|
|
394
|
+
with self._api_inputs_lock:
|
|
395
|
+
inputs = self._api_inputs.get(component_name)
|
|
396
|
+
if inputs is None:
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
# Get existing def to preserve html unless new content is provided later
|
|
400
|
+
existing_def = self._registry.get(component_name)
|
|
401
|
+
html_value = existing_def.html if existing_def else None
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
# Resolve a fully validated definition from stored API inputs and
|
|
405
|
+
# the preserved html value from the existing definition.
|
|
406
|
+
new_def = self.build_definition_with_validation(
|
|
407
|
+
component_key=component_name,
|
|
408
|
+
html=html_value,
|
|
409
|
+
css=inputs.css,
|
|
410
|
+
js=inputs.js,
|
|
411
|
+
)
|
|
412
|
+
except Exception as e:
|
|
413
|
+
_LOGGER.debug(
|
|
414
|
+
"Skipping update for %s due to re-resolution error: %s",
|
|
415
|
+
component_name,
|
|
416
|
+
e,
|
|
417
|
+
)
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
return new_def
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def is_file_watching_started(self) -> bool:
|
|
424
|
+
"""Check if file watching is currently active.
|
|
425
|
+
|
|
426
|
+
Returns
|
|
427
|
+
-------
|
|
428
|
+
bool
|
|
429
|
+
True if file watching is started, False otherwise
|
|
430
|
+
"""
|
|
431
|
+
return self._file_watcher.is_watching_active
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from collections.abc import MutableMapping
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from streamlit.components.v2.manifest_scanner import ComponentManifest
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ComponentManifestHandler:
|
|
27
|
+
"""Handles component registration from parsed ComponentManifest objects."""
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
# Component metadata from pyproject.toml
|
|
31
|
+
self._metadata: MutableMapping[str, ComponentManifest] = {}
|
|
32
|
+
# Resolved asset roots keyed by fully-qualified component name
|
|
33
|
+
self._asset_roots: MutableMapping[str, Path] = {}
|
|
34
|
+
|
|
35
|
+
def process_manifest(
|
|
36
|
+
self, manifest: ComponentManifest, package_root: Path
|
|
37
|
+
) -> dict[str, dict[str, Any]]:
|
|
38
|
+
"""Process a manifest and return component definitions to register.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
manifest : ComponentManifest
|
|
43
|
+
The manifest to process
|
|
44
|
+
package_root : Path
|
|
45
|
+
The package root directory
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
dict[str, dict[str, Any]]
|
|
50
|
+
Dictionary mapping component names to their definitions
|
|
51
|
+
|
|
52
|
+
Raises
|
|
53
|
+
------
|
|
54
|
+
StreamlitComponentRegistryError
|
|
55
|
+
If a declared ``asset_dir`` does not exist, is not a directory, or
|
|
56
|
+
resolves (after following symlinks) outside of ``package_root``.
|
|
57
|
+
"""
|
|
58
|
+
base_name = manifest.name
|
|
59
|
+
component_definitions = {}
|
|
60
|
+
|
|
61
|
+
# Process each component in the manifest
|
|
62
|
+
for comp_config in manifest.components:
|
|
63
|
+
comp_name = comp_config.name
|
|
64
|
+
|
|
65
|
+
component_name = f"{base_name}.{comp_name}"
|
|
66
|
+
|
|
67
|
+
# Parse and persist asset_dir if provided. This is the component's
|
|
68
|
+
# root directory for all future file references.
|
|
69
|
+
asset_root = comp_config.resolve_asset_root(package_root)
|
|
70
|
+
if asset_root is not None:
|
|
71
|
+
self._asset_roots[component_name] = asset_root
|
|
72
|
+
|
|
73
|
+
# Create component definition data
|
|
74
|
+
component_definitions[component_name] = {
|
|
75
|
+
"name": component_name,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Store metadata
|
|
79
|
+
self._metadata[component_name] = manifest
|
|
80
|
+
|
|
81
|
+
return component_definitions
|
|
82
|
+
|
|
83
|
+
def get_metadata(self, component_name: str) -> ComponentManifest | None:
|
|
84
|
+
"""Get metadata for a specific component.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
component_name : str
|
|
89
|
+
Fully-qualified component name (e.g., ``"package.component"``).
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
ComponentManifest | None
|
|
94
|
+
The manifest that declared this component, or ``None`` if unknown.
|
|
95
|
+
"""
|
|
96
|
+
return self._metadata.get(component_name)
|
|
97
|
+
|
|
98
|
+
def get_asset_root(self, component_name: str) -> Path | None:
|
|
99
|
+
"""Get the absolute asset root directory for a component if declared.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
component_name : str
|
|
104
|
+
Fully-qualified component name (e.g. "package.component").
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
Path | None
|
|
109
|
+
Absolute path to the component's asset root if present, otherwise None.
|
|
110
|
+
"""
|
|
111
|
+
return self._asset_roots.get(component_name)
|
|
112
|
+
|
|
113
|
+
def get_asset_watch_roots(self) -> dict[str, Path]:
|
|
114
|
+
"""Get a mapping of component names to their asset root directories.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
dict[str, Path]
|
|
119
|
+
A shallow copy mapping fully-qualified component names to absolute
|
|
120
|
+
asset root directories.
|
|
121
|
+
"""
|
|
122
|
+
return dict(self._asset_roots)
|