streamlit 1.51.0__py3-none-any.whl → 1.52.1__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 (314) hide show
  1. streamlit/__init__.py +1 -0
  2. streamlit/commands/execution_control.py +89 -14
  3. streamlit/components/v1/component_arrow.py +7 -7
  4. streamlit/components/v2/__init__.py +59 -3
  5. streamlit/components/v2/bidi_component/main.py +161 -13
  6. streamlit/components/v2/bidi_component/serialization.py +13 -6
  7. streamlit/components/v2/component_manager.py +11 -3
  8. streamlit/components/v2/component_registry.py +18 -1
  9. streamlit/components/v2/types.py +2 -2
  10. streamlit/connections/snowflake_connection.py +1 -1
  11. streamlit/connections/snowpark_connection.py +1 -1
  12. streamlit/dataframe_util.py +18 -18
  13. streamlit/delta_generator.py +7 -0
  14. streamlit/delta_generator_singletons.py +8 -14
  15. streamlit/elements/alert.py +16 -0
  16. streamlit/elements/arrow.py +36 -6
  17. streamlit/elements/bokeh_chart.py +10 -78
  18. streamlit/elements/code.py +2 -2
  19. streamlit/elements/deck_gl_json_chart.py +1 -1
  20. streamlit/elements/exception.py +1 -1
  21. streamlit/elements/form.py +27 -0
  22. streamlit/elements/heading.py +60 -5
  23. streamlit/elements/html.py +13 -2
  24. streamlit/elements/image.py +1 -1
  25. streamlit/elements/layouts.py +28 -22
  26. streamlit/elements/lib/built_in_chart_utils.py +49 -16
  27. streamlit/elements/lib/color_util.py +1 -1
  28. streamlit/elements/lib/column_config_utils.py +6 -5
  29. streamlit/elements/lib/layout_utils.py +50 -0
  30. streamlit/elements/lib/pandas_styler_utils.py +17 -9
  31. streamlit/elements/lib/shortcut_utils.py +152 -0
  32. streamlit/elements/markdown.py +50 -3
  33. streamlit/elements/metric.py +31 -1
  34. streamlit/elements/plotly_chart.py +75 -6
  35. streamlit/elements/spinner.py +1 -1
  36. streamlit/elements/text.py +20 -3
  37. streamlit/elements/toast.py +2 -0
  38. streamlit/elements/vega_charts.py +17 -1
  39. streamlit/elements/widgets/audio_input.py +8 -7
  40. streamlit/elements/widgets/button.py +279 -40
  41. streamlit/elements/widgets/button_group.py +27 -2
  42. streamlit/elements/widgets/camera_input.py +1 -1
  43. streamlit/elements/widgets/chat.py +300 -42
  44. streamlit/elements/widgets/color_picker.py +7 -0
  45. streamlit/elements/widgets/data_editor.py +68 -28
  46. streamlit/elements/widgets/file_uploader.py +4 -1
  47. streamlit/elements/widgets/number_input.py +2 -0
  48. streamlit/elements/widgets/text_widgets.py +2 -0
  49. streamlit/elements/widgets/time_widgets.py +581 -9
  50. streamlit/errors.py +22 -0
  51. streamlit/git_util.py +1 -1
  52. streamlit/navigation/page.py +7 -0
  53. streamlit/net_util.py +2 -2
  54. streamlit/proto/Alert_pb2.pyi +3 -3
  55. streamlit/proto/AppPage_pb2.pyi +7 -1
  56. streamlit/proto/ArrowData_pb2.pyi +7 -1
  57. streamlit/proto/ArrowNamedDataSet_pb2.pyi +7 -1
  58. streamlit/proto/ArrowVegaLiteChart_pb2.pyi +7 -1
  59. streamlit/proto/Arrow_pb2.py +10 -10
  60. streamlit/proto/Arrow_pb2.pyi +19 -12
  61. streamlit/proto/AudioInput_pb2.pyi +7 -1
  62. streamlit/proto/Audio_pb2.pyi +7 -1
  63. streamlit/proto/AuthRedirect_pb2.pyi +7 -1
  64. streamlit/proto/AutoRerun_pb2.pyi +7 -1
  65. streamlit/proto/BackMsg_pb2.py +4 -2
  66. streamlit/proto/BackMsg_pb2.pyi +34 -4
  67. streamlit/proto/Balloons_pb2.pyi +7 -1
  68. streamlit/proto/BidiComponent_pb2.pyi +10 -4
  69. streamlit/proto/Block_pb2.pyi +35 -35
  70. streamlit/proto/BokehChart_pb2.pyi +7 -1
  71. streamlit/proto/ButtonGroup_pb2.pyi +9 -9
  72. streamlit/proto/Button_pb2.py +2 -2
  73. streamlit/proto/Button_pb2.pyi +11 -2
  74. streamlit/proto/CameraInput_pb2.pyi +7 -1
  75. streamlit/proto/ChatInput_pb2.py +6 -6
  76. streamlit/proto/ChatInput_pb2.pyi +18 -6
  77. streamlit/proto/Checkbox_pb2.pyi +3 -3
  78. streamlit/proto/ClientState_pb2.pyi +10 -4
  79. streamlit/proto/Code_pb2.pyi +7 -1
  80. streamlit/proto/ColorPicker_pb2.pyi +7 -1
  81. streamlit/proto/Common_pb2.py +3 -3
  82. streamlit/proto/Common_pb2.pyi +35 -23
  83. streamlit/proto/Components_pb2.pyi +19 -13
  84. streamlit/proto/DataFrame_pb2.pyi +55 -49
  85. streamlit/proto/DateInput_pb2.pyi +7 -1
  86. streamlit/proto/DateTimeInput_pb2.py +28 -0
  87. streamlit/proto/DateTimeInput_pb2.pyi +92 -0
  88. streamlit/proto/DeckGlJsonChart_pb2.pyi +3 -3
  89. streamlit/proto/Delta_pb2.pyi +7 -1
  90. streamlit/proto/DocString_pb2.pyi +10 -4
  91. streamlit/proto/DownloadButton_pb2.py +2 -2
  92. streamlit/proto/DownloadButton_pb2.pyi +16 -2
  93. streamlit/proto/Element_pb2.py +5 -3
  94. streamlit/proto/Element_pb2.pyi +23 -5
  95. streamlit/proto/Empty_pb2.pyi +7 -1
  96. streamlit/proto/Exception_pb2.pyi +7 -1
  97. streamlit/proto/Favicon_pb2.pyi +7 -1
  98. streamlit/proto/FileUploader_pb2.pyi +7 -1
  99. streamlit/proto/ForwardMsg_pb2.py +12 -10
  100. streamlit/proto/ForwardMsg_pb2.pyi +42 -15
  101. streamlit/proto/GapSize_pb2.pyi +4 -4
  102. streamlit/proto/GitInfo_pb2.pyi +3 -3
  103. streamlit/proto/GraphVizChart_pb2.pyi +7 -1
  104. streamlit/proto/Heading_pb2.pyi +7 -1
  105. streamlit/proto/HeightConfig_pb2.pyi +7 -1
  106. streamlit/proto/Html_pb2.py +2 -2
  107. streamlit/proto/Html_pb2.pyi +11 -2
  108. streamlit/proto/IFrame_pb2.pyi +7 -1
  109. streamlit/proto/Image_pb2.pyi +10 -4
  110. streamlit/proto/Json_pb2.pyi +7 -1
  111. streamlit/proto/LabelVisibilityMessage_pb2.pyi +3 -3
  112. streamlit/proto/LinkButton_pb2.py +2 -2
  113. streamlit/proto/LinkButton_pb2.pyi +15 -2
  114. streamlit/proto/Logo_pb2.pyi +7 -1
  115. streamlit/proto/Markdown_pb2.pyi +3 -3
  116. streamlit/proto/Metric_pb2.pyi +7 -7
  117. streamlit/proto/MetricsEvent_pb2.pyi +10 -4
  118. streamlit/proto/MultiSelect_pb2.pyi +7 -1
  119. streamlit/proto/NamedDataSet_pb2.pyi +7 -1
  120. streamlit/proto/Navigation_pb2.pyi +3 -3
  121. streamlit/proto/NewSession_pb2.pyi +40 -40
  122. streamlit/proto/NumberInput_pb2.pyi +3 -3
  123. streamlit/proto/PageConfig_pb2.pyi +7 -7
  124. streamlit/proto/PageInfo_pb2.pyi +7 -1
  125. streamlit/proto/PageLink_pb2.py +2 -2
  126. streamlit/proto/PageLink_pb2.pyi +11 -2
  127. streamlit/proto/PageNotFound_pb2.pyi +7 -1
  128. streamlit/proto/PageProfile_pb2.pyi +13 -7
  129. streamlit/proto/PagesChanged_pb2.pyi +7 -1
  130. streamlit/proto/ParentMessage_pb2.pyi +7 -1
  131. streamlit/proto/PlotlyChart_pb2.pyi +6 -6
  132. streamlit/proto/Progress_pb2.pyi +7 -1
  133. streamlit/proto/Radio_pb2.pyi +7 -1
  134. streamlit/proto/RootContainer_pb2.pyi +1 -1
  135. streamlit/proto/Selectbox_pb2.pyi +7 -1
  136. streamlit/proto/SessionEvent_pb2.pyi +7 -1
  137. streamlit/proto/SessionStatus_pb2.pyi +7 -1
  138. streamlit/proto/Skeleton_pb2.pyi +3 -3
  139. streamlit/proto/Slider_pb2.pyi +5 -5
  140. streamlit/proto/Snow_pb2.pyi +7 -1
  141. streamlit/proto/Space_pb2.pyi +7 -1
  142. streamlit/proto/Spinner_pb2.pyi +7 -1
  143. streamlit/proto/TextAlignmentConfig_pb2.py +29 -0
  144. streamlit/proto/TextAlignmentConfig_pb2.pyi +68 -0
  145. streamlit/proto/TextArea_pb2.pyi +7 -1
  146. streamlit/proto/TextInput_pb2.pyi +3 -3
  147. streamlit/proto/Text_pb2.pyi +7 -1
  148. streamlit/proto/TimeInput_pb2.pyi +7 -1
  149. streamlit/proto/Toast_pb2.pyi +7 -1
  150. streamlit/proto/VegaLiteChart_pb2.pyi +7 -1
  151. streamlit/proto/Video_pb2.pyi +6 -6
  152. streamlit/proto/WidgetStates_pb2.pyi +10 -4
  153. streamlit/proto/WidthConfig_pb2.pyi +7 -1
  154. streamlit/proto/openmetrics_data_model_pb2.pyi +52 -52
  155. streamlit/runtime/app_session.py +38 -1
  156. streamlit/runtime/caching/cache_data_api.py +1 -1
  157. streamlit/runtime/caching/cache_resource_api.py +2 -2
  158. streamlit/runtime/caching/cache_utils.py +1 -1
  159. streamlit/runtime/caching/hashing.py +1 -1
  160. streamlit/runtime/download_data_util.py +53 -0
  161. streamlit/runtime/forward_msg_queue.py +1 -0
  162. streamlit/runtime/media_file_manager.py +178 -2
  163. streamlit/runtime/metrics_util.py +87 -3
  164. streamlit/runtime/scriptrunner/script_runner.py +3 -1
  165. streamlit/runtime/state/query_params.py +80 -29
  166. streamlit/runtime/state/session_state.py +2 -2
  167. streamlit/static/index.html +1 -1
  168. streamlit/static/manifest.json +530 -229
  169. streamlit/static/static/js/{ErrorOutline.esm.YoJdlW1p.js → ErrorOutline.esm.sMJdFExW.js} +1 -1
  170. streamlit/static/static/js/{FileDownload.esm.Ddx8VEYy.js → FileDownload.esm.CV-WYqBn.js} +1 -1
  171. streamlit/static/static/js/{FileHelper.90EtOmj9.js → FileHelper.5nCh9KDY.js} +3 -3
  172. streamlit/static/static/js/{FormClearHelper.BB1Km6eP.js → FormClearHelper.-9RbsnV0.js} +1 -1
  173. streamlit/static/static/js/IFrameUtil.DefezniK.js +1 -0
  174. streamlit/static/static/js/InputInstructions.2R3tBtW9.js +1 -0
  175. streamlit/static/static/js/Particles.DDHoXFxh.js +1 -0
  176. streamlit/static/static/js/{ProgressBar.DLY8H6nE.js → ProgressBar.BxmfHxKu.js} +2 -2
  177. streamlit/static/static/js/StreamlitSyntaxHighlighter.BFWV0oqR.js +20 -0
  178. streamlit/static/static/js/{Toolbar.D8nHCkuz.js → Toolbar.DMgU0Vgw.js} +1 -1
  179. streamlit/static/static/js/_arrayIncludes.B19Iyn2B.js +1 -0
  180. streamlit/static/static/js/_baseIndexOf.BTknn6Gb.js +1 -0
  181. streamlit/static/static/js/{base-input.CJGiNqed.js → base-input.BXTqYbyG.js} +4 -4
  182. streamlit/static/static/js/{checkbox.Cpdd482O.js → checkbox.5xWaqPqm.js} +1 -1
  183. streamlit/static/static/js/{createSuper.CuQIogbW.js → createSuper.OIgV8wc-.js} +1 -1
  184. streamlit/static/static/js/data-grid-overlay-editor.B4RIu9cw.js +1 -0
  185. streamlit/static/static/js/{downloader.CN0K7xlu.js → downloader.DwCJck8O.js} +1 -1
  186. streamlit/static/static/js/embed.HKcgTiLB.js +195 -0
  187. streamlit/static/static/js/{es6.BJcsVXQ0.js → es6.4AP97RGk.js} +2 -2
  188. streamlit/static/static/js/{iframeResizer.contentWindow.XzUvQqcZ.js → iframeResizer.contentWindow.BZAsvL9q.js} +1 -1
  189. streamlit/static/static/js/index.-3selq_5.js +2 -0
  190. streamlit/static/static/js/index.1ylynMAS.js +7 -0
  191. streamlit/static/static/js/{index.CxIUUfab.js → index.6UunrySF.js} +53 -122
  192. streamlit/static/static/js/index.8HslT92O.js +14 -0
  193. streamlit/static/static/js/index.B0TPxAZ1.js +1 -0
  194. streamlit/static/static/js/index.B0yp3bM1.js +6 -0
  195. streamlit/static/static/js/index.BHWBaOWH.js +1 -0
  196. streamlit/static/static/js/index.BJas6XzW.js +1 -0
  197. streamlit/static/static/js/index.BKIlG7Ng.js +3 -0
  198. streamlit/static/static/js/index.BMU6zZRk.js +1 -0
  199. streamlit/static/static/js/index.BNMLO-0p.js +2 -0
  200. streamlit/static/static/js/index.BPmBNTel.js +1 -0
  201. streamlit/static/static/js/index.BVuohWM1.js +1 -0
  202. streamlit/static/static/js/index.B_AvdOKC.js +1 -0
  203. streamlit/static/static/js/index.BjQIH-3U.js +1 -0
  204. streamlit/static/static/js/index.BrqtKtSu.js +2 -0
  205. streamlit/static/static/js/index.Buc7XrOl.js +188 -0
  206. streamlit/static/static/js/index.CIC9pLsG.js +2 -0
  207. streamlit/static/static/js/index.CP2YZ73v.js +1 -0
  208. streamlit/static/static/js/index.CSbah0y4.js +27 -0
  209. streamlit/static/static/js/index.CbiYVMT1.js +1 -0
  210. streamlit/static/static/js/index.CbxllBj8.js +1 -0
  211. streamlit/static/static/js/index.Cd1D2eGF.js +263 -0
  212. streamlit/static/static/js/index.Ckcqwai8.js +2 -0
  213. streamlit/static/static/js/index.CqTPbV5Y.js +151 -0
  214. streamlit/static/static/js/index.CxXo5UKy.js +1 -0
  215. streamlit/static/static/js/index.CxbL5FgL.js +1 -0
  216. streamlit/static/static/js/index.D52dMvK5.js +1 -0
  217. streamlit/static/static/js/index.DBUdji-9.js +3 -0
  218. streamlit/static/static/js/index.DMU3coc2.js +1 -0
  219. streamlit/static/static/js/index.DN4sfQLP.js +1 -0
  220. streamlit/static/static/js/{index.DPUXkcQL.js → index.DRDE9rnx.js} +1 -1
  221. streamlit/static/static/js/{index.B_dWA3vd.js → index.DY9Ac89e.js} +2 -2
  222. streamlit/static/static/js/index.DYKCsDvl.js +1 -0
  223. streamlit/static/static/js/index.Da9gznCC.js +1 -0
  224. streamlit/static/static/js/index.DfIRibXG.js +1 -0
  225. streamlit/static/static/js/{index.D3GPA5k4.js → index.Dg5zbEp2.js} +9 -40
  226. streamlit/static/static/js/index.Di9I2cid.js +1 -0
  227. streamlit/static/static/js/index.DkpEv0uV.js +1 -0
  228. streamlit/static/static/js/index.DwJ9Vhsl.js +1 -0
  229. streamlit/static/static/js/index.L7erTnMm.js +1 -0
  230. streamlit/static/static/js/{index.DOFlg3dS.js → index.NaDyAN1s.js} +1 -1
  231. streamlit/static/static/js/index.RNTPpVde.js +1 -0
  232. streamlit/static/static/js/index.VFDFuf_7.js +1 -0
  233. streamlit/static/static/js/index.W-bl3NDo.js +1 -0
  234. streamlit/static/static/js/index.XYozEjwK.js +1 -0
  235. streamlit/static/static/js/index.oyLQ4pue.js +1 -0
  236. streamlit/static/static/js/index.q4fLUQtC.js +11 -0
  237. streamlit/static/static/js/index.q9puCQgK.js +2 -0
  238. streamlit/static/static/js/index.xZBTXGNC.js +1 -0
  239. streamlit/static/static/js/{input.D4MN_FzN.js → input.CcvrgErO.js} +2 -2
  240. streamlit/static/static/js/main.eVHOp4Th.js +13 -0
  241. streamlit/static/static/js/{memory.DrZjtdGT.js → memory.Ck_sLv5Y.js} +1 -1
  242. streamlit/static/static/js/moment.C3j7ZXd7.js +4 -0
  243. streamlit/static/static/js/number-overlay-editor.DgcLMWOy.js +9 -0
  244. streamlit/static/static/js/pandasStylerUtils.DqP0h70z.js +1 -0
  245. streamlit/static/static/js/{possibleConstructorReturn.exeeJQEP.js → possibleConstructorReturn.C_51n46K.js} +1 -1
  246. streamlit/static/static/js/{sandbox.ClO3IuUr.js → sandbox.Q-g3QIZJ.js} +1 -1
  247. streamlit/static/static/js/styled-components.e0V96rJw.js +1 -0
  248. streamlit/static/static/js/threshold.Q1mXg5rX.js +1 -0
  249. streamlit/static/static/js/throttle.D3b5WILl.js +1 -0
  250. streamlit/static/static/js/{timepicker.DAhu-vcF.js → timepicker.Bpn70xGc.js} +1 -1
  251. streamlit/static/static/js/timer.C2hYhUse.js +1 -0
  252. streamlit/static/static/js/{toConsumableArray.DNbljYEC.js → toConsumableArray.DIN_ys1J.js} +1 -1
  253. streamlit/static/static/js/uniqueId.B27POWT6.js +1 -0
  254. streamlit/static/static/js/urls.BwSlolu9.js +1 -0
  255. streamlit/static/static/js/{useBasicWidgetState.D6sOH6oI.js → useBasicWidgetState.DA3_qaXD.js} +1 -1
  256. streamlit/static/static/js/useIntlLocale.BSq6SANa.js +12 -0
  257. streamlit/static/static/js/{useTextInputAutoExpand.4u3_GcuN.js → useTextInputAutoExpand.ytEW5QmA.js} +1 -1
  258. streamlit/static/static/js/useUpdateUiValue.DOxWBNiI.js +1 -0
  259. streamlit/static/static/js/useWaveformController.BCmk6WLk.js +1 -0
  260. streamlit/static/static/js/value.B4vHRSi7.js +1 -0
  261. streamlit/static/static/js/withCalculatedWidth.ChdrMItN.js +1 -0
  262. streamlit/static/static/js/withFullScreenWrapper.7j_lzlaF.js +1 -0
  263. streamlit/string_util.py +8 -1
  264. streamlit/testing/v1/app_test.py +15 -0
  265. streamlit/testing/v1/element_tree.py +62 -0
  266. streamlit/web/bootstrap.py +24 -0
  267. streamlit/web/server/oauth_authlib_routes.py +5 -2
  268. streamlit/web/server/upload_file_request_handler.py +16 -0
  269. {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/METADATA +9 -5
  270. {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/RECORD +274 -239
  271. streamlit/static/static/js/InputInstructions.jhH15PqV.js +0 -1
  272. streamlit/static/static/js/Particles.DUsputn1.js +0 -1
  273. streamlit/static/static/js/data-grid-overlay-editor.2Ufgxc6y.js +0 -1
  274. streamlit/static/static/js/index.B1ZQh4P1.js +0 -1
  275. streamlit/static/static/js/index.BKstZk0M.js +0 -27
  276. streamlit/static/static/js/index.BMcFsUee.js +0 -1
  277. streamlit/static/static/js/index.BR-IdcTb.js +0 -2
  278. streamlit/static/static/js/index.BgnZEMVh.js +0 -1
  279. streamlit/static/static/js/index.BohqXifI.js +0 -1
  280. streamlit/static/static/js/index.Br5nxKNj.js +0 -2
  281. streamlit/static/static/js/index.BrIKVbNc.js +0 -3
  282. streamlit/static/static/js/index.BtWUPzle.js +0 -1
  283. streamlit/static/static/js/index.C0RLraek.js +0 -1
  284. streamlit/static/static/js/index.CAIjskgG.js +0 -1
  285. streamlit/static/static/js/index.CAj-7vWz.js +0 -949
  286. streamlit/static/static/js/index.CMtEit2O.js +0 -1
  287. streamlit/static/static/js/index.CkRlykEE.js +0 -12
  288. streamlit/static/static/js/index.CmN3FXfI.js +0 -1617
  289. streamlit/static/static/js/index.CwbFI1_-.js +0 -1
  290. streamlit/static/static/js/index.D2KPNy7e.js +0 -1
  291. streamlit/static/static/js/index.DGAh7DMq.js +0 -1
  292. streamlit/static/static/js/index.DKb_NvmG.js +0 -197
  293. streamlit/static/static/js/index.DMqgUYKq.js +0 -1
  294. streamlit/static/static/js/index.DX1xY89g.js +0 -1
  295. streamlit/static/static/js/index.DYATBCsq.js +0 -2
  296. streamlit/static/static/js/index.DaSmGJ76.js +0 -3
  297. streamlit/static/static/js/index.Dd7bMeLP.js +0 -1
  298. streamlit/static/static/js/index.DjmmgI5U.js +0 -1
  299. streamlit/static/static/js/index.Dq56CyM2.js +0 -1
  300. streamlit/static/static/js/index.DuiXaS5_.js +0 -7
  301. streamlit/static/static/js/index.DvFidMLe.js +0 -2
  302. streamlit/static/static/js/index.DwkhC5Pc.js +0 -1
  303. streamlit/static/static/js/index.Q-3sFn1v.js +0 -1
  304. streamlit/static/static/js/index.QJ5QO9sJ.js +0 -1
  305. streamlit/static/static/js/index.VwTaeety.js +0 -1
  306. streamlit/static/static/js/index.YOqQbeX8.js +0 -1
  307. streamlit/static/static/js/number-overlay-editor.DRwAw1In.js +0 -9
  308. streamlit/static/static/js/uniqueId.oG4Gvj1v.js +0 -1
  309. streamlit/static/static/js/useUpdateUiValue.F2R3eTeR.js +0 -1
  310. streamlit/static/static/js/withFullScreenWrapper.zothJIsI.js +0 -1
  311. {streamlit-1.51.0.data → streamlit-1.52.1.data}/scripts/streamlit.cmd +0 -0
  312. {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/WHEEL +0 -0
  313. {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/entry_points.txt +0 -0
  314. {streamlit-1.51.0.dist-info → streamlit-1.52.1.dist-info}/top_level.txt +0 -0
@@ -519,7 +519,10 @@ def _reset_counter_pattern(prefix: str, vega_spec: str) -> str:
519
519
  We need to reset these counters on a spec-level to make the
520
520
  spec stable across reruns and avoid changes to the element ID.
521
521
  """
522
- pattern = re.compile(rf'"{prefix}\d+"')
522
+
523
+ # Altair 6.0.0 introduced a new way to handle parameters,
524
+ # by using hashes instead of pure counters:
525
+ pattern = re.compile(rf'"{prefix}[0-9a-z]+"')
523
526
  # Get all matches without duplicates in order of appearance.
524
527
  # Using a set here would not guarantee the order of appearance,
525
528
  # which might lead to different replacements on each run.
@@ -2090,6 +2093,11 @@ class VegaChartsMixin:
2090
2093
  The Vega-Lite spec for the chart as keywords. This is an alternative
2091
2094
  to ``spec``.
2092
2095
 
2096
+ .. deprecated::
2097
+ ``**kwargs`` are deprecated and will be removed in a future
2098
+ release. To specify Vega-Lite configuration options, use the
2099
+ ``spec`` argument instead.
2100
+
2093
2101
  Returns
2094
2102
  -------
2095
2103
  element or dict
@@ -2130,6 +2138,14 @@ class VegaChartsMixin:
2130
2138
  translated to the syntax shown above.
2131
2139
 
2132
2140
  """
2141
+ if kwargs:
2142
+ show_deprecation_warning(
2143
+ "Variable keyword arguments for `st.vega_lite_chart` have been "
2144
+ "deprecated and will be removed in a future release. Use the "
2145
+ "`spec` argument instead to specify Vega-Lite configuration "
2146
+ "options."
2147
+ )
2148
+
2133
2149
  return self._vega_lite_chart(
2134
2150
  data=data,
2135
2151
  spec=spec,
@@ -129,13 +129,14 @@ class AudioInputMixin:
129
129
  .. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
130
130
 
131
131
  sample_rate : int or None
132
- The target sample rate for the audio recording in Hz.
133
- This defaults to 16000 Hz, which is optimal for speech recognition.
134
-
135
- The following sample rates are supported: 8000, 11025, 16000,
136
- 22050, 24000, 32000, 44100, or 48000. If this is ``None``, the
137
- widget uses the browser's default sample rate (typically 44100 or
138
- 48000 Hz).
132
+ The target sample rate for the audio recording in Hz. This
133
+ defaults to ``16000``, which is optimal for speech recognition.
134
+
135
+ The following values are supported: ``8000`` (telephone quality),
136
+ ``11025``, ``16000`` (speech-recognition quality), ``22050``,
137
+ ``24000``, ``32000``, ``44100``, ``48000`` (high-quality), or
138
+ ``None``. If this is ``None``, the widget uses the browser's
139
+ default sample rate (typically 44100 or 48000 Hz).
139
140
 
140
141
  key : str or int
141
142
  An optional string or integer to use as the unique key for the widget.
@@ -16,6 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  import io
18
18
  import os
19
+ from collections.abc import Callable
19
20
  from dataclasses import dataclass
20
21
  from pathlib import Path
21
22
  from textwrap import dedent
@@ -33,6 +34,7 @@ from streamlit import runtime
33
34
  from streamlit.elements.lib.form_utils import current_form_id, is_in_form
34
35
  from streamlit.elements.lib.layout_utils import LayoutConfig, Width, validate_width
35
36
  from streamlit.elements.lib.policies import check_widget_policies
37
+ from streamlit.elements.lib.shortcut_utils import normalize_shortcut
36
38
  from streamlit.elements.lib.utils import (
37
39
  Key,
38
40
  compute_and_register_element_id,
@@ -50,6 +52,7 @@ from streamlit.proto.Button_pb2 import Button as ButtonProto
50
52
  from streamlit.proto.DownloadButton_pb2 import DownloadButton as DownloadButtonProto
51
53
  from streamlit.proto.LinkButton_pb2 import LinkButton as LinkButtonProto
52
54
  from streamlit.proto.PageLink_pb2 import PageLink as PageLinkProto
55
+ from streamlit.runtime.download_data_util import convert_data_to_bytes_and_infer_mime
53
56
  from streamlit.runtime.metrics_util import gather_metrics
54
57
  from streamlit.runtime.pages_manager import PagesManager
55
58
  from streamlit.runtime.scriptrunner import ScriptRunContext, get_script_run_ctx
@@ -59,12 +62,14 @@ from streamlit.runtime.state import (
59
62
  WidgetKwargs,
60
63
  register_widget,
61
64
  )
65
+ from streamlit.runtime.state.query_params import process_query_params
62
66
  from streamlit.string_util import validate_icon_or_emoji
63
67
  from streamlit.url_util import is_url
64
68
  from streamlit.util import in_sidebar
65
69
 
66
70
  if TYPE_CHECKING:
67
71
  from streamlit.delta_generator import DeltaGenerator
72
+ from streamlit.runtime.state.query_params import QueryParamsInput
68
73
 
69
74
  FORM_DOCS_INFO: Final = """
70
75
 
@@ -72,7 +77,14 @@ For more information, refer to the
72
77
  [documentation for forms](https://docs.streamlit.io/develop/api-reference/execution-flow/st.form).
73
78
  """
74
79
 
75
- DownloadButtonDataType: TypeAlias = str | bytes | TextIO | BinaryIO | io.RawIOBase
80
+ DownloadButtonDataType: TypeAlias = (
81
+ str
82
+ | bytes
83
+ | TextIO
84
+ | BinaryIO
85
+ | io.RawIOBase
86
+ | Callable[[], str | bytes | TextIO | BinaryIO | io.RawIOBase]
87
+ )
76
88
 
77
89
 
78
90
  @dataclass
@@ -100,6 +112,7 @@ class ButtonMixin:
100
112
  disabled: bool = False,
101
113
  use_container_width: bool | None = None,
102
114
  width: Width = "content",
115
+ shortcut: str | None = None,
103
116
  ) -> bool:
104
117
  r"""Display a button widget.
105
118
 
@@ -173,6 +186,8 @@ class ButtonMixin:
173
186
  <https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
174
187
  font library.
175
188
 
189
+ - ``"spinner"``: Displays a spinner as an icon.
190
+
176
191
  disabled : bool
177
192
  An optional boolean that disables the button if set to ``True``.
178
193
  The default is ``False``.
@@ -205,6 +220,33 @@ class ButtonMixin:
205
220
  the parent container, the width of the button matches the width
206
221
  of the parent container.
207
222
 
223
+ shortcut : str or None
224
+ An optional keyboard shortcut that triggers the button. This can be
225
+ one of the following strings:
226
+
227
+ - A single alphanumeric key like ``"K"`` or ``"4"``.
228
+ - A function key like ``"F11"``.
229
+ - A special key like ``"Enter"``, ``"Esc"``, or ``"Tab"``.
230
+ - Any of the above combined with modifiers. For example, you can use
231
+ ``"Ctrl+K"`` or ``"Cmd+Shift+O"``.
232
+
233
+ .. important::
234
+ The keys ``"C"`` and ``"R"`` are reserved and can't be used,
235
+ even with modifiers. Punctuation keys like ``"."`` and ``","``
236
+ aren't currently supported.
237
+
238
+ The following special keys are supported: Backspace, Delete, Down,
239
+ End, Enter, Esc, Home, Left, PageDown, PageUp, Right, Space, Tab,
240
+ and Up.
241
+
242
+ The following modifiers are supported: Alt, Ctrl, Cmd, Meta, Mod,
243
+ Option, Shift.
244
+
245
+ - Ctrl, Cmd, Meta, and Mod are interchangeable and will display to
246
+ the user to match their platform.
247
+ - Option and Alt are interchangeable and will display to the user
248
+ to match their platform.
249
+
208
250
  Returns
209
251
  -------
210
252
  bool
@@ -249,6 +291,25 @@ class ButtonMixin:
249
291
  https://doc-button-icons.streamlit.app/
250
292
  height: 220px
251
293
 
294
+ **Example 3: Use keyboard shortcuts**
295
+
296
+ The following example shows how to use keyboard shortcuts to trigger a
297
+ button. If you use any of the platform-dependent modifiers (Ctrl, Cmd,
298
+ or Mod), they are interpreted interchangeably and always displayed to
299
+ the user to match their platform.
300
+
301
+ >>> import streamlit as st
302
+ >>>
303
+ >>> with st.container(horizontal=True, horizontal_alignment="distribute"):
304
+ >>> "`A`" if st.button("A", shortcut="A") else "` `"
305
+ >>> "`S`" if st.button("S", shortcut="Ctrl+S") else "` `"
306
+ >>> "`D`" if st.button("D", shortcut="Cmd+Shift+D") else "` `"
307
+ >>> "`F`" if st.button("F", shortcut="Mod+Alt+Shift+F") else "` `"
308
+
309
+ .. output::
310
+ https://doc-button-shortcuts.streamlit.app/
311
+ height: 220px
312
+
252
313
  """
253
314
  key = to_key(key)
254
315
  ctx = get_script_run_ctx()
@@ -276,6 +337,7 @@ class ButtonMixin:
276
337
  icon=icon,
277
338
  ctx=ctx,
278
339
  width=width,
340
+ shortcut=shortcut,
279
341
  )
280
342
 
281
343
  @gather_metrics("download_button")
@@ -296,15 +358,18 @@ class ButtonMixin:
296
358
  disabled: bool = False,
297
359
  use_container_width: bool | None = None,
298
360
  width: Width = "content",
361
+ shortcut: str | None = None,
299
362
  ) -> bool:
300
363
  r"""Display a download button widget.
301
364
 
302
365
  This is useful when you would like to provide a way for your users
303
366
  to download a file directly from your app.
304
367
 
305
- Note that the data to be downloaded is stored in-memory while the
306
- user is connected, so it's a good idea to keep file sizes under a
307
- couple hundred megabytes to conserve memory.
368
+ If you pass the data directly to the ``data`` parameter, then the data
369
+ is stored in-memory while the user is connected. It's a good idea to
370
+ keep file sizes under a couple hundred megabytes to conserve memory or
371
+ use deferred data generation by passing a callable to the ``data``
372
+ parameter.
308
373
 
309
374
  If you want to prevent your app from rerunning when a user clicks the
310
375
  download button, wrap the download button in a `fragment
@@ -330,10 +395,22 @@ class ButtonMixin:
330
395
  .. |st.markdown| replace:: ``st.markdown``
331
396
  .. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
332
397
 
333
- data : str, bytes, or file
334
- The contents of the file to be downloaded.
398
+ data : str, bytes, file-like, or callable
399
+ The contents of the file to be downloaded or a callable that
400
+ returns the contents of the file.
401
+
402
+ File contents can be a string, bytes, or file-like object.
403
+ File-like objects include ``io.BytesIO``, ``io.StringIO``, or any
404
+ class that implements the abstract base class ``io.RawIOBase``.
405
+
406
+ If a callable is passed, it is executed when the user clicks
407
+ the download button and runs on a separate thread from the
408
+ resulting script rerun. This deferred generation is helpful for
409
+ large files to avoid blocking the page script. The callable can't
410
+ accept any arguments. If any Streamlit commands are executed inside
411
+ the callable, they will be ignored.
335
412
 
336
- To prevent unncecessary recomputation, use caching when converting
413
+ To prevent unnecessary recomputation, use caching when converting
337
414
  your data for download. For more information, see the Example 1
338
415
  below.
339
416
 
@@ -417,6 +494,8 @@ class ButtonMixin:
417
494
  <https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
418
495
  font library.
419
496
 
497
+ - ``"spinner"``: Displays a spinner as an icon.
498
+
420
499
  disabled : bool
421
500
  An optional boolean that disables the download button if set to
422
501
  ``True``. The default is ``False``.
@@ -449,6 +528,27 @@ class ButtonMixin:
449
528
  the parent container, the width of the button matches the width
450
529
  of the parent container.
451
530
 
531
+ shortcut : str or None
532
+ An optional keyboard shortcut that triggers the button. This can be
533
+ one of the following strings:
534
+
535
+ - A single alphanumeric key like ``"K"`` or ``"4"``.
536
+ - A function key like ``"F11"``.
537
+ - A special key like ``"Enter"``, ``"Esc"``, or ``"Tab"``.
538
+ - Any of the above combined with modifiers. For example, you can use
539
+ ``"Ctrl+K"`` or ``"Cmd+Shift+O"``.
540
+
541
+ .. important::
542
+ The keys ``"C"`` and ``"R"`` are reserved and can't be used,
543
+ even with modifiers. Punctuation keys like ``"."`` and ``","``
544
+ aren't currently supported.
545
+
546
+ For a list of supported keys and modifiers, see the documentation
547
+ for |st.button|_.
548
+
549
+ .. |st.button| replace:: ``st.button``
550
+ .. _st.button: https://docs.streamlit.io/develop/api-reference/widgets/st.button
551
+
452
552
  Returns
453
553
  -------
454
554
  bool
@@ -559,6 +659,31 @@ class ButtonMixin:
559
659
  https://doc-download-button-file.streamlit.app/
560
660
  height: 200px
561
661
 
662
+ **Example 4: Generate the data on-click with a callable**
663
+
664
+ Pass a callable to ``data`` to generate the bytes lazily when the user
665
+ clicks the button. Streamlit commands inside this callable are ignored.
666
+ The callable can't accept any arguments and must return a file-like
667
+ object.
668
+
669
+ >>> import streamlit as st
670
+ >>> import time
671
+ >>>
672
+ >>> def make_report():
673
+ >>> time.sleep(1)
674
+ >>> return "col1,col2\n1,2\n3,4".encode("utf-8")
675
+ >>>
676
+ >>> st.download_button(
677
+ ... label="Download report",
678
+ ... data=make_report,
679
+ ... file_name="report.csv",
680
+ ... mime="text/csv",
681
+ ... )
682
+
683
+ .. output::
684
+ https://doc-download-button-deferred.streamlit.app/
685
+ height: 200px
686
+
562
687
  """
563
688
  ctx = get_script_run_ctx()
564
689
 
@@ -586,6 +711,7 @@ class ButtonMixin:
586
711
  disabled=disabled,
587
712
  ctx=ctx,
588
713
  width=width,
714
+ shortcut=shortcut,
589
715
  )
590
716
 
591
717
  @gather_metrics("link_button")
@@ -600,6 +726,7 @@ class ButtonMixin:
600
726
  disabled: bool = False,
601
727
  use_container_width: bool | None = None,
602
728
  width: Width = "content",
729
+ shortcut: str | None = None,
603
730
  ) -> DeltaGenerator:
604
731
  r"""Display a link button element.
605
732
 
@@ -665,6 +792,8 @@ class ButtonMixin:
665
792
  <https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
666
793
  font library.
667
794
 
795
+ - ``"spinner"``: Displays a spinner as an icon.
796
+
668
797
  disabled : bool
669
798
  An optional boolean that disables the link button if set to
670
799
  ``True``. The default is ``False``.
@@ -697,6 +826,27 @@ class ButtonMixin:
697
826
  the parent container, the width of the button matches the width
698
827
  of the parent container.
699
828
 
829
+ shortcut : str or None
830
+ An optional keyboard shortcut that triggers the button. This can be
831
+ one of the following strings:
832
+
833
+ - A single alphanumeric key like ``"K"`` or ``"4"``.
834
+ - A function key like ``"F11"``.
835
+ - A special key like ``"Enter"``, ``"Esc"``, or ``"Tab"``.
836
+ - Any of the above combined with modifiers. For example, you can use
837
+ ``"Ctrl+K"`` or ``"Cmd+Shift+O"``.
838
+
839
+ .. important::
840
+ The keys ``"C"`` and ``"R"`` are reserved and can't be used,
841
+ even with modifiers. Punctuation keys like ``"."`` and ``","``
842
+ aren't currently supported.
843
+
844
+ For a list of supported keys and modifiers, see the documentation
845
+ for |st.button|_.
846
+
847
+ .. |st.button| replace:: ``st.button``
848
+ .. _st.button: https://docs.streamlit.io/develop/api-reference/widgets/st.button
849
+
700
850
  Example
701
851
  -------
702
852
  >>> import streamlit as st
@@ -726,6 +876,7 @@ class ButtonMixin:
726
876
  type=type,
727
877
  icon=icon,
728
878
  width=width,
879
+ shortcut=shortcut,
729
880
  )
730
881
 
731
882
  @gather_metrics("page_link")
@@ -739,6 +890,7 @@ class ButtonMixin:
739
890
  disabled: bool = False,
740
891
  use_container_width: bool | None = None,
741
892
  width: Width = "content",
893
+ query_params: QueryParamsInput | None = None,
742
894
  ) -> DeltaGenerator:
743
895
  r"""Display a link to another page in a multipage app or to an external page.
744
896
 
@@ -793,6 +945,8 @@ class ButtonMixin:
793
945
  <https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
794
946
  font library.
795
947
 
948
+ - ``"spinner"``: Displays a spinner as an icon.
949
+
796
950
  help : str or None
797
951
  A tooltip that gets displayed when the link is hovered over. If
798
952
  this is ``None`` (default), no tooltip is displayed.
@@ -829,15 +983,27 @@ class ButtonMixin:
829
983
  the parent container, the width of the button matches the width
830
984
  of the parent container.
831
985
 
986
+ query_params : dict, list of tuples, or None
987
+ Query parameters to apply when navigating to the target page.
988
+ This can be a dictionary or an iterable of key-value tuples. Values can
989
+ be strings or iterables of strings (for repeated keys). When this is
990
+ ``None`` (default), all non-embed query parameters are cleared during
991
+ navigation.
992
+
832
993
  Example
833
994
  -------
834
- Consider the following example given this file structure:
995
+ **Example 1: Basic usage**
835
996
 
836
- >>> your-repository/
837
- >>> ├── pages/
838
- >>> │ ├── page_1.py
839
- >>> │ └── page_2.py
840
- >>> └── your_app.py
997
+ The following example shows how to create page links in a multipage app
998
+ that uses the ``pages/`` directory:
999
+
1000
+ .. code-block:: text
1001
+
1002
+ your-repository/
1003
+ ├── pages/
1004
+ │ ├── page_1.py
1005
+ │ └── page_2.py
1006
+ └── your_app.py
841
1007
 
842
1008
  >>> import streamlit as st
843
1009
  >>>
@@ -854,10 +1020,34 @@ class ButtonMixin:
854
1020
  .. |client.showSidebarNavigation| replace:: ``client.showSidebarNavigation``
855
1021
  .. _client.showSidebarNavigation: https://docs.streamlit.io/develop/api-reference/configuration/config.toml#client
856
1022
 
857
- .. output ::
1023
+ .. output::
858
1024
  https://doc-page-link.streamlit.app/
859
1025
  height: 350px
860
1026
 
1027
+ **Example 2: Passing query parameters**
1028
+
1029
+ The following example shows how to pass query parameters when creating a
1030
+ page link in a multipage app:
1031
+
1032
+ .. code-block:: text
1033
+
1034
+ your-repository/
1035
+ ├── page_2.py
1036
+ └── your_app.py
1037
+
1038
+ >>> import streamlit as st
1039
+ >>>
1040
+ >>> def page_1():
1041
+ >>> st.title("Page 1")
1042
+ >>> st.page_link("page_2.py", query_params={"utm_source": "page_1"})
1043
+ >>>
1044
+ >>> pg = st.navigation([page_1, "page_2.py"])
1045
+ >>> pg.run()
1046
+
1047
+ .. output::
1048
+ https://doc-page-link-query-params.streamlit.app/
1049
+ height: 350px
1050
+
861
1051
  """
862
1052
  if use_container_width is not None:
863
1053
  width = "stretch" if use_container_width else "content"
@@ -873,6 +1063,7 @@ class ButtonMixin:
873
1063
  help=help,
874
1064
  disabled=disabled,
875
1065
  width=width,
1066
+ query_params=query_params,
876
1067
  )
877
1068
 
878
1069
  def _download_button(
@@ -892,6 +1083,7 @@ class ButtonMixin:
892
1083
  disabled: bool = False,
893
1084
  ctx: ScriptRunContext | None = None,
894
1085
  width: Width = "content",
1086
+ shortcut: str | None = None,
895
1087
  ) -> bool:
896
1088
  key = to_key(key)
897
1089
 
@@ -901,6 +1093,10 @@ class ButtonMixin:
901
1093
  else cast("WidgetCallback", on_click)
902
1094
  )
903
1095
 
1096
+ normalized_shortcut: str | None = None
1097
+ if shortcut is not None:
1098
+ normalized_shortcut = normalize_shortcut(shortcut)
1099
+
904
1100
  check_widget_policies(
905
1101
  self.dg,
906
1102
  key,
@@ -921,6 +1117,7 @@ class ButtonMixin:
921
1117
  help=help,
922
1118
  type=type,
923
1119
  width=width,
1120
+ shortcut=normalized_shortcut,
924
1121
  )
925
1122
 
926
1123
  if is_in_form(self.dg):
@@ -949,6 +1146,9 @@ class ButtonMixin:
949
1146
  else:
950
1147
  download_button_proto.ignore_rerun = False
951
1148
 
1149
+ if normalized_shortcut is not None:
1150
+ download_button_proto.shortcut = normalized_shortcut
1151
+
952
1152
  serde = ButtonSerde()
953
1153
 
954
1154
  button_state = register_widget(
@@ -979,8 +1179,30 @@ class ButtonMixin:
979
1179
  icon: str | None = None,
980
1180
  disabled: bool = False,
981
1181
  width: Width = "content",
1182
+ shortcut: str | None = None,
982
1183
  ) -> DeltaGenerator:
983
1184
  link_button_proto = LinkButtonProto()
1185
+ normalized_shortcut: str | None = None
1186
+ if shortcut is not None:
1187
+ normalized_shortcut = normalize_shortcut(shortcut)
1188
+
1189
+ if normalized_shortcut is not None:
1190
+ # We only register the element ID if a shortcut is provide.
1191
+ # The ID is required to correctly register and handle the shortcut
1192
+ # on the client side.
1193
+ link_button_proto.id = compute_and_register_element_id(
1194
+ "link_button",
1195
+ user_key=None,
1196
+ key_as_main_identity=False,
1197
+ dg=self.dg,
1198
+ label=label,
1199
+ icon=icon,
1200
+ url=url,
1201
+ help=help,
1202
+ type=type,
1203
+ width=width,
1204
+ shortcut=normalized_shortcut,
1205
+ )
984
1206
  link_button_proto.label = label
985
1207
  link_button_proto.url = url
986
1208
  link_button_proto.type = type
@@ -992,6 +1214,9 @@ class ButtonMixin:
992
1214
  if icon is not None:
993
1215
  link_button_proto.icon = validate_icon_or_emoji(icon)
994
1216
 
1217
+ if normalized_shortcut is not None:
1218
+ link_button_proto.shortcut = normalized_shortcut
1219
+
995
1220
  validate_width(width, allow_content=True)
996
1221
  layout_config = LayoutConfig(width=width)
997
1222
  return self.dg._enqueue(
@@ -1007,8 +1232,12 @@ class ButtonMixin:
1007
1232
  help: str | None = None,
1008
1233
  disabled: bool = False,
1009
1234
  width: Width = "content",
1235
+ query_params: QueryParamsInput | None = None,
1010
1236
  ) -> DeltaGenerator:
1011
1237
  page_link_proto = PageLinkProto()
1238
+ if query_params:
1239
+ page_link_proto.query_string = process_query_params(query_params)
1240
+
1012
1241
  validate_width(width, allow_content=True)
1013
1242
 
1014
1243
  ctx = get_script_run_ctx()
@@ -1103,9 +1332,14 @@ class ButtonMixin:
1103
1332
  disabled: bool = False,
1104
1333
  ctx: ScriptRunContext | None = None,
1105
1334
  width: Width = "content",
1335
+ shortcut: str | None = None,
1106
1336
  ) -> bool:
1107
1337
  key = to_key(key)
1108
1338
 
1339
+ normalized_shortcut: str | None = None
1340
+ if shortcut is not None:
1341
+ normalized_shortcut = normalize_shortcut(shortcut)
1342
+
1109
1343
  check_widget_policies(
1110
1344
  self.dg,
1111
1345
  key,
@@ -1128,6 +1362,7 @@ class ButtonMixin:
1128
1362
  is_form_submitter=is_form_submitter,
1129
1363
  type=type,
1130
1364
  width=width,
1365
+ shortcut=normalized_shortcut,
1131
1366
  )
1132
1367
 
1133
1368
  # It doesn't make sense to create a button inside a form (except
@@ -1160,6 +1395,9 @@ class ButtonMixin:
1160
1395
  if icon is not None:
1161
1396
  button_proto.icon = validate_icon_or_emoji(icon)
1162
1397
 
1398
+ if normalized_shortcut is not None:
1399
+ button_proto.shortcut = normalized_shortcut
1400
+
1163
1401
  serde = ButtonSerde()
1164
1402
 
1165
1403
  button_state = register_widget(
@@ -1195,32 +1433,33 @@ def marshall_file(
1195
1433
  mimetype: str | None,
1196
1434
  file_name: str | None = None,
1197
1435
  ) -> None:
1198
- data_as_bytes: bytes
1199
- if isinstance(data, str):
1200
- data_as_bytes = data.encode()
1201
- mimetype = mimetype or "text/plain"
1202
- elif isinstance(data, io.TextIOWrapper):
1203
- string_data = data.read()
1204
- data_as_bytes = string_data.encode()
1205
- mimetype = mimetype or "text/plain"
1206
- # Assume bytes; try methods until we run out.
1207
- elif isinstance(data, bytes):
1208
- data_as_bytes = data
1209
- mimetype = mimetype or "application/octet-stream"
1210
- elif isinstance(data, io.BytesIO):
1211
- data.seek(0)
1212
- data_as_bytes = data.getvalue()
1213
- mimetype = mimetype or "application/octet-stream"
1214
- elif isinstance(data, io.BufferedReader):
1215
- data.seek(0)
1216
- data_as_bytes = data.read()
1217
- mimetype = mimetype or "application/octet-stream"
1218
- elif isinstance(data, io.RawIOBase):
1219
- data.seek(0)
1220
- data_as_bytes = data.read() or b""
1221
- mimetype = mimetype or "application/octet-stream"
1222
- else:
1223
- raise StreamlitAPIException(f"Invalid binary data format: {type(data)}")
1436
+ # Check if data is a callable (for deferred downloads)
1437
+ if callable(data):
1438
+ if not runtime.exists():
1439
+ # When running in "raw mode", we can't access the MediaFileManager.
1440
+ proto_download_button.url = ""
1441
+ return
1442
+
1443
+ # Register the callable for deferred execution
1444
+ file_id = runtime.get_instance().media_file_mgr.add_deferred(
1445
+ data,
1446
+ mimetype,
1447
+ coordinates,
1448
+ file_name=file_name,
1449
+ )
1450
+ proto_download_button.deferred_file_id = file_id
1451
+ proto_download_button.url = "" # No URL yet, will be generated on click
1452
+ return
1453
+
1454
+ # Existing logic for non-callable data
1455
+ data_as_bytes, inferred_mime_type = convert_data_to_bytes_and_infer_mime(
1456
+ data,
1457
+ unsupported_error=StreamlitAPIException(
1458
+ f"Invalid binary data format: {type(data)}"
1459
+ ),
1460
+ )
1461
+ if mimetype is None:
1462
+ mimetype = inferred_mime_type
1224
1463
 
1225
1464
  if runtime.exists():
1226
1465
  file_url = runtime.get_instance().media_file_mgr.add(