streamlit-nightly 1.43.3.dev20250317__py3-none-any.whl → 1.43.3.dev20250319__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 (138) hide show
  1. streamlit/auth_util.py +1 -1
  2. streamlit/commands/page_config.py +1 -1
  3. streamlit/config.py +141 -45
  4. streamlit/config_option.py +4 -1
  5. streamlit/config_util.py +1 -1
  6. streamlit/connections/snowflake_connection.py +2 -2
  7. streamlit/connections/snowpark_connection.py +1 -1
  8. streamlit/connections/sql_connection.py +1 -1
  9. streamlit/connections/util.py +1 -1
  10. streamlit/dataframe_util.py +12 -12
  11. streamlit/delta_generator.py +2 -2
  12. streamlit/deprecation_util.py +2 -2
  13. streamlit/elements/arrow.py +3 -3
  14. streamlit/elements/deck_gl_json_chart.py +4 -4
  15. streamlit/elements/dialog_decorator.py +2 -2
  16. streamlit/elements/heading.py +1 -1
  17. streamlit/elements/html.py +3 -3
  18. streamlit/elements/lib/built_in_chart_utils.py +9 -8
  19. streamlit/elements/lib/color_util.py +7 -7
  20. streamlit/elements/lib/dialog.py +1 -1
  21. streamlit/elements/lib/image_utils.py +9 -7
  22. streamlit/elements/lib/mutable_status_container.py +1 -1
  23. streamlit/elements/media.py +2 -2
  24. streamlit/elements/metric.py +1 -1
  25. streamlit/elements/plotly_chart.py +4 -4
  26. streamlit/elements/vega_charts.py +5 -5
  27. streamlit/elements/widgets/button_group.py +1 -1
  28. streamlit/elements/widgets/multiselect.py +3 -2
  29. streamlit/elements/widgets/select_slider.py +3 -5
  30. streamlit/elements/widgets/slider.py +4 -4
  31. streamlit/elements/widgets/time_widgets.py +4 -4
  32. streamlit/elements/write.py +1 -1
  33. streamlit/material_icon_names.py +1 -1
  34. streamlit/proto/NewSession_pb2.py +16 -16
  35. streamlit/proto/NewSession_pb2.pyi +22 -11
  36. streamlit/runtime/app_session.py +10 -7
  37. streamlit/runtime/caching/cache_data_api.py +2 -2
  38. streamlit/runtime/caching/cache_resource_api.py +2 -2
  39. streamlit/runtime/caching/hashing.py +7 -7
  40. streamlit/runtime/metrics_util.py +1 -1
  41. streamlit/runtime/scriptrunner/script_runner.py +2 -2
  42. streamlit/runtime/scriptrunner_utils/script_requests.py +1 -1
  43. streamlit/runtime/session_manager.py +1 -1
  44. streamlit/runtime/state/session_state.py +3 -3
  45. streamlit/runtime/websocket_session_manager.py +2 -2
  46. streamlit/static/index.html +2 -2
  47. streamlit/static/static/css/{index.DQZt7VFg.css → index.BOl9eq08.css} +1 -1
  48. streamlit/static/static/css/index.C5t3M85E.css +1 -0
  49. streamlit/static/static/js/{FileDownload.esm.DPWNg8L0.js → FileDownload.esm.Dz4lqu2t.js} +1 -1
  50. streamlit/static/static/js/{FileHelper.Be9l1B23.js → FileHelper.B_o0E5mY.js} +1 -1
  51. streamlit/static/static/js/{FormClearHelper.Ck_v4Tgg.js → FormClearHelper.B5ARMjis.js} +1 -1
  52. streamlit/static/static/js/{Hooks.B0Mdn8OE.js → Hooks.gYcZ3nte.js} +1 -1
  53. streamlit/static/static/js/{InputInstructions.DEfhAJgc.js → InputInstructions.K_vAW_MG.js} +1 -1
  54. streamlit/static/static/js/{ProgressBar.D34ZfmP5.js → ProgressBar.DOjGuUdH.js} +1 -1
  55. streamlit/static/static/js/{RenderInPortalIfExists.D7rG7dkm.js → RenderInPortalIfExists.C4HdMGrO.js} +1 -1
  56. streamlit/static/static/js/{Toolbar.BKx7cxA3.js → Toolbar.BZqFuVCo.js} +1 -1
  57. streamlit/static/static/js/{base-input.D400PpZv.js → base-input.DtNLPEYc.js} +1 -1
  58. streamlit/static/static/js/{checkbox.T2_9UyBa.js → checkbox.De5lDKlG.js} +1 -1
  59. streamlit/static/static/js/{createSuper.B6r1ToQR.js → createSuper.DUfdjejQ.js} +1 -1
  60. streamlit/static/static/js/data-grid-overlay-editor.ByFOuSZw.js +1 -0
  61. streamlit/static/static/js/{downloader.CfgCJjqI.js → downloader.Bndt7ayy.js} +1 -1
  62. streamlit/static/static/js/{es6.BIR4_s9Q.js → es6.B9axslaM.js} +2 -2
  63. streamlit/static/static/js/{iframeResizer.contentWindow.BvHB3v_q.js → iframeResizer.contentWindow.C19fTVqS.js} +1 -1
  64. streamlit/static/static/js/{index.P1KdGEyu.js → index.3vPW8lC3.js} +1 -1
  65. streamlit/static/static/js/{index.-19Q4tZa.js → index.8fEQ0naY.js} +1 -1
  66. streamlit/static/static/js/{index.ChU_mie6.js → index.B9N7bKyh.js} +1 -1
  67. streamlit/static/static/js/{index.4XxlReRC.js → index.BBkaxxeO.js} +1 -1
  68. streamlit/static/static/js/index.BDWxQ6IH.js +3 -0
  69. streamlit/static/static/js/{index.CY8FtTk0.js → index.BELm1MW6.js} +1 -1
  70. streamlit/static/static/js/{index.B6BwNYBs.js → index.BKE695Zg.js} +1 -1
  71. streamlit/static/static/js/{index.BlTWh14x.js → index.BKhwcUMH.js} +83 -83
  72. streamlit/static/static/js/index.BOsCHoek.js +1 -0
  73. streamlit/static/static/js/{index.CPHXX3ut.js → index.BQkmoKYZ.js} +1 -1
  74. streamlit/static/static/js/{index.1Y_wlVII.js → index.BRjv0YAi.js} +1 -1
  75. streamlit/static/static/js/index.BTbDncVq.js +1 -0
  76. streamlit/static/static/js/{index.wuPpurow.js → index.BVKRKiWg.js} +1 -1
  77. streamlit/static/static/js/{index.B8cYOVy0.js → index.Bbd47eDn.js} +1 -1
  78. streamlit/static/static/js/index.Bk2R3MJ7.js +1 -0
  79. streamlit/static/static/js/{index.BsoT67Bp.js → index.BoIgF6Mf.js} +2 -2
  80. streamlit/static/static/js/{index.CxgOTC1d.js → index.BpkIFU8i.js} +1 -1
  81. streamlit/static/static/js/{index.BvRYYdLA.js → index.BrZWnIcP.js} +1 -1
  82. streamlit/static/static/js/index.Bu7fiR4T.js +1 -0
  83. streamlit/static/static/js/{index.mDzBI6l2.js → index.C4zUVQy-.js} +1 -1
  84. streamlit/static/static/js/index.CBtAFng_.js +1 -0
  85. streamlit/static/static/js/{index.D3XyyrNG.js → index.CGzFdF4L.js} +2 -2
  86. streamlit/static/static/js/{index.B1y58sqw.js → index.CKmyhCjm.js} +1 -1
  87. streamlit/static/static/js/index.CVbLNEv4.js +1 -0
  88. streamlit/static/static/js/{index.CW3wpu7l.js → index.CYG25qUi.js} +1 -1
  89. streamlit/static/static/js/{index.M8jMYQHQ.js → index.DCgNoHge.js} +3 -3
  90. streamlit/static/static/js/{index.Bw2C-uyr.js → index.DJzV_Hd9.js} +1 -1
  91. streamlit/static/static/js/{index.DdaG6BEp.js → index.DQ8O6p2Z.js} +1 -1
  92. streamlit/static/static/js/{index.D7KT3HOJ.js → index.DWQ-qPH7.js} +1 -1
  93. streamlit/static/static/js/{index.BZJaY1ZU.js → index.DxsZ6SOh.js} +1 -1
  94. streamlit/static/static/js/index.K2n8XAB3.js +2 -0
  95. streamlit/static/static/js/{index.D7j_uG-4.js → index.KmpTf7BC.js} +16 -16
  96. streamlit/static/static/js/{index.2z1MQMgc.js → index.TIHw6394.js} +1 -1
  97. streamlit/static/static/js/{index.BebPGQSk.js → index.TXEfAqTZ.js} +1 -1
  98. streamlit/static/static/js/index.bco4avsR.js +1 -0
  99. streamlit/static/static/js/{index.C01swFeE.js → index.cHVEPjHw.js} +2 -2
  100. streamlit/static/static/js/index.xvt4PCc-.js +1 -0
  101. streamlit/static/static/js/{input.BjUJQgVM.js → input.5dHsg5IP.js} +1 -1
  102. streamlit/static/static/js/{memory.7TMii04U.js → memory.7AvKwFql.js} +1 -1
  103. streamlit/static/static/js/{mergeWith.BXLP9sI5.js → mergeWith.Bz2fCjZ-.js} +1 -1
  104. streamlit/static/static/js/{number-overlay-editor.C2Z8Sd28.js → number-overlay-editor.BzFQCoPo.js} +1 -1
  105. streamlit/static/static/js/{possibleConstructorReturn.3LXCoGeT.js → possibleConstructorReturn.BWYpSIhJ.js} +1 -1
  106. streamlit/static/static/js/{sandbox.BRs-5jl_.js → sandbox.DDDzfccc.js} +1 -1
  107. streamlit/static/static/js/{textarea.DT8T2CKm.js → textarea.UlZOYsGE.js} +1 -1
  108. streamlit/static/static/js/{timepicker.BQDylQL_.js → timepicker.C8uTDs22.js} +1 -1
  109. streamlit/static/static/js/{toConsumableArray.C9LiuPCd.js → toConsumableArray.CdG0Nv6r.js} +1 -1
  110. streamlit/static/static/js/{uniqueId.CVZfBH3U.js → uniqueId.hWzaDSRc.js} +1 -1
  111. streamlit/static/static/js/{useBasicWidgetState.DgSV341D.js → useBasicWidgetState.CtYBARrn.js} +1 -1
  112. streamlit/static/static/js/{useOnInputChange.D1W4Nc13.js → useOnInputChange.Cpz9bdhf.js} +1 -1
  113. streamlit/static/static/js/withFullScreenWrapper.BcDfXDtz.js +1 -0
  114. streamlit/static/static/media/MaterialSymbols-Rounded.BFCIvovZ.woff2 +0 -0
  115. streamlit/string_util.py +1 -1
  116. streamlit/testing/v1/element_tree.py +14 -14
  117. streamlit/watcher/event_based_path_watcher.py +1 -1
  118. streamlit/web/server/routes.py +1 -0
  119. {streamlit_nightly-1.43.3.dev20250317.dist-info → streamlit_nightly-1.43.3.dev20250319.dist-info}/METADATA +2 -2
  120. {streamlit_nightly-1.43.3.dev20250317.dist-info → streamlit_nightly-1.43.3.dev20250319.dist-info}/RECORD +124 -124
  121. {streamlit_nightly-1.43.3.dev20250317.dist-info → streamlit_nightly-1.43.3.dev20250319.dist-info}/WHEEL +1 -1
  122. streamlit/static/static/css/index.Bmkmz40k.css +0 -1
  123. streamlit/static/static/js/data-grid-overlay-editor.CGbd5q6g.js +0 -1
  124. streamlit/static/static/js/index.B5Gc6Qwv.js +0 -1
  125. streamlit/static/static/js/index.BD1Jat0A.js +0 -1
  126. streamlit/static/static/js/index.By9mG2hj.js +0 -1
  127. streamlit/static/static/js/index.C5wG0y4e.js +0 -3
  128. streamlit/static/static/js/index.CWQVC9i9.js +0 -1
  129. streamlit/static/static/js/index.CzCK3xQd.js +0 -1
  130. streamlit/static/static/js/index.DHk_4dX7.js +0 -2
  131. streamlit/static/static/js/index.DgjRgqmt.js +0 -1
  132. streamlit/static/static/js/index.Dl7G-PpD.js +0 -1
  133. streamlit/static/static/js/index.YQ7W-YC2.js +0 -1
  134. streamlit/static/static/js/withFullScreenWrapper.DIGqD0pT.js +0 -1
  135. streamlit/static/static/media/MaterialSymbols-Rounded.CRt5Q-14.woff2 +0 -0
  136. {streamlit_nightly-1.43.3.dev20250317.data → streamlit_nightly-1.43.3.dev20250319.data}/scripts/streamlit.cmd +0 -0
  137. {streamlit_nightly-1.43.3.dev20250317.dist-info → streamlit_nightly-1.43.3.dev20250319.dist-info}/entry_points.txt +0 -0
  138. {streamlit_nightly-1.43.3.dev20250317.dist-info → streamlit_nightly-1.43.3.dev20250319.dist-info}/top_level.txt +0 -0
streamlit/auth_util.py CHANGED
@@ -79,7 +79,7 @@ def get_secrets_auth_section() -> AttrDict:
79
79
  auth_section = AttrDict({})
80
80
  """Get the 'auth' section of the secrets.toml."""
81
81
  if secrets_singleton.load_if_toml_exists():
82
- auth_section = cast(AttrDict, secrets_singleton.get("auth"))
82
+ auth_section = cast("AttrDict", secrets_singleton.get("auth"))
83
83
 
84
84
  return auth_section
85
85
 
@@ -262,7 +262,7 @@ def set_page_config(
262
262
  msg.page_config_changed.initial_sidebar_state = pb_sidebar_state
263
263
 
264
264
  if menu_items is not None:
265
- lowercase_menu_items = cast(MenuItems, _lower_clean_dict_keys(menu_items))
265
+ lowercase_menu_items = cast("MenuItems", _lower_clean_dict_keys(menu_items))
266
266
  validate_menu_items(lowercase_menu_items)
267
267
  menu_items_proto = msg.page_config_changed.menu_items
268
268
  set_menu_items_proto(lowercase_menu_items, menu_items_proto)
streamlit/config.py CHANGED
@@ -22,7 +22,7 @@ import secrets
22
22
  import threading
23
23
  from collections import OrderedDict
24
24
  from enum import Enum
25
- from typing import Any, Callable
25
+ from typing import Any, Callable, Literal
26
26
 
27
27
  from blinker import Signal
28
28
 
@@ -86,6 +86,12 @@ class ShowErrorDetailsConfigOptions(str, Enum):
86
86
  # command-line and bool when set via user script (e.g. st.set_option("client.showErrorDetails", False)).
87
87
 
88
88
 
89
+ class CustomThemeCategories(str, Enum):
90
+ """Theme categories that can be set with custom theme config."""
91
+
92
+ SIDEBAR = "sidebar"
93
+
94
+
89
95
  def set_option(key: str, value: Any, where_defined: str = _USER_DEFINED) -> None:
90
96
  """Set config option.
91
97
 
@@ -293,6 +299,36 @@ def _create_option(
293
299
  return option
294
300
 
295
301
 
302
+ def _create_theme_options(
303
+ name: str,
304
+ categories: list[Literal["theme"] | CustomThemeCategories],
305
+ description: str | None = None,
306
+ default_val: Any | None = None,
307
+ visibility: str = "visible",
308
+ type_: type = str,
309
+ ) -> None:
310
+ """
311
+ Create ConfigOption(s) for a theme-related config option and store it globally in this module.
312
+ The same config option can be supported for multiple categories, e.g. "theme" and "theme.sidebar".
313
+ """
314
+ for cat in categories:
315
+ section = cat if cat == "theme" else f"theme.{cat.value}"
316
+
317
+ _create_option(
318
+ f"{section}.{name}",
319
+ description=description,
320
+ default_val=default_val,
321
+ visibility=visibility,
322
+ type_=type_,
323
+ scriptable=False,
324
+ deprecated=False,
325
+ deprecation_text=None,
326
+ expiration_date=None,
327
+ replaced_by=None,
328
+ sensitive=False,
329
+ )
330
+
331
+
296
332
  def _delete_option(key: str) -> None:
297
333
  """Remove a ConfigOption by key from the global store.
298
334
 
@@ -1004,42 +1040,61 @@ _create_option(
1004
1040
 
1005
1041
  _create_section("theme", "Settings to define a custom theme for your Streamlit app.")
1006
1042
 
1007
- _create_option(
1008
- "theme.base",
1043
+ # Create a section for each custom theme element
1044
+ for cat in list(CustomThemeCategories):
1045
+ _create_section(
1046
+ f"theme.{cat.value}",
1047
+ f"Settings to define a custom {cat.value} theme in your Streamlit app.",
1048
+ )
1049
+
1050
+ _create_theme_options(
1051
+ "base",
1052
+ categories=["theme"],
1009
1053
  description="""
1010
1054
  The preset Streamlit theme that your custom theme inherits from.
1011
1055
  One of "light" or "dark".
1012
1056
  """,
1013
1057
  )
1014
1058
 
1015
- _create_option(
1016
- "theme.primaryColor",
1059
+ _create_theme_options(
1060
+ "primaryColor",
1061
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1017
1062
  description="Primary accent color for interactive elements.",
1018
1063
  )
1019
1064
 
1020
- _create_option(
1021
- "theme.backgroundColor",
1065
+ _create_theme_options(
1066
+ "backgroundColor",
1067
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1022
1068
  description="Background color for the main content area.",
1023
1069
  )
1024
1070
 
1025
- _create_option(
1026
- "theme.secondaryBackgroundColor",
1071
+ _create_theme_options(
1072
+ "secondaryBackgroundColor",
1073
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1027
1074
  description="Background color used for the sidebar and most interactive widgets.",
1028
1075
  )
1029
1076
 
1030
- _create_option(
1031
- "theme.textColor",
1077
+ _create_theme_options(
1078
+ "textColor",
1079
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1032
1080
  description="Color used for almost all text.",
1033
1081
  )
1034
1082
 
1035
- _create_option(
1036
- "theme.linkColor",
1083
+ _create_theme_options(
1084
+ "linkColor",
1085
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1037
1086
  description="Color used for all links.",
1038
- visibility="hidden",
1039
1087
  )
1040
1088
 
1041
- _create_option(
1042
- "theme.font",
1089
+ _create_theme_options(
1090
+ "codeBackgroundColor",
1091
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1092
+ description="Background color used for code blocks.",
1093
+ )
1094
+
1095
+ _create_theme_options(
1096
+ "font",
1097
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1043
1098
  description="""
1044
1099
  The font family for all text in the app, except code blocks. One of "sans serif",
1045
1100
  "serif", or "monospace".
@@ -1047,78 +1102,77 @@ _create_option(
1047
1102
  """,
1048
1103
  )
1049
1104
 
1050
- _create_option(
1051
- "theme.codeFont",
1105
+ _create_theme_options(
1106
+ "codeFont",
1107
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1052
1108
  description="""
1053
1109
  The font family to use for code (monospace) in the app.
1054
1110
  To use a custom font, it needs to be added via [theme.fontFaces].
1055
1111
  """,
1056
- visibility="hidden",
1057
1112
  )
1058
1113
 
1059
- _create_option(
1060
- "theme.headingFont",
1114
+ _create_theme_options(
1115
+ "headingFont",
1116
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1061
1117
  description="""
1062
1118
  The font family to use for headings in the app.
1063
1119
  To use a custom font, it needs to be added via [theme.fontFaces].
1064
1120
  """,
1065
- visibility="hidden",
1066
1121
  )
1067
1122
 
1068
- _create_option(
1069
- "theme.fontFaces",
1123
+ _create_theme_options(
1124
+ "fontFaces",
1125
+ categories=["theme"],
1070
1126
  description="""
1071
1127
  Configure a list of font faces that you can use for the app & code fonts.
1072
1128
  """,
1073
- visibility="hidden",
1074
1129
  )
1075
1130
 
1076
-
1077
- _create_option(
1078
- "theme.baseRadius",
1131
+ _create_theme_options(
1132
+ "baseRadius",
1133
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1079
1134
  description="""
1080
1135
  The radius used as basis for the corners of most UI elements. Can be:
1081
1136
  "none", "small", "medium", "large", "full", or the number in pixel or rem.
1082
1137
  For example: "10px", "0.5rem", "1.2rem", "2rem".
1083
1138
  """,
1084
- visibility="hidden",
1085
1139
  )
1086
1140
 
1087
- _create_option(
1088
- "theme.borderColor",
1141
+ _create_theme_options(
1142
+ "borderColor",
1143
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1089
1144
  description="""
1090
1145
  The color of the border around elements.
1091
1146
  """,
1092
- visibility="hidden",
1093
1147
  )
1094
1148
 
1095
- _create_option(
1096
- "theme.showBorderAroundInputs",
1149
+ _create_theme_options(
1150
+ "showWidgetBorder",
1151
+ categories=["theme", CustomThemeCategories.SIDEBAR],
1097
1152
  description="""
1098
- Whether to show a border around input elements (e.g. text_input, number_input,
1153
+ Whether to show a border around input widgets (e.g. text_input, number_input,
1099
1154
  file_uploader, etc).
1100
1155
  """,
1101
1156
  type_=bool,
1102
- visibility="hidden",
1103
1157
  )
1104
1158
 
1105
- _create_option(
1106
- "theme.baseFontSize",
1159
+ _create_theme_options(
1160
+ "baseFontSize",
1161
+ categories=["theme"],
1107
1162
  description="""
1108
1163
  Sets the root font size (in pixels) for the app, which determines the overall
1109
1164
  scale of text and UI elements. The default base font size is 16.
1110
1165
  """,
1111
1166
  type_=int,
1112
- visibility="hidden",
1113
1167
  )
1114
1168
 
1115
- _create_option(
1116
- "theme.showSidebarSeparator",
1169
+ _create_theme_options(
1170
+ "showSidebarBorder",
1171
+ categories=["theme"],
1117
1172
  description="""
1118
1173
  Whether to show a vertical separator between the sidebar and the main content.
1119
1174
  """,
1120
1175
  type_=bool,
1121
- visibility="hidden",
1122
1176
  )
1123
1177
 
1124
1178
  # Config Section: Secrets #
@@ -1275,10 +1329,52 @@ def _update_config_with_toml(raw_toml: str, where_defined: str) -> None:
1275
1329
 
1276
1330
  parsed_config_file = toml.loads(raw_toml)
1277
1331
 
1332
+ def process_section(section_path: str, section_data: dict[str, Any]) -> None:
1333
+ """Recursively process nested sections of the config file.
1334
+
1335
+ Parameters
1336
+ ----------
1337
+ section_path : str
1338
+ The dot-separated path to the current section (e.g., "server" or "theme")
1339
+ section_data : dict[str, Any]
1340
+ The dictionary containing configuration values for this section
1341
+
1342
+ Notes
1343
+ -----
1344
+ TOML's hierarchical structure gets parsed into nested dictionaries.
1345
+ For example:
1346
+ [main]
1347
+ option = "value"
1348
+
1349
+ [main.subsection]
1350
+ another = "value2"
1351
+
1352
+ Will be loaded by the TOML parser as:
1353
+ {
1354
+ "main": {
1355
+ "option": "value",
1356
+ "subsection": {
1357
+ "another": "value2"
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ This function traverses these nested dictionaries and converts them
1363
+ to dot-notation config options.
1364
+ """
1365
+
1366
+ for name, value in section_data.items():
1367
+ option_name = f"{section_path}.{name}"
1368
+ # Process it as a nested config section if it's a custom theme sub-category
1369
+ if name in [CustomThemeCategories.SIDEBAR.value]:
1370
+ process_section(option_name, value)
1371
+ else:
1372
+ # It's a regular config option, set it
1373
+ value = _maybe_read_env_variable(value)
1374
+ _set_option(option_name, value, where_defined)
1375
+
1278
1376
  for section, options in parsed_config_file.items():
1279
- for name, value in options.items():
1280
- value = _maybe_read_env_variable(value)
1281
- _set_option(f"{section}.{name}", value, where_defined)
1377
+ process_section(section, options)
1282
1378
 
1283
1379
 
1284
1380
  def _maybe_read_env_variable(value: Any) -> Any:
@@ -152,8 +152,11 @@ class ConfigOption:
152
152
  # with a lowercase letter with an optional "_" preceding it.
153
153
  # Examples: "_section", "section1"
154
154
  r"\_?[a-z][a-zA-Z0-9]*"
155
+ # Handling zero or additional parts, separated by period
156
+ # Examples: "_section.subsection", "section1._section2"
157
+ r"(\.[a-z][a-zA-Z0-9]*)*"
155
158
  r")"
156
- # Separator between groups
159
+ # The final period, separating section and name
157
160
  r"\."
158
161
  # Capture a group called "name"
159
162
  r"(?P<name>"
streamlit/config_util.py CHANGED
@@ -88,7 +88,7 @@ def show_config(
88
88
  out.append("")
89
89
 
90
90
  for key, option in section_options.items():
91
- key = option.key.split(".")[1]
91
+ key = option.key.split(".")[-1]
92
92
  description_paragraphs = _clean_paragraphs(option.description or "")
93
93
 
94
94
  last_paragraph_idx = len(description_paragraphs) - 1
@@ -264,7 +264,7 @@ class SnowflakeConnection(BaseConnection["InternalSnowflakeConnection"]):
264
264
 
265
265
  return snowflake.connector.connect(**kwargs)
266
266
  except SnowflakeError as e:
267
- if not len(st_secrets) and not len(kwargs):
267
+ if not len(st_secrets) and not kwargs:
268
268
  raise StreamlitAPIException(
269
269
  "Missing Snowflake connection configuration. "
270
270
  "Did you forget to set this in `secrets.toml`, a Snowflake configuration file, "
@@ -557,5 +557,5 @@ class SnowflakeConnection(BaseConnection["InternalSnowflakeConnection"]):
557
557
  return get_active_session()
558
558
 
559
559
  return cast(
560
- Session, Session.builder.configs({"connection": self._instance}).create()
560
+ "Session", Session.builder.configs({"connection": self._instance}).create()
561
561
  )
@@ -91,7 +91,7 @@ class SnowparkConnection(BaseConnection["Session"]):
91
91
  if p not in conn_params:
92
92
  raise StreamlitAPIException(f"Missing Snowpark connection param: {p}")
93
93
 
94
- return cast(Session, Session.builder.configs(conn_params).create())
94
+ return cast("Session", Session.builder.configs(conn_params).create())
95
95
 
96
96
  def query(
97
97
  self,
@@ -390,7 +390,7 @@ class SQLConnection(BaseConnection["Engine"]):
390
390
  str
391
391
  The name of the driver. For example, ``"pyodbc"`` or ``"psycopg2"``.
392
392
  """
393
- return cast(str, self._instance.driver)
393
+ return cast("str", self._instance.driver)
394
394
 
395
395
  @property
396
396
  def session(self) -> Session:
@@ -92,6 +92,6 @@ def running_in_sis() -> bool:
92
92
  is_in_stored_procedure,
93
93
  )
94
94
 
95
- return cast(bool, is_in_stored_procedure())
95
+ return cast("bool", is_in_stored_procedure())
96
96
  except ModuleNotFoundError:
97
97
  return False
@@ -559,13 +559,13 @@ def convert_anything_to_pandas_df(
559
559
  import pandas as pd
560
560
 
561
561
  if isinstance(data, pd.DataFrame):
562
- return data.copy() if ensure_copy else cast(pd.DataFrame, data)
562
+ return data.copy() if ensure_copy else cast("pd.DataFrame", data)
563
563
 
564
564
  if isinstance(data, (pd.Series, pd.Index, pd.api.extensions.ExtensionArray)):
565
565
  return pd.DataFrame(data)
566
566
 
567
567
  if is_pandas_styler(data):
568
- return cast(pd.DataFrame, data.data.copy() if ensure_copy else data.data)
568
+ return cast("pd.DataFrame", data.data.copy() if ensure_copy else data.data)
569
569
 
570
570
  if isinstance(data, np.ndarray):
571
571
  return (
@@ -589,7 +589,7 @@ def convert_anything_to_pandas_df(
589
589
  f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
590
590
  "rows. Call `collect()` on the dataframe to show more."
591
591
  )
592
- return cast(pd.DataFrame, data)
592
+ return cast("pd.DataFrame", data)
593
593
 
594
594
  if is_xarray_dataset(data):
595
595
  if ensure_copy:
@@ -614,7 +614,7 @@ def convert_anything_to_pandas_df(
614
614
  f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
615
615
  "rows. Call `compute()` on the data object to show more."
616
616
  )
617
- return cast(pd.DataFrame, data)
617
+ return cast("pd.DataFrame", data)
618
618
 
619
619
  if is_ray_dataset(data):
620
620
  data = data.limit(max_unevaluated_rows).to_pandas()
@@ -624,7 +624,7 @@ def convert_anything_to_pandas_df(
624
624
  f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
625
625
  "rows. Call `to_pandas()` on the dataset to show more."
626
626
  )
627
- return cast(pd.DataFrame, data)
627
+ return cast("pd.DataFrame", data)
628
628
 
629
629
  if is_modin_data_object(data):
630
630
  data = data.head(max_unevaluated_rows)._to_pandas()
@@ -637,7 +637,7 @@ def convert_anything_to_pandas_df(
637
637
  f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
638
638
  "rows. Call `_to_pandas()` on the data object to show more."
639
639
  )
640
- return cast(pd.DataFrame, data)
640
+ return cast("pd.DataFrame", data)
641
641
 
642
642
  if is_pyspark_data_object(data):
643
643
  data = data.limit(max_unevaluated_rows).toPandas()
@@ -646,7 +646,7 @@ def convert_anything_to_pandas_df(
646
646
  f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
647
647
  "rows. Call `toPandas()` on the data object to show more."
648
648
  )
649
- return cast(pd.DataFrame, data)
649
+ return cast("pd.DataFrame", data)
650
650
 
651
651
  if is_snowpandas_data_object(data):
652
652
  data = data[:max_unevaluated_rows].to_pandas()
@@ -659,7 +659,7 @@ def convert_anything_to_pandas_df(
659
659
  f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
660
660
  "rows. Call `to_pandas()` on the data object to show more."
661
661
  )
662
- return cast(pd.DataFrame, data)
662
+ return cast("pd.DataFrame", data)
663
663
 
664
664
  if is_snowpark_data_object(data):
665
665
  data = data.limit(max_unevaluated_rows).to_pandas()
@@ -668,7 +668,7 @@ def convert_anything_to_pandas_df(
668
668
  f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
669
669
  "rows. Call `to_pandas()` on the data object to show more."
670
670
  )
671
- return cast(pd.DataFrame, data)
671
+ return cast("pd.DataFrame", data)
672
672
 
673
673
  if is_duckdb_relation(data):
674
674
  data = data.limit(max_unevaluated_rows).df()
@@ -800,7 +800,7 @@ def convert_arrow_table_to_arrow_bytes(table: pa.Table) -> bytes:
800
800
  writer = pa.RecordBatchStreamWriter(sink, table.schema)
801
801
  writer.write_table(table)
802
802
  writer.close()
803
- return cast(bytes, sink.getvalue().to_pybytes())
803
+ return cast("bytes", sink.getvalue().to_pybytes())
804
804
 
805
805
 
806
806
  def convert_pandas_df_to_arrow_bytes(df: DataFrame) -> bytes:
@@ -951,7 +951,7 @@ def convert_anything_to_list(obj: OptionSequence[V_co]) -> list[V_co]:
951
951
  return (
952
952
  []
953
953
  if data_df.empty
954
- else cast(list[V_co], list(data_df.iloc[:, 0].to_list()))
954
+ else cast("list[V_co]", list(data_df.iloc[:, 0].to_list()))
955
955
  )
956
956
  except errors.StreamlitAPIException:
957
957
  # Wrap the object into a list
@@ -1167,7 +1167,7 @@ def determine_data_format(input_data: Any) -> DataFormat:
1167
1167
  elif isinstance(input_data, pd.DataFrame):
1168
1168
  return DataFormat.PANDAS_DATAFRAME
1169
1169
  elif isinstance(input_data, np.ndarray):
1170
- if len(cast(NumpyShape, input_data.shape)) == 1:
1170
+ if len(cast("NumpyShape", input_data.shape)) == 1:
1171
1171
  # For technical reasons, we need to distinguish one
1172
1172
  # one-dimensional numpy array from multidimensional ones.
1173
1173
  return DataFormat.NUMPY_LIST
@@ -538,7 +538,7 @@ class DeltaGenerator(
538
538
  dg_type = DeltaGenerator
539
539
 
540
540
  block_dg = cast(
541
- DeltaGenerator,
541
+ "DeltaGenerator",
542
542
  dg_type(
543
543
  root_container=dg._root_container,
544
544
  cursor=block_cursor,
@@ -566,7 +566,7 @@ class DeltaGenerator(
566
566
 
567
567
  def _writes_directly_to_sidebar(dg: DeltaGenerator) -> bool:
568
568
  in_sidebar = any(a._root_container == RootContainer.SIDEBAR for a in dg._ancestors)
569
- has_container = bool(len(list(dg._ancestor_block_types)))
569
+ has_container = bool(list(dg._ancestor_block_types))
570
570
  return in_sidebar and not has_container
571
571
 
572
572
 
@@ -105,7 +105,7 @@ def deprecate_func_name(
105
105
  # Update the wrapped func's name & docstring so st.help does the right thing
106
106
  wrapped_func.__name__ = old_name
107
107
  wrapped_func.__doc__ = func.__doc__
108
- return cast(TFunc, wrapped_func)
108
+ return cast("TFunc", wrapped_func)
109
109
 
110
110
 
111
111
  def deprecate_obj_name(
@@ -206,4 +206,4 @@ def _create_deprecated_obj_wrapper(obj: TObj, show_warning: Callable[[], Any]) -
206
206
 
207
207
  return proxy
208
208
 
209
- return cast(TObj, Wrapper())
209
+ return cast("TObj", Wrapper())
@@ -174,7 +174,7 @@ class DataframeSelectionSerde:
174
174
  if "selection" not in selection_state:
175
175
  selection_state = empty_selection_state
176
176
 
177
- return cast(DataframeState, AttributeDictionary(selection_state))
177
+ return cast("DataframeState", AttributeDictionary(selection_state))
178
178
 
179
179
  def serialize(self, editing_state: DataframeState) -> str:
180
180
  return json.dumps(editing_state, default=str)
@@ -549,7 +549,7 @@ class ArrowMixin:
549
549
  check_widget_policies(
550
550
  self.dg,
551
551
  key,
552
- on_change=cast(WidgetCallback, on_select) if is_callback else None,
552
+ on_change=cast("WidgetCallback", on_select) if is_callback else None,
553
553
  default_value=None,
554
554
  writes_allowed=False,
555
555
  enable_check_callback_rules=is_callback,
@@ -644,7 +644,7 @@ class ArrowMixin:
644
644
  value_type="string_value",
645
645
  )
646
646
  self.dg._enqueue("arrow_data_frame", proto)
647
- return cast(DataframeState, widget_state.value)
647
+ return cast("DataframeState", widget_state.value)
648
648
  else:
649
649
  return self.dg._enqueue("arrow_data_frame", proto)
650
650
 
@@ -251,7 +251,7 @@ class PydeckSelectionSerde:
251
251
  if "selection" not in selection_state:
252
252
  selection_state = empty_selection_state
253
253
 
254
- return cast(PydeckState, AttributeDictionary(selection_state))
254
+ return cast("PydeckState", AttributeDictionary(selection_state))
255
255
 
256
256
  def serialize(self, selection_state: PydeckState) -> str:
257
257
  return json.dumps(selection_state, default=str)
@@ -488,7 +488,7 @@ class PydeckMixin:
488
488
  check_widget_policies(
489
489
  self.dg,
490
490
  key,
491
- on_change=cast(WidgetCallback, on_select) if is_callback else None,
491
+ on_change=cast("WidgetCallback", on_select) if is_callback else None,
492
492
  default_value=None,
493
493
  writes_allowed=False,
494
494
  enable_check_callback_rules=is_callback,
@@ -518,7 +518,7 @@ class PydeckMixin:
518
518
 
519
519
  self.dg._enqueue("deck_gl_json_chart", pydeck_proto)
520
520
 
521
- return cast(PydeckState, widget_state.value)
521
+ return cast("PydeckState", widget_state.value)
522
522
 
523
523
  return self.dg._enqueue("deck_gl_json_chart", pydeck_proto)
524
524
 
@@ -541,6 +541,6 @@ def _get_pydeck_tooltip(pydeck_obj: Deck | None) -> dict[str, str] | None:
541
541
  # For details, see: https://github.com/visgl/deck.gl/pull/7125/files
542
542
  tooltip = getattr(pydeck_obj, "_tooltip", None)
543
543
  if tooltip is not None and isinstance(tooltip, dict):
544
- return cast(dict[str, str], tooltip)
544
+ return cast("dict[str, str]", tooltip)
545
545
 
546
546
  return None
@@ -102,7 +102,7 @@ def _dialog_decorator(
102
102
  # the fragment decorator has multiple return types so that you can pass
103
103
  # arguments to it. Here we know the return type, so we cast
104
104
  fragmented_dialog_content = cast(
105
- Callable[[], None],
105
+ "Callable[[], None]",
106
106
  _fragment(
107
107
  dialog_content, additional_hash_info=non_optional_func.__qualname__
108
108
  ),
@@ -112,7 +112,7 @@ def _dialog_decorator(
112
112
  fragmented_dialog_content()
113
113
  return None
114
114
 
115
- return cast(F, wrap)
115
+ return cast("F", wrap)
116
116
 
117
117
 
118
118
  @overload
@@ -261,7 +261,7 @@ class HeadingMixin:
261
261
  "rainbow",
262
262
  ]
263
263
  if divider in valid_colors:
264
- return cast(str, divider)
264
+ return cast("str", divider)
265
265
  else:
266
266
  raise StreamlitAPIException(
267
267
  f"Divider parameter has invalid value: `{divider}`. Please choose from: {', '.join(valid_colors)}."
@@ -75,16 +75,16 @@ class HtmlMixin:
75
75
 
76
76
  # If body supports _repr_html_, use that.
77
77
  if has_callable_attr(body, "_repr_html_"):
78
- html_proto.body = cast(SupportsReprHtml, body)._repr_html_()
78
+ html_proto.body = cast("SupportsReprHtml", body)._repr_html_()
79
79
 
80
80
  # Check if the body is a file path. May include filesystem lookup.
81
81
  elif isinstance(body, Path) or _is_file(body):
82
- with open(cast(str, body), encoding="utf-8") as f:
82
+ with open(cast("str", body), encoding="utf-8") as f:
83
83
  html_proto.body = f.read()
84
84
 
85
85
  # OK, let's just try converting to string and hope for the best.
86
86
  else:
87
- html_proto.body = clean_text(cast(SupportsStr, body))
87
+ html_proto.body = clean_text(cast("SupportsStr", body))
88
88
 
89
89
  return self.dg._enqueue("html", html_proto)
90
90