streamlit 1.45.1__py3-none-any.whl → 1.46.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 (335) hide show
  1. streamlit/__init__.py +5 -1
  2. streamlit/auth_util.py +12 -12
  3. streamlit/cli_util.py +4 -3
  4. streamlit/column_config.py +11 -9
  5. streamlit/commands/echo.py +6 -4
  6. streamlit/commands/execution_control.py +33 -32
  7. streamlit/commands/experimental_query_params.py +2 -2
  8. streamlit/commands/logo.py +9 -4
  9. streamlit/commands/navigation.py +61 -18
  10. streamlit/commands/page_config.py +57 -47
  11. streamlit/components/types/base_custom_component.py +7 -7
  12. streamlit/components/v1/component_registry.py +7 -3
  13. streamlit/components/v1/components.py +1 -1
  14. streamlit/components/v1/custom_component.py +8 -8
  15. streamlit/config.py +289 -144
  16. streamlit/config_option.py +19 -15
  17. streamlit/config_util.py +29 -23
  18. streamlit/connections/__init__.py +2 -2
  19. streamlit/connections/base_connection.py +5 -5
  20. streamlit/connections/snowflake_connection.py +13 -11
  21. streamlit/connections/snowpark_connection.py +3 -3
  22. streamlit/connections/sql_connection.py +20 -18
  23. streamlit/connections/util.py +2 -2
  24. streamlit/cursor.py +6 -6
  25. streamlit/dataframe_util.py +52 -52
  26. streamlit/delta_generator.py +46 -48
  27. streamlit/delta_generator_singletons.py +3 -3
  28. streamlit/deprecation_util.py +6 -6
  29. streamlit/elements/alert.py +37 -29
  30. streamlit/elements/arrow.py +40 -22
  31. streamlit/elements/code.py +46 -13
  32. streamlit/elements/deck_gl_json_chart.py +38 -27
  33. streamlit/elements/dialog_decorator.py +3 -4
  34. streamlit/elements/doc_string.py +64 -58
  35. streamlit/elements/exception.py +23 -27
  36. streamlit/elements/form.py +41 -0
  37. streamlit/elements/graphviz_chart.py +1 -1
  38. streamlit/elements/heading.py +60 -9
  39. streamlit/elements/html.py +3 -4
  40. streamlit/elements/image.py +8 -9
  41. streamlit/elements/json.py +21 -2
  42. streamlit/elements/layouts.py +120 -31
  43. streamlit/elements/lib/built_in_chart_utils.py +96 -73
  44. streamlit/elements/lib/color_util.py +3 -3
  45. streamlit/elements/lib/column_config_utils.py +2 -4
  46. streamlit/elements/lib/column_types.py +14 -8
  47. streamlit/elements/lib/dialog.py +9 -5
  48. streamlit/elements/lib/image_utils.py +39 -40
  49. streamlit/elements/lib/js_number.py +4 -4
  50. streamlit/elements/lib/layout_utils.py +65 -1
  51. streamlit/elements/lib/mutable_status_container.py +14 -3
  52. streamlit/elements/lib/options_selector_utils.py +22 -12
  53. streamlit/elements/lib/pandas_styler_utils.py +25 -21
  54. streamlit/elements/lib/policies.py +6 -5
  55. streamlit/elements/lib/streamlit_plotly_theme.py +54 -53
  56. streamlit/elements/lib/subtitle_utils.py +6 -9
  57. streamlit/elements/lib/utils.py +20 -5
  58. streamlit/elements/map.py +32 -56
  59. streamlit/elements/markdown.py +101 -12
  60. streamlit/elements/media.py +78 -21
  61. streamlit/elements/metric.py +32 -16
  62. streamlit/elements/plotly_chart.py +15 -15
  63. streamlit/elements/progress.py +33 -15
  64. streamlit/elements/spinner.py +31 -6
  65. streamlit/elements/text.py +21 -1
  66. streamlit/elements/toast.py +1 -2
  67. streamlit/elements/vega_charts.py +54 -23
  68. streamlit/elements/widgets/audio_input.py +24 -7
  69. streamlit/elements/widgets/button.py +26 -19
  70. streamlit/elements/widgets/button_group.py +10 -15
  71. streamlit/elements/widgets/camera_input.py +27 -7
  72. streamlit/elements/widgets/chat.py +91 -38
  73. streamlit/elements/widgets/checkbox.py +45 -4
  74. streamlit/elements/widgets/color_picker.py +40 -17
  75. streamlit/elements/widgets/data_editor.py +76 -37
  76. streamlit/elements/widgets/file_uploader.py +42 -13
  77. streamlit/elements/widgets/multiselect.py +7 -10
  78. streamlit/elements/widgets/number_input.py +123 -47
  79. streamlit/elements/widgets/radio.py +59 -13
  80. streamlit/elements/widgets/select_slider.py +35 -30
  81. streamlit/elements/widgets/selectbox.py +56 -9
  82. streamlit/elements/widgets/slider.py +190 -99
  83. streamlit/elements/widgets/text_widgets.py +54 -8
  84. streamlit/elements/widgets/time_widgets.py +53 -14
  85. streamlit/elements/write.py +5 -8
  86. streamlit/env_util.py +2 -7
  87. streamlit/error_util.py +16 -9
  88. streamlit/errors.py +69 -48
  89. streamlit/external/langchain/streamlit_callback_handler.py +10 -5
  90. streamlit/file_util.py +27 -10
  91. streamlit/git_util.py +29 -24
  92. streamlit/hello/animation_demo.py +9 -9
  93. streamlit/hello/dataframe_demo.py +5 -5
  94. streamlit/hello/hello.py +1 -0
  95. streamlit/hello/mapping_demo.py +7 -8
  96. streamlit/hello/plotting_demo.py +3 -3
  97. streamlit/hello/streamlit_app.py +28 -26
  98. streamlit/hello/utils.py +2 -1
  99. streamlit/logger.py +10 -11
  100. streamlit/navigation/page.py +11 -8
  101. streamlit/proto/Audio_pb2.py +4 -3
  102. streamlit/proto/Audio_pb2.pyi +8 -1
  103. streamlit/proto/Block_pb2.py +38 -29
  104. streamlit/proto/Block_pb2.pyi +72 -4
  105. streamlit/proto/ClientState_pb2.py +4 -4
  106. streamlit/proto/ClientState_pb2.pyi +7 -2
  107. streamlit/proto/Code_pb2.py +4 -2
  108. streamlit/proto/Code_pb2.pyi +1 -0
  109. streamlit/proto/DataFrame_pb2.pyi +1 -1
  110. streamlit/proto/DeckGlJsonChart_pb2.pyi +1 -1
  111. streamlit/proto/Element_pb2.py +5 -3
  112. streamlit/proto/Element_pb2.pyi +20 -3
  113. streamlit/proto/GapSize_pb2.py +29 -0
  114. streamlit/proto/GapSize_pb2.pyi +70 -0
  115. streamlit/proto/HeightConfig_pb2.py +27 -0
  116. streamlit/proto/HeightConfig_pb2.pyi +48 -0
  117. streamlit/proto/NamedDataSet_pb2.pyi +1 -1
  118. streamlit/proto/Navigation_pb2.py +3 -3
  119. streamlit/proto/Navigation_pb2.pyi +4 -0
  120. streamlit/proto/NewSession_pb2.py +18 -16
  121. streamlit/proto/NewSession_pb2.pyi +29 -3
  122. streamlit/proto/PageConfig_pb2.py +7 -7
  123. streamlit/proto/PageConfig_pb2.pyi +21 -1
  124. streamlit/proto/Video_pb2.py +8 -7
  125. streamlit/proto/Video_pb2.pyi +8 -1
  126. streamlit/proto/WidthConfig_pb2.py +2 -2
  127. streamlit/proto/WidthConfig_pb2.pyi +15 -1
  128. streamlit/runtime/__init__.py +1 -1
  129. streamlit/runtime/app_session.py +53 -40
  130. streamlit/runtime/caching/__init__.py +9 -9
  131. streamlit/runtime/caching/cache_data_api.py +36 -30
  132. streamlit/runtime/caching/cache_errors.py +4 -4
  133. streamlit/runtime/caching/cache_resource_api.py +8 -8
  134. streamlit/runtime/caching/cache_utils.py +15 -14
  135. streamlit/runtime/caching/cached_message_replay.py +14 -8
  136. streamlit/runtime/caching/hashing.py +91 -97
  137. streamlit/runtime/caching/legacy_cache_api.py +2 -2
  138. streamlit/runtime/caching/storage/cache_storage_protocol.py +1 -1
  139. streamlit/runtime/caching/storage/dummy_cache_storage.py +1 -1
  140. streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py +12 -14
  141. streamlit/runtime/caching/storage/local_disk_cache_storage.py +6 -6
  142. streamlit/runtime/connection_factory.py +36 -36
  143. streamlit/runtime/context.py +58 -9
  144. streamlit/runtime/credentials.py +29 -40
  145. streamlit/runtime/forward_msg_queue.py +11 -11
  146. streamlit/runtime/fragment.py +7 -7
  147. streamlit/runtime/media_file_manager.py +3 -4
  148. streamlit/runtime/memory_media_file_storage.py +6 -5
  149. streamlit/runtime/memory_uploaded_file_manager.py +2 -2
  150. streamlit/runtime/metrics_util.py +11 -12
  151. streamlit/runtime/pages_manager.py +4 -6
  152. streamlit/runtime/runtime.py +8 -6
  153. streamlit/runtime/runtime_util.py +7 -6
  154. streamlit/runtime/scriptrunner/__init__.py +4 -4
  155. streamlit/runtime/scriptrunner/exec_code.py +12 -5
  156. streamlit/runtime/scriptrunner/magic.py +16 -12
  157. streamlit/runtime/scriptrunner/script_cache.py +1 -1
  158. streamlit/runtime/scriptrunner/script_runner.py +53 -29
  159. streamlit/runtime/scriptrunner_utils/exceptions.py +1 -1
  160. streamlit/runtime/scriptrunner_utils/script_requests.py +7 -4
  161. streamlit/runtime/scriptrunner_utils/script_run_context.py +10 -23
  162. streamlit/runtime/secrets.py +40 -35
  163. streamlit/runtime/session_manager.py +2 -1
  164. streamlit/runtime/state/__init__.py +5 -5
  165. streamlit/runtime/state/common.py +2 -2
  166. streamlit/runtime/state/query_params.py +13 -15
  167. streamlit/runtime/state/query_params_proxy.py +17 -13
  168. streamlit/runtime/state/safe_session_state.py +2 -2
  169. streamlit/runtime/state/session_state.py +52 -34
  170. streamlit/runtime/stats.py +2 -2
  171. streamlit/runtime/uploaded_file_manager.py +1 -1
  172. streamlit/runtime/websocket_session_manager.py +10 -6
  173. streamlit/source_util.py +8 -6
  174. streamlit/static/index.html +3 -17
  175. streamlit/static/manifest.json +1180 -0
  176. streamlit/static/static/css/{index.DqDwtg6_.css → index.CJVRHjQZ.css} +1 -1
  177. streamlit/static/static/js/{ErrorOutline.esm.DU9IrB3M.js → ErrorOutline.esm.DitPpe1Y.js} +1 -1
  178. streamlit/static/static/js/{FileDownload.esm.P9rKwKo8.js → FileDownload.esm.AI3watX9.js} +1 -1
  179. streamlit/static/static/js/{FileHelper.D7RMkx0e.js → FileHelper.kt7mhnu8.js} +5 -5
  180. streamlit/static/static/js/{FormClearHelper.B67tgll0.js → FormClearHelper.D1M9GM_c.js} +1 -1
  181. streamlit/static/static/js/{Hooks.ncTJktu9.js → Hooks.BGwHKeUc.js} +1 -1
  182. streamlit/static/static/js/{InputInstructions.D-Y8geDN.js → InputInstructions.DaZ89mzH.js} +1 -1
  183. streamlit/static/static/js/{ProgressBar.B-kexwwD.js → ProgressBar.C0zPMe-p.js} +2 -2
  184. streamlit/static/static/js/{RenderInPortalIfExists.BgaoZgep.js → RenderInPortalIfExists.Ox8gQvdz.js} +1 -1
  185. streamlit/static/static/js/Toolbar.KhlcEc0K.js +1 -0
  186. streamlit/static/static/js/UploadFileInfo.0DCkpDDf.js +6 -0
  187. streamlit/static/static/js/{base-input.BoAa1U94.js → base-input.BJ4qsfSq.js} +4 -4
  188. streamlit/static/static/js/{checkbox.Z6iSfe5F.js → checkbox.DSDh78Xz.js} +2 -2
  189. streamlit/static/static/js/{createSuper.B4oGDYRm.js → createSuper.wQ9SIXEJ.js} +1 -1
  190. streamlit/static/static/js/{data-grid-overlay-editor.msYws2Ou.js → data-grid-overlay-editor.DvbdPJ15.js} +1 -1
  191. streamlit/static/static/js/{downloader.kc14n2Hv.js → downloader.CD9rzih5.js} +1 -1
  192. streamlit/static/static/js/{es6.CxQz807-.js → es6.48Q9Qjgb.js} +2 -2
  193. streamlit/static/static/js/{iframeResizer.contentWindow.B19u0ONI.js → iframeResizer.contentWindow.CKdem3Bn.js} +1 -1
  194. streamlit/static/static/js/{index.LaIasviC.js → index.6md5Qhod.js} +1 -1
  195. streamlit/static/static/js/index.7hy6AeJ1.js +1 -0
  196. streamlit/static/static/js/index.B4CGJiBW.js +1 -0
  197. streamlit/static/static/js/index.B8oW0ZTD.js +1 -0
  198. streamlit/static/static/js/index.BU6RnlHI.js +73 -0
  199. streamlit/static/static/js/index.BUq9Wcf8.js +197 -0
  200. streamlit/static/static/js/{index.BFz9U2y0.js → index.BXXo-Yoj.js} +1 -1
  201. streamlit/static/static/js/index.Bae9H0OS.js +1 -0
  202. streamlit/static/static/js/{index.-5ruC9At.js → index.BhTl2Uyb.js} +1 -1
  203. streamlit/static/static/js/{index.BpILzHf_.js → index.BiSaCB1o.js} +20 -20
  204. streamlit/static/static/js/{index.xNQq3Ei5.js → index.BulSAJ9z.js} +1 -1
  205. streamlit/static/static/js/{index.9V1KdxfP.js → index.Bv-EuTKR.js} +1 -1
  206. streamlit/static/static/js/index.BvMLYCHi.js +1 -0
  207. streamlit/static/static/js/index.C1NIn1Y2.js +783 -0
  208. streamlit/static/static/js/index.CP-fthOJ.js +2 -0
  209. streamlit/static/static/js/{index.BoigZiu7.js → index.CS9guO3p.js} +1 -1
  210. streamlit/static/static/js/index.CYTBHth8.js +1 -0
  211. streamlit/static/static/js/{index.CmTAF0dM.js → index.CcJufcuD.js} +1 -1
  212. streamlit/static/static/js/index.CnENU1yn.js +1 -0
  213. streamlit/static/static/js/index.Cns13qBb.js +1 -0
  214. streamlit/static/static/js/index.Ct_xXq7w.js +1 -0
  215. streamlit/static/static/js/{index.BqfdT8-Q.js → index.CxGSemHL.js} +1 -1
  216. streamlit/static/static/js/index.D5S0ldVb.js +1 -0
  217. streamlit/static/static/js/index.D72B_ksb.js +2 -0
  218. streamlit/static/static/js/index.DI4yZ27M.js +1 -0
  219. streamlit/static/static/js/index.DN51vLxR.js +1 -0
  220. streamlit/static/static/js/index.DRtq5dka.js +1 -0
  221. streamlit/static/static/js/{index.BHXxWdde.js → index.DX-oiXlb.js} +1 -1
  222. streamlit/static/static/js/index.DlFE4_Aq.js +12 -0
  223. streamlit/static/static/js/{index.BHGGDa8K.js → index.J7BJwXOi.js} +2 -2
  224. streamlit/static/static/js/index.Jg38kJPP.js +1 -0
  225. streamlit/static/static/js/index.JhIO6abf.js +3 -0
  226. streamlit/static/static/js/{index.DeB9iKFW.js → index.NkRcWwc5.js} +255 -255
  227. streamlit/static/static/js/{index.BGga-hcS.js → index.prekPLrm.js} +25 -25
  228. streamlit/static/static/js/{index.BRXmLIsC.js → index.wyzngKUE.js} +1 -1
  229. streamlit/static/static/js/index.xW7mVdI8.js +1 -0
  230. streamlit/static/static/js/index.yk07dYGx.js +1 -0
  231. streamlit/static/static/js/{input.DsCfafm0.js → input.CxKZ5Wrc.js} +2 -2
  232. streamlit/static/static/js/{memory.nY_lMTtu.js → memory.DeZ9VUvl.js} +1 -1
  233. streamlit/static/static/js/{mergeWith.B_7zmsM4.js → mergeWith.CVkhrWUb.js} +1 -1
  234. streamlit/static/static/js/{number-overlay-editor.CSeVhHRU.js → number-overlay-editor.Bpkm3nTq.js} +1 -1
  235. streamlit/static/static/js/{possibleConstructorReturn.nNhsvgRd.js → possibleConstructorReturn.CIDCId52.js} +1 -1
  236. streamlit/static/static/js/{sandbox.Cgm3iuL6.js → sandbox.TrkMaokR.js} +1 -1
  237. streamlit/static/static/js/{textarea.BR8rlyih.js → textarea.QKjxR64N.js} +2 -2
  238. streamlit/static/static/js/{timepicker.w4XhAenH.js → timepicker.DJYmE1dK.js} +1 -1
  239. streamlit/static/static/js/{toConsumableArray.CgkEPBwD.js → toConsumableArray.BZoworE-.js} +1 -1
  240. streamlit/static/static/js/{uniqueId.j-1rlNNH.js → uniqueId.O0UbJ2Bu.js} +1 -1
  241. streamlit/static/static/js/{useBasicWidgetState.zXY9CjFS.js → useBasicWidgetState.Ci89jaH5.js} +1 -1
  242. streamlit/static/static/js/useOnInputChange.Cxh6ExEn.js +1 -0
  243. streamlit/static/static/js/{withFullScreenWrapper.Ov13692o.js → withFullScreenWrapper.iW37lS8Z.js} +1 -1
  244. streamlit/static/static/media/SourceCodeVF-Italic.ttf.Ba1oaZG1.woff2 +0 -0
  245. streamlit/static/static/media/SourceCodeVF-Upright.ttf.BjWn63N-.woff2 +0 -0
  246. streamlit/static/static/media/SourceSansVF-Italic.ttf.Bt9VkdQ3.woff2 +0 -0
  247. streamlit/static/static/media/SourceSansVF-Upright.ttf.BsWL4Kly.woff2 +0 -0
  248. streamlit/static/static/media/SourceSerifVariable-Italic.ttf.CVdzAtxO.woff2 +0 -0
  249. streamlit/static/static/media/SourceSerifVariable-Roman.ttf.mdpVL9bi.woff2 +0 -0
  250. streamlit/string_util.py +14 -19
  251. streamlit/temporary_directory.py +13 -4
  252. streamlit/testing/v1/app_test.py +15 -10
  253. streamlit/testing/v1/element_tree.py +157 -178
  254. streamlit/testing/v1/local_script_runner.py +11 -15
  255. streamlit/testing/v1/util.py +11 -4
  256. streamlit/type_util.py +8 -12
  257. streamlit/url_util.py +1 -1
  258. streamlit/user_info.py +6 -5
  259. streamlit/util.py +25 -1
  260. streamlit/vendor/pympler/asizeof.py +3 -2
  261. streamlit/watcher/event_based_path_watcher.py +21 -2
  262. streamlit/watcher/folder_black_list.py +2 -2
  263. streamlit/watcher/local_sources_watcher.py +64 -18
  264. streamlit/watcher/path_watcher.py +6 -10
  265. streamlit/watcher/polling_path_watcher.py +8 -7
  266. streamlit/watcher/util.py +7 -6
  267. streamlit/web/bootstrap.py +16 -14
  268. streamlit/web/cli.py +52 -45
  269. streamlit/web/server/__init__.py +7 -3
  270. streamlit/web/server/app_static_file_handler.py +1 -1
  271. streamlit/web/server/authlib_tornado_integration.py +9 -4
  272. streamlit/web/server/browser_websocket_handler.py +8 -2
  273. streamlit/web/server/component_request_handler.py +14 -10
  274. streamlit/web/server/media_file_handler.py +14 -7
  275. streamlit/web/server/oauth_authlib_routes.py +41 -9
  276. streamlit/web/server/oidc_mixin.py +35 -17
  277. streamlit/web/server/routes.py +32 -22
  278. streamlit/web/server/server.py +13 -24
  279. streamlit/web/server/server_util.py +43 -9
  280. streamlit/web/server/stats_request_handler.py +7 -5
  281. streamlit/web/server/upload_file_request_handler.py +22 -19
  282. streamlit/web/server/websocket_headers.py +1 -1
  283. {streamlit-1.45.1.dist-info → streamlit-1.46.1.dist-info}/METADATA +4 -4
  284. streamlit-1.46.1.dist-info/RECORD +559 -0
  285. {streamlit-1.45.1.dist-info → streamlit-1.46.1.dist-info}/WHEEL +1 -1
  286. streamlit/elements/lib/event_utils.py +0 -39
  287. streamlit/static/static/js/Toolbar.D9RUZv9G.js +0 -1
  288. streamlit/static/static/js/UploadFileInfo.C-jY39rj.js +0 -1
  289. streamlit/static/static/js/index.8jhZBWF2.js +0 -3
  290. streamlit/static/static/js/index.BCx3C6e_.js +0 -1
  291. streamlit/static/static/js/index.BRuTz_S4.js +0 -1
  292. streamlit/static/static/js/index.Bcru_ti-.js +0 -1
  293. streamlit/static/static/js/index.Bl1FMJRd.js +0 -1
  294. streamlit/static/static/js/index.C1z8KpLA.js +0 -779
  295. streamlit/static/static/js/index.C32I2PUe.js +0 -2
  296. streamlit/static/static/js/index.C5GnDRB7.js +0 -1
  297. streamlit/static/static/js/index.CG4qPaaW.js +0 -2
  298. streamlit/static/static/js/index.C_msmT1u.js +0 -1
  299. streamlit/static/static/js/index.CbeNTdd6.js +0 -1
  300. streamlit/static/static/js/index.CnGQVJcw.js +0 -12
  301. streamlit/static/static/js/index.CopVVq4l.js +0 -1
  302. streamlit/static/static/js/index.CtXupx4d.js +0 -197
  303. streamlit/static/static/js/index.DGmCchO7.js +0 -1
  304. streamlit/static/static/js/index.DH6zBk0e.js +0 -1
  305. streamlit/static/static/js/index.DHVlVWsm.js +0 -1
  306. streamlit/static/static/js/index.DRKIVBoi.js +0 -1
  307. streamlit/static/static/js/index.DUd-lFXx.js +0 -73
  308. streamlit/static/static/js/index.D_uRBA4B.js +0 -1
  309. streamlit/static/static/js/index.QHNfgPJd.js +0 -1
  310. streamlit/static/static/js/index.a-RJocYL.js +0 -1
  311. streamlit/static/static/js/index.cvz4B1gy.js +0 -1
  312. streamlit/static/static/js/index.t--hEgTQ.js +0 -6
  313. streamlit/static/static/js/useOnInputChange.z04u96A8.js +0 -1
  314. streamlit/static/static/media/SourceCodePro-Bold.CFEfr7-q.woff2 +0 -0
  315. streamlit/static/static/media/SourceCodePro-BoldItalic.C-LkFXxa.woff2 +0 -0
  316. streamlit/static/static/media/SourceCodePro-Italic.CxFOx7N-.woff2 +0 -0
  317. streamlit/static/static/media/SourceCodePro-Regular.CBOlD63d.woff2 +0 -0
  318. streamlit/static/static/media/SourceCodePro-SemiBold.CFHwW3Wd.woff2 +0 -0
  319. streamlit/static/static/media/SourceCodePro-SemiBoldItalic.Cg2yRu82.woff2 +0 -0
  320. streamlit/static/static/media/SourceSansPro-Bold.-6c9oR8J.woff2 +0 -0
  321. streamlit/static/static/media/SourceSansPro-BoldItalic.DmM_grLY.woff2 +0 -0
  322. streamlit/static/static/media/SourceSansPro-Italic.I1ipWe7Q.woff2 +0 -0
  323. streamlit/static/static/media/SourceSansPro-Regular.DZLUzqI4.woff2 +0 -0
  324. streamlit/static/static/media/SourceSansPro-SemiBold.sKQIyTMz.woff2 +0 -0
  325. streamlit/static/static/media/SourceSansPro-SemiBoldItalic.C0wP0icr.woff2 +0 -0
  326. streamlit/static/static/media/SourceSerifPro-Bold.8TUnKj4x.woff2 +0 -0
  327. streamlit/static/static/media/SourceSerifPro-BoldItalic.CBVO7Ve7.woff2 +0 -0
  328. streamlit/static/static/media/SourceSerifPro-Italic.DkFgL2HZ.woff2 +0 -0
  329. streamlit/static/static/media/SourceSerifPro-Regular.CNJNET2S.woff2 +0 -0
  330. streamlit/static/static/media/SourceSerifPro-SemiBold.CHyh9GC5.woff2 +0 -0
  331. streamlit/static/static/media/SourceSerifPro-SemiBoldItalic.CBtz8sWN.woff2 +0 -0
  332. streamlit-1.45.1.dist-info/RECORD +0 -568
  333. {streamlit-1.45.1.data → streamlit-1.46.1.data}/scripts/streamlit.cmd +0 -0
  334. {streamlit-1.45.1.dist-info → streamlit-1.46.1.dist-info}/entry_points.txt +0 -0
  335. {streamlit-1.45.1.dist-info → streamlit-1.46.1.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,7 @@
14
14
  from __future__ import annotations
15
15
 
16
16
  import json
17
- from typing import Any
17
+ from typing import Any, Final, cast
18
18
  from urllib.parse import urlparse
19
19
 
20
20
  import tornado.web
@@ -26,10 +26,13 @@ from streamlit.auth_util import (
26
26
  get_secrets_auth_section,
27
27
  )
28
28
  from streamlit.errors import StreamlitAuthError
29
+ from streamlit.logger import get_logger
29
30
  from streamlit.url_util import make_url_path
30
31
  from streamlit.web.server.oidc_mixin import TornadoOAuth, TornadoOAuth2App
31
32
  from streamlit.web.server.server_util import AUTH_COOKIE_NAME
32
33
 
34
+ _LOGGER: Final = get_logger(__name__)
35
+
33
36
  auth_cache = AuthCache()
34
37
 
35
38
 
@@ -57,7 +60,7 @@ def create_oauth_client(provider: str) -> tuple[TornadoOAuth2App, str]:
57
60
 
58
61
  oauth = TornadoOAuth(config, cache=auth_cache)
59
62
  oauth.register(provider)
60
- return oauth.create_client(provider), redirect_uri
63
+ return oauth.create_client(provider), redirect_uri # type: ignore[no-untyped-call]
61
64
 
62
65
 
63
66
  class AuthHandlerMixin(tornado.web.RequestHandler):
@@ -71,6 +74,13 @@ class AuthHandlerMixin(tornado.web.RequestHandler):
71
74
 
72
75
  def set_auth_cookie(self, user_info: dict[str, Any]) -> None:
73
76
  serialized_cookie_value = json.dumps(user_info)
77
+
78
+ # log error if cookie value is larger than 4096 bytes
79
+ if len(serialized_cookie_value.encode()) > 4096:
80
+ _LOGGER.error(
81
+ "Authentication cookie size exceeds maximum browser limit of 4096 bytes. Authentication may fail."
82
+ )
83
+
74
84
  try:
75
85
  # We don't specify Tornado secure flag here because it leads to missing cookie on Safari.
76
86
  # The OIDC flow should work only on secure context anyway (localhost or HTTPS),
@@ -92,7 +102,7 @@ class AuthHandlerMixin(tornado.web.RequestHandler):
92
102
 
93
103
 
94
104
  class AuthLoginHandler(AuthHandlerMixin, tornado.web.RequestHandler):
95
- async def get(self):
105
+ async def get(self) -> None:
96
106
  """Redirect to the OAuth provider login page."""
97
107
  provider = self._parse_provider_token()
98
108
  if provider is None:
@@ -107,9 +117,9 @@ class AuthLoginHandler(AuthHandlerMixin, tornado.web.RequestHandler):
107
117
 
108
118
  def _parse_provider_token(self) -> str | None:
109
119
  provider_token = self.get_argument("provider", None)
120
+ if provider_token is None:
121
+ return None
110
122
  try:
111
- if provider_token is None:
112
- raise StreamlitAuthError("Missing provider token")
113
123
  payload = decode_provider_token(provider_token)
114
124
  except StreamlitAuthError:
115
125
  return None
@@ -118,35 +128,57 @@ class AuthLoginHandler(AuthHandlerMixin, tornado.web.RequestHandler):
118
128
 
119
129
 
120
130
  class AuthLogoutHandler(AuthHandlerMixin, tornado.web.RequestHandler):
121
- def get(self):
131
+ def get(self) -> None:
122
132
  self.clear_auth_cookie()
123
133
  self.redirect_to_base()
124
134
 
125
135
 
126
136
  class AuthCallbackHandler(AuthHandlerMixin, tornado.web.RequestHandler):
127
- async def get(self):
137
+ async def get(self) -> None:
128
138
  provider = self._get_provider_by_state()
129
139
  origin = self._get_origin_from_secrets()
130
140
  if origin is None:
141
+ _LOGGER.error(
142
+ "Error, misconfigured origin for `redirect_uri` in secrets. ",
143
+ )
131
144
  self.redirect_to_base()
132
145
  return
133
146
 
134
147
  error = self.get_argument("error", None)
135
148
  if error:
149
+ error_description = self.get_argument("error_description", None)
150
+ sanitized_error = error.replace("\n", "").replace("\r", "")
151
+ sanitized_error_description = (
152
+ error_description.replace("\n", "").replace("\r", "")
153
+ if error_description
154
+ else None
155
+ )
156
+ _LOGGER.error(
157
+ "Error during authentication: %s. Error description: %s",
158
+ sanitized_error,
159
+ sanitized_error_description,
160
+ )
136
161
  self.redirect_to_base()
137
162
  return
138
163
 
139
164
  if provider is None:
165
+ _LOGGER.error(
166
+ "Error, missing provider for oauth callback.",
167
+ )
140
168
  self.redirect_to_base()
141
169
  return
142
170
 
143
171
  client, _ = create_oauth_client(provider)
144
172
  token = client.authorize_access_token(self)
145
- user = token.get("userinfo")
173
+ user = cast("dict[str, Any]", token.get("userinfo"))
146
174
 
147
175
  cookie_value = dict(user, origin=origin, is_logged_in=True)
148
176
  if user:
149
177
  self.set_auth_cookie(cookie_value)
178
+ else:
179
+ _LOGGER.error(
180
+ "Error, missing user info.",
181
+ )
150
182
  self.redirect_to_base()
151
183
 
152
184
  def _get_provider_by_state(self) -> str | None:
@@ -157,7 +189,7 @@ class AuthCallbackHandler(AuthHandlerMixin, tornado.web.RequestHandler):
157
189
  _, _, recorded_provider, code = key.split("_")
158
190
  state_provider_mapping[code] = recorded_provider
159
191
 
160
- provider: str | None = state_provider_mapping.get(state_code_from_url, None)
192
+ provider: str | None = state_provider_mapping.get(state_code_from_url)
161
193
  return provider
162
194
 
163
195
  def _get_origin_from_secrets(self) -> str | None:
@@ -12,34 +12,48 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import tornado.web
16
- from authlib.integrations.base_client import ( # type: ignore[import-untyped]
15
+ # mypy: disable-error-code="no-untyped-call"
16
+ # ruff: noqa: ANN201
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import TYPE_CHECKING, Any, Callable, cast
21
+
22
+ from authlib.integrations.base_client import (
17
23
  BaseApp,
18
24
  BaseOAuth,
19
25
  OAuth2Mixin,
20
26
  OAuthError,
21
27
  OpenIDMixin,
22
28
  )
23
- from authlib.integrations.requests_client import ( # type: ignore[import-untyped]
29
+ from authlib.integrations.requests_client import (
24
30
  OAuth2Session,
25
31
  )
26
32
 
27
33
  from streamlit.web.server.authlib_tornado_integration import TornadoIntegration
28
34
 
35
+ if TYPE_CHECKING:
36
+ import tornado.web
37
+
38
+ from streamlit.auth_util import AuthCache
29
39
 
30
- class TornadoOAuth2App(OAuth2Mixin, OpenIDMixin, BaseApp): # type: ignore[misc]
40
+
41
+ class TornadoOAuth2App(OAuth2Mixin, OpenIDMixin, BaseApp):
31
42
  client_cls = OAuth2Session
32
43
 
33
- def load_server_metadata(self):
44
+ def load_server_metadata(self) -> dict[str, Any]:
34
45
  """We enforce S256 code challenge method if it is supported by the server."""
35
- result = super().load_server_metadata()
46
+ result = cast("dict[str, Any]", super().load_server_metadata())
36
47
  if "S256" in result.get("code_challenge_methods_supported", []):
37
48
  self.client_kwargs["code_challenge_method"] = "S256"
38
49
  return result
39
50
 
40
51
  def authorize_redirect(
41
- self, request_handler: tornado.web.RequestHandler, redirect_uri=None, **kwargs
42
- ):
52
+ self,
53
+ request_handler: tornado.web.RequestHandler,
54
+ redirect_uri: Any = None,
55
+ **kwargs: Any,
56
+ ) -> None:
43
57
  """Create a HTTP Redirect for Authorization Endpoint.
44
58
 
45
59
  :param request_handler: HTTP request instance from Tornado.
@@ -52,8 +66,8 @@ class TornadoOAuth2App(OAuth2Mixin, OpenIDMixin, BaseApp): # type: ignore[misc]
52
66
  request_handler.redirect(auth_context["url"], status=302)
53
67
 
54
68
  def authorize_access_token(
55
- self, request_handler: tornado.web.RequestHandler, **kwargs
56
- ):
69
+ self, request_handler: tornado.web.RequestHandler, **kwargs: Any
70
+ ) -> dict[str, Any]:
57
71
  """
58
72
  :param request_handler: HTTP request instance from Tornado.
59
73
  :return: A token dict.
@@ -68,13 +82,12 @@ class TornadoOAuth2App(OAuth2Mixin, OpenIDMixin, BaseApp): # type: ignore[misc]
68
82
  "state": request_handler.get_argument("state"),
69
83
  }
70
84
 
71
- assert self.framework.cache is not None
72
85
  session = None
73
86
 
74
87
  claims_options = kwargs.pop("claims_options", None)
75
88
  state_data = self.framework.get_state_data(session, params.get("state"))
76
89
  self.framework.clear_state_data(session, params.get("state"))
77
- params = self._format_state_params(state_data, params)
90
+ params = self._format_state_params(state_data, params) # type: ignore[attr-defined]
78
91
  token = self.fetch_access_token(**params, **kwargs)
79
92
 
80
93
  if "id_token" in token and "nonce" in state_data:
@@ -82,26 +95,31 @@ class TornadoOAuth2App(OAuth2Mixin, OpenIDMixin, BaseApp): # type: ignore[misc]
82
95
  token, nonce=state_data["nonce"], claims_options=claims_options
83
96
  )
84
97
  token = {**token, "userinfo": userinfo}
85
- return token
98
+ return cast("dict[str, Any]", token)
86
99
 
87
- def _save_authorize_data(self, **kwargs):
100
+ def _save_authorize_data(self, **kwargs: Any) -> None:
88
101
  """Authlib underlying uses the concept of "session" to store state data.
89
102
  In Tornado, we don't have a session, so we use the framework's cache option.
90
103
  """
91
104
  state = kwargs.pop("state", None)
92
105
  if state:
93
- assert self.framework.cache is not None
94
106
  session = None
95
107
  self.framework.set_state_data(session, state, kwargs)
96
108
  else:
97
109
  raise RuntimeError("Missing state value")
98
110
 
99
111
 
100
- class TornadoOAuth(BaseOAuth): # type: ignore[misc]
112
+ class TornadoOAuth(BaseOAuth):
101
113
  oauth2_client_cls = TornadoOAuth2App
102
114
  framework_integration_cls = TornadoIntegration
103
115
 
104
- def __init__(self, config=None, cache=None, fetch_token=None, update_token=None):
116
+ def __init__(
117
+ self,
118
+ config: dict[str, Any] | None = None,
119
+ cache: AuthCache | None = None,
120
+ fetch_token: Callable[[dict[str, Any]], dict[str, Any]] | None = None,
121
+ update_token: Callable[[dict[str, Any]], dict[str, Any]] | None = None,
122
+ ):
105
123
  super().__init__(
106
124
  cache=cache, fetch_token=fetch_token, update_token=update_token
107
125
  )
@@ -15,40 +15,48 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import os
18
- from typing import TYPE_CHECKING
18
+ from typing import TYPE_CHECKING, Any, Callable, cast
19
19
 
20
20
  import tornado.web
21
21
 
22
22
  from streamlit import config, file_util
23
23
  from streamlit.web.server.server_util import (
24
+ allowlisted_origins,
24
25
  emit_endpoint_deprecation_notice,
25
26
  is_xsrf_enabled,
26
27
  )
27
28
 
28
29
  if TYPE_CHECKING:
29
- from collections.abc import Sequence
30
+ from collections.abc import Awaitable, Sequence
30
31
 
31
32
 
32
- def allow_cross_origin_requests() -> bool:
33
- """True if cross-origin requests are allowed.
34
-
35
- We only allow cross-origin requests when CORS protection has been disabled
36
- with server.enableCORS=False or if using the Node server. When using the
37
- Node server, we have a dev and prod port, which count as two origins.
33
+ def allow_all_cross_origin_requests() -> bool:
34
+ """True if cross-origin requests from any origin are allowed.
38
35
 
36
+ We only allow ALL cross-origin requests when CORS protection has been
37
+ disabled with server.enableCORS=False or if using the Node server in dev
38
+ mode. When in dev mode, we have a dev and prod port, which count as two
39
+ origins.
39
40
  """
41
+
40
42
  return not config.get_option("server.enableCORS") or config.get_option(
41
43
  "global.developmentMode"
42
44
  )
43
45
 
44
46
 
47
+ def is_allowed_origin(origin: Any) -> bool:
48
+ if not isinstance(origin, str):
49
+ return False
50
+ return origin in allowlisted_origins()
51
+
52
+
45
53
  class StaticFileHandler(tornado.web.StaticFileHandler):
46
54
  def initialize(
47
55
  self,
48
56
  path: str,
49
57
  default_filename: str | None = None,
50
58
  reserved_paths: Sequence[str] = (),
51
- ):
59
+ ) -> None:
52
60
  self._reserved_paths = reserved_paths
53
61
 
54
62
  super().initialize(path, default_filename)
@@ -78,15 +86,15 @@ class StaticFileHandler(tornado.web.StaticFileHandler):
78
86
  if os.path.sep != "/":
79
87
  url_path = url_path.replace(os.path.sep, "/")
80
88
  if any(url_path.endswith(x) for x in self._reserved_paths):
81
- raise e
89
+ raise
82
90
 
83
91
  self.path = self.parse_url_path(self.default_filename or "index.html")
84
92
  absolute_path = self.get_absolute_path(self.root, self.path)
85
93
  return super().validate_absolute_path(root, absolute_path)
86
94
 
87
- raise e
95
+ raise
88
96
 
89
- def write_error(self, status_code: int, **kwargs) -> None:
97
+ def write_error(self, status_code: int, **kwargs: Any) -> None:
90
98
  if status_code == 404:
91
99
  index_file = os.path.join(file_util.get_static_dir(), "index.html")
92
100
  self.render(index_file)
@@ -96,25 +104,27 @@ class StaticFileHandler(tornado.web.StaticFileHandler):
96
104
 
97
105
  class AddSlashHandler(tornado.web.RequestHandler):
98
106
  @tornado.web.addslash
99
- def get(self):
107
+ def get(self) -> None:
100
108
  pass
101
109
 
102
110
 
103
111
  class RemoveSlashHandler(tornado.web.RequestHandler):
104
112
  @tornado.web.removeslash
105
- def get(self):
113
+ def get(self) -> None:
106
114
  pass
107
115
 
108
116
 
109
117
  class _SpecialRequestHandler(tornado.web.RequestHandler):
110
118
  """Superclass for "special" endpoints, like /healthz."""
111
119
 
112
- def set_default_headers(self):
120
+ def set_default_headers(self) -> None:
113
121
  self.set_header("Cache-Control", "no-cache")
114
- if allow_cross_origin_requests():
122
+ if allow_all_cross_origin_requests():
115
123
  self.set_header("Access-Control-Allow-Origin", "*")
124
+ elif is_allowed_origin(origin := self.request.headers.get("Origin")):
125
+ self.set_header("Access-Control-Allow-Origin", cast("str", origin))
116
126
 
117
- def options(self):
127
+ def options(self) -> None:
118
128
  """/OPTIONS handler for preflight CORS checks.
119
129
 
120
130
  When a browser is making a CORS request, it may sometimes first
@@ -136,7 +146,7 @@ class _SpecialRequestHandler(tornado.web.RequestHandler):
136
146
 
137
147
 
138
148
  class HealthHandler(_SpecialRequestHandler):
139
- def initialize(self, callback):
149
+ def initialize(self, callback: Callable[[], Awaitable[tuple[bool, str]]]) -> None:
140
150
  """Initialize the handler.
141
151
 
142
152
  Parameters
@@ -147,15 +157,15 @@ class HealthHandler(_SpecialRequestHandler):
147
157
  """
148
158
  self._callback = callback
149
159
 
150
- async def get(self):
160
+ async def get(self) -> None:
151
161
  await self.handle_request()
152
162
 
153
163
  # Some monitoring services only support the HTTP HEAD method for requests to
154
164
  # healthcheck endpoints, so we support HEAD as well to play nicely with them.
155
- async def head(self):
165
+ async def head(self) -> None:
156
166
  await self.handle_request()
157
167
 
158
- async def handle_request(self):
168
+ async def handle_request(self) -> None:
159
169
  if self.request.uri and "_stcore/" not in self.request.uri:
160
170
  new_path = (
161
171
  "/_stcore/script-health-check"
@@ -212,7 +222,7 @@ _DEFAULT_ALLOWED_MESSAGE_ORIGINS = [
212
222
 
213
223
 
214
224
  class HostConfigHandler(_SpecialRequestHandler):
215
- def initialize(self):
225
+ def initialize(self) -> None:
216
226
  # Make a copy of the allowedOrigins list, since we might modify it later:
217
227
  self._allowed_origins = _DEFAULT_ALLOWED_MESSAGE_ORIGINS.copy()
218
228
 
@@ -53,8 +53,8 @@ from streamlit.web.server.routes import (
53
53
  StaticFileHandler,
54
54
  )
55
55
  from streamlit.web.server.server_util import (
56
- DEVELOPMENT_PORT,
57
56
  get_cookie_secret,
57
+ is_tornado_version_less_than,
58
58
  is_xsrf_enabled,
59
59
  make_url_path_regex,
60
60
  )
@@ -70,12 +70,12 @@ _LOGGER: Final = get_logger(__name__)
70
70
  TORNADO_SETTINGS = {
71
71
  # Gzip HTTP responses.
72
72
  "compress_response": True,
73
- # Ping every 1s to keep WS alive.
74
- # 2021.06.22: this value was previously 20s, and was causing
75
- # connection instability for a small number of users. This smaller
76
- # ping_interval fixes that instability.
77
- # https://github.com/streamlit/streamlit/issues/3196
78
- "websocket_ping_interval": 1,
73
+ # Ping every 30s to keep WS alive.
74
+ # With recent versions of Tornado, this value must be greater than or
75
+ # equal to websocket_ping_timeout.
76
+ # For details, see https://github.com/tornadoweb/tornado/pull/3376
77
+ # For compatibility with older versions of Tornado, we set the value to 1.
78
+ "websocket_ping_interval": 1 if is_tornado_version_less_than("6.5.0") else 30,
79
79
  # If we don't get a ping response within 30s, the connection
80
80
  # is timed out.
81
81
  "websocket_ping_timeout": 30,
@@ -112,7 +112,7 @@ AUTH_LOGIN_ENDPOINT: Final = "/auth/login"
112
112
  AUTH_LOGOUT_ENDPOINT: Final = "/auth/logout"
113
113
 
114
114
 
115
- class RetriesExceeded(Exception):
115
+ class RetriesExceededError(Exception):
116
116
  pass
117
117
 
118
118
 
@@ -175,7 +175,7 @@ def _get_ssl_options(cert_file: str | None, key_file: str | None) -> SSLContext
175
175
  try:
176
176
  ssl_ctx.load_cert_chain(cert_file, key_file)
177
177
  except ssl.SSLError:
178
- _LOGGER.error(
178
+ _LOGGER.exception(
179
179
  "Failed to load SSL certificate. Make sure "
180
180
  "cert file '%s' and key file '%s' are correct.",
181
181
  cert_file,
@@ -203,14 +203,6 @@ def start_listening_tcp_socket(http_server: HTTPServer) -> None:
203
203
  address = config.get_option("server.address")
204
204
  port = config.get_option("server.port")
205
205
 
206
- if int(port) == DEVELOPMENT_PORT:
207
- _LOGGER.warning(
208
- "Port %s is reserved for internal development. "
209
- "It is strongly recommended to select an alternative port "
210
- "for `server.port`.",
211
- DEVELOPMENT_PORT,
212
- )
213
-
214
206
  try:
215
207
  http_server.listen(port, address)
216
208
  break # It worked! So let's break out of the loop.
@@ -218,16 +210,13 @@ def start_listening_tcp_socket(http_server: HTTPServer) -> None:
218
210
  except OSError as e:
219
211
  if e.errno == errno.EADDRINUSE:
220
212
  if server_port_is_manually_set():
221
- _LOGGER.error("Port %s is already in use", port)
213
+ _LOGGER.error("Port %s is already in use", port) # noqa: TRY400
222
214
  sys.exit(1)
223
215
  else:
224
216
  _LOGGER.debug(
225
217
  "Port %s already in use, trying to use the next one.", port
226
218
  )
227
219
  port += 1
228
- # Don't use the development port here:
229
- if port == DEVELOPMENT_PORT:
230
- port += 1
231
220
 
232
221
  config.set_option(
233
222
  "server.port", port, ConfigOption.STREAMLIT_DEFINITION
@@ -237,14 +226,14 @@ def start_listening_tcp_socket(http_server: HTTPServer) -> None:
237
226
  raise
238
227
 
239
228
  if call_count >= MAX_PORT_SEARCH_RETRIES:
240
- raise RetriesExceeded(
229
+ raise RetriesExceededError(
241
230
  f"Cannot start Streamlit server. Port {port} is already in use, and "
242
231
  f"Streamlit was unable to find a free port after {MAX_PORT_SEARCH_RETRIES} attempts.",
243
232
  )
244
233
 
245
234
 
246
235
  class Server:
247
- def __init__(self, main_script_path: str, is_hello: bool):
236
+ def __init__(self, main_script_path: str, is_hello: bool) -> None:
248
237
  """Create the server. It won't be started yet."""
249
238
  _set_tornado_log_levels()
250
239
  self.initialize_mimetypes()
@@ -428,7 +417,7 @@ class Server:
428
417
  make_url_path_regex(base, "(.*)"),
429
418
  StaticFileHandler,
430
419
  {
431
- "path": "%s/" % static_path,
420
+ "path": f"{static_path}/",
432
421
  "default_filename": "index.html",
433
422
  "reserved_paths": [
434
423
  # These paths are required for identifying
@@ -16,21 +16,49 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- from typing import TYPE_CHECKING, Callable, Final, Literal
19
+ from typing import TYPE_CHECKING, Callable, Final, Literal, cast
20
20
  from urllib.parse import urljoin
21
21
 
22
22
  from streamlit import config, net_util, url_util
23
23
  from streamlit.runtime.secrets import secrets_singleton
24
+ from streamlit.type_util import is_version_less_than
24
25
 
25
26
  if TYPE_CHECKING:
26
27
  from tornado.web import RequestHandler
27
28
 
28
- # The port reserved for internal development.
29
+ # The port used for internal development.
29
30
  DEVELOPMENT_PORT: Final = 3000
30
31
 
31
32
  AUTH_COOKIE_NAME: Final = "_streamlit_user"
32
33
 
33
34
 
35
+ def allowlisted_origins() -> set[str]:
36
+ return {origin.strip() for origin in config.get_option("server.corsAllowedOrigins")}
37
+
38
+
39
+ def is_tornado_version_less_than(v: str) -> bool:
40
+ """Return True if the current Tornado version is less than the input version.
41
+
42
+ Parameters
43
+ ----------
44
+ v : str
45
+ Version string, e.g. "0.25.0"
46
+
47
+ Returns
48
+ -------
49
+ bool
50
+
51
+
52
+ Raises
53
+ ------
54
+ InvalidVersion
55
+ If the version strings are not valid.
56
+ """
57
+ import tornado
58
+
59
+ return is_version_less_than(tornado.version, v)
60
+
61
+
34
62
  def is_url_from_allowed_origins(url: str) -> bool:
35
63
  """Return True if URL is from allowed origins (for CORS purpose).
36
64
 
@@ -47,10 +75,14 @@ def is_url_from_allowed_origins(url: str) -> bool:
47
75
 
48
76
  hostname = url_util.get_hostname(url)
49
77
 
50
- allowed_domains: list[str | Callable[[], str | None]] = [
78
+ allowlisted_domains = [
79
+ url_util.get_hostname(origin) for origin in allowlisted_origins()
80
+ ]
81
+
82
+ allowed_domains: list[str | None | Callable[[], str | None]] = [
51
83
  # Check localhost first.
52
84
  "localhost",
53
- "0.0.0.0",
85
+ "0.0.0.0", # noqa: S104
54
86
  "127.0.0.1",
55
87
  # Try to avoid making unnecessary HTTP requests by checking if the user
56
88
  # manually specified a server address.
@@ -58,6 +90,7 @@ def is_url_from_allowed_origins(url: str) -> bool:
58
90
  # Then try the options that depend on HTTP requests or opening sockets.
59
91
  net_util.get_internal_ip,
60
92
  net_util.get_external_ip,
93
+ *allowlisted_domains,
61
94
  ]
62
95
 
63
96
  for allowed_domain in allowed_domains:
@@ -87,12 +120,12 @@ def get_cookie_secret() -> str:
87
120
  return cookie_secret
88
121
 
89
122
 
90
- def is_xsrf_enabled():
123
+ def is_xsrf_enabled() -> bool:
91
124
  csrf_enabled = config.get_option("server.enableXsrfProtection")
92
125
  if not csrf_enabled and secrets_singleton.load_if_toml_exists():
93
126
  auth_section = secrets_singleton.get("auth", None)
94
127
  csrf_enabled = csrf_enabled or auth_section is not None
95
- return csrf_enabled
128
+ return cast("bool", csrf_enabled)
96
129
 
97
130
 
98
131
  def _get_server_address_if_manually_set() -> str | None:
@@ -102,17 +135,18 @@ def _get_server_address_if_manually_set() -> str | None:
102
135
 
103
136
 
104
137
  def make_url_path_regex(
105
- *path, trailing_slash: Literal["optional", "required", "prohibited"] = "optional"
138
+ *path: str,
139
+ trailing_slash: Literal["optional", "required", "prohibited"] = "optional",
106
140
  ) -> str:
107
141
  """Get a regex of the form ^/foo/bar/baz/?$ for a path (foo, bar, baz)."""
108
- path = [x.strip("/") for x in path if x] # Filter out falsely components.
142
+ filtered_paths = [x.strip("/") for x in path if x] # Filter out falsely components.
109
143
  path_format = r"^/%s$"
110
144
  if trailing_slash == "optional":
111
145
  path_format = r"^/%s/?$"
112
146
  elif trailing_slash == "required":
113
147
  path_format = r"^/%s/$"
114
148
 
115
- return path_format % "/".join(path)
149
+ return path_format % "/".join(filtered_paths)
116
150
 
117
151
 
118
152
  def get_url(host_ip: str) -> str:
@@ -14,11 +14,11 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from typing import TYPE_CHECKING
17
+ from typing import TYPE_CHECKING, cast
18
18
 
19
19
  import tornado.web
20
20
 
21
- from streamlit.web.server import allow_cross_origin_requests
21
+ from streamlit.web.server import allow_all_cross_origin_requests, is_allowed_origin
22
22
  from streamlit.web.server.server_util import emit_endpoint_deprecation_notice
23
23
 
24
24
  if TYPE_CHECKING:
@@ -30,11 +30,13 @@ class StatsRequestHandler(tornado.web.RequestHandler):
30
30
  def initialize(self, stats_manager: StatsManager) -> None:
31
31
  self._manager = stats_manager
32
32
 
33
- def set_default_headers(self):
34
- if allow_cross_origin_requests():
33
+ def set_default_headers(self) -> None:
34
+ if allow_all_cross_origin_requests():
35
35
  self.set_header("Access-Control-Allow-Origin", "*")
36
+ elif is_allowed_origin(origin := self.request.headers.get("Origin")):
37
+ self.set_header("Access-Control-Allow-Origin", cast("str", origin))
36
38
 
37
- def options(self):
39
+ def options(self) -> None:
38
40
  """/OPTIONS handler for preflight CORS checks."""
39
41
  self.set_status(204)
40
42
  self.finish()