streamlit 1.49.1__py3-none-any.whl → 1.50.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.
- streamlit/column_config.py +2 -0
- streamlit/commands/navigation.py +3 -1
- streamlit/components/v1/custom_component.py +17 -42
- streamlit/config.py +306 -0
- streamlit/connections/base_connection.py +4 -2
- streamlit/dataframe_util.py +3 -2
- streamlit/delta_generator.py +2 -3
- streamlit/elements/arrow.py +63 -43
- streamlit/elements/deck_gl_json_chart.py +1 -0
- streamlit/elements/form.py +6 -6
- streamlit/elements/graphviz_chart.py +23 -6
- streamlit/elements/iframe.py +0 -2
- streamlit/elements/image.py +10 -9
- streamlit/elements/layouts.py +58 -11
- streamlit/elements/lib/built_in_chart_utils.py +95 -29
- streamlit/elements/lib/column_config_utils.py +5 -0
- streamlit/elements/lib/column_types.py +563 -144
- streamlit/elements/lib/dialog.py +1 -0
- streamlit/elements/lib/pandas_styler_utils.py +30 -14
- streamlit/elements/lib/utils.py +17 -5
- streamlit/elements/map.py +1 -3
- streamlit/elements/media.py +2 -0
- streamlit/elements/metric.py +10 -32
- streamlit/elements/plotly_chart.py +17 -9
- streamlit/elements/pyplot.py +6 -6
- streamlit/elements/vega_charts.py +110 -44
- streamlit/elements/widgets/audio_input.py +48 -0
- streamlit/elements/widgets/button.py +27 -25
- streamlit/elements/widgets/button_group.py +1 -0
- streamlit/elements/widgets/camera_input.py +1 -0
- streamlit/elements/widgets/chat.py +1 -0
- streamlit/elements/widgets/checkbox.py +1 -0
- streamlit/elements/widgets/color_picker.py +1 -0
- streamlit/elements/widgets/data_editor.py +6 -5
- streamlit/elements/widgets/file_uploader.py +1 -0
- streamlit/elements/widgets/multiselect.py +10 -0
- streamlit/elements/widgets/number_input.py +3 -0
- streamlit/elements/widgets/radio.py +1 -0
- streamlit/elements/widgets/select_slider.py +1 -0
- streamlit/elements/widgets/selectbox.py +4 -0
- streamlit/elements/widgets/slider.py +1 -0
- streamlit/elements/widgets/text_widgets.py +6 -0
- streamlit/elements/widgets/time_widgets.py +9 -0
- streamlit/elements/write.py +1 -17
- streamlit/git_util.py +65 -43
- streamlit/material_icon_names.py +1 -1
- streamlit/proto/Arrow_pb2.py +10 -8
- streamlit/proto/Arrow_pb2.pyi +31 -2
- streamlit/proto/AudioInput_pb2.py +2 -2
- streamlit/proto/AudioInput_pb2.pyi +6 -2
- streamlit/proto/Block_pb2.py +11 -11
- streamlit/proto/Block_pb2.pyi +5 -0
- streamlit/proto/NewSession_pb2.py +18 -16
- streamlit/proto/NewSession_pb2.pyi +135 -2
- streamlit/runtime/app_session.py +18 -5
- streamlit/runtime/theme_util.py +148 -0
- streamlit/static/index.html +2 -2
- streamlit/static/manifest.json +222 -222
- streamlit/static/static/css/index.CHEnSPGk.css +1 -0
- streamlit/static/static/css/{index.C8X8rNzw.css → index.CIiu7Ygf.css} +1 -1
- streamlit/static/static/js/{ErrorOutline.esm.DcGrhbBP.js → ErrorOutline.esm.DUpR0_Ka.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.DgBvV6Pq.js → FileDownload.esm.CN4j9-1w.js} +1 -1
- streamlit/static/static/js/{FileHelper.M6AAaeuA.js → FileHelper.CaIUKG91.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.DHh1GFzm.js → FormClearHelper.DTcdrasw.js} +1 -1
- streamlit/static/static/js/{Hooks.DGu1od_L.js → Hooks.BRba_Own.js} +1 -1
- streamlit/static/static/js/InputInstructions.xnSDuYeQ.js +1 -0
- streamlit/static/static/js/{Particles.DDVT-6Qc.js → Particles.CElH0XX2.js} +1 -1
- streamlit/static/static/js/{ProgressBar.BEY0cXXV.js → ProgressBar.DetlP5aY.js} +2 -2
- streamlit/static/static/js/Toolbar.C77ar7rq.js +1 -0
- streamlit/static/static/js/{base-input.CK3UVGp1.js → base-input.BQft14La.js} +3 -3
- streamlit/static/static/js/{checkbox.D8W881TL.js → checkbox.yZOfXCeX.js} +1 -1
- streamlit/static/static/js/{createSuper.B6W-Dh9S.js → createSuper.Dh9w1cs8.js} +1 -1
- streamlit/static/static/js/data-grid-overlay-editor.DcuHuCyW.js +1 -0
- streamlit/static/static/js/{downloader.DiKpuU_S.js → downloader.MeHtkq8r.js} +1 -1
- streamlit/static/static/js/{es6.B8zRNPZ-.js → es6.VpBPGCnM.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.DIewJmmh.js → iframeResizer.contentWindow.yMw_ARIL.js} +1 -1
- streamlit/static/static/js/{index.B9mjBcgE.js → index.64ejlaaT.js} +1 -1
- streamlit/static/static/js/{index.CD8HuT3N.js → index.6xX1278W.js} +90 -91
- streamlit/static/static/js/index.B-hiXRzw.js +1 -0
- streamlit/static/static/js/{index.Ch7MBCx0.js → index.B0H9IXUJ.js} +47 -47
- streamlit/static/static/js/{index.4eF4NxG2.js → index.B4cAbHP6.js} +1 -1
- streamlit/static/static/js/{index.Dk4C7X3i.js → index.B4dUQfni.js} +1 -1
- streamlit/static/static/js/{index.CvYYtxD_.js → index.BPQo7BKk.js} +1 -1
- streamlit/static/static/js/index.Baqa90pe.js +2 -0
- streamlit/static/static/js/{index.D5naqx-J.js → index.Bj9JgOEC.js} +1 -1
- streamlit/static/static/js/index.BjCwMzj4.js +3 -0
- streamlit/static/static/js/{index.C_tmcx4B.js → index.Bm3VbPB5.js} +1 -1
- streamlit/static/static/js/{index.C7fRKRs4.js → index.Bxz2yX3P.js} +1 -1
- streamlit/static/static/js/{index.ho6NIXGl.js → index.BycLveZ4.js} +1 -1
- streamlit/static/static/js/{index.452cqrrL.js → index.C9BdUqTi.js} +1 -1
- streamlit/static/static/js/index.CFMf5_ez.js +197 -0
- streamlit/static/static/js/index.CGYqqs6j.js +1 -0
- streamlit/static/static/js/{index.zecpGxtj.js → index.CH1tqnSs.js} +1 -1
- streamlit/static/static/js/{index.CjXWwH-y.js → index.CMItVsFA.js} +1 -1
- streamlit/static/static/js/{index.B6U8LQo3.js → index.CTBk8Vk2.js} +1 -1
- streamlit/static/static/js/index.CiAQIz1H.js +7 -0
- streamlit/static/static/js/index.Cj7DSzVR.js +73 -0
- streamlit/static/static/js/index.Ck8rQ9OL.js +1 -0
- streamlit/static/static/js/{index.Ts_0SdB9.js → index.ClELlchS.js} +2 -2
- streamlit/static/static/js/{index.Bte_9Lyq.js → index.Cnpi3o3E.js} +1 -1
- streamlit/static/static/js/{index.CcJf6BCU.js → index.Ctn27_AE.js} +1 -1
- streamlit/static/static/js/{index.CP5TD2z1.js → index.D2QEXQq_.js} +1 -1
- streamlit/static/static/js/index.DH71Ezyj.js +1 -0
- streamlit/static/static/js/{index.D2-atlaQ.js → index.DHh-U0dK.js} +2 -2
- streamlit/static/static/js/{index.DtYN2x4k.js → index.DK7hD7_w.js} +1 -1
- streamlit/static/static/js/{index.qhs54UAB.js → index.DKv_lNO7.js} +1 -1
- streamlit/static/static/js/index.DNLrMXgm.js +12 -0
- streamlit/static/static/js/index.DW0Grddz.js +1 -0
- streamlit/static/static/js/{index.cnnXF7xQ.js → index.Dbe-Q3C-.js} +1 -1
- streamlit/static/static/js/index.DcPNYEUo.js +1 -0
- streamlit/static/static/js/index.DuxqVQpd.js +1 -0
- streamlit/static/static/js/{index.CejBxbg1.js → index.FFOzOWzC.js} +1 -1
- streamlit/static/static/js/{index.BnEpvLEz.js → index.GRUzrudl.js} +1 -1
- streamlit/static/static/js/{input.nzVJphXi.js → input.s6pjQ49A.js} +1 -1
- streamlit/static/static/js/{memory.CjCgTQz3.js → memory.Cuvsdfrl.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.DaRFzZEO.js → number-overlay-editor.DdgVR5m3.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.DgiPnZ9N.js → possibleConstructorReturn.CqidKeei.js} +1 -1
- streamlit/static/static/js/{sandbox.mithfq7Z.js → sandbox.CCQREcJx.js} +1 -1
- streamlit/static/static/js/{timepicker.Dbl5KFh6.js → timepicker.mkJF97Bb.js} +4 -4
- streamlit/static/static/js/{toConsumableArray.D-Dx88BQ.js → toConsumableArray.De7I7KVR.js} +1 -1
- streamlit/static/static/js/{uniqueId.Bh26R_3S.js → uniqueId.RI1LJdtz.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.DeK-QJpD.js → useBasicWidgetState.CedkNjUW.js} +1 -1
- streamlit/static/static/js/{useTextInputAutoExpand.4iAdLWD-.js → useTextInputAutoExpand.Ca7w8dVs.js} +2 -2
- streamlit/static/static/js/{useUpdateUiValue.CmT7_nJN.js → useUpdateUiValue.DeXelfRH.js} +1 -1
- streamlit/static/static/js/withFullScreenWrapper.C3561XxJ.js +1 -0
- streamlit/static/static/media/MaterialSymbols-Rounded.DeCZgS-4.woff2 +0 -0
- streamlit/string_util.py +58 -1
- streamlit/web/bootstrap.py +0 -31
- streamlit/web/server/routes.py +17 -4
- streamlit/web/server/server.py +1 -0
- {streamlit-1.49.1.dist-info → streamlit-1.50.0.dist-info}/METADATA +1 -1
- {streamlit-1.49.1.dist-info → streamlit-1.50.0.dist-info}/RECORD +136 -135
- streamlit/static/static/css/index.COe1010n.css +0 -1
- streamlit/static/static/js/InputInstructions.z6sVgyYt.js +0 -1
- streamlit/static/static/js/Toolbar.DSnK1fUh.js +0 -1
- streamlit/static/static/js/data-grid-overlay-editor.DRTHOydk.js +0 -1
- streamlit/static/static/js/index.BXYmrqnf.js +0 -1
- streamlit/static/static/js/index.B_8AnktO.js +0 -1
- streamlit/static/static/js/index.Bl7zGQSh.js +0 -7
- streamlit/static/static/js/index.BnJIOYn9.js +0 -73
- streamlit/static/static/js/index.C1HcTl5K.js +0 -1
- streamlit/static/static/js/index.C7lSmSOP.js +0 -1
- streamlit/static/static/js/index.D3K5nOu9.js +0 -197
- streamlit/static/static/js/index.DkKT3LUI.js +0 -1
- streamlit/static/static/js/index.MTPPBDHk.js +0 -2
- streamlit/static/static/js/index.pqW9AMJD.js +0 -3
- streamlit/static/static/js/index.urHgTgMQ.js +0 -12
- streamlit/static/static/js/index.wzkv_11M.js +0 -1
- streamlit/static/static/js/index.yF5AncHY.js +0 -1
- streamlit/static/static/js/withFullScreenWrapper.DLp1ENGm.js +0 -1
- streamlit/static/static/media/MaterialSymbols-Rounded.CBxVaFdk.woff2 +0 -0
- {streamlit-1.49.1.data → streamlit-1.50.0.data}/scripts/streamlit.cmd +0 -0
- {streamlit-1.49.1.dist-info → streamlit-1.50.0.dist-info}/WHEEL +0 -0
- {streamlit-1.49.1.dist-info → streamlit-1.50.0.dist-info}/entry_points.txt +0 -0
- {streamlit-1.49.1.dist-info → streamlit-1.50.0.dist-info}/top_level.txt +0 -0
|
@@ -35,6 +35,7 @@ from streamlit.elements.lib.utils import (
|
|
|
35
35
|
to_key,
|
|
36
36
|
)
|
|
37
37
|
from streamlit.elements.widgets.file_uploader import _get_upload_files
|
|
38
|
+
from streamlit.errors import StreamlitAPIException
|
|
38
39
|
from streamlit.proto.AudioInput_pb2 import AudioInput as AudioInputProto
|
|
39
40
|
from streamlit.proto.Common_pb2 import FileUploaderState as FileUploaderStateProto
|
|
40
41
|
from streamlit.proto.Common_pb2 import UploadedFileInfo as UploadedFileInfoProto
|
|
@@ -54,6 +55,9 @@ if TYPE_CHECKING:
|
|
|
54
55
|
|
|
55
56
|
SomeUploadedAudioFile: TypeAlias = Union[UploadedFile, DeletedFile, None]
|
|
56
57
|
|
|
58
|
+
# Allowed sample rates for audio recording
|
|
59
|
+
ALLOWED_SAMPLE_RATES = {8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000}
|
|
60
|
+
|
|
57
61
|
|
|
58
62
|
@dataclass
|
|
59
63
|
class AudioInputSerde:
|
|
@@ -90,6 +94,7 @@ class AudioInputMixin:
|
|
|
90
94
|
self,
|
|
91
95
|
label: str,
|
|
92
96
|
*,
|
|
97
|
+
sample_rate: int | None = 16000,
|
|
93
98
|
key: Key | None = None,
|
|
94
99
|
help: str | None = None,
|
|
95
100
|
on_change: WidgetCallback | None = None,
|
|
@@ -125,6 +130,15 @@ class AudioInputMixin:
|
|
|
125
130
|
.. |st.markdown| replace:: ``st.markdown``
|
|
126
131
|
.. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
|
|
127
132
|
|
|
133
|
+
sample_rate : int or None
|
|
134
|
+
The target sample rate for the audio recording in Hz.
|
|
135
|
+
This defaults to 16000 Hz, which is optimal for speech recognition.
|
|
136
|
+
|
|
137
|
+
The following sample rates are supported: 8000, 11025, 16000,
|
|
138
|
+
22050, 24000, 32000, 44100, or 48000. If this is ``None``, the
|
|
139
|
+
widget uses the browser's default sample rate (typically 44100 or
|
|
140
|
+
48000 Hz).
|
|
141
|
+
|
|
128
142
|
key : str or int
|
|
129
143
|
An optional string or integer to use as the unique key for the widget.
|
|
130
144
|
If this is omitted, a key will be generated for the widget
|
|
@@ -185,6 +199,10 @@ class AudioInputMixin:
|
|
|
185
199
|
|
|
186
200
|
Examples
|
|
187
201
|
--------
|
|
202
|
+
*Example 1:* Record a voice message and play it back.*
|
|
203
|
+
|
|
204
|
+
The default sample rate of 16000 Hz is optimal for speech recognition.
|
|
205
|
+
|
|
188
206
|
>>> import streamlit as st
|
|
189
207
|
>>>
|
|
190
208
|
>>> audio_value = st.audio_input("Record a voice message")
|
|
@@ -196,10 +214,34 @@ class AudioInputMixin:
|
|
|
196
214
|
https://doc-audio-input.streamlit.app/
|
|
197
215
|
height: 260px
|
|
198
216
|
|
|
217
|
+
*Example 2:* Record high-fidelity audio and play it back.*
|
|
218
|
+
|
|
219
|
+
Higher sample rates can create higher-quality, larger audio files. This
|
|
220
|
+
might require a nicer microphone to fully appreciate the difference.
|
|
221
|
+
|
|
222
|
+
>>> import streamlit as st
|
|
223
|
+
>>>
|
|
224
|
+
>>> audio_value = st.audio_input("Record high quality audio", sample_rate=48000)
|
|
225
|
+
>>>
|
|
226
|
+
>>> if audio_value:
|
|
227
|
+
... st.audio(audio_value)
|
|
228
|
+
|
|
229
|
+
.. output::
|
|
230
|
+
https://doc-audio-input-high-rate.streamlit.app/
|
|
231
|
+
height: 260px
|
|
232
|
+
|
|
199
233
|
"""
|
|
234
|
+
# Validate sample_rate parameter
|
|
235
|
+
if sample_rate is not None and sample_rate not in ALLOWED_SAMPLE_RATES:
|
|
236
|
+
raise StreamlitAPIException(
|
|
237
|
+
f"Invalid sample_rate: {sample_rate}. "
|
|
238
|
+
f"Must be one of {sorted(ALLOWED_SAMPLE_RATES)} Hz, or None for browser default."
|
|
239
|
+
)
|
|
240
|
+
|
|
200
241
|
ctx = get_script_run_ctx()
|
|
201
242
|
return self._audio_input(
|
|
202
243
|
label=label,
|
|
244
|
+
sample_rate=sample_rate,
|
|
203
245
|
key=key,
|
|
204
246
|
help=help,
|
|
205
247
|
on_change=on_change,
|
|
@@ -214,6 +256,7 @@ class AudioInputMixin:
|
|
|
214
256
|
def _audio_input(
|
|
215
257
|
self,
|
|
216
258
|
label: str,
|
|
259
|
+
sample_rate: int | None = 16000,
|
|
217
260
|
key: Key | None = None,
|
|
218
261
|
help: str | None = None,
|
|
219
262
|
on_change: WidgetCallback | None = None,
|
|
@@ -239,6 +282,7 @@ class AudioInputMixin:
|
|
|
239
282
|
element_id = compute_and_register_element_id(
|
|
240
283
|
"audio_input",
|
|
241
284
|
user_key=key,
|
|
285
|
+
key_as_main_identity=False,
|
|
242
286
|
dg=self.dg,
|
|
243
287
|
label=label,
|
|
244
288
|
help=help,
|
|
@@ -254,6 +298,10 @@ class AudioInputMixin:
|
|
|
254
298
|
label_visibility
|
|
255
299
|
)
|
|
256
300
|
|
|
301
|
+
# Set sample_rate in protobuf if specified
|
|
302
|
+
if sample_rate is not None:
|
|
303
|
+
audio_input_proto.sample_rate = sample_rate
|
|
304
|
+
|
|
257
305
|
if label and help is not None:
|
|
258
306
|
audio_input_proto.help = dedent(help)
|
|
259
307
|
|
|
@@ -188,6 +188,12 @@ class ButtonMixin:
|
|
|
188
188
|
In both cases, if the contents of the button are wider than the
|
|
189
189
|
parent container, the contents will line wrap.
|
|
190
190
|
|
|
191
|
+
.. deprecated::
|
|
192
|
+
``use_container_width`` is deprecated and will be removed in a
|
|
193
|
+
future release. For ``use_container_width=True``, use
|
|
194
|
+
``width="stretch"``. For ``use_container_width=False``, use
|
|
195
|
+
``width="content"``.
|
|
196
|
+
|
|
191
197
|
width : "content", "stretch", or int
|
|
192
198
|
The width of the button. This can be one of the following:
|
|
193
199
|
|
|
@@ -201,12 +207,6 @@ class ButtonMixin:
|
|
|
201
207
|
the parent container, the width of the button matches the width
|
|
202
208
|
of the parent container.
|
|
203
209
|
|
|
204
|
-
.. deprecated::
|
|
205
|
-
``use_container_width`` is deprecated and will be removed in a
|
|
206
|
-
future release. For ``use_container_width=True``, use
|
|
207
|
-
``width="stretch"``. For ``use_container_width=False``, use
|
|
208
|
-
``width="content"``.
|
|
209
|
-
|
|
210
210
|
Returns
|
|
211
211
|
-------
|
|
212
212
|
bool
|
|
@@ -432,6 +432,12 @@ class ButtonMixin:
|
|
|
432
432
|
In both cases, if the contents of the button are wider than the
|
|
433
433
|
parent container, the contents will line wrap.
|
|
434
434
|
|
|
435
|
+
.. deprecated::
|
|
436
|
+
``use_container_width`` is deprecated and will be removed in a
|
|
437
|
+
future release. For ``use_container_width=True``, use
|
|
438
|
+
``width="stretch"``. For ``use_container_width=False``, use
|
|
439
|
+
``width="content"``.
|
|
440
|
+
|
|
435
441
|
width : "content", "stretch", or int
|
|
436
442
|
The width of the download button. This can be one of the following:
|
|
437
443
|
|
|
@@ -445,12 +451,6 @@ class ButtonMixin:
|
|
|
445
451
|
the parent container, the width of the button matches the width
|
|
446
452
|
of the parent container.
|
|
447
453
|
|
|
448
|
-
.. deprecated::
|
|
449
|
-
``use_container_width`` is deprecated and will be removed in a
|
|
450
|
-
future release. For ``use_container_width=True``, use
|
|
451
|
-
``width="stretch"``. For ``use_container_width=False``, use
|
|
452
|
-
``width="content"``.
|
|
453
|
-
|
|
454
454
|
Returns
|
|
455
455
|
-------
|
|
456
456
|
bool
|
|
@@ -680,6 +680,12 @@ class ButtonMixin:
|
|
|
680
680
|
In both cases, if the contents of the button are wider than the
|
|
681
681
|
parent container, the contents will line wrap.
|
|
682
682
|
|
|
683
|
+
.. deprecated::
|
|
684
|
+
``use_container_width`` is deprecated and will be removed in a
|
|
685
|
+
future release. For ``use_container_width=True``, use
|
|
686
|
+
``width="stretch"``. For ``use_container_width=False``, use
|
|
687
|
+
``width="content"``.
|
|
688
|
+
|
|
683
689
|
width : "content", "stretch", or int
|
|
684
690
|
The width of the link button. This can be one of the following:
|
|
685
691
|
|
|
@@ -693,12 +699,6 @@ class ButtonMixin:
|
|
|
693
699
|
the parent container, the width of the button matches the width
|
|
694
700
|
of the parent container.
|
|
695
701
|
|
|
696
|
-
.. deprecated::
|
|
697
|
-
``use_container_width`` is deprecated and will be removed in a
|
|
698
|
-
future release. For ``use_container_width=True``, use
|
|
699
|
-
``width="stretch"``. For ``use_container_width=False``, use
|
|
700
|
-
``width="content"``.
|
|
701
|
-
|
|
702
702
|
Example
|
|
703
703
|
-------
|
|
704
704
|
>>> import streamlit as st
|
|
@@ -812,6 +812,12 @@ class ButtonMixin:
|
|
|
812
812
|
The default is ``True`` for page links in the sidebar and ``False``
|
|
813
813
|
for those in the main app.
|
|
814
814
|
|
|
815
|
+
.. deprecated::
|
|
816
|
+
``use_container_width`` is deprecated and will be removed in a
|
|
817
|
+
future release. For ``use_container_width=True``, use
|
|
818
|
+
``width="stretch"``. For ``use_container_width=False``, use
|
|
819
|
+
``width="content"``.
|
|
820
|
+
|
|
815
821
|
width : "content", "stretch", or int
|
|
816
822
|
The width of the page-link button. This can be one of the following:
|
|
817
823
|
|
|
@@ -825,12 +831,6 @@ class ButtonMixin:
|
|
|
825
831
|
the parent container, the width of the button matches the width
|
|
826
832
|
of the parent container.
|
|
827
833
|
|
|
828
|
-
.. deprecated::
|
|
829
|
-
``use_container_width`` is deprecated and will be removed in a
|
|
830
|
-
future release. For ``use_container_width=True``, use
|
|
831
|
-
``width="stretch"``. For ``use_container_width=False``, use
|
|
832
|
-
``width="content"``.
|
|
833
|
-
|
|
834
834
|
Example
|
|
835
835
|
-------
|
|
836
836
|
Consider the following example given this file structure:
|
|
@@ -914,6 +914,7 @@ class ButtonMixin:
|
|
|
914
914
|
element_id = compute_and_register_element_id(
|
|
915
915
|
"download_button",
|
|
916
916
|
user_key=key,
|
|
917
|
+
key_as_main_identity=True,
|
|
917
918
|
dg=self.dg,
|
|
918
919
|
label=label,
|
|
919
920
|
icon=icon,
|
|
@@ -1119,8 +1120,9 @@ class ButtonMixin:
|
|
|
1119
1120
|
# Only the form submitter button needs a form ID at the moment.
|
|
1120
1121
|
form_id = current_form_id(self.dg) if is_form_submitter else ""
|
|
1121
1122
|
element_id = compute_and_register_element_id(
|
|
1122
|
-
"button",
|
|
1123
|
+
"form_submit_button" if is_form_submitter else "button",
|
|
1123
1124
|
user_key=key,
|
|
1125
|
+
key_as_main_identity=True,
|
|
1124
1126
|
dg=self.dg,
|
|
1125
1127
|
label=label,
|
|
1126
1128
|
icon=icon,
|
|
@@ -711,6 +711,11 @@ class DataEditorMixin:
|
|
|
711
711
|
this is ``False``, Streamlit sets the data editor's width according
|
|
712
712
|
to ``width``.
|
|
713
713
|
|
|
714
|
+
.. deprecated::
|
|
715
|
+
``use_container_width`` is deprecated and will be removed in a
|
|
716
|
+
future release. For ``use_container_width=True``, use
|
|
717
|
+
``width="stretch"``.
|
|
718
|
+
|
|
714
719
|
hide_index : bool or None
|
|
715
720
|
Whether to hide the index column(s). If ``hide_index`` is ``None``
|
|
716
721
|
(default), the visibility of index columns is automatically
|
|
@@ -793,11 +798,6 @@ class DataEditorMixin:
|
|
|
793
798
|
is ``None`` (default), Streamlit will use a default row height,
|
|
794
799
|
which fits one line of text.
|
|
795
800
|
|
|
796
|
-
.. deprecated::
|
|
797
|
-
``use_container_width`` is deprecated and will be removed in a
|
|
798
|
-
future release. For ``use_container_width=True``, use
|
|
799
|
-
``width="stretch"``.
|
|
800
|
-
|
|
801
801
|
Returns
|
|
802
802
|
-------
|
|
803
803
|
pandas.DataFrame, pandas.Series, pyarrow.Table, numpy.ndarray, list, set, tuple, or dict.
|
|
@@ -1028,6 +1028,7 @@ class DataEditorMixin:
|
|
|
1028
1028
|
element_id = compute_and_register_element_id(
|
|
1029
1029
|
"data_editor",
|
|
1030
1030
|
user_key=key,
|
|
1031
|
+
key_as_main_identity=False,
|
|
1031
1032
|
dg=self.dg,
|
|
1032
1033
|
data=arrow_bytes,
|
|
1033
1034
|
width=width,
|
|
@@ -492,6 +492,16 @@ class MultiSelectMixin:
|
|
|
492
492
|
element_id = compute_and_register_element_id(
|
|
493
493
|
widget_name,
|
|
494
494
|
user_key=key,
|
|
495
|
+
# Treat the provided key as the main identity. Only include
|
|
496
|
+
# changes to the options, accept_new_options, and max_selections
|
|
497
|
+
# in the identity computation as those can invalidate the
|
|
498
|
+
# current selection.
|
|
499
|
+
key_as_main_identity={
|
|
500
|
+
"options",
|
|
501
|
+
"max_selections",
|
|
502
|
+
"accept_new_options",
|
|
503
|
+
"format_func",
|
|
504
|
+
},
|
|
495
505
|
dg=self.dg,
|
|
496
506
|
label=label,
|
|
497
507
|
options=formatted_options,
|
|
@@ -452,6 +452,9 @@ class NumberInputMixin:
|
|
|
452
452
|
element_id = compute_and_register_element_id(
|
|
453
453
|
"number_input",
|
|
454
454
|
user_key=key,
|
|
455
|
+
# Ensure stable ID when key is provided; explicitly whitelist parameters
|
|
456
|
+
# that might invalidate the current widget state.
|
|
457
|
+
key_as_main_identity={"min_value", "max_value", "step"},
|
|
455
458
|
dg=self.dg,
|
|
456
459
|
label=label,
|
|
457
460
|
min_value=min_value,
|
|
@@ -541,6 +541,10 @@ class SelectboxMixin:
|
|
|
541
541
|
element_id = compute_and_register_element_id(
|
|
542
542
|
"selectbox",
|
|
543
543
|
user_key=key,
|
|
544
|
+
# Treat the provided key as the main identity. Only include
|
|
545
|
+
# the options and accept_new_options in the identity computation
|
|
546
|
+
# as those can invalidate the current selection.
|
|
547
|
+
key_as_main_identity={"options", "accept_new_options", "format_func"},
|
|
544
548
|
dg=self.dg,
|
|
545
549
|
label=label,
|
|
546
550
|
options=formatted_options,
|
|
@@ -328,6 +328,9 @@ class TextWidgetsMixin:
|
|
|
328
328
|
element_id = compute_and_register_element_id(
|
|
329
329
|
"text_input",
|
|
330
330
|
user_key=key,
|
|
331
|
+
# Explicitly whitelist max_chars to make sure the ID changes when it changes
|
|
332
|
+
# since the widget value might become invalid based on a different max_chars
|
|
333
|
+
key_as_main_identity={"max_chars"},
|
|
331
334
|
dg=self.dg,
|
|
332
335
|
label=label,
|
|
333
336
|
value=value,
|
|
@@ -642,6 +645,9 @@ class TextWidgetsMixin:
|
|
|
642
645
|
element_id = compute_and_register_element_id(
|
|
643
646
|
"text_area",
|
|
644
647
|
user_key=key,
|
|
648
|
+
# Explicitly whitelist max_chars to make sure the ID changes when it changes
|
|
649
|
+
# since the widget value might become invalid based on a different max_chars
|
|
650
|
+
key_as_main_identity={"max_chars"},
|
|
645
651
|
dg=self.dg,
|
|
646
652
|
label=label,
|
|
647
653
|
value=value,
|
|
@@ -529,6 +529,9 @@ class TimeWidgetsMixin:
|
|
|
529
529
|
element_id = compute_and_register_element_id(
|
|
530
530
|
"time_input",
|
|
531
531
|
user_key=key,
|
|
532
|
+
# Ensure stable ID when key is provided; only whitelist step since it
|
|
533
|
+
# affects the selection granularity and available options.
|
|
534
|
+
key_as_main_identity={"step"},
|
|
532
535
|
dg=self.dg,
|
|
533
536
|
label=label,
|
|
534
537
|
value=parsed_time if isinstance(value, (datetime, time)) else value,
|
|
@@ -911,6 +914,12 @@ class TimeWidgetsMixin:
|
|
|
911
914
|
element_id = compute_and_register_element_id(
|
|
912
915
|
"date_input",
|
|
913
916
|
user_key=key,
|
|
917
|
+
# Ensure stable ID when key is provided; explicitly whitelist parameters
|
|
918
|
+
# that might invalidate the current widget state.
|
|
919
|
+
# format should be supported. However, there is a bug in baseweb where
|
|
920
|
+
# changing the format dynamically leads to a wrongly formatted date.
|
|
921
|
+
# So, we whitelist it for now until we migrate this away from baseweb.
|
|
922
|
+
key_as_main_identity={"min_value", "max_value", "format"},
|
|
914
923
|
dg=self.dg,
|
|
915
924
|
label=label,
|
|
916
925
|
value=parsed,
|
streamlit/elements/write.py
CHANGED
|
@@ -37,7 +37,6 @@ from typing import (
|
|
|
37
37
|
|
|
38
38
|
from streamlit import dataframe_util, type_util
|
|
39
39
|
from streamlit.errors import StreamlitAPIException
|
|
40
|
-
from streamlit.logger import get_logger
|
|
41
40
|
from streamlit.runtime.metrics_util import gather_metrics
|
|
42
41
|
from streamlit.string_util import (
|
|
43
42
|
is_mem_address_str,
|
|
@@ -56,7 +55,6 @@ HELP_TYPES: Final[tuple[type[Any], ...]] = (
|
|
|
56
55
|
types.ModuleType,
|
|
57
56
|
)
|
|
58
57
|
|
|
59
|
-
_LOGGER: Final = get_logger(__name__)
|
|
60
58
|
|
|
61
59
|
_TEXT_CURSOR: Final = " ▏"
|
|
62
60
|
|
|
@@ -254,7 +252,7 @@ class WriteMixin:
|
|
|
254
252
|
return written_content
|
|
255
253
|
|
|
256
254
|
@gather_metrics("write")
|
|
257
|
-
def write(self, *args: Any, unsafe_allow_html: bool = False
|
|
255
|
+
def write(self, *args: Any, unsafe_allow_html: bool = False) -> None:
|
|
258
256
|
"""Displays arguments in the app.
|
|
259
257
|
|
|
260
258
|
This is the Swiss Army knife of Streamlit commands: it does different
|
|
@@ -323,13 +321,6 @@ class WriteMixin:
|
|
|
323
321
|
If you only want to insert HTML or CSS without Markdown text,
|
|
324
322
|
we recommend using ``st.html`` instead.
|
|
325
323
|
|
|
326
|
-
**kwargs : any
|
|
327
|
-
Keyword arguments. Not used.
|
|
328
|
-
|
|
329
|
-
.. deprecated::
|
|
330
|
-
``**kwargs`` is deprecated and will be removed in a later version.
|
|
331
|
-
Use other, more specific Streamlit commands to pass additional
|
|
332
|
-
keyword arguments.
|
|
333
324
|
|
|
334
325
|
Returns
|
|
335
326
|
-------
|
|
@@ -400,13 +391,6 @@ class WriteMixin:
|
|
|
400
391
|
height: 300px
|
|
401
392
|
|
|
402
393
|
"""
|
|
403
|
-
if kwargs:
|
|
404
|
-
_LOGGER.warning(
|
|
405
|
-
'Invalid arguments were passed to "st.write" function. Support for '
|
|
406
|
-
"passing such unknown keywords arguments will be dropped in future. "
|
|
407
|
-
"Invalid arguments were: %s",
|
|
408
|
-
kwargs,
|
|
409
|
-
)
|
|
410
394
|
|
|
411
395
|
if len(args) == 1 and isinstance(args[0], str):
|
|
412
396
|
# Optimization: If there is only one arg, and it's a string,
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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 >=
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 >=
|
|
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 >=
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return
|
|
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
|
|
165
|
-
|
|
166
|
-
|
|
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
|