streamlit 1.49.1__py3-none-any.whl → 1.51.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. streamlit/__init__.py +4 -1
  2. streamlit/column_config.py +2 -0
  3. streamlit/commands/navigation.py +7 -7
  4. streamlit/commands/page_config.py +4 -6
  5. streamlit/components/v1/custom_component.py +17 -42
  6. streamlit/components/v2/__init__.py +458 -0
  7. streamlit/components/v2/bidi_component/__init__.py +20 -0
  8. streamlit/components/v2/bidi_component/constants.py +29 -0
  9. streamlit/components/v2/bidi_component/main.py +386 -0
  10. streamlit/components/v2/bidi_component/serialization.py +265 -0
  11. streamlit/components/v2/bidi_component/state.py +92 -0
  12. streamlit/components/v2/component_definition_resolver.py +143 -0
  13. streamlit/components/v2/component_file_watcher.py +403 -0
  14. streamlit/components/v2/component_manager.py +431 -0
  15. streamlit/components/v2/component_manifest_handler.py +122 -0
  16. streamlit/components/v2/component_path_utils.py +245 -0
  17. streamlit/components/v2/component_registry.py +409 -0
  18. streamlit/components/v2/get_bidi_component_manager.py +51 -0
  19. streamlit/components/v2/manifest_scanner.py +615 -0
  20. streamlit/components/v2/presentation.py +198 -0
  21. streamlit/components/v2/types.py +324 -0
  22. streamlit/config.py +741 -32
  23. streamlit/config_option.py +4 -1
  24. streamlit/config_util.py +650 -1
  25. streamlit/connections/base_connection.py +4 -2
  26. streamlit/dataframe_util.py +18 -10
  27. streamlit/delta_generator.py +8 -7
  28. streamlit/delta_generator_singletons.py +3 -1
  29. streamlit/deprecation_util.py +17 -6
  30. streamlit/elements/arrow.py +90 -42
  31. streamlit/elements/deck_gl_json_chart.py +98 -39
  32. streamlit/elements/dialog_decorator.py +2 -1
  33. streamlit/elements/exception.py +3 -1
  34. streamlit/elements/form.py +6 -6
  35. streamlit/elements/graphviz_chart.py +24 -9
  36. streamlit/elements/heading.py +3 -5
  37. streamlit/elements/iframe.py +0 -2
  38. streamlit/elements/image.py +12 -13
  39. streamlit/elements/layouts.py +89 -22
  40. streamlit/elements/lib/built_in_chart_utils.py +95 -31
  41. streamlit/elements/lib/color_util.py +8 -18
  42. streamlit/elements/lib/column_config_utils.py +9 -8
  43. streamlit/elements/lib/column_types.py +595 -148
  44. streamlit/elements/lib/dialog.py +3 -2
  45. streamlit/elements/lib/image_utils.py +3 -5
  46. streamlit/elements/lib/layout_utils.py +50 -13
  47. streamlit/elements/lib/mutable_status_container.py +2 -2
  48. streamlit/elements/lib/options_selector_utils.py +2 -2
  49. streamlit/elements/lib/pandas_styler_utils.py +30 -14
  50. streamlit/elements/lib/utils.py +21 -9
  51. streamlit/elements/map.py +81 -40
  52. streamlit/elements/media.py +7 -7
  53. streamlit/elements/metric.py +11 -35
  54. streamlit/elements/pdf.py +2 -4
  55. streamlit/elements/plotly_chart.py +142 -26
  56. streamlit/elements/progress.py +2 -4
  57. streamlit/elements/pyplot.py +6 -6
  58. streamlit/elements/space.py +113 -0
  59. streamlit/elements/vega_charts.py +400 -143
  60. streamlit/elements/widgets/audio_input.py +52 -4
  61. streamlit/elements/widgets/button.py +29 -29
  62. streamlit/elements/widgets/button_group.py +33 -6
  63. streamlit/elements/widgets/camera_input.py +3 -4
  64. streamlit/elements/widgets/chat.py +7 -0
  65. streamlit/elements/widgets/checkbox.py +1 -0
  66. streamlit/elements/widgets/color_picker.py +1 -0
  67. streamlit/elements/widgets/data_editor.py +34 -29
  68. streamlit/elements/widgets/file_uploader.py +6 -10
  69. streamlit/elements/widgets/multiselect.py +14 -3
  70. streamlit/elements/widgets/number_input.py +5 -4
  71. streamlit/elements/widgets/radio.py +10 -2
  72. streamlit/elements/widgets/select_slider.py +8 -4
  73. streamlit/elements/widgets/selectbox.py +9 -2
  74. streamlit/elements/widgets/slider.py +38 -41
  75. streamlit/elements/widgets/text_widgets.py +6 -0
  76. streamlit/elements/widgets/time_widgets.py +15 -12
  77. streamlit/elements/write.py +28 -23
  78. streamlit/emojis.py +1 -1
  79. streamlit/errors.py +115 -0
  80. streamlit/git_util.py +65 -43
  81. streamlit/hello/hello.py +8 -0
  82. streamlit/hello/utils.py +2 -1
  83. streamlit/material_icon_names.py +1 -1
  84. streamlit/navigation/page.py +4 -1
  85. streamlit/proto/ArrowData_pb2.py +27 -0
  86. streamlit/proto/ArrowData_pb2.pyi +46 -0
  87. streamlit/proto/Arrow_pb2.py +10 -8
  88. streamlit/proto/Arrow_pb2.pyi +31 -2
  89. streamlit/proto/AudioInput_pb2.py +2 -2
  90. streamlit/proto/AudioInput_pb2.pyi +6 -2
  91. streamlit/proto/BidiComponent_pb2.py +34 -0
  92. streamlit/proto/BidiComponent_pb2.pyi +153 -0
  93. streamlit/proto/Block_pb2.py +11 -11
  94. streamlit/proto/Block_pb2.pyi +9 -1
  95. streamlit/proto/DeckGlJsonChart_pb2.py +10 -4
  96. streamlit/proto/DeckGlJsonChart_pb2.pyi +9 -3
  97. streamlit/proto/Element_pb2.py +5 -3
  98. streamlit/proto/Element_pb2.pyi +14 -4
  99. streamlit/proto/HeightConfig_pb2.py +2 -2
  100. streamlit/proto/HeightConfig_pb2.pyi +6 -3
  101. streamlit/proto/NewSession_pb2.py +18 -16
  102. streamlit/proto/NewSession_pb2.pyi +158 -6
  103. streamlit/proto/PlotlyChart_pb2.py +8 -6
  104. streamlit/proto/PlotlyChart_pb2.pyi +3 -1
  105. streamlit/proto/Space_pb2.py +27 -0
  106. streamlit/proto/Space_pb2.pyi +42 -0
  107. streamlit/proto/WidgetStates_pb2.py +2 -2
  108. streamlit/proto/WidgetStates_pb2.pyi +13 -3
  109. streamlit/proto/WidthConfig_pb2.py +2 -2
  110. streamlit/proto/WidthConfig_pb2.pyi +6 -3
  111. streamlit/runtime/app_session.py +45 -6
  112. streamlit/runtime/caching/cache_data_api.py +4 -4
  113. streamlit/runtime/caching/cache_errors.py +4 -1
  114. streamlit/runtime/caching/cache_resource_api.py +3 -2
  115. streamlit/runtime/caching/cache_utils.py +2 -1
  116. streamlit/runtime/caching/cached_message_replay.py +3 -3
  117. streamlit/runtime/caching/hashing.py +3 -4
  118. streamlit/runtime/caching/legacy_cache_api.py +2 -1
  119. streamlit/runtime/connection_factory.py +1 -3
  120. streamlit/runtime/forward_msg_queue.py +4 -1
  121. streamlit/runtime/fragment.py +2 -1
  122. streamlit/runtime/memory_media_file_storage.py +1 -1
  123. streamlit/runtime/metrics_util.py +6 -2
  124. streamlit/runtime/runtime.py +14 -0
  125. streamlit/runtime/scriptrunner/exec_code.py +2 -1
  126. streamlit/runtime/scriptrunner/script_runner.py +2 -2
  127. streamlit/runtime/scriptrunner_utils/script_run_context.py +3 -6
  128. streamlit/runtime/secrets.py +2 -4
  129. streamlit/runtime/session_manager.py +3 -1
  130. streamlit/runtime/state/common.py +30 -5
  131. streamlit/runtime/state/presentation.py +85 -0
  132. streamlit/runtime/state/safe_session_state.py +2 -2
  133. streamlit/runtime/state/session_state.py +220 -16
  134. streamlit/runtime/state/widgets.py +19 -3
  135. streamlit/runtime/theme_util.py +148 -0
  136. streamlit/runtime/websocket_session_manager.py +3 -1
  137. streamlit/source_util.py +2 -2
  138. streamlit/static/index.html +2 -2
  139. streamlit/static/manifest.json +244 -227
  140. streamlit/static/static/css/{index.C8X8rNzw.css → index.BpABIXK9.css} +1 -1
  141. streamlit/static/static/css/index.DgR7E2CV.css +1 -0
  142. streamlit/static/static/js/{ErrorOutline.esm.DcGrhbBP.js → ErrorOutline.esm.YoJdlW1p.js} +1 -1
  143. streamlit/static/static/js/{FileDownload.esm.DgBvV6Pq.js → FileDownload.esm.Ddx8VEYy.js} +1 -1
  144. streamlit/static/static/js/{FileHelper.M6AAaeuA.js → FileHelper.90EtOmj9.js} +1 -1
  145. streamlit/static/static/js/{FormClearHelper.DHh1GFzm.js → FormClearHelper.BB1Km6eP.js} +1 -1
  146. streamlit/static/static/js/InputInstructions.jhH15PqV.js +1 -0
  147. streamlit/static/static/js/{Particles.DDVT-6Qc.js → Particles.DUsputn1.js} +1 -1
  148. streamlit/static/static/js/{ProgressBar.BEY0cXXV.js → ProgressBar.DLY8H6nE.js} +2 -2
  149. streamlit/static/static/js/Toolbar.D8nHCkuz.js +1 -0
  150. streamlit/static/static/js/{base-input.CK3UVGp1.js → base-input.CJGiNqed.js} +3 -3
  151. streamlit/static/static/js/{checkbox.D8W881TL.js → checkbox.Cpdd482O.js} +1 -1
  152. streamlit/static/static/js/{createSuper.B6W-Dh9S.js → createSuper.CuQIogbW.js} +1 -1
  153. streamlit/static/static/js/data-grid-overlay-editor.2Ufgxc6y.js +1 -0
  154. streamlit/static/static/js/{downloader.DiKpuU_S.js → downloader.CN0K7xlu.js} +1 -1
  155. streamlit/static/static/js/{es6.B8zRNPZ-.js → es6.BJcsVXQ0.js} +2 -2
  156. streamlit/static/static/js/{iframeResizer.contentWindow.DIewJmmh.js → iframeResizer.contentWindow.XzUvQqcZ.js} +1 -1
  157. streamlit/static/static/js/index.B1ZQh4P1.js +1 -0
  158. streamlit/static/static/js/index.BKstZk0M.js +27 -0
  159. streamlit/static/static/js/{index.Bte_9Lyq.js → index.BMcFsUee.js} +1 -1
  160. streamlit/static/static/js/{index.qhs54UAB.js → index.BR-IdcTb.js} +1 -1
  161. streamlit/static/static/js/{index.CejBxbg1.js → index.B_dWA3vd.js} +1 -1
  162. streamlit/static/static/js/{index.D5naqx-J.js → index.BgnZEMVh.js} +1 -1
  163. streamlit/static/static/js/{index.C7fRKRs4.js → index.BohqXifI.js} +1 -1
  164. streamlit/static/static/js/{index.cnnXF7xQ.js → index.Br5nxKNj.js} +1 -1
  165. streamlit/static/static/js/index.BrIKVbNc.js +3 -0
  166. streamlit/static/static/js/index.BtWUPzle.js +1 -0
  167. streamlit/static/static/js/index.C0RLraek.js +1 -0
  168. streamlit/static/static/js/{index.CP5TD2z1.js → index.CAIjskgG.js} +1 -1
  169. streamlit/static/static/js/{index.CD8HuT3N.js → index.CAj-7vWz.js} +135 -162
  170. streamlit/static/static/js/{index.DtYN2x4k.js → index.CMtEit2O.js} +1 -1
  171. streamlit/static/static/js/index.CkRlykEE.js +12 -0
  172. streamlit/static/static/js/{index.Ts_0SdB9.js → index.CmN3FXfI.js} +2 -2
  173. streamlit/static/static/js/{index.BnEpvLEz.js → index.CwbFI1_-.js} +1 -1
  174. streamlit/static/static/js/{index.CcJf6BCU.js → index.CxIUUfab.js} +27 -27
  175. streamlit/static/static/js/index.D2KPNy7e.js +1 -0
  176. streamlit/static/static/js/{index.Ch7MBCx0.js → index.D3GPA5k4.js} +47 -47
  177. streamlit/static/static/js/{index.ho6NIXGl.js → index.DGAh7DMq.js} +1 -1
  178. streamlit/static/static/js/index.DKb_NvmG.js +197 -0
  179. streamlit/static/static/js/{index.CvYYtxD_.js → index.DMqgUYKq.js} +1 -1
  180. streamlit/static/static/js/{index.zecpGxtj.js → index.DOFlg3dS.js} +1 -1
  181. streamlit/static/static/js/{index.B9mjBcgE.js → index.DPUXkcQL.js} +1 -1
  182. streamlit/static/static/js/index.DX1xY89g.js +1 -0
  183. streamlit/static/static/js/index.DYATBCsq.js +2 -0
  184. streamlit/static/static/js/{index.D2-atlaQ.js → index.DaSmGJ76.js} +3 -3
  185. streamlit/static/static/js/index.Dd7bMeLP.js +1 -0
  186. streamlit/static/static/js/{index.4eF4NxG2.js → index.DjmmgI5U.js} +1 -1
  187. streamlit/static/static/js/index.Dq56CyM2.js +1 -0
  188. streamlit/static/static/js/index.DuiXaS5_.js +7 -0
  189. streamlit/static/static/js/index.DvFidMLe.js +2 -0
  190. streamlit/static/static/js/{index.452cqrrL.js → index.DwkhC5Pc.js} +1 -1
  191. streamlit/static/static/js/{index.Dk4C7X3i.js → index.Q-3sFn1v.js} +1 -1
  192. streamlit/static/static/js/{index.CjXWwH-y.js → index.QJ5QO9sJ.js} +1 -1
  193. streamlit/static/static/js/{index.B6U8LQo3.js → index.VwTaeety.js} +1 -1
  194. streamlit/static/static/js/index.YOqQbeX8.js +1 -0
  195. streamlit/static/static/js/{input.nzVJphXi.js → input.D4MN_FzN.js} +1 -1
  196. streamlit/static/static/js/{memory.CjCgTQz3.js → memory.DrZjtdGT.js} +1 -1
  197. streamlit/static/static/js/{number-overlay-editor.DaRFzZEO.js → number-overlay-editor.DRwAw1In.js} +1 -1
  198. streamlit/static/static/js/{possibleConstructorReturn.DgiPnZ9N.js → possibleConstructorReturn.exeeJQEP.js} +1 -1
  199. streamlit/static/static/js/record.B-tDciZb.js +1 -0
  200. streamlit/static/static/js/{sandbox.mithfq7Z.js → sandbox.ClO3IuUr.js} +1 -1
  201. streamlit/static/static/js/{timepicker.Dbl5KFh6.js → timepicker.DAhu-vcF.js} +4 -4
  202. streamlit/static/static/js/{toConsumableArray.D-Dx88BQ.js → toConsumableArray.DNbljYEC.js} +1 -1
  203. streamlit/static/static/js/{uniqueId.Bh26R_3S.js → uniqueId.oG4Gvj1v.js} +1 -1
  204. streamlit/static/static/js/{useBasicWidgetState.DeK-QJpD.js → useBasicWidgetState.D6sOH6oI.js} +1 -1
  205. streamlit/static/static/js/{useTextInputAutoExpand.4iAdLWD-.js → useTextInputAutoExpand.4u3_GcuN.js} +2 -2
  206. streamlit/static/static/js/{useUpdateUiValue.CmT7_nJN.js → useUpdateUiValue.F2R3eTeR.js} +1 -1
  207. streamlit/static/static/js/wavesurfer.esm.vI8Eid4k.js +73 -0
  208. streamlit/static/static/js/withFullScreenWrapper.zothJIsI.js +1 -0
  209. streamlit/static/static/media/MaterialSymbols-Rounded.C7IFxh57.woff2 +0 -0
  210. streamlit/string_util.py +56 -1
  211. streamlit/testing/v1/app_test.py +2 -2
  212. streamlit/testing/v1/element_tree.py +23 -9
  213. streamlit/testing/v1/util.py +2 -2
  214. streamlit/type_util.py +3 -4
  215. streamlit/url_util.py +1 -3
  216. streamlit/user_info.py +1 -2
  217. streamlit/util.py +3 -1
  218. streamlit/watcher/event_based_path_watcher.py +23 -12
  219. streamlit/watcher/local_sources_watcher.py +11 -1
  220. streamlit/watcher/path_watcher.py +9 -6
  221. streamlit/watcher/polling_path_watcher.py +4 -1
  222. streamlit/watcher/util.py +2 -2
  223. streamlit/web/bootstrap.py +0 -31
  224. streamlit/web/cli.py +51 -22
  225. streamlit/web/server/bidi_component_request_handler.py +193 -0
  226. streamlit/web/server/component_file_utils.py +97 -0
  227. streamlit/web/server/component_request_handler.py +8 -21
  228. streamlit/web/server/oidc_mixin.py +3 -1
  229. streamlit/web/server/routes.py +18 -5
  230. streamlit/web/server/server.py +10 -0
  231. streamlit/web/server/server_util.py +3 -1
  232. streamlit/web/server/upload_file_request_handler.py +3 -1
  233. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/METADATA +4 -5
  234. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/RECORD +238 -209
  235. streamlit/static/static/css/index.COe1010n.css +0 -1
  236. streamlit/static/static/js/Hooks.DGu1od_L.js +0 -1
  237. streamlit/static/static/js/InputInstructions.z6sVgyYt.js +0 -1
  238. streamlit/static/static/js/Toolbar.DSnK1fUh.js +0 -1
  239. streamlit/static/static/js/data-grid-overlay-editor.DRTHOydk.js +0 -1
  240. streamlit/static/static/js/index.BXYmrqnf.js +0 -1
  241. streamlit/static/static/js/index.B_8AnktO.js +0 -1
  242. streamlit/static/static/js/index.Bl7zGQSh.js +0 -7
  243. streamlit/static/static/js/index.BnJIOYn9.js +0 -73
  244. streamlit/static/static/js/index.C1HcTl5K.js +0 -1
  245. streamlit/static/static/js/index.C7lSmSOP.js +0 -1
  246. streamlit/static/static/js/index.C_tmcx4B.js +0 -1
  247. streamlit/static/static/js/index.D3K5nOu9.js +0 -197
  248. streamlit/static/static/js/index.DkKT3LUI.js +0 -1
  249. streamlit/static/static/js/index.MTPPBDHk.js +0 -2
  250. streamlit/static/static/js/index.pqW9AMJD.js +0 -3
  251. streamlit/static/static/js/index.urHgTgMQ.js +0 -12
  252. streamlit/static/static/js/index.wzkv_11M.js +0 -1
  253. streamlit/static/static/js/index.yF5AncHY.js +0 -1
  254. streamlit/static/static/js/withFullScreenWrapper.DLp1ENGm.js +0 -1
  255. streamlit/static/static/media/MaterialSymbols-Rounded.CBxVaFdk.woff2 +0 -0
  256. {streamlit-1.49.1.data → streamlit-1.51.0.data}/scripts/streamlit.cmd +0 -0
  257. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/WHEEL +0 -0
  258. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/entry_points.txt +0 -0
  259. {streamlit-1.49.1.dist-info → streamlit-1.51.0.dist-info}/top_level.txt +0 -0
streamlit/errors.py CHANGED
@@ -41,6 +41,17 @@ class CustomComponentError(Error):
41
41
  pass
42
42
 
43
43
 
44
+ class StreamlitComponentRegistryError(Error):
45
+ """Exceptions raised while discovering or registering Streamlit components.
46
+
47
+ These errors occur during Streamlit startup when scanning installed
48
+ distributions for component metadata and registering them with the component
49
+ registry.
50
+ """
51
+
52
+ pass
53
+
54
+
44
55
  class DeprecationError(Error):
45
56
  pass
46
57
 
@@ -428,6 +439,67 @@ class StreamlitPageNotFoundError(LocalizableStreamlitException):
428
439
  )
429
440
 
430
441
 
442
+ # Bidirectional Components
443
+ class BidiComponentInvalidIdError(LocalizableStreamlitException):
444
+ """Exception raised when an invalid ID component is provided."""
445
+
446
+ def __init__(self, part: str, delimiter: str) -> None:
447
+ super().__init__(
448
+ "The `{part}` of a bidirectional component's ID must not contain "
449
+ "the delimiter sequence `{delimiter}`.",
450
+ part=part,
451
+ delimiter=delimiter,
452
+ )
453
+
454
+
455
+ class BidiComponentMissingContentError(LocalizableStreamlitException):
456
+ """Exception raised when a component is missing required content."""
457
+
458
+ def __init__(self, component_name: str) -> None:
459
+ super().__init__(
460
+ "Component `{component_name}` must have either JavaScript content "
461
+ "(`js_content` or `js_url`) or HTML content (`html_content`), or both. "
462
+ "Please ensure the component definition includes at least one of these.",
463
+ component_name=component_name,
464
+ )
465
+
466
+
467
+ class BidiComponentInvalidCallbackNameError(LocalizableStreamlitException):
468
+ """Exception raised when a callback with an invalid name is provided."""
469
+
470
+ def __init__(self, callback_name: str) -> None:
471
+ super().__init__(
472
+ "The callback name `'{callback_name}'` is not allowed. "
473
+ "Callback names must follow the pattern `on_{{event_name}}_change` "
474
+ "where `event_name` is not empty.",
475
+ callback_name=callback_name,
476
+ )
477
+
478
+
479
+ class BidiComponentInvalidDefaultKeyError(LocalizableStreamlitException):
480
+ """Exception raised when an invalid key is provided in the default dict."""
481
+
482
+ def __init__(self, state_key: str, available_keys: list[str]) -> None:
483
+ super().__init__(
484
+ "Key `'{state_key}'` in `default` is not a valid state name. "
485
+ "Valid state names are those with corresponding `on_{{state_name}}_change` "
486
+ "callbacks. Available state names: `{available_keys}`",
487
+ state_key=state_key,
488
+ available_keys=available_keys or "none",
489
+ )
490
+
491
+
492
+ class BidiComponentUnserializableDataError(LocalizableStreamlitException):
493
+ """Exception raised when data provided to a bidirectional component cannot be serialized."""
494
+
495
+ def __init__(self) -> None:
496
+ super().__init__(
497
+ "The `data` provided to the bidirectional component could not be serialized. "
498
+ "Please ensure the data is JSON-serializable, or is a supported data structure "
499
+ "like a pandas DataFrame."
500
+ )
501
+
502
+
431
503
  # policies
432
504
  class StreamlitFragmentWidgetsNotAllowedOutsideError(LocalizableStreamlitException):
433
505
  """Exception raised when the fragment attempts to write to an element outside of its container."""
@@ -519,6 +591,17 @@ class StreamlitInvalidHeightError(LocalizableStreamlitException):
519
591
  )
520
592
 
521
593
 
594
+ class StreamlitInvalidSizeError(LocalizableStreamlitException):
595
+ """Exception raised when an invalid size value is provided."""
596
+
597
+ def __init__(self, size: Any) -> None:
598
+ super().__init__(
599
+ "Invalid size value: {size}. Size must be either an integer (pixels), "
600
+ "'stretch', 'small', 'medium', or 'large'.",
601
+ size=repr(size),
602
+ )
603
+
604
+
522
605
  class StreamlitValueError(LocalizableStreamlitException):
523
606
  """Exception raised when a value is not valid for a parameter."""
524
607
 
@@ -528,3 +611,35 @@ class StreamlitValueError(LocalizableStreamlitException):
528
611
  parameter=parameter,
529
612
  valid_values=", ".join(valid_values),
530
613
  )
614
+
615
+
616
+ # config
617
+ class StreamlitInvalidThemeError(LocalizableStreamlitException):
618
+ """Exception raised for general theme errors."""
619
+
620
+ def __init__(self, message: str) -> None:
621
+ super().__init__(
622
+ message,
623
+ )
624
+
625
+
626
+ class StreamlitInvalidThemeOptionError(LocalizableStreamlitException):
627
+ """Exception raised when an invalid theme config option is provided."""
628
+
629
+ def __init__(self, message: str) -> None:
630
+ super().__init__(
631
+ message,
632
+ )
633
+
634
+
635
+ class StreamlitInvalidThemeSectionError(LocalizableStreamlitException):
636
+ """Exception raised when an invalid theme section is provided."""
637
+
638
+ def __init__(self, option_name: str, file_path_or_url: str = "config.toml") -> None:
639
+ super().__init__(
640
+ "Invalid theme section: `{option_name}` found in {file_path_or_url}. "
641
+ "Valid sections are: `theme`, `theme.light`, `theme.dark`, `theme.sidebar`, `theme.light.sidebar`, "
642
+ "and `theme.dark.sidebar`.",
643
+ option_name=option_name,
644
+ file_path_or_url=file_path_or_url,
645
+ )
streamlit/git_util.py CHANGED
@@ -16,21 +16,50 @@ from __future__ import annotations
16
16
 
17
17
  import os
18
18
  import re
19
- from typing import TYPE_CHECKING, cast
19
+ from typing import TYPE_CHECKING, Final, cast
20
20
 
21
21
  from streamlit import util
22
+ from streamlit.logger import get_logger
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from git import Commit, Remote, RemoteReference, Repo
25
26
 
26
- # Github has two URLs, one that is https and one that is ssh
27
- GITHUB_HTTP_URL = r"^https://(www\.)?github.com/(.+)/(.+)(?:.git)?$"
28
- GITHUB_SSH_URL = r"^git@github.com:(.+)/(.+)(?:.git)?$"
27
+ _LOGGER: Final = get_logger(__name__)
28
+
29
+ # Github repo extractor: match owner/repo for https/ssh/scp forms, with optional
30
+ # userinfo/port/trailing slash. This is just to extract the repo name, not validate the URL.
31
+ _GITHUB_URL_PATTERN: Final = re.compile(
32
+ r"github\.com(?::\d+)?[/:]([^/]+)/([^/]+?)(?:\.git)?/?$"
33
+ )
29
34
 
30
35
  # We don't support git < 2.7, because we can't get repo info without
31
36
  # talking to the remote server, which results in the user being prompted
32
37
  # for credentials.
33
- MIN_GIT_VERSION = (2, 7, 0)
38
+ _MIN_GIT_VERSION: Final = (2, 7, 0)
39
+
40
+
41
+ def _extract_github_repo_from_url(url: str) -> str | None:
42
+ """Extract the ``owner/repo`` from a GitHub remote URL.
43
+
44
+ This supports HTTPS and SSH URL forms including optional user info, port,
45
+ trailing slash, and ``.git`` suffix. Validation of the scheme is not
46
+ performed; we only extract if the URL contains ``github.com`` and ends with
47
+ a path of the shape ``owner/repo``.
48
+
49
+ Parameters
50
+ ----------
51
+ url
52
+ The remote URL string.
53
+
54
+ Returns
55
+ -------
56
+ str | None
57
+ The extracted ``owner/repo`` if found; otherwise ``None``.
58
+ """
59
+ match = _GITHUB_URL_PATTERN.search(url.strip())
60
+ if match is None:
61
+ return None
62
+ return f"{match.group(1)}/{match.group(2)}"
34
63
 
35
64
 
36
65
  class GitRepo:
@@ -40,6 +69,7 @@ class GitRepo:
40
69
  # If we have a valid repo, git_version will be a tuple
41
70
  # of 3+ ints: (major, minor, patch, possible_additional_patch_number)
42
71
  self.git_version: tuple[int, ...] | None = None
72
+ self.module: str = ""
43
73
 
44
74
  try:
45
75
  import git
@@ -47,26 +77,31 @@ class GitRepo:
47
77
  self.repo = git.Repo(path, search_parent_directories=True)
48
78
  self.git_version = self.repo.git.version_info
49
79
 
50
- if self.git_version is not None and self.git_version >= MIN_GIT_VERSION:
80
+ if self.git_version is not None and self.git_version >= _MIN_GIT_VERSION:
51
81
  git_root = self.repo.git.rev_parse("--show-toplevel")
52
82
  self.module = os.path.relpath(path, git_root)
53
83
  except Exception:
54
- # The git repo must be invalid for the following reasons:
55
- # * git binary or GitPython not installed
56
- # * No .git folder
57
- # * Corrupted .git folder
58
- # * Path is invalid
84
+ _LOGGER.debug(
85
+ "Did not find a git repo at %s. This is expected if this isn't a git repo, but could "
86
+ "also fail for other reasons: "
87
+ "1) git binary or GitPython not installed "
88
+ "2) No .git folder "
89
+ "3) Corrupted .git folder "
90
+ "4) Path is invalid.",
91
+ path,
92
+ exc_info=True,
93
+ )
59
94
  self.repo = None
60
95
 
61
96
  def __repr__(self) -> str:
62
97
  return util.repr_(self)
63
98
 
64
99
  def is_valid(self) -> bool:
65
- """True if there's a git repo here, and git.version >= MIN_GIT_VERSION."""
100
+ """True if there's a git repo here, and git.version >= _MIN_GIT_VERSION."""
66
101
  return (
67
102
  self.repo is not None
68
103
  and self.git_version is not None
69
- and self.git_version >= MIN_GIT_VERSION
104
+ and self.git_version >= _MIN_GIT_VERSION
70
105
  )
71
106
 
72
107
  @property
@@ -129,50 +164,37 @@ class GitRepo:
129
164
  remote_name, *branch = tracking_branch.name.split("/")
130
165
  branch_name = "/".join(branch)
131
166
 
132
- return self.repo.remote(remote_name), branch_name
133
-
134
- def is_github_repo(self) -> bool:
135
- if not self.is_valid():
136
- return False
137
-
138
- remote_info = self.get_tracking_branch_remote()
139
- if remote_info is None:
140
- return False
141
-
142
- remote, _branch = remote_info
143
-
144
- for url in remote.urls:
145
- if (
146
- re.match(GITHUB_HTTP_URL, url) is not None
147
- or re.match(GITHUB_SSH_URL, url) is not None
148
- ):
149
- return True
150
-
151
- return False
167
+ try:
168
+ return self.repo.remote(remote_name), branch_name
169
+ except Exception:
170
+ _LOGGER.debug("Failed to resolve remote %s", remote_name, exc_info=True)
171
+ return None
152
172
 
153
173
  def get_repo_info(self) -> tuple[str, str, str] | None:
154
174
  if not self.is_valid():
175
+ _LOGGER.debug(
176
+ "No valid git information found. Git version: %s", self.git_version
177
+ )
155
178
  return None
156
179
 
157
180
  remote_info = self.get_tracking_branch_remote()
158
181
  if remote_info is None:
182
+ _LOGGER.debug("No tracking remote branch found for the git repo.")
159
183
  return None
160
184
 
161
185
  remote, branch = remote_info
162
-
186
+ remote_urls = list(remote.urls)
163
187
  repo = None
164
- for url in remote.urls:
165
- https_matches = re.match(GITHUB_HTTP_URL, url)
166
- ssh_matches = re.match(GITHUB_SSH_URL, url)
167
- if https_matches is not None:
168
- repo = f"{https_matches.group(2)}/{https_matches.group(3)}"
169
- break
170
-
171
- if ssh_matches is not None:
172
- repo = f"{ssh_matches.group(1)}/{ssh_matches.group(2)}"
188
+ for url in remote_urls:
189
+ repo = _extract_github_repo_from_url(url)
190
+ if repo is not None:
173
191
  break
174
192
 
175
193
  if repo is None:
194
+ _LOGGER.debug(
195
+ "Unable to determine repo name from configured remote URLs. URLs: %s",
196
+ remote_urls,
197
+ )
176
198
  return None
177
199
 
178
200
  return repo, branch, self.module
streamlit/hello/hello.py CHANGED
@@ -36,3 +36,11 @@ st.write(
36
36
  - Explore a [New York City rideshare dataset](https://github.com/streamlit/demo-uber-nyc-pickups)
37
37
  """
38
38
  )
39
+
40
+ # Preload Python modules that take a while to compile in a new venv.
41
+ # Otherwise, when users switch to another page, it seems that Streamlit
42
+ # is slow, when in reality this is just an artifact of loading/compiling
43
+ # large modules from zero.
44
+ with st.spinner("Preloading Python modules for other pages..."):
45
+ import numpy # noqa: ICN001 F401
46
+ import pandas # noqa: ICN001 F401
streamlit/hello/utils.py CHANGED
@@ -14,7 +14,8 @@
14
14
 
15
15
  import inspect
16
16
  import textwrap
17
- from typing import Any, Callable
17
+ from collections.abc import Callable
18
+ from typing import Any
18
19
 
19
20
  import streamlit as st
20
21