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.
Files changed (259) hide show
  1. streamlit/__init__.py +4 -1
  2. streamlit/column_config.py +2 -0
  3. streamlit/commands/navigation.py +7 -7
  4. streamlit/commands/page_config.py +4 -6
  5. streamlit/components/v1/custom_component.py +17 -42
  6. streamlit/components/v2/__init__.py +458 -0
  7. streamlit/components/v2/bidi_component/__init__.py +20 -0
  8. streamlit/components/v2/bidi_component/constants.py +29 -0
  9. streamlit/components/v2/bidi_component/main.py +386 -0
  10. streamlit/components/v2/bidi_component/serialization.py +265 -0
  11. streamlit/components/v2/bidi_component/state.py +92 -0
  12. streamlit/components/v2/component_definition_resolver.py +143 -0
  13. streamlit/components/v2/component_file_watcher.py +403 -0
  14. streamlit/components/v2/component_manager.py +431 -0
  15. streamlit/components/v2/component_manifest_handler.py +122 -0
  16. streamlit/components/v2/component_path_utils.py +245 -0
  17. streamlit/components/v2/component_registry.py +409 -0
  18. streamlit/components/v2/get_bidi_component_manager.py +51 -0
  19. streamlit/components/v2/manifest_scanner.py +615 -0
  20. streamlit/components/v2/presentation.py +198 -0
  21. streamlit/components/v2/types.py +324 -0
  22. streamlit/config.py +741 -32
  23. streamlit/config_option.py +4 -1
  24. streamlit/config_util.py +650 -1
  25. streamlit/connections/base_connection.py +4 -2
  26. streamlit/dataframe_util.py +18 -10
  27. streamlit/delta_generator.py +8 -7
  28. streamlit/delta_generator_singletons.py +3 -1
  29. streamlit/deprecation_util.py +17 -6
  30. streamlit/elements/arrow.py +90 -42
  31. streamlit/elements/deck_gl_json_chart.py +98 -39
  32. streamlit/elements/dialog_decorator.py +2 -1
  33. streamlit/elements/exception.py +3 -1
  34. streamlit/elements/form.py +6 -6
  35. streamlit/elements/graphviz_chart.py +24 -9
  36. streamlit/elements/heading.py +3 -5
  37. streamlit/elements/iframe.py +0 -2
  38. streamlit/elements/image.py +12 -13
  39. streamlit/elements/layouts.py +89 -22
  40. streamlit/elements/lib/built_in_chart_utils.py +95 -31
  41. streamlit/elements/lib/color_util.py +8 -18
  42. streamlit/elements/lib/column_config_utils.py +9 -8
  43. streamlit/elements/lib/column_types.py +595 -148
  44. streamlit/elements/lib/dialog.py +3 -2
  45. streamlit/elements/lib/image_utils.py +3 -5
  46. streamlit/elements/lib/layout_utils.py +50 -13
  47. streamlit/elements/lib/mutable_status_container.py +2 -2
  48. streamlit/elements/lib/options_selector_utils.py +2 -2
  49. streamlit/elements/lib/pandas_styler_utils.py +30 -14
  50. streamlit/elements/lib/utils.py +21 -9
  51. streamlit/elements/map.py +81 -40
  52. streamlit/elements/media.py +7 -7
  53. streamlit/elements/metric.py +11 -35
  54. streamlit/elements/pdf.py +2 -4
  55. streamlit/elements/plotly_chart.py +142 -26
  56. streamlit/elements/progress.py +2 -4
  57. streamlit/elements/pyplot.py +6 -6
  58. streamlit/elements/space.py +113 -0
  59. streamlit/elements/vega_charts.py +400 -143
  60. streamlit/elements/widgets/audio_input.py +52 -4
  61. streamlit/elements/widgets/button.py +29 -29
  62. streamlit/elements/widgets/button_group.py +33 -6
  63. streamlit/elements/widgets/camera_input.py +3 -4
  64. streamlit/elements/widgets/chat.py +7 -0
  65. streamlit/elements/widgets/checkbox.py +1 -0
  66. streamlit/elements/widgets/color_picker.py +1 -0
  67. streamlit/elements/widgets/data_editor.py +34 -29
  68. streamlit/elements/widgets/file_uploader.py +6 -10
  69. streamlit/elements/widgets/multiselect.py +14 -3
  70. streamlit/elements/widgets/number_input.py +5 -4
  71. streamlit/elements/widgets/radio.py +10 -2
  72. streamlit/elements/widgets/select_slider.py +8 -4
  73. streamlit/elements/widgets/selectbox.py +9 -2
  74. streamlit/elements/widgets/slider.py +38 -41
  75. streamlit/elements/widgets/text_widgets.py +6 -0
  76. streamlit/elements/widgets/time_widgets.py +15 -12
  77. streamlit/elements/write.py +28 -23
  78. streamlit/emojis.py +1 -1
  79. streamlit/errors.py +115 -0
  80. streamlit/git_util.py +65 -43
  81. streamlit/hello/hello.py +8 -0
  82. streamlit/hello/utils.py +2 -1
  83. streamlit/material_icon_names.py +1 -1
  84. streamlit/navigation/page.py +4 -1
  85. streamlit/proto/ArrowData_pb2.py +27 -0
  86. streamlit/proto/ArrowData_pb2.pyi +46 -0
  87. streamlit/proto/Arrow_pb2.py +10 -8
  88. streamlit/proto/Arrow_pb2.pyi +31 -2
  89. streamlit/proto/AudioInput_pb2.py +2 -2
  90. streamlit/proto/AudioInput_pb2.pyi +6 -2
  91. streamlit/proto/BidiComponent_pb2.py +34 -0
  92. streamlit/proto/BidiComponent_pb2.pyi +153 -0
  93. streamlit/proto/Block_pb2.py +11 -11
  94. streamlit/proto/Block_pb2.pyi +9 -1
  95. streamlit/proto/DeckGlJsonChart_pb2.py +10 -4
  96. streamlit/proto/DeckGlJsonChart_pb2.pyi +9 -3
  97. streamlit/proto/Element_pb2.py +5 -3
  98. streamlit/proto/Element_pb2.pyi +14 -4
  99. streamlit/proto/HeightConfig_pb2.py +2 -2
  100. streamlit/proto/HeightConfig_pb2.pyi +6 -3
  101. streamlit/proto/NewSession_pb2.py +18 -16
  102. streamlit/proto/NewSession_pb2.pyi +158 -6
  103. streamlit/proto/PlotlyChart_pb2.py +8 -6
  104. streamlit/proto/PlotlyChart_pb2.pyi +3 -1
  105. streamlit/proto/Space_pb2.py +27 -0
  106. streamlit/proto/Space_pb2.pyi +42 -0
  107. streamlit/proto/WidgetStates_pb2.py +2 -2
  108. streamlit/proto/WidgetStates_pb2.pyi +13 -3
  109. streamlit/proto/WidthConfig_pb2.py +2 -2
  110. streamlit/proto/WidthConfig_pb2.pyi +6 -3
  111. streamlit/runtime/app_session.py +45 -6
  112. streamlit/runtime/caching/cache_data_api.py +4 -4
  113. streamlit/runtime/caching/cache_errors.py +4 -1
  114. streamlit/runtime/caching/cache_resource_api.py +3 -2
  115. streamlit/runtime/caching/cache_utils.py +2 -1
  116. streamlit/runtime/caching/cached_message_replay.py +3 -3
  117. streamlit/runtime/caching/hashing.py +3 -4
  118. streamlit/runtime/caching/legacy_cache_api.py +2 -1
  119. streamlit/runtime/connection_factory.py +1 -3
  120. streamlit/runtime/forward_msg_queue.py +4 -1
  121. streamlit/runtime/fragment.py +2 -1
  122. streamlit/runtime/memory_media_file_storage.py +1 -1
  123. streamlit/runtime/metrics_util.py +6 -2
  124. streamlit/runtime/runtime.py +14 -0
  125. streamlit/runtime/scriptrunner/exec_code.py +2 -1
  126. streamlit/runtime/scriptrunner/script_runner.py +2 -2
  127. streamlit/runtime/scriptrunner_utils/script_run_context.py +3 -6
  128. streamlit/runtime/secrets.py +2 -4
  129. streamlit/runtime/session_manager.py +3 -1
  130. streamlit/runtime/state/common.py +30 -5
  131. streamlit/runtime/state/presentation.py +85 -0
  132. streamlit/runtime/state/safe_session_state.py +2 -2
  133. streamlit/runtime/state/session_state.py +220 -16
  134. streamlit/runtime/state/widgets.py +19 -3
  135. streamlit/runtime/theme_util.py +148 -0
  136. streamlit/runtime/websocket_session_manager.py +3 -1
  137. streamlit/source_util.py +2 -2
  138. streamlit/static/index.html +2 -2
  139. streamlit/static/manifest.json +244 -227
  140. streamlit/static/static/css/{index.C8X8rNzw.css → index.BpABIXK9.css} +1 -1
  141. streamlit/static/static/css/index.DgR7E2CV.css +1 -0
  142. streamlit/static/static/js/{ErrorOutline.esm.DcGrhbBP.js → ErrorOutline.esm.YoJdlW1p.js} +1 -1
  143. streamlit/static/static/js/{FileDownload.esm.DgBvV6Pq.js → FileDownload.esm.Ddx8VEYy.js} +1 -1
  144. streamlit/static/static/js/{FileHelper.M6AAaeuA.js → FileHelper.90EtOmj9.js} +1 -1
  145. streamlit/static/static/js/{FormClearHelper.DHh1GFzm.js → FormClearHelper.BB1Km6eP.js} +1 -1
  146. streamlit/static/static/js/InputInstructions.jhH15PqV.js +1 -0
  147. streamlit/static/static/js/{Particles.DDVT-6Qc.js → Particles.DUsputn1.js} +1 -1
  148. streamlit/static/static/js/{ProgressBar.BEY0cXXV.js → ProgressBar.DLY8H6nE.js} +2 -2
  149. streamlit/static/static/js/Toolbar.D8nHCkuz.js +1 -0
  150. streamlit/static/static/js/{base-input.CK3UVGp1.js → base-input.CJGiNqed.js} +3 -3
  151. streamlit/static/static/js/{checkbox.D8W881TL.js → checkbox.Cpdd482O.js} +1 -1
  152. streamlit/static/static/js/{createSuper.B6W-Dh9S.js → createSuper.CuQIogbW.js} +1 -1
  153. streamlit/static/static/js/data-grid-overlay-editor.2Ufgxc6y.js +1 -0
  154. streamlit/static/static/js/{downloader.DiKpuU_S.js → downloader.CN0K7xlu.js} +1 -1
  155. streamlit/static/static/js/{es6.B8zRNPZ-.js → es6.BJcsVXQ0.js} +2 -2
  156. streamlit/static/static/js/{iframeResizer.contentWindow.DIewJmmh.js → iframeResizer.contentWindow.XzUvQqcZ.js} +1 -1
  157. streamlit/static/static/js/index.B1ZQh4P1.js +1 -0
  158. streamlit/static/static/js/index.BKstZk0M.js +27 -0
  159. streamlit/static/static/js/{index.Bte_9Lyq.js → index.BMcFsUee.js} +1 -1
  160. streamlit/static/static/js/{index.qhs54UAB.js → index.BR-IdcTb.js} +1 -1
  161. streamlit/static/static/js/{index.CejBxbg1.js → index.B_dWA3vd.js} +1 -1
  162. streamlit/static/static/js/{index.D5naqx-J.js → index.BgnZEMVh.js} +1 -1
  163. streamlit/static/static/js/{index.C7fRKRs4.js → index.BohqXifI.js} +1 -1
  164. streamlit/static/static/js/{index.cnnXF7xQ.js → index.Br5nxKNj.js} +1 -1
  165. streamlit/static/static/js/index.BrIKVbNc.js +3 -0
  166. streamlit/static/static/js/index.BtWUPzle.js +1 -0
  167. streamlit/static/static/js/index.C0RLraek.js +1 -0
  168. streamlit/static/static/js/{index.CP5TD2z1.js → index.CAIjskgG.js} +1 -1
  169. streamlit/static/static/js/{index.CD8HuT3N.js → index.CAj-7vWz.js} +135 -162
  170. streamlit/static/static/js/{index.DtYN2x4k.js → index.CMtEit2O.js} +1 -1
  171. streamlit/static/static/js/index.CkRlykEE.js +12 -0
  172. streamlit/static/static/js/{index.Ts_0SdB9.js → index.CmN3FXfI.js} +2 -2
  173. streamlit/static/static/js/{index.BnEpvLEz.js → index.CwbFI1_-.js} +1 -1
  174. streamlit/static/static/js/{index.CcJf6BCU.js → index.CxIUUfab.js} +27 -27
  175. streamlit/static/static/js/index.D2KPNy7e.js +1 -0
  176. streamlit/static/static/js/{index.Ch7MBCx0.js → index.D3GPA5k4.js} +47 -47
  177. streamlit/static/static/js/{index.ho6NIXGl.js → index.DGAh7DMq.js} +1 -1
  178. streamlit/static/static/js/index.DKb_NvmG.js +197 -0
  179. streamlit/static/static/js/{index.CvYYtxD_.js → index.DMqgUYKq.js} +1 -1
  180. streamlit/static/static/js/{index.zecpGxtj.js → index.DOFlg3dS.js} +1 -1
  181. streamlit/static/static/js/{index.B9mjBcgE.js → index.DPUXkcQL.js} +1 -1
  182. streamlit/static/static/js/index.DX1xY89g.js +1 -0
  183. streamlit/static/static/js/index.DYATBCsq.js +2 -0
  184. streamlit/static/static/js/{index.D2-atlaQ.js → index.DaSmGJ76.js} +3 -3
  185. streamlit/static/static/js/index.Dd7bMeLP.js +1 -0
  186. streamlit/static/static/js/{index.4eF4NxG2.js → index.DjmmgI5U.js} +1 -1
  187. streamlit/static/static/js/index.Dq56CyM2.js +1 -0
  188. streamlit/static/static/js/index.DuiXaS5_.js +7 -0
  189. streamlit/static/static/js/index.DvFidMLe.js +2 -0
  190. streamlit/static/static/js/{index.452cqrrL.js → index.DwkhC5Pc.js} +1 -1
  191. streamlit/static/static/js/{index.Dk4C7X3i.js → index.Q-3sFn1v.js} +1 -1
  192. streamlit/static/static/js/{index.CjXWwH-y.js → index.QJ5QO9sJ.js} +1 -1
  193. streamlit/static/static/js/{index.B6U8LQo3.js → index.VwTaeety.js} +1 -1
  194. streamlit/static/static/js/index.YOqQbeX8.js +1 -0
  195. streamlit/static/static/js/{input.nzVJphXi.js → input.D4MN_FzN.js} +1 -1
  196. streamlit/static/static/js/{memory.CjCgTQz3.js → memory.DrZjtdGT.js} +1 -1
  197. streamlit/static/static/js/{number-overlay-editor.DaRFzZEO.js → number-overlay-editor.DRwAw1In.js} +1 -1
  198. streamlit/static/static/js/{possibleConstructorReturn.DgiPnZ9N.js → possibleConstructorReturn.exeeJQEP.js} +1 -1
  199. streamlit/static/static/js/record.B-tDciZb.js +1 -0
  200. streamlit/static/static/js/{sandbox.mithfq7Z.js → sandbox.ClO3IuUr.js} +1 -1
  201. streamlit/static/static/js/{timepicker.Dbl5KFh6.js → timepicker.DAhu-vcF.js} +4 -4
  202. streamlit/static/static/js/{toConsumableArray.D-Dx88BQ.js → toConsumableArray.DNbljYEC.js} +1 -1
  203. streamlit/static/static/js/{uniqueId.Bh26R_3S.js → uniqueId.oG4Gvj1v.js} +1 -1
  204. streamlit/static/static/js/{useBasicWidgetState.DeK-QJpD.js → useBasicWidgetState.D6sOH6oI.js} +1 -1
  205. streamlit/static/static/js/{useTextInputAutoExpand.4iAdLWD-.js → useTextInputAutoExpand.4u3_GcuN.js} +2 -2
  206. streamlit/static/static/js/{useUpdateUiValue.CmT7_nJN.js → useUpdateUiValue.F2R3eTeR.js} +1 -1
  207. streamlit/static/static/js/wavesurfer.esm.vI8Eid4k.js +73 -0
  208. streamlit/static/static/js/withFullScreenWrapper.zothJIsI.js +1 -0
  209. streamlit/static/static/media/MaterialSymbols-Rounded.C7IFxh57.woff2 +0 -0
  210. streamlit/string_util.py +56 -1
  211. streamlit/testing/v1/app_test.py +2 -2
  212. streamlit/testing/v1/element_tree.py +23 -9
  213. streamlit/testing/v1/util.py +2 -2
  214. streamlit/type_util.py +3 -4
  215. streamlit/url_util.py +1 -3
  216. streamlit/user_info.py +1 -2
  217. streamlit/util.py +3 -1
  218. streamlit/watcher/event_based_path_watcher.py +23 -12
  219. streamlit/watcher/local_sources_watcher.py +11 -1
  220. streamlit/watcher/path_watcher.py +9 -6
  221. streamlit/watcher/polling_path_watcher.py +4 -1
  222. streamlit/watcher/util.py +2 -2
  223. streamlit/web/bootstrap.py +0 -31
  224. streamlit/web/cli.py +51 -22
  225. streamlit/web/server/bidi_component_request_handler.py +193 -0
  226. streamlit/web/server/component_file_utils.py +97 -0
  227. streamlit/web/server/component_request_handler.py +8 -21
  228. streamlit/web/server/oidc_mixin.py +3 -1
  229. streamlit/web/server/routes.py +18 -5
  230. streamlit/web/server/server.py +10 -0
  231. streamlit/web/server/server_util.py +3 -1
  232. streamlit/web/server/upload_file_request_handler.py +3 -1
  233. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/METADATA +4 -5
  234. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/RECORD +238 -209
  235. streamlit/static/static/css/index.COe1010n.css +0 -1
  236. streamlit/static/static/js/Hooks.DGu1od_L.js +0 -1
  237. streamlit/static/static/js/InputInstructions.z6sVgyYt.js +0 -1
  238. streamlit/static/static/js/Toolbar.DSnK1fUh.js +0 -1
  239. streamlit/static/static/js/data-grid-overlay-editor.DRTHOydk.js +0 -1
  240. streamlit/static/static/js/index.BXYmrqnf.js +0 -1
  241. streamlit/static/static/js/index.B_8AnktO.js +0 -1
  242. streamlit/static/static/js/index.Bl7zGQSh.js +0 -7
  243. streamlit/static/static/js/index.BnJIOYn9.js +0 -73
  244. streamlit/static/static/js/index.C1HcTl5K.js +0 -1
  245. streamlit/static/static/js/index.C7lSmSOP.js +0 -1
  246. streamlit/static/static/js/index.C_tmcx4B.js +0 -1
  247. streamlit/static/static/js/index.D3K5nOu9.js +0 -197
  248. streamlit/static/static/js/index.DkKT3LUI.js +0 -1
  249. streamlit/static/static/js/index.MTPPBDHk.js +0 -2
  250. streamlit/static/static/js/index.pqW9AMJD.js +0 -3
  251. streamlit/static/static/js/index.urHgTgMQ.js +0 -12
  252. streamlit/static/static/js/index.wzkv_11M.js +0 -1
  253. streamlit/static/static/js/index.yF5AncHY.js +0 -1
  254. streamlit/static/static/js/withFullScreenWrapper.DLp1ENGm.js +0 -1
  255. streamlit/static/static/media/MaterialSymbols-Rounded.CBxVaFdk.woff2 +0 -0
  256. {streamlit-1.49.1.data → streamlit-1.51.0.data}/scripts/streamlit.cmd +0 -0
  257. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/WHEEL +0 -0
  258. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/entry_points.txt +0 -0
  259. {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)