streamlit 1.50.0__py3-none-any.whl → 1.52.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 (406) hide show
  1. streamlit/__init__.py +5 -1
  2. streamlit/commands/execution_control.py +89 -14
  3. streamlit/commands/navigation.py +4 -6
  4. streamlit/commands/page_config.py +4 -6
  5. streamlit/components/v1/component_arrow.py +7 -7
  6. streamlit/components/v2/__init__.py +514 -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 +534 -0
  10. streamlit/components/v2/bidi_component/serialization.py +272 -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 +439 -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 +426 -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 +456 -53
  23. streamlit/config_option.py +4 -1
  24. streamlit/config_util.py +650 -1
  25. streamlit/connections/snowflake_connection.py +1 -1
  26. streamlit/connections/snowpark_connection.py +1 -1
  27. streamlit/dataframe_util.py +33 -26
  28. streamlit/delta_generator.py +13 -4
  29. streamlit/delta_generator_singletons.py +11 -15
  30. streamlit/deprecation_util.py +17 -6
  31. streamlit/elements/alert.py +16 -0
  32. streamlit/elements/arrow.py +68 -10
  33. streamlit/elements/bokeh_chart.py +10 -78
  34. streamlit/elements/code.py +2 -2
  35. streamlit/elements/deck_gl_json_chart.py +98 -40
  36. streamlit/elements/dialog_decorator.py +2 -1
  37. streamlit/elements/exception.py +4 -2
  38. streamlit/elements/form.py +27 -0
  39. streamlit/elements/graphviz_chart.py +1 -3
  40. streamlit/elements/heading.py +63 -10
  41. streamlit/elements/html.py +13 -2
  42. streamlit/elements/image.py +3 -5
  43. streamlit/elements/layouts.py +59 -33
  44. streamlit/elements/lib/built_in_chart_utils.py +50 -19
  45. streamlit/elements/lib/color_util.py +9 -19
  46. streamlit/elements/lib/column_config_utils.py +9 -12
  47. streamlit/elements/lib/column_types.py +40 -12
  48. streamlit/elements/lib/dialog.py +2 -2
  49. streamlit/elements/lib/image_utils.py +3 -5
  50. streamlit/elements/lib/layout_utils.py +100 -13
  51. streamlit/elements/lib/mutable_status_container.py +2 -2
  52. streamlit/elements/lib/options_selector_utils.py +2 -2
  53. streamlit/elements/lib/pandas_styler_utils.py +17 -9
  54. streamlit/elements/lib/shortcut_utils.py +152 -0
  55. streamlit/elements/lib/utils.py +4 -4
  56. streamlit/elements/map.py +80 -37
  57. streamlit/elements/markdown.py +50 -3
  58. streamlit/elements/media.py +5 -7
  59. streamlit/elements/metric.py +34 -6
  60. streamlit/elements/pdf.py +2 -4
  61. streamlit/elements/plotly_chart.py +197 -20
  62. streamlit/elements/progress.py +2 -4
  63. streamlit/elements/space.py +113 -0
  64. streamlit/elements/spinner.py +1 -1
  65. streamlit/elements/text.py +20 -3
  66. streamlit/elements/toast.py +2 -0
  67. streamlit/elements/vega_charts.py +356 -149
  68. streamlit/elements/widgets/audio_input.py +12 -11
  69. streamlit/elements/widgets/button.py +280 -43
  70. streamlit/elements/widgets/button_group.py +60 -9
  71. streamlit/elements/widgets/camera_input.py +3 -5
  72. streamlit/elements/widgets/chat.py +307 -43
  73. streamlit/elements/widgets/color_picker.py +8 -1
  74. streamlit/elements/widgets/data_editor.py +88 -44
  75. streamlit/elements/widgets/file_uploader.py +9 -11
  76. streamlit/elements/widgets/multiselect.py +4 -3
  77. streamlit/elements/widgets/number_input.py +4 -4
  78. streamlit/elements/widgets/radio.py +10 -3
  79. streamlit/elements/widgets/select_slider.py +8 -5
  80. streamlit/elements/widgets/selectbox.py +6 -3
  81. streamlit/elements/widgets/slider.py +38 -42
  82. streamlit/elements/widgets/text_widgets.py +2 -0
  83. streamlit/elements/widgets/time_widgets.py +587 -21
  84. streamlit/elements/write.py +27 -6
  85. streamlit/emojis.py +1 -1
  86. streamlit/errors.py +137 -0
  87. streamlit/git_util.py +1 -1
  88. streamlit/hello/hello.py +8 -0
  89. streamlit/hello/utils.py +2 -1
  90. streamlit/material_icon_names.py +1 -1
  91. streamlit/navigation/page.py +11 -1
  92. streamlit/net_util.py +2 -2
  93. streamlit/proto/Alert_pb2.pyi +3 -3
  94. streamlit/proto/AppPage_pb2.pyi +7 -1
  95. streamlit/proto/ArrowData_pb2.py +27 -0
  96. streamlit/proto/ArrowData_pb2.pyi +52 -0
  97. streamlit/proto/ArrowNamedDataSet_pb2.pyi +7 -1
  98. streamlit/proto/ArrowVegaLiteChart_pb2.pyi +7 -1
  99. streamlit/proto/Arrow_pb2.py +10 -10
  100. streamlit/proto/Arrow_pb2.pyi +19 -12
  101. streamlit/proto/AudioInput_pb2.pyi +7 -1
  102. streamlit/proto/Audio_pb2.pyi +7 -1
  103. streamlit/proto/AuthRedirect_pb2.pyi +7 -1
  104. streamlit/proto/AutoRerun_pb2.pyi +7 -1
  105. streamlit/proto/BackMsg_pb2.py +4 -2
  106. streamlit/proto/BackMsg_pb2.pyi +34 -4
  107. streamlit/proto/Balloons_pb2.pyi +7 -1
  108. streamlit/proto/BidiComponent_pb2.py +34 -0
  109. streamlit/proto/BidiComponent_pb2.pyi +159 -0
  110. streamlit/proto/Block_pb2.py +7 -7
  111. streamlit/proto/Block_pb2.pyi +39 -36
  112. streamlit/proto/BokehChart_pb2.pyi +7 -1
  113. streamlit/proto/ButtonGroup_pb2.pyi +9 -9
  114. streamlit/proto/Button_pb2.py +2 -2
  115. streamlit/proto/Button_pb2.pyi +11 -2
  116. streamlit/proto/CameraInput_pb2.pyi +7 -1
  117. streamlit/proto/ChatInput_pb2.py +6 -6
  118. streamlit/proto/ChatInput_pb2.pyi +18 -6
  119. streamlit/proto/Checkbox_pb2.pyi +3 -3
  120. streamlit/proto/ClientState_pb2.pyi +10 -4
  121. streamlit/proto/Code_pb2.pyi +7 -1
  122. streamlit/proto/ColorPicker_pb2.pyi +7 -1
  123. streamlit/proto/Common_pb2.py +3 -3
  124. streamlit/proto/Common_pb2.pyi +35 -23
  125. streamlit/proto/Components_pb2.pyi +19 -13
  126. streamlit/proto/DataFrame_pb2.pyi +55 -49
  127. streamlit/proto/DateInput_pb2.pyi +7 -1
  128. streamlit/proto/DateTimeInput_pb2.py +28 -0
  129. streamlit/proto/DateTimeInput_pb2.pyi +92 -0
  130. streamlit/proto/DeckGlJsonChart_pb2.py +10 -4
  131. streamlit/proto/DeckGlJsonChart_pb2.pyi +12 -6
  132. streamlit/proto/Delta_pb2.pyi +7 -1
  133. streamlit/proto/DocString_pb2.pyi +10 -4
  134. streamlit/proto/DownloadButton_pb2.py +2 -2
  135. streamlit/proto/DownloadButton_pb2.pyi +16 -2
  136. streamlit/proto/Element_pb2.py +7 -3
  137. streamlit/proto/Element_pb2.pyi +33 -5
  138. streamlit/proto/Empty_pb2.pyi +7 -1
  139. streamlit/proto/Exception_pb2.pyi +7 -1
  140. streamlit/proto/Favicon_pb2.pyi +7 -1
  141. streamlit/proto/FileUploader_pb2.pyi +7 -1
  142. streamlit/proto/ForwardMsg_pb2.py +12 -10
  143. streamlit/proto/ForwardMsg_pb2.pyi +42 -15
  144. streamlit/proto/GapSize_pb2.pyi +4 -4
  145. streamlit/proto/GitInfo_pb2.pyi +3 -3
  146. streamlit/proto/GraphVizChart_pb2.pyi +7 -1
  147. streamlit/proto/Heading_pb2.pyi +7 -1
  148. streamlit/proto/HeightConfig_pb2.py +2 -2
  149. streamlit/proto/HeightConfig_pb2.pyi +13 -4
  150. streamlit/proto/Html_pb2.py +2 -2
  151. streamlit/proto/Html_pb2.pyi +11 -2
  152. streamlit/proto/IFrame_pb2.pyi +7 -1
  153. streamlit/proto/Image_pb2.pyi +10 -4
  154. streamlit/proto/Json_pb2.pyi +7 -1
  155. streamlit/proto/LabelVisibilityMessage_pb2.pyi +3 -3
  156. streamlit/proto/LinkButton_pb2.py +2 -2
  157. streamlit/proto/LinkButton_pb2.pyi +15 -2
  158. streamlit/proto/Logo_pb2.pyi +7 -1
  159. streamlit/proto/Markdown_pb2.pyi +3 -3
  160. streamlit/proto/Metric_pb2.pyi +7 -7
  161. streamlit/proto/MetricsEvent_pb2.pyi +10 -4
  162. streamlit/proto/MultiSelect_pb2.pyi +7 -1
  163. streamlit/proto/NamedDataSet_pb2.pyi +7 -1
  164. streamlit/proto/Navigation_pb2.pyi +3 -3
  165. streamlit/proto/NewSession_pb2.py +18 -18
  166. streamlit/proto/NewSession_pb2.pyi +59 -40
  167. streamlit/proto/NumberInput_pb2.pyi +3 -3
  168. streamlit/proto/PageConfig_pb2.pyi +7 -7
  169. streamlit/proto/PageInfo_pb2.pyi +7 -1
  170. streamlit/proto/PageLink_pb2.py +2 -2
  171. streamlit/proto/PageLink_pb2.pyi +11 -2
  172. streamlit/proto/PageNotFound_pb2.pyi +7 -1
  173. streamlit/proto/PageProfile_pb2.pyi +13 -7
  174. streamlit/proto/PagesChanged_pb2.pyi +7 -1
  175. streamlit/proto/ParentMessage_pb2.pyi +7 -1
  176. streamlit/proto/PlotlyChart_pb2.py +8 -6
  177. streamlit/proto/PlotlyChart_pb2.pyi +9 -7
  178. streamlit/proto/Progress_pb2.pyi +7 -1
  179. streamlit/proto/Radio_pb2.pyi +7 -1
  180. streamlit/proto/RootContainer_pb2.pyi +1 -1
  181. streamlit/proto/Selectbox_pb2.pyi +7 -1
  182. streamlit/proto/SessionEvent_pb2.pyi +7 -1
  183. streamlit/proto/SessionStatus_pb2.pyi +7 -1
  184. streamlit/proto/Skeleton_pb2.pyi +3 -3
  185. streamlit/proto/Slider_pb2.pyi +5 -5
  186. streamlit/proto/Snow_pb2.pyi +7 -1
  187. streamlit/proto/Space_pb2.py +27 -0
  188. streamlit/proto/Space_pb2.pyi +48 -0
  189. streamlit/proto/Spinner_pb2.pyi +7 -1
  190. streamlit/proto/TextAlignmentConfig_pb2.py +29 -0
  191. streamlit/proto/TextAlignmentConfig_pb2.pyi +68 -0
  192. streamlit/proto/TextArea_pb2.pyi +7 -1
  193. streamlit/proto/TextInput_pb2.pyi +3 -3
  194. streamlit/proto/Text_pb2.pyi +7 -1
  195. streamlit/proto/TimeInput_pb2.pyi +7 -1
  196. streamlit/proto/Toast_pb2.pyi +7 -1
  197. streamlit/proto/VegaLiteChart_pb2.pyi +7 -1
  198. streamlit/proto/Video_pb2.pyi +6 -6
  199. streamlit/proto/WidgetStates_pb2.py +2 -2
  200. streamlit/proto/WidgetStates_pb2.pyi +23 -7
  201. streamlit/proto/WidthConfig_pb2.py +2 -2
  202. streamlit/proto/WidthConfig_pb2.pyi +13 -4
  203. streamlit/proto/openmetrics_data_model_pb2.pyi +52 -52
  204. streamlit/runtime/app_session.py +65 -2
  205. streamlit/runtime/caching/cache_data_api.py +5 -5
  206. streamlit/runtime/caching/cache_errors.py +4 -1
  207. streamlit/runtime/caching/cache_resource_api.py +5 -4
  208. streamlit/runtime/caching/cache_utils.py +3 -2
  209. streamlit/runtime/caching/cached_message_replay.py +3 -3
  210. streamlit/runtime/caching/hashing.py +4 -5
  211. streamlit/runtime/caching/legacy_cache_api.py +2 -1
  212. streamlit/runtime/connection_factory.py +1 -3
  213. streamlit/runtime/download_data_util.py +53 -0
  214. streamlit/runtime/forward_msg_queue.py +5 -1
  215. streamlit/runtime/fragment.py +2 -1
  216. streamlit/runtime/media_file_manager.py +178 -2
  217. streamlit/runtime/memory_media_file_storage.py +1 -1
  218. streamlit/runtime/metrics_util.py +91 -3
  219. streamlit/runtime/runtime.py +14 -0
  220. streamlit/runtime/scriptrunner/exec_code.py +2 -1
  221. streamlit/runtime/scriptrunner/script_runner.py +5 -3
  222. streamlit/runtime/scriptrunner_utils/script_run_context.py +3 -6
  223. streamlit/runtime/secrets.py +2 -4
  224. streamlit/runtime/session_manager.py +3 -1
  225. streamlit/runtime/state/common.py +30 -5
  226. streamlit/runtime/state/presentation.py +85 -0
  227. streamlit/runtime/state/query_params.py +80 -29
  228. streamlit/runtime/state/safe_session_state.py +2 -2
  229. streamlit/runtime/state/session_state.py +221 -17
  230. streamlit/runtime/state/widgets.py +19 -3
  231. streamlit/runtime/websocket_session_manager.py +3 -1
  232. streamlit/source_util.py +2 -2
  233. streamlit/static/index.html +2 -2
  234. streamlit/static/manifest.json +557 -239
  235. streamlit/static/static/css/{index.CIiu7Ygf.css → index.BpABIXK9.css} +1 -1
  236. streamlit/static/static/css/index.DgR7E2CV.css +1 -0
  237. streamlit/static/static/js/{ErrorOutline.esm.DUpR0_Ka.js → ErrorOutline.esm.ZJDbmVTx.js} +1 -1
  238. streamlit/static/static/js/{FileDownload.esm.CN4j9-1w.js → FileDownload.esm.Dx0vI3vH.js} +1 -1
  239. streamlit/static/static/js/{FileHelper.CaIUKG91.js → FileHelper.B7Ero7qQ.js} +3 -3
  240. streamlit/static/static/js/{FormClearHelper.DTcdrasw.js → FormClearHelper.CG2XN1_g.js} +1 -1
  241. streamlit/static/static/js/IFrameUtil.DefezniK.js +1 -0
  242. streamlit/static/static/js/InputInstructions.Cj5-1zf6.js +1 -0
  243. streamlit/static/static/js/Particles.BfWfv0Aw.js +1 -0
  244. streamlit/static/static/js/{ProgressBar.DetlP5aY.js → ProgressBar.CGQ8OgfO.js} +2 -2
  245. streamlit/static/static/js/StreamlitSyntaxHighlighter.DTKLpwhl.js +20 -0
  246. streamlit/static/static/js/{Toolbar.C77ar7rq.js → Toolbar.B2qFUmd9.js} +1 -1
  247. streamlit/static/static/js/_arrayIncludes.B19Iyn2B.js +1 -0
  248. streamlit/static/static/js/_baseIndexOf.BTknn6Gb.js +1 -0
  249. streamlit/static/static/js/{base-input.BQft14La.js → base-input.o9tL8MDP.js} +4 -4
  250. streamlit/static/static/js/{checkbox.yZOfXCeX.js → checkbox.0BeV1IBL.js} +1 -1
  251. streamlit/static/static/js/{createSuper.Dh9w1cs8.js → createSuper.RBO59fEm.js} +1 -1
  252. streamlit/static/static/js/data-grid-overlay-editor.CiTkUy0t.js +1 -0
  253. streamlit/static/static/js/{downloader.MeHtkq8r.js → downloader.DwNZg3Mw.js} +1 -1
  254. streamlit/static/static/js/embed.XT9xNd3F.js +195 -0
  255. streamlit/static/static/js/{es6.VpBPGCnM.js → es6.x9KsYQg-.js} +2 -2
  256. streamlit/static/static/js/{iframeResizer.contentWindow.yMw_ARIL.js → iframeResizer.contentWindow.ZVXpMPi0.js} +1 -1
  257. streamlit/static/static/js/index.5VPOamri.js +1 -0
  258. streamlit/static/static/js/index.8HslT92O.js +14 -0
  259. streamlit/static/static/js/index.AnXMIBz3.js +7 -0
  260. streamlit/static/static/js/index.B0yp3bM1.js +6 -0
  261. streamlit/static/static/js/index.B1fRb5wF.js +1 -0
  262. streamlit/static/static/js/index.B527JZdO.js +3 -0
  263. streamlit/static/static/js/index.BHgV-yW4.js +1 -0
  264. streamlit/static/static/js/index.BQr-XwGV.js +1 -0
  265. streamlit/static/static/js/index.BTtmaLDB.js +1 -0
  266. streamlit/static/static/js/index.BWB_91TA.js +1 -0
  267. streamlit/static/static/js/index.BfEKaEmw.js +1 -0
  268. streamlit/static/static/js/index.BfXjTO8b.js +1 -0
  269. streamlit/static/static/js/index.Bjy4NRu9.js +3 -0
  270. streamlit/static/static/js/index.Bu5JWpT_.js +1 -0
  271. streamlit/static/static/js/index.BuCx76ZV.js +1 -0
  272. streamlit/static/static/js/index.BxjzhVUb.js +2 -0
  273. streamlit/static/static/js/index.By55VdPY.js +1 -0
  274. streamlit/static/static/js/index.CF5MxTbK.js +1 -0
  275. streamlit/static/static/js/index.CLmq_z9K.js +1 -0
  276. streamlit/static/static/js/index.CNH4rdSz.js +1 -0
  277. streamlit/static/static/js/{index.B0H9IXUJ.js → index.CTgm_-jO.js} +10 -41
  278. streamlit/static/static/js/index.C_rK-Swb.js +188 -0
  279. streamlit/static/static/js/index.CjozwSzS.js +1 -0
  280. streamlit/static/static/js/{index.CH1tqnSs.js → index.CkGVt6-G.js} +1 -1
  281. streamlit/static/static/js/index.CuvXOyER.js +2 -0
  282. streamlit/static/static/js/{index.FFOzOWzC.js → index.CyUHWoCC.js} +2 -2
  283. streamlit/static/static/js/index.CyroQtI4.js +2 -0
  284. streamlit/static/static/js/index.D6HmkoDm.js +263 -0
  285. streamlit/static/static/js/index.DAqCNvsO.js +1 -0
  286. streamlit/static/static/js/index.DB_w_CZQ.js +1 -0
  287. streamlit/static/static/js/index.DBalctjj.js +2 -0
  288. streamlit/static/static/js/index.DK0RFJUG.js +11 -0
  289. streamlit/static/static/js/index.DMxc2XFp.js +151 -0
  290. streamlit/static/static/js/index.DO5utP74.js +2 -0
  291. streamlit/static/static/js/index.DS7lf09n.js +1 -0
  292. streamlit/static/static/js/index.DWexTVLY.js +1 -0
  293. streamlit/static/static/js/index.DXxnU5ej.js +1 -0
  294. streamlit/static/static/js/index.DcU3uDvB.js +2 -0
  295. streamlit/static/static/js/index.DlltaH7J.js +1 -0
  296. streamlit/static/static/js/index.DpNTZz82.js +27 -0
  297. streamlit/static/static/js/index.Dr9HIhQw.js +1 -0
  298. streamlit/static/static/js/index.DsgAU5lc.js +1 -0
  299. streamlit/static/static/js/{index.64ejlaaT.js → index.KfXqjDYy.js} +1 -1
  300. streamlit/static/static/js/index.PaidgjCs.js +1 -0
  301. streamlit/static/static/js/index.RJZuWCGA.js +1 -0
  302. streamlit/static/static/js/{index.Ctn27_AE.js → index.hbeqcRTn.js} +53 -122
  303. streamlit/static/static/js/index.q5hIQwAY.js +1 -0
  304. streamlit/static/static/js/index.rORSX6IW.js +1 -0
  305. streamlit/static/static/js/index.uSX757_v.js +1 -0
  306. streamlit/static/static/js/index.x_QRaLMd.js +1 -0
  307. streamlit/static/static/js/{input.s6pjQ49A.js → input.D5oh9-aB.js} +2 -2
  308. streamlit/static/static/js/main.q9oGOg0H.js +13 -0
  309. streamlit/static/static/js/{memory.Cuvsdfrl.js → memory.5kCSFUJS.js} +1 -1
  310. streamlit/static/static/js/moment.C3j7ZXd7.js +4 -0
  311. streamlit/static/static/js/number-overlay-editor.Cn_LsK8N.js +9 -0
  312. streamlit/static/static/js/pandasStylerUtils.BqhXt51_.js +1 -0
  313. streamlit/static/static/js/{possibleConstructorReturn.CqidKeei.js → possibleConstructorReturn.DD9NK1Z8.js} +1 -1
  314. streamlit/static/static/js/record.B-tDciZb.js +1 -0
  315. streamlit/static/static/js/{sandbox.CCQREcJx.js → sandbox.DACSyz29.js} +1 -1
  316. streamlit/static/static/js/styled-components.C3R090At.js +1 -0
  317. streamlit/static/static/js/threshold.Q1mXg5rX.js +1 -0
  318. streamlit/static/static/js/throttle.B0GR3Iyz.js +1 -0
  319. streamlit/static/static/js/{timepicker.mkJF97Bb.js → timepicker.BdhzPxrv.js} +1 -1
  320. streamlit/static/static/js/timer.C2hYhUse.js +1 -0
  321. streamlit/static/static/js/{toConsumableArray.De7I7KVR.js → toConsumableArray.Db2pdqM2.js} +1 -1
  322. streamlit/static/static/js/uniqueId.CtqIr-Yh.js +1 -0
  323. streamlit/static/static/js/urls.BwSlolu9.js +1 -0
  324. streamlit/static/static/js/{useBasicWidgetState.CedkNjUW.js → useBasicWidgetState.Bfp6TnSw.js} +1 -1
  325. streamlit/static/static/js/useIntlLocale.hRV75Xgj.js +12 -0
  326. streamlit/static/static/js/{useTextInputAutoExpand.Ca7w8dVs.js → useTextInputAutoExpand.QepX7n8Y.js} +1 -1
  327. streamlit/static/static/js/useUpdateUiValue.DHx8TzX6.js +1 -0
  328. streamlit/static/static/js/useWaveformController.WxVzpzEX.js +1 -0
  329. streamlit/static/static/js/value.B4vHRSi7.js +1 -0
  330. streamlit/static/static/js/wavesurfer.esm.vI8Eid4k.js +73 -0
  331. streamlit/static/static/js/withCalculatedWidth.DcKeRSWJ.js +1 -0
  332. streamlit/static/static/js/withFullScreenWrapper.CrHddARq.js +1 -0
  333. streamlit/static/static/media/MaterialSymbols-Rounded.C7IFxh57.woff2 +0 -0
  334. streamlit/string_util.py +9 -4
  335. streamlit/testing/v1/app_test.py +17 -2
  336. streamlit/testing/v1/element_tree.py +85 -9
  337. streamlit/testing/v1/util.py +2 -2
  338. streamlit/type_util.py +3 -4
  339. streamlit/url_util.py +1 -3
  340. streamlit/user_info.py +1 -2
  341. streamlit/util.py +3 -1
  342. streamlit/watcher/event_based_path_watcher.py +23 -12
  343. streamlit/watcher/local_sources_watcher.py +11 -1
  344. streamlit/watcher/path_watcher.py +9 -6
  345. streamlit/watcher/polling_path_watcher.py +4 -1
  346. streamlit/watcher/util.py +2 -2
  347. streamlit/web/bootstrap.py +24 -0
  348. streamlit/web/cli.py +51 -22
  349. streamlit/web/server/bidi_component_request_handler.py +193 -0
  350. streamlit/web/server/component_file_utils.py +97 -0
  351. streamlit/web/server/component_request_handler.py +8 -21
  352. streamlit/web/server/oauth_authlib_routes.py +5 -2
  353. streamlit/web/server/oidc_mixin.py +3 -1
  354. streamlit/web/server/routes.py +2 -2
  355. streamlit/web/server/server.py +9 -0
  356. streamlit/web/server/server_util.py +3 -1
  357. streamlit/web/server/upload_file_request_handler.py +19 -1
  358. {streamlit-1.50.0.dist-info → streamlit-1.52.0.dist-info}/METADATA +10 -7
  359. streamlit-1.52.0.dist-info/RECORD +620 -0
  360. streamlit/static/static/css/index.CHEnSPGk.css +0 -1
  361. streamlit/static/static/js/Hooks.BRba_Own.js +0 -1
  362. streamlit/static/static/js/InputInstructions.xnSDuYeQ.js +0 -1
  363. streamlit/static/static/js/Particles.CElH0XX2.js +0 -1
  364. streamlit/static/static/js/data-grid-overlay-editor.DcuHuCyW.js +0 -1
  365. streamlit/static/static/js/index.6xX1278W.js +0 -975
  366. streamlit/static/static/js/index.B-hiXRzw.js +0 -1
  367. streamlit/static/static/js/index.B4cAbHP6.js +0 -1
  368. streamlit/static/static/js/index.B4dUQfni.js +0 -1
  369. streamlit/static/static/js/index.BPQo7BKk.js +0 -1
  370. streamlit/static/static/js/index.Baqa90pe.js +0 -2
  371. streamlit/static/static/js/index.Bj9JgOEC.js +0 -1
  372. streamlit/static/static/js/index.BjCwMzj4.js +0 -3
  373. streamlit/static/static/js/index.Bm3VbPB5.js +0 -1
  374. streamlit/static/static/js/index.Bxz2yX3P.js +0 -1
  375. streamlit/static/static/js/index.BycLveZ4.js +0 -1
  376. streamlit/static/static/js/index.C9BdUqTi.js +0 -1
  377. streamlit/static/static/js/index.CFMf5_ez.js +0 -197
  378. streamlit/static/static/js/index.CGYqqs6j.js +0 -1
  379. streamlit/static/static/js/index.CMItVsFA.js +0 -1
  380. streamlit/static/static/js/index.CTBk8Vk2.js +0 -1
  381. streamlit/static/static/js/index.CiAQIz1H.js +0 -7
  382. streamlit/static/static/js/index.Cj7DSzVR.js +0 -73
  383. streamlit/static/static/js/index.Ck8rQ9OL.js +0 -1
  384. streamlit/static/static/js/index.ClELlchS.js +0 -1617
  385. streamlit/static/static/js/index.Cnpi3o3E.js +0 -1
  386. streamlit/static/static/js/index.D2QEXQq_.js +0 -1
  387. streamlit/static/static/js/index.DH71Ezyj.js +0 -1
  388. streamlit/static/static/js/index.DHh-U0dK.js +0 -3
  389. streamlit/static/static/js/index.DK7hD7_w.js +0 -1
  390. streamlit/static/static/js/index.DKv_lNO7.js +0 -2
  391. streamlit/static/static/js/index.DNLrMXgm.js +0 -12
  392. streamlit/static/static/js/index.DW0Grddz.js +0 -1
  393. streamlit/static/static/js/index.Dbe-Q3C-.js +0 -2
  394. streamlit/static/static/js/index.DcPNYEUo.js +0 -1
  395. streamlit/static/static/js/index.DuxqVQpd.js +0 -1
  396. streamlit/static/static/js/index.GRUzrudl.js +0 -1
  397. streamlit/static/static/js/number-overlay-editor.DdgVR5m3.js +0 -9
  398. streamlit/static/static/js/uniqueId.RI1LJdtz.js +0 -1
  399. streamlit/static/static/js/useUpdateUiValue.DeXelfRH.js +0 -1
  400. streamlit/static/static/js/withFullScreenWrapper.C3561XxJ.js +0 -1
  401. streamlit/static/static/media/MaterialSymbols-Rounded.DeCZgS-4.woff2 +0 -0
  402. streamlit-1.50.0.dist-info/RECORD +0 -557
  403. {streamlit-1.50.0.data → streamlit-1.52.0.data}/scripts/streamlit.cmd +0 -0
  404. {streamlit-1.50.0.dist-info → streamlit-1.52.0.dist-info}/WHEEL +0 -0
  405. {streamlit-1.50.0.dist-info → streamlit-1.52.0.dist-info}/entry_points.txt +0 -0
  406. {streamlit-1.50.0.dist-info → streamlit-1.52.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,51 @@
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
+ """Accessors for the BidiComponentManager used by Custom Components v2.
16
+
17
+ This module exposes `get_bidi_component_manager`, which returns the singleton
18
+ `BidiComponentManager` registered on the active Streamlit runtime. When no
19
+ runtime is active, a local manager instance is created and returned.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import TYPE_CHECKING
25
+
26
+ if TYPE_CHECKING:
27
+ from streamlit.components.v2.component_manager import BidiComponentManager
28
+
29
+
30
+ def get_bidi_component_manager() -> BidiComponentManager:
31
+ """Return the singleton ``BidiComponentManager`` instance.
32
+
33
+ Returns
34
+ -------
35
+ BidiComponentManager
36
+ The singleton BidiComponentManager instance.
37
+
38
+
39
+ Notes
40
+ -----
41
+ If the Streamlit runtime is not running, a local ``BidiComponentManager``
42
+ is created and returned.
43
+ """
44
+ from streamlit.components.v2.component_manager import BidiComponentManager
45
+ from streamlit.runtime import Runtime
46
+
47
+ if Runtime.exists():
48
+ return Runtime.instance().bidi_component_registry
49
+
50
+ # Return a local manager when running without the streamlit runtime
51
+ return BidiComponentManager()
@@ -0,0 +1,615 @@
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
+ """Discovery utilities for Component v2 manifests in installed packages.
16
+
17
+ The scanner searches installed distributions for a ``pyproject.toml`` with
18
+ ``[tool.streamlit.component]`` configuration and extracts the component
19
+ manifests along with their package roots.
20
+
21
+ The implementation prioritizes efficiency and safety by filtering likely
22
+ candidates and avoiding excessive filesystem operations.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import importlib.metadata
28
+ import importlib.util
29
+ import os
30
+ from concurrent.futures import ThreadPoolExecutor, as_completed
31
+ from dataclasses import dataclass
32
+ from pathlib import Path
33
+ from typing import Any, Final
34
+
35
+ import toml
36
+ from packaging import utils as packaging_utils
37
+
38
+ from streamlit.components.v2.component_path_utils import ComponentPathUtils
39
+ from streamlit.errors import StreamlitComponentRegistryError
40
+ from streamlit.logger import get_logger
41
+
42
+ _LOGGER: Final = get_logger(__name__)
43
+
44
+
45
+ def _normalize_package_name(dist_name: str) -> str:
46
+ """Normalize a distribution name to an importable package name.
47
+
48
+ This helper converts hyphens to underscores to derive a best-effort
49
+ importable module/package name from a distribution name.
50
+
51
+ Parameters
52
+ ----------
53
+ dist_name : str
54
+ The distribution/project name (e.g., "my-awesome-component").
55
+
56
+ Returns
57
+ -------
58
+ str
59
+ The normalized package name suitable for import lookups
60
+ (e.g., "my_awesome_component").
61
+ """
62
+ return dist_name.replace("-", "_")
63
+
64
+
65
+ @dataclass
66
+ class ComponentManifest:
67
+ """Parsed component manifest data."""
68
+
69
+ name: str
70
+ version: str
71
+ components: list[ComponentConfig]
72
+
73
+
74
+ @dataclass
75
+ class ComponentConfig:
76
+ """Structured configuration for a single component entry.
77
+
78
+ Parameters
79
+ ----------
80
+ name
81
+ Component name as declared in ``pyproject.toml``.
82
+ asset_dir
83
+ Optional relative directory containing component assets.
84
+ """
85
+
86
+ name: str
87
+ asset_dir: str | None = None
88
+
89
+ @staticmethod
90
+ def from_dict(config: dict[str, Any]) -> ComponentConfig:
91
+ """Create a ComponentConfig from a raw dict.
92
+
93
+ Parameters
94
+ ----------
95
+ config
96
+ Raw component dictionary parsed from TOML.
97
+
98
+ Returns
99
+ -------
100
+ ComponentConfig
101
+ Parsed and validated component configuration.
102
+ """
103
+ name_value = config.get("name")
104
+ if not isinstance(name_value, str) or not name_value:
105
+ # Fail closed: invalid component entry
106
+ raise ValueError("Component entry missing required 'name' field")
107
+
108
+ asset_dir_value = config.get("asset_dir")
109
+ if asset_dir_value is not None and not isinstance(asset_dir_value, str):
110
+ # Fail closed: invalid asset_dir value
111
+ raise ValueError("'asset_dir' must be a string")
112
+
113
+ return ComponentConfig(
114
+ name=name_value,
115
+ asset_dir=asset_dir_value,
116
+ )
117
+
118
+ @staticmethod
119
+ def parse_or_none(config: dict[str, Any]) -> ComponentConfig | None:
120
+ """Best-effort parse without raising; returns None on malformed input."""
121
+ try:
122
+ return ComponentConfig.from_dict(config)
123
+ except Exception as e:
124
+ _LOGGER.debug("Skipping malformed component entry: %s", e)
125
+ return None
126
+
127
+ def resolve_asset_root(self, package_root: Path) -> Path | None:
128
+ """Resolve and security-check the component's asset root directory.
129
+
130
+ Parameters
131
+ ----------
132
+ package_root : Path
133
+ The root directory of the installed component package.
134
+
135
+ Returns
136
+ -------
137
+ Path | None
138
+ Absolute, resolved path to the asset directory, or ``None`` if
139
+ ``asset_dir`` is not declared.
140
+
141
+ Raises
142
+ ------
143
+ StreamlitComponentRegistryError
144
+ If the declared directory does not exist, is not a directory, or
145
+ resolves outside of ``package_root``.
146
+ """
147
+ if self.asset_dir is None:
148
+ return None
149
+
150
+ # Validate the configured path string first
151
+ ComponentPathUtils.validate_path_security(self.asset_dir)
152
+
153
+ asset_root = (package_root / self.asset_dir).resolve()
154
+
155
+ if not asset_root.exists() or not asset_root.is_dir():
156
+ raise StreamlitComponentRegistryError(
157
+ f"Declared asset_dir '{self.asset_dir}' for component '{self.name}' "
158
+ f"does not exist or is not a directory under package root '{package_root}'."
159
+ )
160
+
161
+ # Ensure the resolved directory is within the package root after following symlinks
162
+ ComponentPathUtils.ensure_within_root(
163
+ abs_path=asset_root,
164
+ root=package_root.resolve(),
165
+ kind="asset_dir",
166
+ )
167
+
168
+ return asset_root
169
+
170
+
171
+ def _is_likely_streamlit_component_package(
172
+ dist: importlib.metadata.Distribution,
173
+ ) -> bool:
174
+ """Check if a package is likely to contain streamlit components before
175
+ expensive operations.
176
+
177
+ This early filter reduces the number of packages that need file I/O
178
+ operations from potentially hundreds down to just a few candidates.
179
+
180
+ Parameters
181
+ ----------
182
+ dist : importlib.metadata.Distribution
183
+ The package distribution to check.
184
+
185
+ Returns
186
+ -------
187
+ bool
188
+ True if the package might contain streamlit components, False otherwise.
189
+ """
190
+ # Get package metadata
191
+ name = dist.name.lower()
192
+ summary = dist.metadata["Summary"].lower() if "Summary" in dist.metadata else ""
193
+
194
+ # Filter 1: Package name suggests streamlit component
195
+ if "streamlit" in name:
196
+ return True
197
+
198
+ # Filter 2: Package description mentions streamlit
199
+ if "streamlit" in summary:
200
+ return True
201
+
202
+ # Filter 3: Check if package depends on streamlit
203
+ try:
204
+ # Check requires_dist for streamlit dependency
205
+ requires_dist = dist.metadata.get_all("Requires-Dist") or []
206
+ for requirement in requires_dist:
207
+ if requirement and "streamlit" in requirement.lower():
208
+ return True
209
+ except Exception as e:
210
+ # Don't fail on metadata parsing issues, but log for debugging purposes
211
+ _LOGGER.debug(
212
+ "Failed to parse package metadata for streamlit component detection: %s", e
213
+ )
214
+
215
+ # Filter 4: Check if this is a known streamlit ecosystem package
216
+ # Common patterns in streamlit component package names. Use anchored checks to
217
+ # avoid matching unrelated packages like "test-utils".
218
+ return name.startswith(("streamlit-", "streamlit_", "st-", "st_"))
219
+
220
+
221
+ def _find_package_pyproject_toml(dist: importlib.metadata.Distribution) -> Path | None:
222
+ """Find ``pyproject.toml`` for a package.
223
+
224
+ Handles both regular and editable installs. The function uses increasingly
225
+ permissive strategies to locate the file while validating that the file
226
+ belongs to the given distribution.
227
+
228
+ Parameters
229
+ ----------
230
+ dist : importlib.metadata.Distribution
231
+ The package distribution to find pyproject.toml for.
232
+
233
+ Returns
234
+ -------
235
+ Path | None
236
+ Path to the ``pyproject.toml`` file if found, otherwise ``None``.
237
+ """
238
+ package_name = _normalize_package_name(dist.name)
239
+
240
+ # Try increasingly permissive strategies
241
+ for finder in (
242
+ _pyproject_via_read_text,
243
+ _pyproject_via_dist_files,
244
+ lambda d: _pyproject_via_import_spec(d, package_name),
245
+ ):
246
+ result = finder(dist)
247
+ if result is not None:
248
+ return result
249
+
250
+ return None
251
+
252
+
253
+ def _pyproject_via_read_text(dist: importlib.metadata.Distribution) -> Path | None:
254
+ """Locate pyproject.toml using the distribution's read_text + nearby files.
255
+
256
+ This works for many types of installations including some editable ones.
257
+ """
258
+ package_name = _normalize_package_name(dist.name)
259
+ try:
260
+ if hasattr(dist, "read_text"):
261
+ pyproject_content = dist.read_text("pyproject.toml")
262
+ if pyproject_content and dist.files:
263
+ # Found content, now find the actual file path
264
+ # Look for a reasonable file to get the directory
265
+ for file in dist.files:
266
+ if "__init__.py" in str(file) or ".py" in str(file):
267
+ try:
268
+ file_path = Path(str(dist.locate_file(file)))
269
+ # Check nearby directories for pyproject.toml
270
+ current_dir = file_path.parent
271
+ # Check current directory and parent
272
+ for search_dir in [current_dir, current_dir.parent]:
273
+ pyproject_path = search_dir / "pyproject.toml"
274
+ if (
275
+ pyproject_path.exists()
276
+ and _validate_pyproject_for_package(
277
+ pyproject_path,
278
+ dist.name,
279
+ package_name,
280
+ )
281
+ ):
282
+ return pyproject_path
283
+ # Stop after first reasonable file
284
+ break
285
+ except Exception: # noqa: S112
286
+ continue
287
+ except Exception:
288
+ return None
289
+ return None
290
+
291
+
292
+ def _pyproject_via_dist_files(dist: importlib.metadata.Distribution) -> Path | None:
293
+ """Locate pyproject.toml by scanning the distribution's file list."""
294
+ package_name = _normalize_package_name(dist.name)
295
+ files = getattr(dist, "files", None)
296
+ if not files:
297
+ return None
298
+ for file in files:
299
+ if getattr(file, "name", None) == "pyproject.toml" or str(file).endswith(
300
+ "pyproject.toml"
301
+ ):
302
+ try:
303
+ pyproject_path = Path(str(dist.locate_file(file)))
304
+ if _validate_pyproject_for_package(
305
+ pyproject_path,
306
+ dist.name,
307
+ package_name,
308
+ ):
309
+ return pyproject_path
310
+ except Exception: # noqa: S112
311
+ continue
312
+ return None
313
+
314
+
315
+ def _pyproject_via_import_spec(
316
+ dist: importlib.metadata.Distribution, package_name: str
317
+ ) -> Path | None:
318
+ """Locate pyproject.toml by resolving the import spec and checking nearby.
319
+
320
+ For editable installs, try the package directory and its parent only.
321
+ """
322
+ try:
323
+ spec = importlib.util.find_spec(package_name)
324
+ if spec and spec.origin:
325
+ package_dir = Path(spec.origin).parent
326
+ for search_dir in [package_dir, package_dir.parent]:
327
+ pyproject_path = search_dir / "pyproject.toml"
328
+ if pyproject_path.exists() and _validate_pyproject_for_package(
329
+ pyproject_path,
330
+ dist.name,
331
+ package_name,
332
+ ):
333
+ return pyproject_path
334
+ except Exception:
335
+ return None
336
+ return None
337
+
338
+
339
+ def _validate_pyproject_for_package(
340
+ pyproject_path: Path, dist_name: str, package_name: str
341
+ ) -> bool:
342
+ """Validate that a ``pyproject.toml`` file belongs to the specified package.
343
+
344
+ Parameters
345
+ ----------
346
+ pyproject_path : Path
347
+ Path to the pyproject.toml file to validate.
348
+ dist_name : str
349
+ The distribution name (e.g., "streamlit-bokeh").
350
+ package_name : str
351
+ The package name (e.g., "streamlit_bokeh").
352
+
353
+ Returns
354
+ -------
355
+ bool
356
+ True if the file belongs to this package, False otherwise.
357
+ """
358
+ try:
359
+ with open(pyproject_path, encoding="utf-8") as f:
360
+ pyproject_data = toml.load(f)
361
+
362
+ # Check if this pyproject.toml is for the package we're looking for
363
+ project_name = None
364
+
365
+ # Try to get the project name from [project] table
366
+ if "project" in pyproject_data and "name" in pyproject_data["project"]:
367
+ project_name = pyproject_data["project"]["name"]
368
+
369
+ # Also try to get it from [tool.setuptools] or other build system configs
370
+ if (
371
+ not project_name
372
+ and "tool" in pyproject_data
373
+ and (
374
+ "setuptools" in pyproject_data["tool"]
375
+ and "package-name" in pyproject_data["tool"]["setuptools"]
376
+ )
377
+ ):
378
+ project_name = pyproject_data["tool"]["setuptools"]["package-name"]
379
+
380
+ # If we found a project name, check if it matches either the dist name or package name
381
+ if project_name:
382
+ # Normalize names for comparison using PEP 503 canonicalization
383
+ # This handles hyphens, underscores, and dots consistently.
384
+ canonical_project = packaging_utils.canonicalize_name(project_name)
385
+ canonical_dist = packaging_utils.canonicalize_name(dist_name)
386
+ canonical_package = packaging_utils.canonicalize_name(package_name)
387
+
388
+ # Check if project name matches either the distribution name or the package name
389
+ return canonical_project in (canonical_dist, canonical_package)
390
+
391
+ # If we can't determine ownership, be conservative and reject it
392
+ return False
393
+
394
+ except Exception as e:
395
+ _LOGGER.debug(
396
+ "Error validating pyproject.toml at %s for %s: %s",
397
+ pyproject_path,
398
+ dist_name,
399
+ e,
400
+ )
401
+ return False
402
+
403
+
404
+ def _load_pyproject(pyproject_path: Path) -> dict[str, Any] | None:
405
+ """Load and parse a pyproject.toml, returning parsed data or None on failure."""
406
+ try:
407
+ with open(pyproject_path, encoding="utf-8") as f:
408
+ return toml.load(f)
409
+ except Exception as e:
410
+ _LOGGER.debug("Failed to parse pyproject.toml at %s: %s", pyproject_path, e)
411
+ return None
412
+
413
+
414
+ def _extract_components(pyproject_data: dict[str, Any]) -> list[dict[str, Any]] | None:
415
+ """Extract raw component dicts from pyproject data; return None if absent."""
416
+ streamlit_component = (
417
+ pyproject_data.get("tool", {}).get("streamlit", {}).get("component")
418
+ )
419
+ if not streamlit_component:
420
+ return None
421
+ raw_components = streamlit_component.get("components")
422
+ if not isinstance(raw_components, list):
423
+ return None
424
+ # Ensure a list of dicts for type safety
425
+ result: list[dict[str, Any]] = [
426
+ item for item in raw_components if isinstance(item, dict)
427
+ ]
428
+ if not result:
429
+ return None
430
+ return result
431
+
432
+
433
+ def _resolve_package_root(
434
+ dist: importlib.metadata.Distribution, package_name: str, pyproject_path: Path
435
+ ) -> Path:
436
+ """Resolve the package root directory with fallbacks."""
437
+ package_root: Path | None = None
438
+ try:
439
+ spec = importlib.util.find_spec(package_name)
440
+ if spec and spec.origin:
441
+ package_root = Path(spec.origin).parent
442
+ except Exception as e:
443
+ _LOGGER.debug(
444
+ "Failed to resolve package root via import spec for %s: %s",
445
+ package_name,
446
+ e,
447
+ )
448
+
449
+ files = getattr(dist, "files", None)
450
+ if not package_root and files:
451
+ for file in files:
452
+ if package_name in str(file) and "__init__.py" in str(file):
453
+ try:
454
+ init_path = Path(str(dist.locate_file(file)))
455
+ package_root = init_path.parent
456
+ break
457
+ except Exception as e:
458
+ _LOGGER.debug(
459
+ "Failed to resolve package root via dist files for %s: %s",
460
+ package_name,
461
+ e,
462
+ )
463
+
464
+ if not package_root:
465
+ package_root = pyproject_path.parent
466
+
467
+ return package_root
468
+
469
+
470
+ def _derive_project_metadata(
471
+ pyproject_data: dict[str, Any], dist: importlib.metadata.Distribution
472
+ ) -> tuple[str, str]:
473
+ """Derive project name and version with safe fallbacks."""
474
+ project_table = pyproject_data.get("project", {})
475
+ derived_name = project_table.get("name") or dist.name
476
+ derived_version = project_table.get("version") or dist.version or "0.0.0"
477
+ return derived_name, derived_version
478
+
479
+
480
+ def _process_single_package(
481
+ dist: importlib.metadata.Distribution,
482
+ ) -> tuple[ComponentManifest, Path] | None:
483
+ """Process a single package to extract component manifest.
484
+
485
+ This function is designed to be called from a thread pool for parallel processing.
486
+
487
+ Parameters
488
+ ----------
489
+ dist : importlib.metadata.Distribution
490
+ The package distribution to process.
491
+
492
+ Returns
493
+ -------
494
+ tuple[ComponentManifest, Path] | None
495
+ The manifest and package root if found, otherwise ``None``.
496
+ """
497
+ try:
498
+ pyproject_path = _find_package_pyproject_toml(dist)
499
+ if not pyproject_path:
500
+ return None
501
+
502
+ pyproject_data = _load_pyproject(pyproject_path)
503
+ if pyproject_data is None:
504
+ return None
505
+
506
+ raw_components = _extract_components(pyproject_data)
507
+ if not raw_components:
508
+ return None
509
+
510
+ package_name = _normalize_package_name(dist.name)
511
+ package_root = _resolve_package_root(dist, package_name, pyproject_path)
512
+
513
+ derived_name, derived_version = _derive_project_metadata(pyproject_data, dist)
514
+
515
+ parsed_components: list[ComponentConfig] = [
516
+ parsed
517
+ for comp in raw_components
518
+ if (parsed := ComponentConfig.parse_or_none(comp)) is not None
519
+ ]
520
+
521
+ if not parsed_components:
522
+ return None
523
+
524
+ manifest = ComponentManifest(
525
+ name=derived_name,
526
+ version=derived_version,
527
+ components=parsed_components,
528
+ )
529
+
530
+ return (manifest, package_root)
531
+
532
+ except Exception as e:
533
+ _LOGGER.debug(
534
+ "Unexpected error processing distribution %s: %s",
535
+ getattr(dist, "name", "<unknown>"),
536
+ e,
537
+ )
538
+ return None
539
+
540
+
541
+ def scan_component_manifests(
542
+ max_workers: int | None = None,
543
+ ) -> list[tuple[ComponentManifest, Path]]:
544
+ """Scan installed packages for Streamlit component metadata.
545
+
546
+ Uses parallel processing to improve performance in environments with many
547
+ installed packages. Applies early filtering to only check packages likely to
548
+ contain streamlit components.
549
+
550
+ Parameters
551
+ ----------
552
+ max_workers : int or None
553
+ Maximum number of worker threads. If None, uses min(32, (os.cpu_count()
554
+ or 1) + 4).
555
+
556
+ Returns
557
+ -------
558
+ list[tuple[ComponentManifest, Path]]
559
+ List of tuples of manifests and their package root paths.
560
+ """
561
+ manifests: list[tuple[ComponentManifest, Path]] = []
562
+
563
+ # Get all distributions first (this is fast)
564
+ all_distributions = list(importlib.metadata.distributions())
565
+
566
+ if not all_distributions:
567
+ return manifests
568
+
569
+ # Apply early filtering to reduce expensive file operations
570
+ candidate_distributions = [
571
+ dist
572
+ for dist in all_distributions
573
+ if _is_likely_streamlit_component_package(dist)
574
+ ]
575
+
576
+ _LOGGER.debug(
577
+ "Filtered %d packages down to %d candidates for component scanning",
578
+ len(all_distributions),
579
+ len(candidate_distributions),
580
+ )
581
+
582
+ if not candidate_distributions:
583
+ return manifests
584
+
585
+ # Default max_workers follows ThreadPoolExecutor's default logic
586
+ if max_workers is None:
587
+ max_workers = min(32, (os.cpu_count() or 1) + 4)
588
+
589
+ # Clamp max_workers to reasonable bounds for this task
590
+ max_workers = min(
591
+ max_workers, len(candidate_distributions), 16
592
+ ) # Don't use more threads than packages or 16
593
+
594
+ _LOGGER.debug(
595
+ "Scanning %d candidate packages for component manifests using %d worker threads",
596
+ len(candidate_distributions),
597
+ max_workers,
598
+ )
599
+
600
+ # Process packages in parallel
601
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
602
+ # Submit all tasks
603
+ future_to_dist = {
604
+ executor.submit(_process_single_package, dist): dist.name
605
+ for dist in candidate_distributions
606
+ }
607
+
608
+ # Collect results as they complete
609
+ for future in as_completed(future_to_dist):
610
+ result = future.result()
611
+ if result:
612
+ manifests.append(result)
613
+
614
+ _LOGGER.debug("Found %d component manifests total", len(manifests))
615
+ return manifests