streamlit 1.48.1__py3-none-any.whl → 1.49.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 (203) hide show
  1. streamlit/__init__.py +3 -10
  2. streamlit/commands/logo.py +4 -3
  3. streamlit/commands/navigation.py +1 -1
  4. streamlit/commands/page_config.py +4 -1
  5. streamlit/components/v1/custom_component.py +2 -2
  6. streamlit/config.py +82 -1
  7. streamlit/connections/snowflake_connection.py +3 -1
  8. streamlit/delta_generator.py +3 -0
  9. streamlit/elements/arrow.py +155 -70
  10. streamlit/elements/bokeh_chart.py +13 -3
  11. streamlit/elements/deck_gl_json_chart.py +0 -1
  12. streamlit/elements/dialog_decorator.py +7 -59
  13. streamlit/elements/form.py +10 -1
  14. streamlit/elements/graphviz_chart.py +57 -6
  15. streamlit/elements/heading.py +17 -16
  16. streamlit/elements/image.py +64 -37
  17. streamlit/elements/layouts.py +2 -2
  18. streamlit/elements/lib/built_in_chart_utils.py +2 -5
  19. streamlit/elements/lib/column_config_utils.py +18 -4
  20. streamlit/elements/lib/column_types.py +75 -30
  21. streamlit/elements/lib/dialog.py +3 -3
  22. streamlit/elements/lib/image_utils.py +19 -11
  23. streamlit/elements/lib/layout_utils.py +15 -2
  24. streamlit/elements/lib/utils.py +20 -41
  25. streamlit/elements/markdown.py +7 -6
  26. streamlit/elements/media.py +6 -13
  27. streamlit/elements/metric.py +78 -1
  28. streamlit/elements/pdf.py +192 -0
  29. streamlit/elements/plotly_chart.py +3 -2
  30. streamlit/elements/pyplot.py +53 -11
  31. streamlit/elements/toast.py +81 -5
  32. streamlit/elements/vega_charts.py +3 -8
  33. streamlit/elements/widgets/audio_input.py +0 -1
  34. streamlit/elements/widgets/button.py +0 -4
  35. streamlit/elements/widgets/button_group.py +5 -4
  36. streamlit/elements/widgets/camera_input.py +0 -1
  37. streamlit/elements/widgets/chat.py +11 -13
  38. streamlit/elements/widgets/checkbox.py +0 -1
  39. streamlit/elements/widgets/color_picker.py +0 -1
  40. streamlit/elements/widgets/data_editor.py +142 -62
  41. streamlit/elements/widgets/file_uploader.py +74 -37
  42. streamlit/elements/widgets/multiselect.py +0 -1
  43. streamlit/elements/widgets/number_input.py +0 -1
  44. streamlit/elements/widgets/radio.py +0 -1
  45. streamlit/elements/widgets/select_slider.py +0 -1
  46. streamlit/elements/widgets/selectbox.py +0 -1
  47. streamlit/elements/widgets/slider.py +0 -1
  48. streamlit/elements/widgets/text_widgets.py +0 -2
  49. streamlit/elements/widgets/time_widgets.py +0 -2
  50. streamlit/errors.py +11 -0
  51. streamlit/material_icon_names.py +1 -1
  52. streamlit/proto/Arrow_pb2.py +14 -8
  53. streamlit/proto/Arrow_pb2.pyi +11 -3
  54. streamlit/proto/Block_pb2.py +16 -16
  55. streamlit/proto/Block_pb2.pyi +2 -0
  56. streamlit/proto/ChatInput_pb2.py +3 -3
  57. streamlit/proto/ChatInput_pb2.pyi +2 -0
  58. streamlit/proto/FileUploader_pb2.py +2 -2
  59. streamlit/proto/FileUploader_pb2.pyi +5 -1
  60. streamlit/proto/GraphVizChart_pb2.py +4 -2
  61. streamlit/proto/GraphVizChart_pb2.pyi +1 -1
  62. streamlit/proto/Image_pb2.py +4 -2
  63. streamlit/proto/Image_pb2.pyi +1 -10
  64. streamlit/proto/Metric_pb2.py +8 -6
  65. streamlit/proto/Metric_pb2.pyi +34 -10
  66. streamlit/proto/Toast_pb2.py +2 -2
  67. streamlit/proto/Toast_pb2.pyi +10 -1
  68. streamlit/runtime/caching/__init__.py +14 -2
  69. streamlit/runtime/caching/cache_data_api.py +0 -17
  70. streamlit/runtime/caching/cache_resource_api.py +0 -16
  71. streamlit/runtime/caching/cached_message_replay.py +8 -20
  72. streamlit/runtime/caching/hashing.py +31 -1
  73. streamlit/runtime/credentials.py +4 -4
  74. streamlit/runtime/fragment.py +0 -42
  75. streamlit/runtime/websocket_session_manager.py +1 -1
  76. streamlit/static/index.html +2 -2
  77. streamlit/static/manifest.json +224 -252
  78. streamlit/static/static/css/{index.CJVRHjQZ.css → index.C8X8rNzw.css} +1 -1
  79. streamlit/static/static/css/index.COe1010n.css +1 -0
  80. streamlit/static/static/js/{ErrorOutline.esm.DjObtx4K.js → ErrorOutline.esm.u9XvzxL8.js} +1 -1
  81. streamlit/static/static/js/{FileDownload.esm.Bz9nxNC5.js → FileDownload.esm.CaRyZ-b2.js} +1 -1
  82. streamlit/static/static/js/FileHelper.Dk2SwIi3.js +5 -0
  83. streamlit/static/static/js/FormClearHelper.l_UPPvkg.js +1 -0
  84. streamlit/static/static/js/{Hooks.DEoLCfOE.js → Hooks.BxrVEftw.js} +1 -1
  85. streamlit/static/static/js/InputInstructions.C254RU9X.js +1 -0
  86. streamlit/static/static/js/Particles.DkY6FDnc.js +1 -0
  87. streamlit/static/static/js/ProgressBar.BPtSM82n.js +2 -0
  88. streamlit/static/static/js/Toolbar.BO_3WBaS.js +1 -0
  89. streamlit/static/static/js/{base-input.BmvSaPd2.js → base-input.egUI4LjJ.js} +4 -4
  90. streamlit/static/static/js/{checkbox.Cgxgc0et.js → checkbox.ButpszcE.js} +2 -2
  91. streamlit/static/static/js/createSuper.DYJA5xa6.js +1 -0
  92. streamlit/static/static/js/data-grid-overlay-editor.C9gQLEnU.js +1 -0
  93. streamlit/static/static/js/{downloader.M6jQeNDf.js → downloader.B3TjsSPZ.js} +1 -1
  94. streamlit/static/static/js/es6.BYSNuG4D.js +2 -0
  95. streamlit/static/static/js/iframeResizer.contentWindow.CNPHJsF2.js +1 -0
  96. streamlit/static/static/js/index.0tDq1WXk.js +1 -0
  97. streamlit/static/static/js/index.BBnWuh07.js +976 -0
  98. streamlit/static/static/js/index.BDZorv41.js +1 -0
  99. streamlit/static/static/js/{index.CbdWnLqS.js → index.BH79B25f.js} +3 -3
  100. streamlit/static/static/js/index.BeTC4Yl-.js +197 -0
  101. streamlit/static/static/js/index.BnOd05Ko.js +2 -0
  102. streamlit/static/static/js/index.BoJaJReB.js +1 -0
  103. streamlit/static/static/js/index.Bp1Of6L8.js +1 -0
  104. streamlit/static/static/js/index.Bpe4-O2W.js +1 -0
  105. streamlit/static/static/js/index.BrD9sbpx.js +1 -0
  106. streamlit/static/static/js/index.C1qCS-sd.js +1 -0
  107. streamlit/static/static/js/index.C3EXAI-u.js +1 -0
  108. streamlit/static/static/js/index.C77g9sAQ.js +3 -0
  109. streamlit/static/static/js/{index.BXDq9dj4.js → index.CFePF7s4.js} +1 -1
  110. streamlit/static/static/js/index.CFjU0x00.js +1 -0
  111. streamlit/static/static/js/index.Ca3y4ztK.js +1 -0
  112. streamlit/static/static/js/{index.CgZDfhN4.js → index.Cb9gN2T2.js} +2 -2
  113. streamlit/static/static/js/{index.tsvTLdio.js → index.CbwuUwu4.js} +9 -9
  114. streamlit/static/static/js/index.CeXLlclc.js +1 -0
  115. streamlit/static/static/js/index.CfiZGqj3.js +3 -0
  116. streamlit/static/static/js/index.CjQnYKID.js +1 -0
  117. streamlit/static/static/js/index.Cl_966eE.js +3858 -0
  118. streamlit/static/static/js/{index.Cqa4gqqN.js → index.CqSRo6zQ.js} +1 -1
  119. streamlit/static/static/js/index.CuEFSQ-o.js +1 -0
  120. streamlit/static/static/js/index.D4jR1m1z.js +1 -0
  121. streamlit/static/static/js/index.DGcW849X.js +1 -0
  122. streamlit/static/static/js/index.DKb-BAE2.js +1 -0
  123. streamlit/static/static/js/index.DP1rDFP0.js +1 -0
  124. streamlit/static/static/js/{index.D1EayrNh.js → index.DStzYLqM.js} +2 -2
  125. streamlit/static/static/js/index.DVKQKDLu.js +1 -0
  126. streamlit/static/static/js/index.DWedOrkQ.js +1 -0
  127. streamlit/static/static/js/index.DYbRPmVF.js +1 -0
  128. streamlit/static/static/js/index.DgpIMUsr.js +1 -0
  129. streamlit/static/static/js/index.DtwkPJs5.js +5367 -0
  130. streamlit/static/static/js/{index.D1jHqUJq.js → index.MQLQLR5Z.js} +1 -1
  131. streamlit/static/static/js/index.uInpwWAP.js +1 -0
  132. streamlit/static/static/js/index.z992t-BQ.js +7 -0
  133. streamlit/static/static/js/{input.DZd6EQlV.js → input.CbP5ZuQ7.js} +2 -2
  134. streamlit/static/static/js/{memory.ptkfuI71.js → memory.BuacVo2L.js} +1 -1
  135. streamlit/static/static/js/number-overlay-editor.BZb9zRl_.js +9 -0
  136. streamlit/static/static/js/{possibleConstructorReturn.Bd4ImlQ9.js → possibleConstructorReturn.DSM84rOS.js} +1 -1
  137. streamlit/static/static/js/{sandbox.DsH8LuID.js → sandbox.C480llMG.js} +1 -1
  138. streamlit/static/static/js/{timepicker.QVekV78C.js → timepicker.BunxCVp7.js} +4 -4
  139. streamlit/static/static/js/{toConsumableArray.BJvaP8gb.js → toConsumableArray.B4o8rEx1.js} +3 -3
  140. streamlit/static/static/js/uniqueId.tii0yosY.js +1 -0
  141. streamlit/static/static/js/{useBasicWidgetState.DB3vMS9V.js → useBasicWidgetState.Bnm4FD6K.js} +1 -1
  142. streamlit/static/static/js/{useTextInputAutoExpand.CBkGkaRt.js → useTextInputAutoExpand.Dgtwc1m0.js} +2 -2
  143. streamlit/static/static/js/useUpdateUiValue.DjXdMFGw.js +1 -0
  144. streamlit/static/static/js/withFullScreenWrapper.0cy2pVf5.js +1 -0
  145. streamlit/static/static/media/MaterialSymbols-Rounded.CBxVaFdk.woff2 +0 -0
  146. streamlit/user_info.py +3 -1
  147. streamlit/web/server/browser_websocket_handler.py +15 -0
  148. {streamlit-1.48.1.dist-info → streamlit-1.49.0.dist-info}/METADATA +4 -2
  149. {streamlit-1.48.1.dist-info → streamlit-1.49.0.dist-info}/RECORD +153 -156
  150. streamlit/static/static/css/index.CQt5TjGB.css +0 -1
  151. streamlit/static/static/js/FileHelper.BrQvUXVD.js +0 -5
  152. streamlit/static/static/js/FormClearHelper.DF4gFAOO.js +0 -1
  153. streamlit/static/static/js/InputInstructions.D8zoMog9.js +0 -1
  154. streamlit/static/static/js/Particles.CCFySwdL.js +0 -1
  155. streamlit/static/static/js/ProgressBar.COK9j1l0.js +0 -2
  156. streamlit/static/static/js/Toolbar.Dt4jIKlY.js +0 -1
  157. streamlit/static/static/js/createSuper.siQeagI2.js +0 -1
  158. streamlit/static/static/js/data-grid-overlay-editor.Ct51iCb_.js +0 -1
  159. streamlit/static/static/js/es6.CMaUdEZ5.js +0 -2
  160. streamlit/static/static/js/iframeResizer.contentWindow.C33BryyP.js +0 -1
  161. streamlit/static/static/js/index.8GJD0eeD.js +0 -1
  162. streamlit/static/static/js/index.8QEYHMQD.js +0 -1
  163. streamlit/static/static/js/index.Ay41Wnu9.js +0 -1
  164. streamlit/static/static/js/index.BLiKiJ7_.js +0 -1
  165. streamlit/static/static/js/index.BT78cJmU.js +0 -1
  166. streamlit/static/static/js/index.BdGvnhlM.js +0 -1
  167. streamlit/static/static/js/index.BfasrT0d.js +0 -1
  168. streamlit/static/static/js/index.CCdtFMFG.js +0 -1
  169. streamlit/static/static/js/index.CFRGZDz1.js +0 -1
  170. streamlit/static/static/js/index.CFSFYiPA.js +0 -5366
  171. streamlit/static/static/js/index.CeiIiXap.js +0 -1
  172. streamlit/static/static/js/index.CzX2xpyc.js +0 -1
  173. streamlit/static/static/js/index.D1ErX5go.js +0 -2
  174. streamlit/static/static/js/index.D5gweoL5.js +0 -7
  175. streamlit/static/static/js/index.DByVKZgq.js +0 -1
  176. streamlit/static/static/js/index.DEND45D1.js +0 -3
  177. streamlit/static/static/js/index.DKN5MVff.js +0 -781
  178. streamlit/static/static/js/index.DfoxW1gP.js +0 -3855
  179. streamlit/static/static/js/index.Dtf1Ac0x.js +0 -1
  180. streamlit/static/static/js/index.DxrLhpeO.js +0 -1
  181. streamlit/static/static/js/index.J7o-_HIh.js +0 -1
  182. streamlit/static/static/js/index.LU8juINp.js +0 -197
  183. streamlit/static/static/js/index.L_N2iylt.js +0 -1
  184. streamlit/static/static/js/index.PZUX2kRz.js +0 -3
  185. streamlit/static/static/js/index.ROjU6K0k.js +0 -1
  186. streamlit/static/static/js/index.WSNLkF94.js +0 -1
  187. streamlit/static/static/js/index.X5W3gJLn.js +0 -1
  188. streamlit/static/static/js/index.k9LYqfSL.js +0 -1
  189. streamlit/static/static/js/index.pnHtHv_c.js +0 -203
  190. streamlit/static/static/js/index.tPUXqsfW.js +0 -1
  191. streamlit/static/static/js/mergeWith.GRNk8iwv.js +0 -1
  192. streamlit/static/static/js/number-overlay-editor.DXS2qb1U.js +0 -9
  193. streamlit/static/static/js/threshold.DjX0wlsa.js +0 -1
  194. streamlit/static/static/js/timer.CAwTRJ_g.js +0 -1
  195. streamlit/static/static/js/uniqueId.D_5M8Dgf.js +0 -1
  196. streamlit/static/static/js/useUpdateUiValue.C7ZKpLQK.js +0 -1
  197. streamlit/static/static/js/value.CgPGBV_l.js +0 -1
  198. streamlit/static/static/js/withFullScreenWrapper.C-gXt0Rl.js +0 -1
  199. streamlit/static/static/media/MaterialSymbols-Rounded.DsbC8sYI.woff2 +0 -0
  200. {streamlit-1.48.1.data → streamlit-1.49.0.data}/scripts/streamlit.cmd +0 -0
  201. {streamlit-1.48.1.dist-info → streamlit-1.49.0.dist-info}/WHEEL +0 -0
  202. {streamlit-1.48.1.dist-info → streamlit-1.49.0.dist-info}/entry_points.txt +0 -0
  203. {streamlit-1.48.1.dist-info → streamlit-1.49.0.dist-info}/top_level.txt +0 -0
@@ -67,6 +67,12 @@ SomeUploadedFiles: TypeAlias = Union[
67
67
  None,
68
68
  ]
69
69
 
70
+ # Type alias for accept_multiple_files parameter.
71
+ # If True, multiple files can be uploaded.
72
+ # If False, only a single file can be uploaded.
73
+ # If set to the literal "directory", users can upload an entire directory (folder) of files.
74
+ AcceptMultipleFiles: TypeAlias = Union[bool, Literal["directory"]]
75
+
70
76
 
71
77
  def _get_upload_files(
72
78
  widget_value: FileUploaderStateProto | None,
@@ -104,7 +110,7 @@ def _get_upload_files(
104
110
 
105
111
  @dataclass
106
112
  class FileUploaderSerde:
107
- accept_multiple_files: bool
113
+ accept_multiple_files: AcceptMultipleFiles
108
114
  allowed_types: Sequence[str] | None = None
109
115
 
110
116
  def deserialize(self, ui_value: FileUploaderStateProto | None) -> SomeUploadedFiles:
@@ -117,12 +123,16 @@ class FileUploaderSerde:
117
123
  if self.allowed_types:
118
124
  enforce_filename_restriction(file.name, self.allowed_types)
119
125
 
126
+ # Directory uploads always return a list, similar to multiple files
127
+ is_multiple_or_directory = (
128
+ self.accept_multiple_files is True
129
+ or self.accept_multiple_files == "directory"
130
+ )
131
+
120
132
  if len(upload_files) == 0:
121
- return_value: SomeUploadedFiles = [] if self.accept_multiple_files else None
133
+ return_value: SomeUploadedFiles = [] if is_multiple_or_directory else None
122
134
  else:
123
- return_value = (
124
- upload_files if self.accept_multiple_files else upload_files[0]
125
- )
135
+ return_value = upload_files if is_multiple_or_directory else upload_files[0]
126
136
  return return_value
127
137
 
128
138
  def serialize(self, files: SomeUploadedFiles) -> FileUploaderStateProto:
@@ -149,21 +159,22 @@ class FileUploaderMixin:
149
159
  # Multiple overloads are defined on `file_uploader()` below to represent
150
160
  # the different return types of `file_uploader()`.
151
161
  # These return types differ according to the value of the `accept_multiple_files` argument.
152
- # There are 2 associated variables, each with 2 options.
153
- # 1. The `accept_multiple_files` argument is set as `True`,
154
- # or it is set as `False` or omitted, in which case the default value `False`.
155
- # 2. The `type` argument may or may not be provided as a keyword-only argument.
156
162
  # There must be 2x2=4 overloads to cover all the possible arguments,
157
163
  # as these overloads must be mutually exclusive for mypy.
164
+ # There are 3 associated variables, each with 2+ options.
165
+ # 1. The `accept_multiple_files` argument is set as `True` or `"directory"`,
166
+ # or it is set as `False` or omitted, in which case the default value `False`.
167
+ # 2. The `type` argument may or may not be provided as a keyword-only argument.
168
+ # 3. Directory uploads always return a list of UploadedFile objects.
158
169
 
159
170
  # 1. type is given as not a keyword-only argument
160
- # 2. accept_multiple_files = True
171
+ # 2. accept_multiple_files = True or "directory"
161
172
  @overload
162
173
  def file_uploader(
163
174
  self,
164
175
  label: str,
165
176
  type: str | Sequence[str] | None,
166
- accept_multiple_files: Literal[True],
177
+ accept_multiple_files: Literal[True, "directory"],
167
178
  key: Key | None = None,
168
179
  help: str | None = None,
169
180
  on_change: WidgetCallback | None = None,
@@ -200,13 +211,13 @@ class FileUploaderMixin:
200
211
  # for the related discussions and examples.
201
212
 
202
213
  # 1. type is skipped or a keyword argument
203
- # 2. accept_multiple_files = True
214
+ # 2. accept_multiple_files = True or "directory"
204
215
  @overload
205
216
  def file_uploader(
206
217
  self,
207
218
  label: str,
208
219
  *,
209
- accept_multiple_files: Literal[True],
220
+ accept_multiple_files: Literal[True, "directory"],
210
221
  type: str | Sequence[str] | None = None,
211
222
  key: Key | None = None,
212
223
  help: str | None = None,
@@ -242,7 +253,7 @@ class FileUploaderMixin:
242
253
  self,
243
254
  label: str,
244
255
  type: str | Sequence[str] | None = None,
245
- accept_multiple_files: bool = False,
256
+ accept_multiple_files: AcceptMultipleFiles = False,
246
257
  key: Key | None = None,
247
258
  help: str | None = None,
248
259
  on_change: WidgetCallback | None = None,
@@ -285,7 +296,7 @@ class FileUploaderMixin:
285
296
  .. |st.markdown| replace:: ``st.markdown``
286
297
  .. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
287
298
 
288
- type : str or list of str or None
299
+ type : str, list of str, or None
289
300
  The allowed file extension(s) for uploaded files. This can be one
290
301
  of the following types:
291
302
 
@@ -302,11 +313,19 @@ class FileUploaderMixin:
302
313
  or type extensions. The correct handling of uploaded files is
303
314
  part of the app developer's responsibility.
304
315
 
305
- accept_multiple_files : bool
306
- Whether to accept more than one file in a submission. If this is
307
- ``False`` (default), the user can only submit one file at a time.
308
- If this is ``True``, the user can upload multiple files at the same
309
- time, in which case the return value will be a list of files.
316
+ accept_multiple_files : bool or "directory"
317
+ Whether to accept more than one file in a submission. This can be one
318
+ of the following values:
319
+
320
+ - ``False`` (default): The user can only submit one file at a time.
321
+ - ``True``: The user can upload multiple files at the same time.
322
+ - ``"directory"``: The user can select a directory to upload all
323
+ files in the directory and its subdirectories. If ``type`` is
324
+ set, only files matching those type(s) will be uploaded.
325
+
326
+ When this is ``True`` or ``"directory"``, the return value will be
327
+ a list and a user can additively select files if they click the
328
+ browse button on the widget multiple times.
310
329
 
311
330
  key : str or int
312
331
  An optional string or integer to use as the unique key for the widget.
@@ -356,19 +375,19 @@ class FileUploaderMixin:
356
375
  Returns
357
376
  -------
358
377
  None, UploadedFile, or list of UploadedFile
359
- - If accept_multiple_files is False, returns either None or
360
- an UploadedFile object.
361
- - If accept_multiple_files is True, returns a list with the
362
- uploaded files as UploadedFile objects. If no files were
363
- uploaded, returns an empty list.
378
+ - If accept_multiple_files is ``False``, returns either ``None`` or
379
+ an ``UploadedFile`` object.
380
+ - If accept_multiple_files is ``True`` or ``"directory"``, returns
381
+ a list with the uploaded files as ``UploadedFile`` objects. If no
382
+ files were uploaded, returns an empty list.
364
383
 
365
- The UploadedFile class is a subclass of BytesIO, and therefore is
366
- "file-like". This means you can pass an instance of it anywhere a
367
- file is expected.
384
+ The ``UploadedFile`` class is a subclass of ``BytesIO``, and
385
+ therefore is "file-like". This means you can pass an instance of it
386
+ anywhere a file is expected.
368
387
 
369
388
  Examples
370
389
  --------
371
- Insert a file uploader that accepts a single file at a time:
390
+ **Example 1: Accept a single file at a time**
372
391
 
373
392
  >>> import streamlit as st
374
393
  >>> import pandas as pd
@@ -392,22 +411,36 @@ class FileUploaderMixin:
392
411
  ... dataframe = pd.read_csv(uploaded_file)
393
412
  ... st.write(dataframe)
394
413
 
395
- Insert a file uploader that accepts multiple files at a time:
414
+ **Example 2: Accept multiple files at a time**
396
415
 
416
+ >>> import pandas as pd
397
417
  >>> import streamlit as st
398
418
  >>>
399
419
  >>> uploaded_files = st.file_uploader(
400
- ... "Choose a CSV file", accept_multiple_files=True
420
+ ... "Upload data", accept_multiple_files=True, type="csv"
401
421
  ... )
402
422
  >>> for uploaded_file in uploaded_files:
403
- ... bytes_data = uploaded_file.read()
404
- ... st.write("filename:", uploaded_file.name)
405
- ... st.write(bytes_data)
423
+ ... df = pd.read_csv(uploaded_file)
424
+ ... st.write(df)
406
425
 
407
426
  .. output::
408
427
  https://doc-file-uploader.streamlit.app/
409
428
  height: 375px
410
429
 
430
+ **Example 3: Accept an entire directory**
431
+
432
+ >>> import streamlit as st
433
+ >>>
434
+ >>> uploaded_files = st.file_uploader(
435
+ ... "Upload images", accept_multiple_files="directory", type=["jpg", "png"]
436
+ ... )
437
+ >>> for uploaded_file in uploaded_files:
438
+ ... st.image(uploaded_file)
439
+
440
+ .. output::
441
+ https://doc-file-uploader-directory.streamlit.app/
442
+ height: 375px
443
+
411
444
  """
412
445
  ctx = get_script_run_ctx()
413
446
  return self._file_uploader(
@@ -429,7 +462,7 @@ class FileUploaderMixin:
429
462
  self,
430
463
  label: str,
431
464
  type: str | Sequence[str] | None = None,
432
- accept_multiple_files: bool = False,
465
+ accept_multiple_files: AcceptMultipleFiles = False,
433
466
  key: Key | None = None,
434
467
  help: str | None = None,
435
468
  on_change: WidgetCallback | None = None,
@@ -455,7 +488,6 @@ class FileUploaderMixin:
455
488
  element_id = compute_and_register_element_id(
456
489
  "file_uploader",
457
490
  user_key=key,
458
- form_id=current_form_id(self.dg),
459
491
  dg=self.dg,
460
492
  label=label,
461
493
  type=type,
@@ -475,7 +507,12 @@ class FileUploaderMixin:
475
507
  file_uploader_proto.max_upload_size_mb = config.get_option(
476
508
  "server.maxUploadSize"
477
509
  )
478
- file_uploader_proto.multiple_files = accept_multiple_files
510
+ # Handle directory uploads - they should enable multiple files and set the directory flag
511
+ is_directory_upload = accept_multiple_files == "directory"
512
+ file_uploader_proto.multiple_files = (
513
+ accept_multiple_files is True or is_directory_upload
514
+ )
515
+ file_uploader_proto.accept_directory = is_directory_upload
479
516
  file_uploader_proto.form_id = current_form_id(self.dg)
480
517
  file_uploader_proto.disabled = disabled
481
518
  file_uploader_proto.label_visibility.value = get_label_visibility_proto_value(
@@ -492,7 +492,6 @@ class MultiSelectMixin:
492
492
  element_id = compute_and_register_element_id(
493
493
  widget_name,
494
494
  user_key=key,
495
- form_id=form_id,
496
495
  dg=self.dg,
497
496
  label=label,
498
497
  options=formatted_options,
@@ -452,7 +452,6 @@ class NumberInputMixin:
452
452
  element_id = compute_and_register_element_id(
453
453
  "number_input",
454
454
  user_key=key,
455
- form_id=current_form_id(self.dg),
456
455
  dg=self.dg,
457
456
  label=label,
458
457
  min_value=min_value,
@@ -369,7 +369,6 @@ class RadioMixin:
369
369
  element_id = compute_and_register_element_id(
370
370
  "radio",
371
371
  user_key=key,
372
- form_id=current_form_id(self.dg),
373
372
  dg=self.dg,
374
373
  label=label,
375
374
  options=[str(format_func(option)) for option in opt],
@@ -377,7 +377,6 @@ class SelectSliderMixin:
377
377
  element_id = compute_and_register_element_id(
378
378
  "select_slider",
379
379
  user_key=key,
380
- form_id=current_form_id(self.dg),
381
380
  dg=self.dg,
382
381
  label=label,
383
382
  options=[str(format_func(option)) for option in opt],
@@ -541,7 +541,6 @@ class SelectboxMixin:
541
541
  element_id = compute_and_register_element_id(
542
542
  "selectbox",
543
543
  user_key=key,
544
- form_id=current_form_id(self.dg),
545
544
  dg=self.dg,
546
545
  label=label,
547
546
  options=formatted_options,
@@ -680,7 +680,6 @@ class SliderMixin:
680
680
  element_id = compute_and_register_element_id(
681
681
  "slider",
682
682
  user_key=key,
683
- form_id=current_form_id(self.dg),
684
683
  dg=self.dg,
685
684
  label=label,
686
685
  min_value=min_value,
@@ -328,7 +328,6 @@ class TextWidgetsMixin:
328
328
  element_id = compute_and_register_element_id(
329
329
  "text_input",
330
330
  user_key=key,
331
- form_id=current_form_id(self.dg),
332
331
  dg=self.dg,
333
332
  label=label,
334
333
  value=value,
@@ -643,7 +642,6 @@ class TextWidgetsMixin:
643
642
  element_id = compute_and_register_element_id(
644
643
  "text_area",
645
644
  user_key=key,
646
- form_id=current_form_id(self.dg),
647
645
  dg=self.dg,
648
646
  label=label,
649
647
  value=value,
@@ -529,7 +529,6 @@ class TimeWidgetsMixin:
529
529
  element_id = compute_and_register_element_id(
530
530
  "time_input",
531
531
  user_key=key,
532
- form_id=current_form_id(self.dg),
533
532
  dg=self.dg,
534
533
  label=label,
535
534
  value=parsed_time if isinstance(value, (datetime, time)) else value,
@@ -912,7 +911,6 @@ class TimeWidgetsMixin:
912
911
  element_id = compute_and_register_element_id(
913
912
  "date_input",
914
913
  user_key=key,
915
- form_id=current_form_id(self.dg),
916
914
  dg=self.dg,
917
915
  label=label,
918
916
  value=parsed,
streamlit/errors.py CHANGED
@@ -517,3 +517,14 @@ class StreamlitInvalidHeightError(LocalizableStreamlitException):
517
517
  height=repr(height),
518
518
  valid_values=valid_values,
519
519
  )
520
+
521
+
522
+ class StreamlitValueError(LocalizableStreamlitException):
523
+ """Exception raised when a value is not valid for a parameter."""
524
+
525
+ def __init__(self, parameter: str, valid_values: list[str]) -> None:
526
+ super().__init__(
527
+ "Invalid `{parameter}` value. Supported values: {valid_values}.",
528
+ parameter=parameter,
529
+ valid_values=", ".join(valid_values),
530
+ )