streamlit-nightly 1.41.2.dev20241227__py2.py3-none-any.whl → 1.41.2.dev20250124__py2.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 (429) hide show
  1. streamlit/__init__.py +11 -2
  2. streamlit/__main__.py +1 -1
  3. streamlit/auth_util.py +209 -0
  4. streamlit/cli_util.py +1 -1
  5. streamlit/column_config.py +1 -1
  6. streamlit/commands/__init__.py +1 -1
  7. streamlit/commands/echo.py +1 -1
  8. streamlit/commands/execution_control.py +1 -1
  9. streamlit/commands/experimental_query_params.py +1 -1
  10. streamlit/commands/logo.py +1 -1
  11. streamlit/commands/navigation.py +16 -15
  12. streamlit/commands/page_config.py +1 -1
  13. streamlit/components/__init__.py +1 -1
  14. streamlit/components/lib/__init__.py +1 -1
  15. streamlit/components/lib/local_component_registry.py +1 -1
  16. streamlit/components/types/__init__.py +1 -1
  17. streamlit/components/types/base_component_registry.py +1 -1
  18. streamlit/components/types/base_custom_component.py +1 -1
  19. streamlit/components/v1/__init__.py +1 -1
  20. streamlit/components/v1/component_arrow.py +1 -1
  21. streamlit/components/v1/component_registry.py +1 -1
  22. streamlit/components/v1/components.py +1 -1
  23. streamlit/components/v1/custom_component.py +1 -1
  24. streamlit/config.py +18 -3
  25. streamlit/config_option.py +10 -19
  26. streamlit/config_util.py +1 -1
  27. streamlit/connections/__init__.py +1 -1
  28. streamlit/connections/base_connection.py +1 -1
  29. streamlit/connections/snowflake_connection.py +31 -50
  30. streamlit/connections/snowpark_connection.py +1 -1
  31. streamlit/connections/sql_connection.py +1 -1
  32. streamlit/connections/util.py +1 -1
  33. streamlit/cursor.py +1 -1
  34. streamlit/dataframe_util.py +3 -3
  35. streamlit/delta_generator.py +7 -3
  36. streamlit/delta_generator_singletons.py +1 -1
  37. streamlit/deprecation_util.py +1 -1
  38. streamlit/development.py +1 -1
  39. streamlit/elements/__init__.py +1 -1
  40. streamlit/elements/alert.py +1 -1
  41. streamlit/elements/arrow.py +25 -3
  42. streamlit/elements/balloons.py +1 -1
  43. streamlit/elements/bokeh_chart.py +1 -1
  44. streamlit/elements/code.py +9 -1
  45. streamlit/elements/deck_gl_json_chart.py +1 -1
  46. streamlit/elements/dialog_decorator.py +1 -1
  47. streamlit/elements/doc_string.py +1 -1
  48. streamlit/elements/empty.py +1 -1
  49. streamlit/elements/exception.py +8 -10
  50. streamlit/elements/form.py +1 -1
  51. streamlit/elements/graphviz_chart.py +1 -1
  52. streamlit/elements/heading.py +1 -1
  53. streamlit/elements/html.py +1 -1
  54. streamlit/elements/iframe.py +1 -1
  55. streamlit/elements/image.py +1 -1
  56. streamlit/elements/json.py +1 -1
  57. streamlit/elements/layouts.py +1 -1
  58. streamlit/elements/lib/__init__.py +1 -1
  59. streamlit/elements/lib/built_in_chart_utils.py +1 -1
  60. streamlit/elements/lib/color_util.py +1 -1
  61. streamlit/elements/lib/column_config_utils.py +1 -1
  62. streamlit/elements/lib/column_types.py +1 -1
  63. streamlit/elements/lib/dialog.py +1 -1
  64. streamlit/elements/lib/dicttools.py +1 -1
  65. streamlit/elements/lib/event_utils.py +1 -1
  66. streamlit/elements/lib/file_uploader_utils.py +1 -1
  67. streamlit/elements/lib/form_utils.py +1 -1
  68. streamlit/elements/lib/image_utils.py +1 -1
  69. streamlit/elements/lib/js_number.py +1 -1
  70. streamlit/elements/lib/mutable_status_container.py +1 -1
  71. streamlit/elements/lib/options_selector_utils.py +1 -1
  72. streamlit/elements/lib/pandas_styler_utils.py +1 -1
  73. streamlit/elements/lib/policies.py +1 -1
  74. streamlit/elements/lib/streamlit_plotly_theme.py +1 -1
  75. streamlit/elements/lib/subtitle_utils.py +1 -1
  76. streamlit/elements/lib/utils.py +1 -1
  77. streamlit/elements/map.py +1 -1
  78. streamlit/elements/markdown.py +3 -3
  79. streamlit/elements/media.py +1 -1
  80. streamlit/elements/metric.py +1 -1
  81. streamlit/elements/plotly_chart.py +1 -1
  82. streamlit/elements/progress.py +1 -1
  83. streamlit/elements/pyplot.py +1 -1
  84. streamlit/elements/snow.py +1 -1
  85. streamlit/elements/spinner.py +14 -7
  86. streamlit/elements/text.py +1 -1
  87. streamlit/elements/toast.py +1 -1
  88. streamlit/elements/vega_charts.py +20 -6
  89. streamlit/elements/widgets/__init__.py +1 -1
  90. streamlit/elements/widgets/audio_input.py +1 -1
  91. streamlit/elements/widgets/button.py +9 -8
  92. streamlit/elements/widgets/button_group.py +6 -4
  93. streamlit/elements/widgets/camera_input.py +1 -1
  94. streamlit/elements/widgets/chat.py +1 -1
  95. streamlit/elements/widgets/checkbox.py +1 -1
  96. streamlit/elements/widgets/color_picker.py +1 -1
  97. streamlit/elements/widgets/data_editor.py +6 -5
  98. streamlit/elements/widgets/file_uploader.py +1 -1
  99. streamlit/elements/widgets/multiselect.py +1 -1
  100. streamlit/elements/widgets/number_input.py +1 -1
  101. streamlit/elements/widgets/radio.py +1 -1
  102. streamlit/elements/widgets/select_slider.py +1 -1
  103. streamlit/elements/widgets/selectbox.py +1 -1
  104. streamlit/elements/widgets/slider.py +1 -1
  105. streamlit/elements/widgets/text_widgets.py +1 -1
  106. streamlit/elements/widgets/time_widgets.py +63 -3
  107. streamlit/elements/write.py +2 -2
  108. streamlit/emojis.py +2 -2
  109. streamlit/env_util.py +1 -1
  110. streamlit/error_util.py +9 -3
  111. streamlit/errors.py +5 -1
  112. streamlit/external/__init__.py +1 -1
  113. streamlit/external/langchain/__init__.py +1 -1
  114. streamlit/external/langchain/streamlit_callback_handler.py +1 -1
  115. streamlit/file_util.py +1 -1
  116. streamlit/git_util.py +1 -1
  117. streamlit/hello/__init__.py +1 -1
  118. streamlit/hello/animation_demo.py +1 -1
  119. streamlit/hello/dataframe_demo.py +1 -1
  120. streamlit/hello/hello.py +1 -1
  121. streamlit/hello/mapping_demo.py +1 -1
  122. streamlit/hello/plotting_demo.py +1 -1
  123. streamlit/hello/streamlit_app.py +1 -1
  124. streamlit/hello/utils.py +1 -1
  125. streamlit/logger.py +1 -1
  126. streamlit/material_icon_names.py +2 -2
  127. streamlit/navigation/__init__.py +1 -1
  128. streamlit/navigation/page.py +7 -4
  129. streamlit/net_util.py +1 -1
  130. streamlit/platform.py +1 -1
  131. streamlit/proto/Alert_pb2.pyi +1 -1
  132. streamlit/proto/AppPage_pb2.pyi +1 -1
  133. streamlit/proto/ArrowNamedDataSet_pb2.pyi +1 -1
  134. streamlit/proto/ArrowVegaLiteChart_pb2.pyi +1 -1
  135. streamlit/proto/Arrow_pb2.pyi +1 -1
  136. streamlit/proto/AudioInput_pb2.pyi +1 -1
  137. streamlit/proto/Audio_pb2.pyi +1 -1
  138. streamlit/proto/AuthRedirect_pb2.py +27 -0
  139. streamlit/proto/AuthRedirect_pb2.pyi +41 -0
  140. streamlit/proto/AutoRerun_pb2.pyi +1 -1
  141. streamlit/proto/BackMsg_pb2.pyi +1 -1
  142. streamlit/proto/Balloons_pb2.pyi +1 -1
  143. streamlit/proto/Block_pb2.pyi +1 -1
  144. streamlit/proto/BokehChart_pb2.pyi +1 -1
  145. streamlit/proto/ButtonGroup_pb2.pyi +1 -1
  146. streamlit/proto/Button_pb2.pyi +1 -1
  147. streamlit/proto/CameraInput_pb2.pyi +1 -1
  148. streamlit/proto/ChatInput_pb2.pyi +1 -1
  149. streamlit/proto/Checkbox_pb2.pyi +1 -1
  150. streamlit/proto/ClientState_pb2.pyi +1 -1
  151. streamlit/proto/Code_pb2.py +2 -2
  152. streamlit/proto/Code_pb2.pyi +5 -2
  153. streamlit/proto/ColorPicker_pb2.pyi +1 -1
  154. streamlit/proto/Common_pb2.pyi +1 -1
  155. streamlit/proto/Components_pb2.pyi +1 -1
  156. streamlit/proto/DataFrame_pb2.pyi +1 -1
  157. streamlit/proto/DateInput_pb2.pyi +1 -1
  158. streamlit/proto/DeckGlJsonChart_pb2.pyi +1 -1
  159. streamlit/proto/Delta_pb2.pyi +1 -1
  160. streamlit/proto/DocString_pb2.pyi +1 -1
  161. streamlit/proto/DownloadButton_pb2.pyi +1 -1
  162. streamlit/proto/Element_pb2.pyi +1 -1
  163. streamlit/proto/Empty_pb2.pyi +1 -1
  164. streamlit/proto/Exception_pb2.pyi +1 -1
  165. streamlit/proto/Favicon_pb2.pyi +1 -1
  166. streamlit/proto/FileUploader_pb2.pyi +1 -1
  167. streamlit/proto/ForwardMsg_pb2.py +12 -9
  168. streamlit/proto/ForwardMsg_pb2.pyi +34 -4
  169. streamlit/proto/GitInfo_pb2.pyi +1 -1
  170. streamlit/proto/GraphVizChart_pb2.pyi +1 -1
  171. streamlit/proto/Heading_pb2.pyi +1 -1
  172. streamlit/proto/Html_pb2.pyi +1 -1
  173. streamlit/proto/IFrame_pb2.pyi +1 -1
  174. streamlit/proto/Image_pb2.pyi +1 -1
  175. streamlit/proto/Json_pb2.pyi +1 -1
  176. streamlit/proto/LabelVisibilityMessage_pb2.pyi +1 -1
  177. streamlit/proto/LinkButton_pb2.pyi +1 -1
  178. streamlit/proto/Logo_pb2.pyi +1 -1
  179. streamlit/proto/Markdown_pb2.pyi +1 -1
  180. streamlit/proto/Metric_pb2.pyi +1 -1
  181. streamlit/proto/MetricsEvent_pb2.pyi +1 -1
  182. streamlit/proto/MultiSelect_pb2.pyi +1 -1
  183. streamlit/proto/NamedDataSet_pb2.pyi +1 -1
  184. streamlit/proto/Navigation_pb2.pyi +1 -1
  185. streamlit/proto/NewSession_pb2.pyi +1 -1
  186. streamlit/proto/NumberInput_pb2.pyi +1 -1
  187. streamlit/proto/PageConfig_pb2.pyi +1 -1
  188. streamlit/proto/PageInfo_pb2.pyi +1 -1
  189. streamlit/proto/PageLink_pb2.pyi +1 -1
  190. streamlit/proto/PageNotFound_pb2.pyi +1 -1
  191. streamlit/proto/PageProfile_pb2.pyi +1 -1
  192. streamlit/proto/PagesChanged_pb2.pyi +1 -1
  193. streamlit/proto/ParentMessage_pb2.pyi +1 -1
  194. streamlit/proto/PlotlyChart_pb2.pyi +1 -1
  195. streamlit/proto/Progress_pb2.pyi +1 -1
  196. streamlit/proto/Radio_pb2.pyi +1 -1
  197. streamlit/proto/RootContainer_pb2.pyi +1 -1
  198. streamlit/proto/Selectbox_pb2.pyi +1 -1
  199. streamlit/proto/SessionEvent_pb2.pyi +1 -1
  200. streamlit/proto/SessionStatus_pb2.pyi +1 -1
  201. streamlit/proto/Skeleton_pb2.pyi +1 -1
  202. streamlit/proto/Slider_pb2.pyi +1 -1
  203. streamlit/proto/Snow_pb2.pyi +1 -1
  204. streamlit/proto/Spinner_pb2.py +2 -2
  205. streamlit/proto/Spinner_pb2.pyi +6 -2
  206. streamlit/proto/TextArea_pb2.pyi +1 -1
  207. streamlit/proto/TextInput_pb2.pyi +1 -1
  208. streamlit/proto/Text_pb2.pyi +1 -1
  209. streamlit/proto/TimeInput_pb2.pyi +1 -1
  210. streamlit/proto/Toast_pb2.pyi +1 -1
  211. streamlit/proto/VegaLiteChart_pb2.pyi +1 -1
  212. streamlit/proto/Video_pb2.pyi +1 -1
  213. streamlit/proto/WidgetStates_pb2.pyi +1 -1
  214. streamlit/proto/__init__.py +1 -1
  215. streamlit/runtime/__init__.py +1 -1
  216. streamlit/runtime/app_session.py +29 -5
  217. streamlit/runtime/caching/__init__.py +1 -1
  218. streamlit/runtime/caching/cache_data_api.py +3 -3
  219. streamlit/runtime/caching/cache_errors.py +1 -1
  220. streamlit/runtime/caching/cache_resource_api.py +1 -1
  221. streamlit/runtime/caching/cache_type.py +1 -1
  222. streamlit/runtime/caching/cache_utils.py +5 -4
  223. streamlit/runtime/caching/cached_message_replay.py +1 -1
  224. streamlit/runtime/caching/hashing.py +1 -1
  225. streamlit/runtime/caching/legacy_cache_api.py +1 -1
  226. streamlit/runtime/caching/storage/__init__.py +1 -1
  227. streamlit/runtime/caching/storage/cache_storage_protocol.py +1 -1
  228. streamlit/runtime/caching/storage/dummy_cache_storage.py +1 -1
  229. streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py +1 -1
  230. streamlit/runtime/caching/storage/local_disk_cache_storage.py +5 -5
  231. streamlit/runtime/connection_factory.py +1 -1
  232. streamlit/runtime/context.py +1 -1
  233. streamlit/runtime/credentials.py +5 -5
  234. streamlit/runtime/forward_msg_cache.py +4 -2
  235. streamlit/runtime/forward_msg_queue.py +33 -5
  236. streamlit/runtime/fragment.py +2 -2
  237. streamlit/runtime/media_file_manager.py +1 -1
  238. streamlit/runtime/media_file_storage.py +1 -1
  239. streamlit/runtime/memory_media_file_storage.py +1 -1
  240. streamlit/runtime/memory_session_storage.py +1 -1
  241. streamlit/runtime/memory_uploaded_file_manager.py +1 -1
  242. streamlit/runtime/metrics_util.py +2 -1
  243. streamlit/runtime/pages_manager.py +4 -2
  244. streamlit/runtime/runtime.py +22 -6
  245. streamlit/runtime/runtime_util.py +1 -1
  246. streamlit/runtime/script_data.py +1 -1
  247. streamlit/runtime/scriptrunner/__init__.py +1 -1
  248. streamlit/runtime/scriptrunner/exec_code.py +34 -1
  249. streamlit/runtime/scriptrunner/magic.py +1 -1
  250. streamlit/runtime/scriptrunner/magic_funcs.py +1 -1
  251. streamlit/runtime/scriptrunner/script_cache.py +1 -1
  252. streamlit/runtime/scriptrunner/script_runner.py +23 -12
  253. streamlit/runtime/scriptrunner_utils/__init__.py +1 -1
  254. streamlit/runtime/scriptrunner_utils/exceptions.py +1 -1
  255. streamlit/runtime/scriptrunner_utils/script_requests.py +2 -1
  256. streamlit/runtime/scriptrunner_utils/script_run_context.py +2 -2
  257. streamlit/runtime/secrets.py +1 -1
  258. streamlit/runtime/session_manager.py +2 -2
  259. streamlit/runtime/state/__init__.py +1 -1
  260. streamlit/runtime/state/common.py +1 -1
  261. streamlit/runtime/state/query_params.py +2 -2
  262. streamlit/runtime/state/query_params_proxy.py +15 -5
  263. streamlit/runtime/state/safe_session_state.py +1 -1
  264. streamlit/runtime/state/session_state.py +1 -1
  265. streamlit/runtime/state/session_state_proxy.py +1 -1
  266. streamlit/runtime/state/widgets.py +1 -1
  267. streamlit/runtime/stats.py +1 -1
  268. streamlit/runtime/uploaded_file_manager.py +1 -1
  269. streamlit/runtime/websocket_session_manager.py +2 -2
  270. streamlit/source_util.py +1 -1
  271. streamlit/static/index.html +3 -3
  272. streamlit/static/static/css/index.mUTQuMqR.css +1 -0
  273. streamlit/static/static/js/{FileDownload.esm.WslOojMp.js → FileDownload.esm.C1QvS8Zm.js} +1 -1
  274. streamlit/static/static/js/{FormClearHelper.DSgVZ-NK.js → FormClearHelper.mT4-5rFn.js} +1 -1
  275. streamlit/static/static/js/{Hooks.wzeYp0oD.js → Hooks.CVNEuaGG.js} +1 -1
  276. streamlit/static/static/js/InputInstructions.BCvAGBeC.js +1 -0
  277. streamlit/static/static/js/{ProgressBar.D1he2Gib.js → ProgressBar.CcQcYD9u.js} +2 -2
  278. streamlit/static/static/js/RenderInPortalIfExists.DHDG9PHn.js +1 -0
  279. streamlit/static/static/js/Toolbar.yTE_O6B7.js +1 -0
  280. streamlit/static/static/js/{base-input.gDrmzpbY.js → base-input.CQPY9_oN.js} +4 -4
  281. streamlit/static/static/js/createSuper.DE47Tkz4.js +1 -0
  282. streamlit/static/static/js/data-grid-overlay-editor.5JEQwfzp.js +1 -0
  283. streamlit/static/static/js/{downloader.B-SkHjg2.js → downloader.CX7_KkfB.js} +1 -1
  284. streamlit/static/static/js/{es6.DoVDVSd_.js → es6.Be2vwxoI.js} +2 -2
  285. streamlit/static/static/js/{iframeResizer.contentWindow.Exq_q4jU.js → iframeResizer.contentWindow.CFHofqC-.js} +1 -1
  286. streamlit/static/static/js/index.0OeiPvr3.js +1 -0
  287. streamlit/static/static/js/index.1GBg_Cdb.js +1 -0
  288. streamlit/static/static/js/index.4nNgvhbk.js +4 -0
  289. streamlit/static/static/js/index.5F9_LJ-9.js +1 -0
  290. streamlit/static/static/js/index.6v5ZgUAy.js +1 -0
  291. streamlit/static/static/js/index.8tVXPrLX.js +1 -0
  292. streamlit/static/static/js/index.AExANgn1.js +1 -0
  293. streamlit/static/static/js/index.AXOgS1NM.js +1 -0
  294. streamlit/static/static/js/index.B-xzXPZE.js +3 -0
  295. streamlit/static/static/js/index.B0pfFJGU.js +1 -0
  296. streamlit/static/static/js/index.BBjf7kH1.js +1 -0
  297. streamlit/static/static/js/index.BGICDmL0.js +201 -0
  298. streamlit/static/static/js/{index.rDDlhQFp.js → index.BOeLQ18h.js} +1 -1
  299. streamlit/static/static/js/index.BUgJRfqp.js +3 -0
  300. streamlit/static/static/js/index.BpeIAypN.js +1 -0
  301. streamlit/static/static/js/{index.BjQJsYG6.js → index.C0ZNaKGF.js} +1 -1
  302. streamlit/static/static/js/index.C3eR9AQ5.js +1 -0
  303. streamlit/static/static/js/index.CCmrDm8Z.js +2 -0
  304. streamlit/static/static/js/index.CHuo89tL.js +1 -0
  305. streamlit/static/static/js/index.Cbi_d9ak.js +197 -0
  306. streamlit/static/static/js/{index.CyRDf6c5.js → index.Cg7T4wJt.js} +3 -3
  307. streamlit/static/static/js/index.Ci3iebpl.js +1 -0
  308. streamlit/static/static/js/index.CjsaBedq.js +1 -0
  309. streamlit/static/static/js/{index.CLHVvMN0.js → index.CvqaNor_.js} +139 -128
  310. streamlit/static/static/js/index.D0lxtOCO.js +2 -0
  311. streamlit/static/static/js/index.D1AD4NsW.js +1 -0
  312. streamlit/static/static/js/{index.4lqQPLdu.js → index.DLjk_wlB.js} +1 -1
  313. streamlit/static/static/js/{index.DfwGDOWW.js → index.DNDhYAPI.js} +2 -2
  314. streamlit/static/static/js/index.DY6666R7.js +1 -0
  315. streamlit/static/static/js/{index.DaTKmBKa.js → index.Dkr_c_9x.js} +100 -100
  316. streamlit/static/static/js/{index.Bcz23DKe.js → index.Hhv6gSq2.js} +12 -12
  317. streamlit/static/static/js/{index.Bm2n8u7I.js → index.OM83ZHKg.js} +2 -2
  318. streamlit/static/static/js/index.SLleoa9s.js +1 -0
  319. streamlit/static/static/js/{index.2Rr5kbVL.js → index.XCwDes79.js} +1 -1
  320. streamlit/static/static/js/{index.B_RzfO7U.js → index.a6pesEXT.js} +1 -1
  321. streamlit/static/static/js/{index.CAkYXoVB.js → index.er3Zfn3e.js} +2 -2
  322. streamlit/static/static/js/{index.CZ-znj8N.js → index.mtSGfsND.js} +1 -1
  323. streamlit/static/static/js/{input.28wXD4uV.js → input.B07wSbwn.js} +2 -2
  324. streamlit/static/static/js/{memory.jbWyoc5k.js → memory.CC_p93jh.js} +1 -1
  325. streamlit/static/static/js/mergeWith.ivc75cD1.js +1 -0
  326. streamlit/static/static/js/number-overlay-editor.Db2Be6nq.js +9 -0
  327. streamlit/static/static/js/possibleConstructorReturn.BNprLGNF.js +1 -0
  328. streamlit/static/static/js/{sandbox.CfK9-cKf.js → sandbox.BlG3HhHL.js} +1 -1
  329. streamlit/static/static/js/{textarea.C5OZk1uL.js → textarea.Bz6Z-kmO.js} +2 -2
  330. streamlit/static/static/js/threshold.B8r8f5kt.js +1 -0
  331. streamlit/static/static/js/{timepicker.DEpQEk7W.js → timepicker.BMSb5NlP.js} +3 -3
  332. streamlit/static/static/js/timer.RueuYoQV.js +1 -0
  333. streamlit/static/static/js/toConsumableArray.uYLXBIlD.js +3 -0
  334. streamlit/static/static/js/uniqueId.BdisvZXg.js +1 -0
  335. streamlit/static/static/js/{useBasicWidgetState.C2DsWpWi.js → useBasicWidgetState.BJFGEQDL.js} +1 -1
  336. streamlit/static/static/js/{useOnInputChange.Bw6YkmJD.js → useOnInputChange.BmSN_ACz.js} +1 -1
  337. streamlit/static/static/js/value.iufjd77T.js +1 -0
  338. streamlit/static/static/js/withFullScreenWrapper.CbOOMX5j.js +1 -0
  339. streamlit/static/static/media/MaterialSymbols-Rounded.DzyB5T7Y.woff2 +0 -0
  340. streamlit/string_util.py +1 -1
  341. streamlit/temporary_directory.py +1 -1
  342. streamlit/testing/__init__.py +1 -1
  343. streamlit/testing/v1/__init__.py +1 -1
  344. streamlit/testing/v1/app_test.py +1 -1
  345. streamlit/testing/v1/element_tree.py +1 -1
  346. streamlit/testing/v1/local_script_runner.py +1 -1
  347. streamlit/testing/v1/util.py +1 -1
  348. streamlit/time_util.py +1 -1
  349. streamlit/type_util.py +2 -2
  350. streamlit/url_util.py +24 -1
  351. streamlit/user_info.py +445 -25
  352. streamlit/util.py +1 -1
  353. streamlit/version.py +1 -1
  354. streamlit/watcher/__init__.py +1 -1
  355. streamlit/watcher/event_based_path_watcher.py +1 -1
  356. streamlit/watcher/folder_black_list.py +1 -1
  357. streamlit/watcher/local_sources_watcher.py +5 -3
  358. streamlit/watcher/path_watcher.py +1 -1
  359. streamlit/watcher/polling_path_watcher.py +1 -1
  360. streamlit/watcher/util.py +87 -21
  361. streamlit/web/__init__.py +1 -1
  362. streamlit/web/bootstrap.py +22 -2
  363. streamlit/web/cache_storage_manager_config.py +1 -1
  364. streamlit/web/cli.py +1 -1
  365. streamlit/web/server/__init__.py +1 -1
  366. streamlit/web/server/app_static_file_handler.py +1 -1
  367. streamlit/web/server/authlib_tornado_integration.py +58 -0
  368. streamlit/web/server/browser_websocket_handler.py +73 -7
  369. streamlit/web/server/component_request_handler.py +1 -1
  370. streamlit/web/server/media_file_handler.py +1 -1
  371. streamlit/web/server/oauth_authlib_routes.py +176 -0
  372. streamlit/web/server/oidc_mixin.py +108 -0
  373. streamlit/web/server/routes.py +6 -4
  374. streamlit/web/server/server.py +41 -4
  375. streamlit/web/server/server_util.py +25 -1
  376. streamlit/web/server/stats_request_handler.py +1 -1
  377. streamlit/web/server/upload_file_request_handler.py +3 -2
  378. streamlit/web/server/websocket_headers.py +1 -1
  379. {streamlit_nightly-1.41.2.dev20241227.data → streamlit_nightly-1.41.2.dev20250124.data}/scripts/streamlit.cmd +1 -1
  380. {streamlit_nightly-1.41.2.dev20241227.dist-info → streamlit_nightly-1.41.2.dev20250124.dist-info}/METADATA +16 -4
  381. streamlit_nightly-1.41.2.dev20250124.dist-info/RECORD +560 -0
  382. {streamlit_nightly-1.41.2.dev20241227.dist-info → streamlit_nightly-1.41.2.dev20250124.dist-info}/WHEEL +1 -1
  383. streamlit/static/static/css/index.ZIJkhegp.css +0 -1
  384. streamlit/static/static/js/InputInstructions.DpTmOmkP.js +0 -1
  385. streamlit/static/static/js/RenderInPortalIfExists.CphMKHZ_.js +0 -1
  386. streamlit/static/static/js/Toolbar.YZjXpW2P.js +0 -1
  387. streamlit/static/static/js/_commonjs-dynamic-modules.TDtrdbi3.js +0 -1
  388. streamlit/static/static/js/createSuper.Cj3WfXha.js +0 -1
  389. streamlit/static/static/js/data-grid-overlay-editor.CXW3Vrt9.js +0 -1
  390. streamlit/static/static/js/getPrototypeOf.BZAK2f3t.js +0 -1
  391. streamlit/static/static/js/index.1auHKWgw.js +0 -3
  392. streamlit/static/static/js/index.2XFKVtzu.js +0 -1
  393. streamlit/static/static/js/index.B9O1l5Yf.js +0 -32
  394. streamlit/static/static/js/index.BjtDBCFh.js +0 -2
  395. streamlit/static/static/js/index.Bp-uIPRI.js +0 -1
  396. streamlit/static/static/js/index.BrGYP4fV.js +0 -1
  397. streamlit/static/static/js/index.BwUmNKlx.js +0 -1
  398. streamlit/static/static/js/index.CCVCD0op.js +0 -1
  399. streamlit/static/static/js/index.CP7C0jmi.js +0 -1
  400. streamlit/static/static/js/index.CVNaQiWI.js +0 -4
  401. streamlit/static/static/js/index.CX7qwffH.js +0 -1
  402. streamlit/static/static/js/index.CaCnIXu_.js +0 -1
  403. streamlit/static/static/js/index.Ck6XWYeb.js +0 -1
  404. streamlit/static/static/js/index.CmD2DSvp.js +0 -201
  405. streamlit/static/static/js/index.CnX4MRBV.js +0 -1
  406. streamlit/static/static/js/index.DR8k91Kp.js +0 -1
  407. streamlit/static/static/js/index.Dc0EFNHf.js +0 -197
  408. streamlit/static/static/js/index.M9USxdKN.js +0 -1
  409. streamlit/static/static/js/index.MCDV8ab_.js +0 -1
  410. streamlit/static/static/js/index.T4c5nSGV.js +0 -2
  411. streamlit/static/static/js/index.brVZtr01.js +0 -1
  412. streamlit/static/static/js/index.fm1fEqFi.js +0 -1
  413. streamlit/static/static/js/index.vm3Bds7I.js +0 -1
  414. streamlit/static/static/js/index.y_EIxzAg.js +0 -1
  415. streamlit/static/static/js/number-overlay-editor.D5dgP2YW.js +0 -9
  416. streamlit/static/static/js/slicedToArray.DVgs1NsC.js +0 -2
  417. streamlit/static/static/js/string.Bl9OLDCw.js +0 -1
  418. streamlit/static/static/js/threshold.skajmqVB.js +0 -1
  419. streamlit/static/static/js/timer.DwZfkapc.js +0 -1
  420. streamlit/static/static/js/uniqueId.OJw6SDpp.js +0 -1
  421. streamlit/static/static/js/withFullScreenWrapper.BjS0eA06.js +0 -1
  422. streamlit/static/static/media/MaterialSymbols-Rounded.MYSe4dsi.woff2 +0 -0
  423. streamlit/vendor/ipython/__init__.py +0 -0
  424. streamlit/vendor/ipython/modified_sys_path.py +0 -67
  425. streamlit_nightly-1.41.2.dev20241227.dist-info/RECORD +0 -556
  426. /streamlit/static/static/css/{index.B26BQfSF.css → index.Bmkmz40k.css} +0 -0
  427. /streamlit/static/static/css/{index.CG16XVnL.css → index.DzuxGC_t.css} +0 -0
  428. {streamlit_nightly-1.41.2.dev20241227.dist-info → streamlit_nightly-1.41.2.dev20250124.dist-info}/entry_points.txt +0 -0
  429. {streamlit_nightly-1.41.2.dev20241227.dist-info → streamlit_nightly-1.41.2.dev20250124.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -218,8 +218,10 @@ def get_module_paths(module: ModuleType) -> set[str]:
218
218
  except AttributeError:
219
219
  # Some modules might not have __file__ or __spec__ attributes.
220
220
  pass
221
- except Exception as e:
222
- _LOGGER.warning(f"Examining the path of {module.__name__} raised: {e}")
221
+ except Exception:
222
+ _LOGGER.warning(
223
+ f"Examining the path of {module.__name__} raised:", exc_info=True
224
+ )
223
225
 
224
226
  all_paths.update(
225
227
  [os.path.abspath(str(p)) for p in potential_paths if _is_valid_path(p)]
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
streamlit/watcher/util.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -24,7 +24,9 @@ import hashlib
24
24
  import os
25
25
  import time
26
26
  from pathlib import Path
27
+ from typing import Callable, TypeVar
27
28
 
29
+ from streamlit.errors import Error
28
30
  from streamlit.util import HASHLIB_KWARGS
29
31
 
30
32
  # How many times to try to grab the MD5 hash.
@@ -56,7 +58,14 @@ def calc_md5_with_blocking_retries(
56
58
  glob_pattern = glob_pattern or "*"
57
59
  content = _stable_dir_identifier(path, glob_pattern).encode("UTF-8")
58
60
  else:
59
- content = _get_file_content_with_blocking_retries(path)
61
+ # There's a race condition where sometimes file_path no longer exists when
62
+ # we try to read it (since the file is in the process of being written).
63
+ # So here we retry a few times using this loop. See issue #186.
64
+ content = _do_with_retries(
65
+ lambda: _get_file_content(path),
66
+ FileNotFoundError,
67
+ path,
68
+ )
60
69
 
61
70
  md5 = hashlib.md5(**HASHLIB_KWARGS)
62
71
  md5.update(content)
@@ -81,24 +90,19 @@ def path_modification_time(path: str, allow_nonexistent: bool = False) -> float:
81
90
  """
82
91
  if allow_nonexistent and not os.path.exists(path):
83
92
  return 0.0
84
- return os.stat(path).st_mtime
85
93
 
94
+ # Use retries to avoid race condition where file may be in the process of being
95
+ # modified.
96
+ return _do_with_retries(
97
+ lambda: os.stat(path).st_mtime,
98
+ FileNotFoundError,
99
+ path,
100
+ )
86
101
 
87
- def _get_file_content_with_blocking_retries(file_path: str) -> bytes:
88
- content = b""
89
- # There's a race condition where sometimes file_path no longer exists when
90
- # we try to read it (since the file is in the process of being written).
91
- # So here we retry a few times using this loop. See issue #186.
92
- for i in range(_MAX_RETRIES):
93
- try:
94
- with open(file_path, "rb") as f:
95
- content = f.read()
96
- break
97
- except FileNotFoundError as e:
98
- if i >= _MAX_RETRIES - 1:
99
- raise e
100
- time.sleep(_RETRY_WAIT_SECS)
101
- return content
102
+
103
+ def _get_file_content(file_path: str) -> bytes:
104
+ with open(file_path, "rb") as f:
105
+ return f.read()
102
106
 
103
107
 
104
108
  def _dirfiles(dir_path: str, glob_pattern: str) -> str:
@@ -134,9 +138,7 @@ def _stable_dir_identifier(dir_path: str, glob_pattern: str) -> str:
134
138
  """
135
139
  dirfiles = _dirfiles(dir_path, glob_pattern)
136
140
 
137
- for _ in range(_MAX_RETRIES):
138
- time.sleep(_RETRY_WAIT_SECS)
139
-
141
+ for _ in _retry_dance():
140
142
  new_dirfiles = _dirfiles(dir_path, glob_pattern)
141
143
  if dirfiles == new_dirfiles:
142
144
  break
@@ -144,3 +146,67 @@ def _stable_dir_identifier(dir_path: str, glob_pattern: str) -> str:
144
146
  dirfiles = new_dirfiles
145
147
 
146
148
  return f"{dir_path}+{dirfiles}"
149
+
150
+
151
+ T = TypeVar("T")
152
+
153
+
154
+ def _do_with_retries(
155
+ orig_fn: Callable[[], T],
156
+ exception: type[Exception],
157
+ path: str | Path,
158
+ ) -> T:
159
+ """Helper for retrying a function.
160
+
161
+ Calls `orig_fn`. If `exception` is raised, retry.
162
+
163
+ To use this, just replace things like this...
164
+
165
+ result = thing_to_do(file_path, a, b, c)
166
+
167
+ ...with this:
168
+
169
+ result = _do_with_retries(
170
+ lambda: thing_to_do(file_path, a, b, c),
171
+ exception: ExceptionThatWillCauseARetry,
172
+ file_path, # For pretty error message.
173
+ )
174
+ """
175
+ for i in _retry_dance():
176
+ try:
177
+ return orig_fn()
178
+ except exception:
179
+ if i >= _MAX_RETRIES - 1:
180
+ raise
181
+ else:
182
+ # Continue with loop to either retry or raise MaxRetriesError.
183
+ pass
184
+
185
+ raise MaxRetriesError(f"Unable to access file or folder: {path}")
186
+
187
+
188
+ def _retry_dance():
189
+ """Helper for writing a retry loop.
190
+
191
+ This is useful to make sure all our retry loops work the same way. For example,
192
+ prior to this helper, some loops had time.sleep() *before the first try*, which just
193
+ slowed things down for no reason.
194
+
195
+ Usage:
196
+
197
+ for i in _retry_dance():
198
+ # Do the thing you want to retry automatically.
199
+ the_thing_worked = do_thing()
200
+
201
+ # Don't forget to include a break/return when the thing you're trying to do
202
+ # works.
203
+ if the_thing_worked:
204
+ break
205
+ """
206
+ for i in range(_MAX_RETRIES):
207
+ yield i
208
+ time.sleep(_RETRY_WAIT_SECS)
209
+
210
+
211
+ class MaxRetriesError(Error):
212
+ pass
streamlit/web/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -298,6 +298,8 @@ def run(
298
298
  is_hello: bool,
299
299
  args: list[str],
300
300
  flag_options: dict[str, Any],
301
+ *,
302
+ stop_immediately_for_testing: bool = False,
301
303
  ) -> None:
302
304
  """Run a script in a separate thread and start a server for the app.
303
305
 
@@ -321,9 +323,27 @@ def run(
321
323
  # and close all our threads
322
324
  _set_up_signal_handler(server)
323
325
 
326
+ # return immediately if we're testing the server start
327
+ if stop_immediately_for_testing:
328
+ _LOGGER.debug("Stopping server immediately for testing")
329
+ server.stop()
330
+
324
331
  # Wait until `Server.stop` is called, either by our signal handler, or
325
332
  # by a debug websocket session.
326
333
  await server.stopped
327
334
 
328
335
  # Run the server. This function will not return until the server is shut down.
329
- asyncio.run(run_server())
336
+ # FIX RuntimeError: asyncio.run() cannot be called from a running event loop on Python 3.10.16
337
+ # asyncio.run(run_server())
338
+
339
+ # Define a main function to handle the event loop logic
340
+ async def main():
341
+ await run_server()
342
+
343
+ # Check if we're already in an event loop
344
+ if asyncio.get_event_loop().is_running():
345
+ # Use `asyncio.create_task` if we're in an async context
346
+ asyncio.create_task(main())
347
+ else:
348
+ # Otherwise, use `asyncio.run`
349
+ asyncio.run(main())
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
streamlit/web/cli.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -0,0 +1,58 @@
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import TYPE_CHECKING, Any, Sequence
18
+
19
+ from authlib.integrations.base_client import ( # type: ignore[import-untyped]
20
+ FrameworkIntegration,
21
+ )
22
+
23
+ from streamlit.runtime.secrets import AttrDict
24
+
25
+ if TYPE_CHECKING:
26
+ from streamlit.web.server.oidc_mixin import TornadoOAuth
27
+
28
+
29
+ class TornadoIntegration(FrameworkIntegration): # type: ignore[misc]
30
+ def update_token(self, token, refresh_token=None, access_token=None):
31
+ """We do not support access token refresh, since we obtain and operate only on
32
+ identity tokens. We override this method explicitly to implement all abstract
33
+ methods of base class.
34
+ """
35
+
36
+ @staticmethod
37
+ def load_config(
38
+ oauth: TornadoOAuth, name: str, params: Sequence[str]
39
+ ) -> dict[str, Any]:
40
+ """Configure Authlib integration with provider parameters
41
+ specified in secrets.toml
42
+ """
43
+
44
+ # oauth.config here is an auth section from secrets.toml
45
+ # We parse it here to transform nested AttrDict (for client_kwargs value)
46
+ # to dict so Authlib can work with it under the hood.
47
+ if not oauth.config:
48
+ return {}
49
+
50
+ prepared_config = {}
51
+ for key in params:
52
+ value = oauth.config.get(name, {}).get(key, None)
53
+ if isinstance(value, AttrDict):
54
+ # We want to modify client_kwargs further after loading server metadata
55
+ value = value.to_dict()
56
+ if value is not None:
57
+ prepared_config[key] = value
58
+ return prepared_config
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -16,14 +16,17 @@ from __future__ import annotations
16
16
 
17
17
  import base64
18
18
  import binascii
19
+ import hmac
19
20
  import json
20
21
  from typing import TYPE_CHECKING, Any, Awaitable, Final
22
+ from urllib.parse import urlparse
21
23
 
22
24
  import tornado.concurrent
23
25
  import tornado.locks
24
26
  import tornado.netutil
25
27
  import tornado.web
26
28
  import tornado.websocket
29
+ from tornado.escape import utf8
27
30
  from tornado.websocket import WebSocketHandler
28
31
 
29
32
  from streamlit import config
@@ -31,7 +34,11 @@ from streamlit.logger import get_logger
31
34
  from streamlit.proto.BackMsg_pb2 import BackMsg
32
35
  from streamlit.runtime import Runtime, SessionClient, SessionClientDisconnectedError
33
36
  from streamlit.runtime.runtime_util import serialize_forward_msg
34
- from streamlit.web.server.server_util import is_url_from_allowed_origins
37
+ from streamlit.web.server.server_util import (
38
+ AUTH_COOKIE_NAME,
39
+ is_url_from_allowed_origins,
40
+ is_xsrf_enabled,
41
+ )
35
42
 
36
43
  if TYPE_CHECKING:
37
44
  from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
@@ -50,13 +57,63 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
50
57
  # need to read the self.xsrf_token manually to set the cookie as a side
51
58
  # effect. See https://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection
52
59
  # for more details.
53
- if config.get_option("server.enableXsrfProtection"):
60
+ if is_xsrf_enabled():
54
61
  _ = self.xsrf_token
55
62
 
63
+ def get_signed_cookie(
64
+ self,
65
+ name: str,
66
+ value: str | None = None,
67
+ max_age_days: float = 31,
68
+ min_version: int | None = None,
69
+ ) -> bytes | None:
70
+ """Get a signed cookie from the request. Added for compatibility with
71
+ Tornado < 6.3.0.
72
+ See release notes: https://www.tornadoweb.org/en/stable/releases/v6.3.0.html#deprecation-notices
73
+ """
74
+ try:
75
+ return super().get_signed_cookie(name, value, max_age_days, min_version)
76
+ except AttributeError:
77
+ return super().get_secure_cookie(name, value, max_age_days, min_version)
78
+
56
79
  def check_origin(self, origin: str) -> bool:
57
80
  """Set up CORS."""
58
81
  return super().check_origin(origin) or is_url_from_allowed_origins(origin)
59
82
 
83
+ def _validate_xsrf_token(self, supplied_token: str) -> bool:
84
+ """Inspired by tornado.web.RequestHandler.check_xsrf_cookie method,
85
+ to check the XSRF token passed in Websocket connection header.
86
+ """
87
+ _, token, _ = self._decode_xsrf_token(supplied_token)
88
+ _, expected_token, _ = self._get_raw_xsrf_token()
89
+
90
+ decoded_token = utf8(token)
91
+ decoded_expected_token = utf8(expected_token)
92
+
93
+ if not decoded_token or not decoded_expected_token:
94
+ return False
95
+ return hmac.compare_digest(decoded_token, decoded_expected_token)
96
+
97
+ def _parse_user_cookie(self, raw_cookie_value: bytes, email: str) -> dict[str, Any]:
98
+ """Process the user cookie and extract the user info after
99
+ validating the origin. Origin is validated for security reasons.
100
+ """
101
+ cookie_value = json.loads(raw_cookie_value)
102
+ user_info = {}
103
+
104
+ cookie_value_origin = cookie_value.get("origin", None)
105
+ parsed_origin_from_header = urlparse(self.request.headers["Origin"])
106
+ expected_origin_value = (
107
+ parsed_origin_from_header.scheme + "://" + parsed_origin_from_header.netloc
108
+ )
109
+ if cookie_value_origin == expected_origin_value:
110
+ user_info["is_logged_in"] = cookie_value.get("is_logged_in", False)
111
+ del cookie_value["origin"]
112
+ del cookie_value["is_logged_in"]
113
+ user_info.update(cookie_value)
114
+
115
+ return user_info
116
+
60
117
  def write_forward_msg(self, msg: ForwardMsg) -> None:
61
118
  """Send a ForwardMsg to the browser."""
62
119
  try:
@@ -102,11 +159,13 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
102
159
  is_public_cloud_app = user_obj["isPublicCloudApp"]
103
160
  except (KeyError, binascii.Error, json.decoder.JSONDecodeError):
104
161
  email = "test@example.com"
105
-
106
- user_info: dict[str, str | None] = {
107
- "email": None if is_public_cloud_app else email
162
+ user_info: dict[str, str | bool | None] = {
163
+ "email": None if is_public_cloud_app else email,
108
164
  }
109
165
 
166
+ if is_public_cloud_app or email == "test@example.com":
167
+ user_info.pop("email", None)
168
+
110
169
  existing_session_id = None
111
170
  try:
112
171
  ws_protocols = [
@@ -114,6 +173,13 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
114
173
  for p in self.request.headers["Sec-Websocket-Protocol"].split(",")
115
174
  ]
116
175
 
176
+ raw_cookie_value = self.get_signed_cookie(AUTH_COOKIE_NAME)
177
+ if is_xsrf_enabled() and raw_cookie_value:
178
+ csrf_protocol_value = ws_protocols[1]
179
+
180
+ if self._validate_xsrf_token(csrf_protocol_value):
181
+ user_info.update(self._parse_user_cookie(raw_cookie_value, email))
182
+
117
183
  if len(ws_protocols) >= 3:
118
184
  # See the NOTE in the docstring of the `select_subprotocol` method above
119
185
  # for a detailed explanation of why this is done.
@@ -166,7 +232,7 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
166
232
  _LOGGER.debug("Received the following back message:\n%s", msg)
167
233
 
168
234
  except Exception as ex:
169
- _LOGGER.error(ex)
235
+ _LOGGER.exception("Error deserializing back message")
170
236
  self._runtime.handle_backmsg_deserialization_exception(self._session_id, ex)
171
237
  return
172
238
 
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -0,0 +1,176 @@
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
+ from __future__ import annotations
15
+
16
+ import json
17
+ from typing import Any
18
+ from urllib.parse import urlparse
19
+
20
+ import tornado.web
21
+
22
+ from streamlit.auth_util import (
23
+ AuthCache,
24
+ decode_provider_token,
25
+ generate_default_provider_section,
26
+ get_secrets_auth_section,
27
+ )
28
+ from streamlit.errors import StreamlitAuthError
29
+ from streamlit.url_util import make_url_path
30
+ from streamlit.web.server.oidc_mixin import TornadoOAuth, TornadoOAuth2App
31
+ from streamlit.web.server.server_util import AUTH_COOKIE_NAME
32
+
33
+ auth_cache = AuthCache()
34
+
35
+
36
+ def create_oauth_client(provider: str) -> tuple[TornadoOAuth2App, str]:
37
+ """Create an OAuth client for the given provider based on secrets.toml configuration."""
38
+ auth_section = get_secrets_auth_section()
39
+ if auth_section:
40
+ redirect_uri = auth_section.get("redirect_uri", None)
41
+ config = auth_section.to_dict()
42
+ else:
43
+ config = {}
44
+ redirect_uri = "/"
45
+
46
+ provider_section = config.setdefault(provider, {})
47
+
48
+ if not provider_section and provider == "default":
49
+ provider_section = generate_default_provider_section(auth_section)
50
+ config["default"] = provider_section
51
+
52
+ provider_client_kwargs = provider_section.setdefault("client_kwargs", {})
53
+ if "scope" not in provider_client_kwargs:
54
+ provider_client_kwargs["scope"] = "openid email profile"
55
+ if "prompt" not in provider_client_kwargs:
56
+ provider_client_kwargs["prompt"] = "select_account"
57
+
58
+ oauth = TornadoOAuth(config, cache=auth_cache)
59
+ oauth.register(provider)
60
+ return oauth.create_client(provider), redirect_uri
61
+
62
+
63
+ class AuthHandlerMixin(tornado.web.RequestHandler):
64
+ """Mixin for handling auth cookies. Added for compatibility with Tornado < 6.3.0."""
65
+
66
+ def initialize(self, base_url: str) -> None:
67
+ self.base_url = base_url
68
+
69
+ def redirect_to_base(self) -> None:
70
+ self.redirect(make_url_path(self.base_url, "/"))
71
+
72
+ def set_auth_cookie(self, user_info: dict[str, Any]) -> None:
73
+ serialized_cookie_value = json.dumps(user_info)
74
+ try:
75
+ # We don't specify Tornado secure flag here because it leads to missing cookie on Safari.
76
+ # The OIDC flow should work only on secure context anyway (localhost or HTTPS),
77
+ # so specifying the secure flag here will not add anything in terms of security.
78
+ self.set_signed_cookie(
79
+ AUTH_COOKIE_NAME,
80
+ serialized_cookie_value,
81
+ httpOnly=True,
82
+ )
83
+ except AttributeError:
84
+ self.set_secure_cookie(
85
+ AUTH_COOKIE_NAME,
86
+ serialized_cookie_value,
87
+ httponly=True,
88
+ )
89
+
90
+ def clear_auth_cookie(self) -> None:
91
+ self.clear_cookie(AUTH_COOKIE_NAME)
92
+
93
+
94
+ class AuthLoginHandler(AuthHandlerMixin, tornado.web.RequestHandler):
95
+ async def get(self):
96
+ """Redirect to the OAuth provider login page."""
97
+ provider = self._parse_provider_token()
98
+ if provider is None:
99
+ self.redirect_to_base()
100
+ return
101
+
102
+ client, redirect_uri = create_oauth_client(provider)
103
+ try:
104
+ client.authorize_redirect(self, redirect_uri)
105
+ except Exception as e:
106
+ self.send_error(400, reason=str(e))
107
+
108
+ def _parse_provider_token(self) -> str | None:
109
+ provider_token = self.get_argument("provider", None)
110
+ try:
111
+ if provider_token is None:
112
+ raise StreamlitAuthError("Missing provider token")
113
+ payload = decode_provider_token(provider_token)
114
+ except StreamlitAuthError:
115
+ return None
116
+
117
+ return payload["provider"]
118
+
119
+
120
+ class AuthLogoutHandler(AuthHandlerMixin, tornado.web.RequestHandler):
121
+ def get(self):
122
+ self.clear_auth_cookie()
123
+ self.redirect_to_base()
124
+
125
+
126
+ class AuthCallbackHandler(AuthHandlerMixin, tornado.web.RequestHandler):
127
+ async def get(self):
128
+ provider = self._get_provider_by_state()
129
+ origin = self._get_origin_from_secrets()
130
+ if origin is None:
131
+ self.redirect_to_base()
132
+ return
133
+
134
+ error = self.get_argument("error", None)
135
+ if error:
136
+ self.redirect_to_base()
137
+ return
138
+
139
+ if provider is None:
140
+ self.redirect_to_base()
141
+ return
142
+
143
+ client, _ = create_oauth_client(provider)
144
+ token = client.authorize_access_token(self)
145
+ user = token.get("userinfo")
146
+
147
+ cookie_value = dict(user, origin=origin, is_logged_in=True)
148
+ if user:
149
+ self.set_auth_cookie(cookie_value)
150
+ self.redirect_to_base()
151
+
152
+ def _get_provider_by_state(self) -> str | None:
153
+ state_code_from_url = self.get_argument("state")
154
+ current_cache_keys = list(auth_cache.get_dict().keys())
155
+ state_provider_mapping = {}
156
+ for key in current_cache_keys:
157
+ _, _, recorded_provider, code = key.split("_")
158
+ state_provider_mapping[code] = recorded_provider
159
+
160
+ provider: str | None = state_provider_mapping.get(state_code_from_url, None)
161
+ return provider
162
+
163
+ def _get_origin_from_secrets(self) -> str | None:
164
+ redirect_uri = None
165
+ auth_section = get_secrets_auth_section()
166
+ if auth_section:
167
+ redirect_uri = auth_section.get("redirect_uri", None)
168
+
169
+ if not redirect_uri:
170
+ return None
171
+
172
+ redirect_uri_parsed = urlparse(redirect_uri)
173
+ origin_from_redirect_uri: str = (
174
+ redirect_uri_parsed.scheme + "://" + redirect_uri_parsed.netloc
175
+ )
176
+ return origin_from_redirect_uri