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,29 @@
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 Final
18
+
19
+ INTERNAL_COMPONENT_NAME: Final[str] = "bidi_component"
20
+
21
+ # Shared constant that delimits the base widget id from the event suffix.
22
+ # This value **must** stay in sync with its TypeScript counterpart defined in
23
+ # `frontend/lib/src/components/widgets/BidiComponent/constants.ts`.
24
+ EVENT_DELIM: Final[str] = "__"
25
+
26
+ # Shared constant that is used to identify ArrowReference objects in the data structure.
27
+ # This value **must** stay in sync with its TypeScript counterpart defined in
28
+ # `frontend/lib/src/components/widgets/BidiComponent/constants.ts`.
29
+ ARROW_REF_KEY: Final[str] = "__streamlit_arrow_ref__"
@@ -0,0 +1,386 @@
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
+ import json
18
+ from collections.abc import Mapping
19
+ from typing import TYPE_CHECKING, Any, cast
20
+
21
+ from streamlit.components.v2.bidi_component.constants import (
22
+ EVENT_DELIM,
23
+ INTERNAL_COMPONENT_NAME,
24
+ )
25
+ from streamlit.components.v2.bidi_component.serialization import (
26
+ BidiComponentSerde,
27
+ deserialize_trigger_list,
28
+ serialize_mixed_data,
29
+ )
30
+ from streamlit.components.v2.bidi_component.state import (
31
+ BidiComponentResult,
32
+ unwrap_component_state,
33
+ )
34
+ from streamlit.components.v2.presentation import make_bidi_component_presenter
35
+ from streamlit.dataframe_util import (
36
+ DataFormat,
37
+ convert_anything_to_arrow_bytes,
38
+ determine_data_format,
39
+ )
40
+ from streamlit.elements.lib.form_utils import current_form_id
41
+ from streamlit.elements.lib.layout_utils import (
42
+ Height,
43
+ LayoutConfig,
44
+ Width,
45
+ validate_width,
46
+ )
47
+ from streamlit.elements.lib.policies import check_cache_replay_rules
48
+ from streamlit.elements.lib.utils import compute_and_register_element_id, to_key
49
+ from streamlit.errors import (
50
+ BidiComponentInvalidCallbackNameError,
51
+ BidiComponentInvalidDefaultKeyError,
52
+ BidiComponentInvalidIdError,
53
+ BidiComponentMissingContentError,
54
+ BidiComponentUnserializableDataError,
55
+ )
56
+ from streamlit.proto.ArrowData_pb2 import ArrowData as ArrowDataProto
57
+ from streamlit.proto.BidiComponent_pb2 import BidiComponent as BidiComponentProto
58
+ from streamlit.runtime.metrics_util import gather_metrics
59
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
60
+ from streamlit.runtime.state import register_widget
61
+
62
+ if TYPE_CHECKING:
63
+ from streamlit.components.v2.types import (
64
+ BidiComponentData,
65
+ BidiComponentDefaults,
66
+ BidiComponentKey,
67
+ ComponentIsolateStyles,
68
+ )
69
+
70
+ if TYPE_CHECKING:
71
+ # Define DeltaGenerator for type checking the dg property
72
+ from streamlit.delta_generator import DeltaGenerator
73
+ from streamlit.runtime.state.common import WidgetCallback
74
+
75
+
76
+ def _make_trigger_id(base: str, event: str) -> str:
77
+ """Construct the per-event *trigger widget* identifier.
78
+
79
+ The widget ID for a trigger is derived from the *base* component ID plus
80
+ an *event* name. We join those two parts with :py:const:`EVENT_DELIM` and
81
+ perform a couple of validations so that downstream logic can always split
82
+ the identifier unambiguously.
83
+
84
+ Trigger widgets are marked as internal by prefixing with an internal key prefix,
85
+ so they won't be exposed in `st.session_state` to end users.
86
+
87
+ Parameters
88
+ ----------
89
+ base
90
+ The unique, framework-assigned ID of the component instance.
91
+ event
92
+ The event name as provided by either the frontend or the developer
93
+ (e.g., "click", "change").
94
+
95
+ Returns
96
+ -------
97
+ str
98
+ The composite widget ID in the form ``"$$STREAMLIT_INTERNAL_KEY_{base}__{event}"``
99
+ where ``__`` is the delimiter.
100
+
101
+ Raises
102
+ ------
103
+ StreamlitAPIException
104
+ If either `base` or `event` already contains the delimiter sequence.
105
+
106
+ """
107
+ from streamlit.runtime.state.session_state import STREAMLIT_INTERNAL_KEY_PREFIX
108
+
109
+ if EVENT_DELIM in base:
110
+ raise BidiComponentInvalidIdError("base", EVENT_DELIM)
111
+ if EVENT_DELIM in event:
112
+ raise BidiComponentInvalidIdError("event", EVENT_DELIM)
113
+
114
+ return f"{STREAMLIT_INTERNAL_KEY_PREFIX}_{base}{EVENT_DELIM}{event}"
115
+
116
+
117
+ class BidiComponentMixin:
118
+ """Mixin class for the bidi_component DeltaGenerator method."""
119
+
120
+ @gather_metrics("_bidi_component")
121
+ def _bidi_component(
122
+ self,
123
+ component_name: str,
124
+ key: BidiComponentKey = None,
125
+ isolate_styles: ComponentIsolateStyles = True,
126
+ data: BidiComponentData = None,
127
+ default: BidiComponentDefaults = None,
128
+ width: Width = "stretch",
129
+ height: Height = "content",
130
+ **kwargs: WidgetCallback | None,
131
+ ) -> BidiComponentResult:
132
+ """Add a bidirectional component instance to the app.
133
+
134
+ This method uses a component that has already been registered with the
135
+ application.
136
+
137
+ Parameters
138
+ ----------
139
+ component_name
140
+ The name of the registered component to use. The component's HTML,
141
+ CSS, and JavaScript will be loaded from the registry.
142
+ key
143
+ An optional string to use as the unique key for the component.
144
+ If this is omitted, a key will be generated based on the
145
+ component's execution sequence.
146
+ isolate_styles
147
+ Whether to sandbox the component's styles in a shadow root.
148
+ Defaults to True.
149
+ data
150
+ Data to pass to the component. This can be any JSON-serializable
151
+ data, or a pandas DataFrame, NumPy array, or other dataframe-like
152
+ object that can be serialized to Arrow.
153
+ default
154
+ A dictionary of default values for the component's state properties.
155
+ These defaults are applied only when the state key doesn't exist
156
+ in session state. Keys must correspond to valid state names (those
157
+ with `on_*_change` callbacks). Trigger values do not support
158
+ defaults.
159
+ width
160
+ The desired width of the component. This can be one of "stretch",
161
+ "content", or a number of pixels.
162
+ height
163
+ The desired height of the component. This can be one of "stretch",
164
+ "content", or a number of pixels.
165
+ **kwargs
166
+ Keyword arguments to pass to the component. Callbacks can be passed
167
+ here, with the naming convention `on_{event_name}_change`.
168
+
169
+ Returns
170
+ -------
171
+ BidiComponentResult
172
+ A dictionary-like object that holds the component's state and
173
+ trigger values.
174
+
175
+ Raises
176
+ ------
177
+ ValueError
178
+ If the component name is not found in the registry.
179
+ StreamlitAPIException
180
+ If the component does not have the required JavaScript or HTML
181
+ content, or if the provided data cannot be serialized.
182
+
183
+ """
184
+ check_cache_replay_rules()
185
+
186
+ key = to_key(key)
187
+ ctx = get_script_run_ctx()
188
+
189
+ if ctx is None:
190
+ # Create an empty state with the default value and return it
191
+ return BidiComponentResult({}, {})
192
+
193
+ # Get the component definition from the registry
194
+ from streamlit.runtime import Runtime
195
+
196
+ registry = Runtime.instance().bidi_component_registry
197
+ component_def = registry.get(component_name)
198
+
199
+ if component_def is None:
200
+ raise ValueError(f"Component '{component_name}' is not registered")
201
+
202
+ # Validate that the component has the required content
203
+ has_js = bool(component_def.js_content or component_def.js_url)
204
+ has_html = bool(component_def.html_content)
205
+
206
+ if not has_js and not has_html:
207
+ raise BidiComponentMissingContentError(component_name)
208
+
209
+ # Compute a unique ID for this component instance
210
+ computed_id = compute_and_register_element_id(
211
+ "bidi_component",
212
+ user_key=key,
213
+ component_name=component_name,
214
+ isolate_styles=isolate_styles,
215
+ width=width,
216
+ height=height,
217
+ dg=self.dg,
218
+ key_as_main_identity=True,
219
+ )
220
+
221
+ # ------------------------------------------------------------------
222
+ # 1. Parse user-supplied callbacks
223
+ # ------------------------------------------------------------------
224
+ # Event-specific callbacks follow the pattern ``on_<event>_change``.
225
+ # We deliberately *do not* support the legacy generic ``on_change``
226
+ # or ``on_<event>`` forms.
227
+ callbacks_by_event: dict[str, WidgetCallback] = {}
228
+ for kwarg_key, kwarg_value in list(kwargs.items()):
229
+ if not callable(kwarg_value):
230
+ continue
231
+
232
+ if kwarg_key.startswith("on_") and kwarg_key.endswith("_change"):
233
+ # Preferred pattern: on_<event>_change
234
+ event_name = kwarg_key[3:-7] # strip prefix + suffix
235
+ else:
236
+ # Not an event callback we recognize - skip.
237
+ continue
238
+
239
+ if not event_name or event_name == "_":
240
+ raise BidiComponentInvalidCallbackNameError(kwarg_key)
241
+
242
+ callbacks_by_event[event_name] = kwarg_value
243
+
244
+ # ------------------------------------------------------------------
245
+ # 2. Validate default keys against registered callbacks
246
+ # ------------------------------------------------------------------
247
+ if default is not None:
248
+ for state_key in default:
249
+ if state_key not in callbacks_by_event:
250
+ raise BidiComponentInvalidDefaultKeyError(
251
+ state_key, list(callbacks_by_event.keys())
252
+ )
253
+
254
+ # Set up the component proto
255
+ bidi_component_proto = BidiComponentProto()
256
+ bidi_component_proto.id = computed_id
257
+ bidi_component_proto.component_name = component_name
258
+ bidi_component_proto.isolate_styles = isolate_styles
259
+ bidi_component_proto.js_content = component_def.js_content or ""
260
+ bidi_component_proto.js_source_path = component_def.js_url or ""
261
+ bidi_component_proto.html_content = component_def.html_content or ""
262
+ bidi_component_proto.css_content = component_def.css_content or ""
263
+ bidi_component_proto.css_source_path = component_def.css_url or ""
264
+
265
+ validate_width(width, allow_content=True)
266
+ layout_config = LayoutConfig(width=width, height=height)
267
+
268
+ if data is not None:
269
+ try:
270
+ # 1. Raw byte payloads - forward as-is.
271
+ if isinstance(data, (bytes, bytearray)):
272
+ bidi_component_proto.bytes = bytes(data)
273
+
274
+ # 2. Mapping-like structures (e.g. plain dict) - check for mixed data.
275
+ elif isinstance(data, (Mapping, list, tuple)):
276
+ serialize_mixed_data(data, bidi_component_proto)
277
+
278
+ # 3. Dataframe-like structures - attempt Arrow serialization.
279
+ else:
280
+ data_format = determine_data_format(data)
281
+
282
+ if data_format != DataFormat.UNKNOWN:
283
+ arrow_bytes = convert_anything_to_arrow_bytes(data)
284
+
285
+ arrow_data_proto = ArrowDataProto()
286
+ arrow_data_proto.data = arrow_bytes
287
+
288
+ bidi_component_proto.arrow_data.CopyFrom(arrow_data_proto)
289
+ else:
290
+ # Fallback to JSON.
291
+ bidi_component_proto.json = json.dumps(data)
292
+ except Exception:
293
+ # As a last resort attempt JSON serialization so that we don't
294
+ # silently drop developer data.
295
+ try:
296
+ bidi_component_proto.json = json.dumps(data)
297
+ except Exception:
298
+ raise BidiComponentUnserializableDataError()
299
+ bidi_component_proto.form_id = current_form_id(self.dg)
300
+
301
+ # Instantiate the Serde for this component instance
302
+ serde = BidiComponentSerde(default=default)
303
+
304
+ # ------------------------------------------------------------------
305
+ # 3. Prepare IDs and register widgets
306
+ # ------------------------------------------------------------------
307
+
308
+ # Compute trigger aggregator id from the base id
309
+ def _make_trigger_aggregator_id(base: str) -> str:
310
+ return _make_trigger_id(base, "events")
311
+
312
+ aggregator_id = _make_trigger_aggregator_id(computed_id)
313
+
314
+ # With generalized runtime dispatch, we can attach per-key callbacks
315
+ # directly to the state widget by passing the callbacks mapping.
316
+ # We also register a presenter to shape the user-visible session_state.
317
+ # Allowed state keys are the ones that have callbacks registered.
318
+ allowed_state_keys = (
319
+ set(callbacks_by_event.keys()) if callbacks_by_event else None
320
+ )
321
+ presenter = make_bidi_component_presenter(
322
+ aggregator_id,
323
+ computed_id,
324
+ allowed_state_keys,
325
+ )
326
+
327
+ component_state = register_widget(
328
+ bidi_component_proto.id,
329
+ deserializer=serde.deserialize,
330
+ serializer=serde.serialize,
331
+ ctx=ctx,
332
+ callbacks=callbacks_by_event if callbacks_by_event else None,
333
+ value_type="json_value",
334
+ presenter=presenter,
335
+ )
336
+
337
+ # ------------------------------------------------------------------
338
+ # 4. Register a single *trigger aggregator* widget
339
+ # ------------------------------------------------------------------
340
+ trigger_vals: dict[str, Any] = {}
341
+
342
+ trig_state = register_widget(
343
+ aggregator_id,
344
+ deserializer=deserialize_trigger_list, # always returns list or None
345
+ serializer=lambda v: json.dumps(v), # send dict as JSON
346
+ ctx=ctx,
347
+ callbacks=callbacks_by_event if callbacks_by_event else None,
348
+ value_type="json_trigger_value",
349
+ )
350
+
351
+ # Surface per-event trigger values derived from the aggregator payload list.
352
+ payloads: list[object] = trig_state.value or []
353
+
354
+ event_to_value: dict[str, Any] = {}
355
+ for payload in payloads:
356
+ if isinstance(payload, dict):
357
+ ev = payload.get("event")
358
+ if isinstance(ev, str):
359
+ event_to_value[ev] = payload.get("value")
360
+
361
+ for evt_name in callbacks_by_event:
362
+ trigger_vals[evt_name] = event_to_value.get(evt_name)
363
+
364
+ # Note: We intentionally do not inspect SessionState for additional
365
+ # trigger widget IDs here because doing so can raise KeyErrors when
366
+ # widgets are freshly registered but their values haven't been
367
+ # populated yet. Only the triggers explicitly registered above are
368
+ # included in the result object.
369
+
370
+ # ------------------------------------------------------------------
371
+ # 5. Enqueue proto and assemble the result object
372
+ # ------------------------------------------------------------------
373
+ self.dg._enqueue(
374
+ INTERNAL_COMPONENT_NAME,
375
+ bidi_component_proto,
376
+ layout_config=layout_config,
377
+ )
378
+
379
+ state_vals = unwrap_component_state(component_state.value)
380
+
381
+ return BidiComponentResult(state_vals, trigger_vals)
382
+
383
+ @property
384
+ def dg(self) -> DeltaGenerator:
385
+ """Get our DeltaGenerator."""
386
+ return cast("DeltaGenerator", self)
@@ -0,0 +1,265 @@
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
+ import json
18
+ from dataclasses import dataclass
19
+ from typing import TYPE_CHECKING, Any, cast
20
+
21
+ from streamlit.components.v2.bidi_component.constants import ARROW_REF_KEY
22
+ from streamlit.dataframe_util import convert_anything_to_arrow_bytes, is_dataframe_like
23
+ from streamlit.logger import get_logger
24
+ from streamlit.proto.BidiComponent_pb2 import BidiComponent as BidiComponentProto
25
+ from streamlit.proto.BidiComponent_pb2 import MixedData as MixedDataProto
26
+ from streamlit.util import AttributeDictionary
27
+
28
+ if TYPE_CHECKING:
29
+ from streamlit.components.v2.bidi_component.state import BidiComponentState
30
+
31
+ _LOGGER = get_logger(__name__)
32
+
33
+
34
+ def _extract_dataframes_from_dict(
35
+ data: dict[str, Any], arrow_blobs: dict[str, bytes] | None = None
36
+ ) -> dict[str, Any]:
37
+ """Extract dataframe-like objects from a dictionary and replace them with
38
+ placeholders.
39
+
40
+ This function traverses the first level of a dictionary, detects any
41
+ dataframe-like objects, stores their Arrow bytes representation in the
42
+ `arrow_blobs` dictionary, and replaces them with JSON-serializable
43
+ placeholder objects.
44
+
45
+ Parameters
46
+ ----------
47
+ data
48
+ The dictionary to process. Only the first level is checked for
49
+ dataframe-like objects.
50
+ arrow_blobs
51
+ The dictionary to store the extracted Arrow bytes in, keyed by a unique
52
+ reference ID.
53
+
54
+ Returns
55
+ -------
56
+ dict[str, Any]
57
+ A new dictionary with dataframe-like objects replaced by placeholders.
58
+ """
59
+ import uuid
60
+
61
+ if arrow_blobs is None:
62
+ arrow_blobs = {}
63
+
64
+ processed_data = {}
65
+
66
+ for key, value in data.items():
67
+ if is_dataframe_like(value):
68
+ # This is a dataframe-like object, serialize it to Arrow
69
+ try:
70
+ arrow_bytes = convert_anything_to_arrow_bytes(value)
71
+ ref_id = str(uuid.uuid4())
72
+ arrow_blobs[ref_id] = arrow_bytes
73
+ processed_data[key] = {ARROW_REF_KEY: ref_id}
74
+ except Exception:
75
+ # If Arrow serialization fails, keep the original value for JSON serialization
76
+ processed_data[key] = value
77
+ else:
78
+ # Not dataframe-like, keep as-is
79
+ processed_data[key] = value
80
+
81
+ return processed_data
82
+
83
+
84
+ def serialize_mixed_data(data: Any, bidi_component_proto: BidiComponentProto) -> None:
85
+ """Serialize mixed data with automatic dataframe detection into a protobuf message.
86
+
87
+ This function detects dataframe-like objects in the first level of a dictionary,
88
+ extracts them into separate Arrow blobs, and populates a `MixedDataProto`
89
+ protobuf message for efficient serialization.
90
+
91
+ Parameters
92
+ ----------
93
+ data
94
+ The data structure to serialize. If it is a dictionary, its first
95
+ level will be scanned for dataframe-like objects.
96
+ bidi_component_proto
97
+ The protobuf message to populate with the serialized data.
98
+
99
+ """
100
+ arrow_blobs: dict[str, bytes] = {}
101
+
102
+ # Only process dictionaries for automatic dataframe detection
103
+ if isinstance(data, dict):
104
+ processed_data = _extract_dataframes_from_dict(data, arrow_blobs)
105
+ else:
106
+ # For non-dict data (lists, tuples, etc.), pass through as-is
107
+ # We don't automatically detect dataframes in these structures
108
+ processed_data = data
109
+
110
+ if arrow_blobs:
111
+ # We have dataframes, use mixed data serialization
112
+ mixed_proto = MixedDataProto()
113
+ try:
114
+ mixed_proto.json = json.dumps(processed_data)
115
+ except TypeError:
116
+ # If JSON serialization fails (e.g., due to undetected dataframes),
117
+ # fall back to string representation
118
+ mixed_proto.json = json.dumps(str(processed_data))
119
+
120
+ # Add Arrow blobs to the protobuf
121
+ for ref_id, arrow_bytes in arrow_blobs.items():
122
+ mixed_proto.arrow_blobs[ref_id].data = arrow_bytes
123
+
124
+ bidi_component_proto.mixed.CopyFrom(mixed_proto)
125
+ else:
126
+ # No dataframes found, use regular JSON serialization
127
+ try:
128
+ bidi_component_proto.json = json.dumps(processed_data)
129
+ except TypeError:
130
+ # If JSON serialization fails (e.g., due to dataframes in lists/tuples),
131
+ # fall back to string representation
132
+ bidi_component_proto.json = json.dumps(str(processed_data))
133
+
134
+
135
+ def handle_deserialize(s: str | None) -> Any:
136
+ """Deserialize a JSON string, returning the string itself if it's not valid JSON.
137
+
138
+ Parameters
139
+ ----------
140
+ s
141
+ The string to deserialize.
142
+
143
+ Returns
144
+ -------
145
+ Any
146
+ The deserialized JSON object, or the original string if parsing fails.
147
+ Returns `None` if the input is `None`.
148
+
149
+ """
150
+ if s is None:
151
+ return None
152
+ try:
153
+ return json.loads(s)
154
+ except json.JSONDecodeError:
155
+ return s
156
+
157
+
158
+ def deserialize_trigger_list(s: str | None) -> list[Any] | None:
159
+ """Deserialize trigger aggregator payloads as a list.
160
+
161
+ For bidirectional components, the frontend always sends a JSON array of payload
162
+ objects. This deserializer normalizes older or singular payloads into a list
163
+ while preserving ``None`` for cleared values.
164
+
165
+ Parameters
166
+ ----------
167
+ s
168
+ The JSON string to deserialize, hopefully representing a list of payloads.
169
+
170
+ Returns
171
+ -------
172
+ list[Any] or None
173
+ A list of payloads, or `None` if the input was `None`.
174
+
175
+ """
176
+ value = handle_deserialize(s)
177
+ if value is None:
178
+ return None
179
+ if isinstance(value, list):
180
+ return value
181
+ return [value]
182
+
183
+
184
+ @dataclass
185
+ class BidiComponentSerde:
186
+ """Serialization and deserialization logic for a bidirectional component.
187
+
188
+ This class handles the conversion of component state between the frontend
189
+ (JSON strings) and the backend (Python objects).
190
+
191
+ The canonical shape is a flat mapping of state keys to values.
192
+
193
+ Parameters
194
+ ----------
195
+ default
196
+ A dictionary of default values to be applied to the state when
197
+ deserializing, if the corresponding keys are not already present.
198
+
199
+ """
200
+
201
+ default: dict[str, Any] | None = None
202
+
203
+ def deserialize(self, ui_value: str | dict[str, Any] | None) -> BidiComponentState:
204
+ """Deserialize the component's state from a frontend value.
205
+
206
+ Parameters
207
+ ----------
208
+ ui_value
209
+ The value received from the frontend, which can be a JSON string,
210
+ a dictionary, or `None`.
211
+
212
+ Returns
213
+ -------
214
+ BidiComponentState
215
+ The deserialized state as a flat mapping.
216
+
217
+ """
218
+ # Normalize the incoming JSON payload into a dict. Any failure to decode
219
+ # (or an unexpected non-mapping structure) results in an empty mapping
220
+ # so that the returned type adheres to :class:`BidiComponentState`.
221
+
222
+ deserialized_value: dict[str, Any]
223
+
224
+ if isinstance(ui_value, dict):
225
+ deserialized_value = ui_value
226
+ elif isinstance(ui_value, str):
227
+ try:
228
+ parsed = json.loads(ui_value)
229
+ deserialized_value = parsed if isinstance(parsed, dict) else {}
230
+ except (json.JSONDecodeError, TypeError) as e:
231
+ _LOGGER.warning(
232
+ "Failed to deserialize component state from frontend: %s",
233
+ e,
234
+ exc_info=e,
235
+ )
236
+ deserialized_value = {}
237
+ else:
238
+ deserialized_value = {}
239
+
240
+ # Apply default values for keys that don't exist in the current state
241
+ if self.default is not None:
242
+ for default_key, default_value in self.default.items():
243
+ if default_key not in deserialized_value:
244
+ deserialized_value[default_key] = default_value
245
+
246
+ state: BidiComponentState = cast(
247
+ "BidiComponentState", AttributeDictionary(deserialized_value)
248
+ )
249
+ return state
250
+
251
+ def serialize(self, value: Any) -> str:
252
+ """Serialize the component's state into a JSON string for the frontend.
253
+
254
+ Parameters
255
+ ----------
256
+ value
257
+ The component state to serialize.
258
+
259
+ Returns
260
+ -------
261
+ str
262
+ A JSON string representation of the value.
263
+
264
+ """
265
+ return json.dumps(value)