streamlit 1.47.1__py3-none-any.whl → 1.48.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit/cli_util.py +1 -1
- streamlit/commands/echo.py +2 -2
- streamlit/commands/execution_control.py +1 -1
- streamlit/commands/page_config.py +16 -16
- streamlit/components/v1/component_arrow.py +4 -4
- streamlit/config.py +23 -5
- streamlit/connections/base_connection.py +2 -2
- streamlit/connections/snowflake_connection.py +2 -2
- streamlit/connections/sql_connection.py +4 -3
- streamlit/dataframe_util.py +1 -1
- streamlit/deprecation_util.py +20 -5
- streamlit/elements/arrow.py +105 -79
- streamlit/elements/deck_gl_json_chart.py +6 -6
- streamlit/elements/dialog_decorator.py +72 -17
- streamlit/elements/form.py +36 -7
- streamlit/elements/graphviz_chart.py +7 -0
- streamlit/elements/iframe.py +13 -22
- streamlit/elements/json.py +3 -3
- streamlit/elements/layouts.py +241 -75
- streamlit/elements/lib/built_in_chart_utils.py +11 -3
- streamlit/elements/lib/dialog.py +43 -0
- streamlit/elements/lib/file_uploader_utils.py +21 -2
- streamlit/elements/lib/layout_utils.py +88 -2
- streamlit/elements/lib/options_selector_utils.py +1 -1
- streamlit/elements/lib/utils.py +23 -3
- streamlit/elements/map.py +12 -11
- streamlit/elements/plotly_chart.py +21 -21
- streamlit/elements/pyplot.py +8 -4
- streamlit/elements/vega_charts.py +237 -143
- streamlit/elements/widgets/audio_input.py +2 -2
- streamlit/elements/widgets/button.py +147 -35
- streamlit/elements/widgets/button_group.py +9 -8
- streamlit/elements/widgets/camera_input.py +2 -2
- streamlit/elements/widgets/chat.py +8 -2
- streamlit/elements/widgets/checkbox.py +4 -4
- streamlit/elements/widgets/color_picker.py +2 -2
- streamlit/elements/widgets/data_editor.py +22 -15
- streamlit/elements/widgets/file_uploader.py +8 -2
- streamlit/elements/widgets/multiselect.py +27 -17
- streamlit/elements/widgets/number_input.py +2 -2
- streamlit/elements/widgets/radio.py +5 -4
- streamlit/elements/widgets/select_slider.py +8 -5
- streamlit/elements/widgets/selectbox.py +14 -4
- streamlit/elements/widgets/slider.py +59 -33
- streamlit/elements/widgets/text_widgets.py +4 -4
- streamlit/elements/widgets/time_widgets.py +4 -4
- streamlit/elements/write.py +9 -8
- streamlit/env_util.py +1 -1
- streamlit/errors.py +20 -4
- streamlit/file_util.py +1 -1
- streamlit/git_util.py +1 -1
- streamlit/logger.py +2 -2
- streamlit/proto/Block_pb2.py +39 -31
- streamlit/proto/Block_pb2.pyi +54 -3
- streamlit/proto/Button_pb2.py +4 -2
- streamlit/proto/Button_pb2.pyi +1 -0
- streamlit/proto/IFrame_pb2.py +8 -2
- streamlit/proto/IFrame_pb2.pyi +3 -0
- streamlit/runtime/caching/cache_data_api.py +15 -2
- streamlit/runtime/caching/cache_errors.py +1 -1
- streamlit/runtime/caching/cache_resource_api.py +24 -2
- streamlit/runtime/caching/cache_utils.py +6 -3
- streamlit/runtime/caching/hashing.py +6 -9
- streamlit/runtime/fragment.py +3 -2
- streamlit/runtime/runtime.py +19 -2
- streamlit/runtime/scriptrunner/script_runner.py +3 -3
- streamlit/runtime/state/common.py +2 -1
- streamlit/runtime/state/query_params.py +2 -1
- streamlit/runtime/state/session_state.py +1 -1
- streamlit/static/index.html +1 -1
- streamlit/static/manifest.json +231 -231
- streamlit/static/static/css/index.CQt5TjGB.css +1 -0
- streamlit/static/static/js/{ErrorOutline.esm.BEZPMjuG.js → ErrorOutline.esm.DjObtx4K.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.Dy1V9a2E.js → FileDownload.esm.Bz9nxNC5.js} +1 -1
- streamlit/static/static/js/{FileHelper.D0K06YBq.js → FileHelper.BrQvUXVD.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.Cdw5Y7_m.js → FormClearHelper.DF4gFAOO.js} +1 -1
- streamlit/static/static/js/{Hooks.C_qx1sSw.js → Hooks.DEoLCfOE.js} +1 -1
- streamlit/static/static/js/{InputInstructions.D3IDU-eY.js → InputInstructions.D8zoMog9.js} +1 -1
- streamlit/static/static/js/Particles.CCFySwdL.js +1 -0
- streamlit/static/static/js/ProgressBar.COK9j1l0.js +2 -0
- streamlit/static/static/js/Toolbar.Dt4jIKlY.js +1 -0
- streamlit/static/static/js/{base-input.DMlw5p7n.js → base-input.BmvSaPd2.js} +4 -4
- streamlit/static/static/js/{checkbox.C7QR6llE.js → checkbox.Cgxgc0et.js} +2 -2
- streamlit/static/static/js/createDownloadLinkElement.ZaXNnPK4.js +1 -0
- streamlit/static/static/js/{createSuper.C5k_2vfB.js → createSuper.siQeagI2.js} +1 -1
- streamlit/static/static/js/data-grid-overlay-editor.Ct51iCb_.js +1 -0
- streamlit/static/static/js/{downloader.Nj6v3ioB.js → downloader.M6jQeNDf.js} +1 -1
- streamlit/static/static/js/{es6.CVz13CSz.js → es6.CMaUdEZ5.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.gZ8zjT0K.js → iframeResizer.contentWindow.C33BryyP.js} +1 -1
- streamlit/static/static/js/index.8GJD0eeD.js +1 -0
- streamlit/static/static/js/index.8QEYHMQD.js +1 -0
- streamlit/static/static/js/index.Ay41Wnu9.js +1 -0
- streamlit/static/static/js/index.BLiKiJ7_.js +1 -0
- streamlit/static/static/js/index.BT78cJmU.js +1 -0
- streamlit/static/static/js/index.BXDq9dj4.js +1 -0
- streamlit/static/static/js/index.BdGvnhlM.js +1 -0
- streamlit/static/static/js/index.BfasrT0d.js +1 -0
- streamlit/static/static/js/index.CCdtFMFG.js +1 -0
- streamlit/static/static/js/index.CFRGZDz1.js +1 -0
- streamlit/static/static/js/{index.MbqsiUV4.js → index.CFSFYiPA.js} +289 -289
- streamlit/static/static/js/{index.B2L574n6.js → index.CbdWnLqS.js} +3 -3
- streamlit/static/static/js/index.CeiIiXap.js +1 -0
- streamlit/static/static/js/index.CgZDfhN4.js +2 -0
- streamlit/static/static/js/{index.xfcNJBLM.js → index.Cqa4gqqN.js} +1 -1
- streamlit/static/static/js/index.CzX2xpyc.js +1 -0
- streamlit/static/static/js/index.D1EayrNh.js +73 -0
- streamlit/static/static/js/index.D1ErX5go.js +2 -0
- streamlit/static/static/js/{index.CTT2YqEU.js → index.D1jHqUJq.js} +1 -1
- streamlit/static/static/js/index.D5gweoL5.js +7 -0
- streamlit/static/static/js/index.DByVKZgq.js +1 -0
- streamlit/static/static/js/index.DEND45D1.js +3 -0
- streamlit/static/static/js/{index.CbQtRkVt.js → index.DKN5MVff.js} +162 -188
- streamlit/static/static/js/index.DfoxW1gP.js +3855 -0
- streamlit/static/static/js/index.Dtf1Ac0x.js +1 -0
- streamlit/static/static/js/index.DxrLhpeO.js +1 -0
- streamlit/static/static/js/{index.BSFzxMXi.js → index.J7o-_HIh.js} +1 -1
- streamlit/static/static/js/index.LU8juINp.js +197 -0
- streamlit/static/static/js/index.L_N2iylt.js +1 -0
- streamlit/static/static/js/{index.BscWuWHL.js → index.PZUX2kRz.js} +3 -3
- streamlit/static/static/js/index.ROjU6K0k.js +1 -0
- streamlit/static/static/js/index.WSNLkF94.js +1 -0
- streamlit/static/static/js/index.X5W3gJLn.js +1 -0
- streamlit/static/static/js/index.k9LYqfSL.js +1 -0
- streamlit/static/static/js/{index.D3wOJJsg.js → index.pnHtHv_c.js} +12 -12
- streamlit/static/static/js/index.tPUXqsfW.js +1 -0
- streamlit/static/static/js/index.tsvTLdio.js +12 -0
- streamlit/static/static/js/{input.D_45B0P-.js → input.DZd6EQlV.js} +2 -2
- streamlit/static/static/js/{memory.BmhrRyO2.js → memory.ptkfuI71.js} +1 -1
- streamlit/static/static/js/{mergeWith.DvOME7eH.js → mergeWith.GRNk8iwv.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.BRNxOzEZ.js → number-overlay-editor.DXS2qb1U.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.C5GiK_Ob.js → possibleConstructorReturn.Bd4ImlQ9.js} +1 -1
- streamlit/static/static/js/{sandbox.B-Q9S7vW.js → sandbox.DsH8LuID.js} +1 -1
- streamlit/static/static/js/{timepicker.DOMbfm_a.js → timepicker.QVekV78C.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.CbNz0Ciu.js → toConsumableArray.BJvaP8gb.js} +1 -1
- streamlit/static/static/js/{uniqueId.-ygIU7IL.js → uniqueId.D_5M8Dgf.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.Bx3VaRHk.js → useBasicWidgetState.DB3vMS9V.js} +1 -1
- streamlit/static/static/js/useTextInputAutoExpand.CBkGkaRt.js +2 -0
- streamlit/static/static/js/useUpdateUiValue.C7ZKpLQK.js +1 -0
- streamlit/static/static/js/withFullScreenWrapper.C-gXt0Rl.js +1 -0
- streamlit/temporary_directory.py +5 -3
- streamlit/testing/v1/app_test.py +4 -1
- streamlit/testing/v1/element_tree.py +4 -5
- streamlit/type_util.py +10 -3
- streamlit/user_info.py +6 -1
- streamlit/util.py +11 -1
- streamlit/watcher/local_sources_watcher.py +4 -3
- streamlit/web/bootstrap.py +18 -12
- streamlit/web/cli.py +2 -1
- streamlit/web/server/server.py +72 -21
- {streamlit-1.47.1.dist-info → streamlit-1.48.1.dist-info}/METADATA +14 -2
- {streamlit-1.47.1.dist-info → streamlit-1.48.1.dist-info}/RECORD +155 -155
- streamlit/static/static/css/index.CsLB_Bnz.css +0 -1
- streamlit/static/static/js/ProgressBar.EhJ_lCOf.js +0 -2
- streamlit/static/static/js/RenderInPortalIfExists.D6a0mMll.js +0 -1
- streamlit/static/static/js/Toolbar.D6yqQ65-.js +0 -1
- streamlit/static/static/js/createDownloadLinkElement.DZMwyjvU.js +0 -1
- streamlit/static/static/js/data-grid-overlay-editor.CoquyZNK.js +0 -1
- streamlit/static/static/js/index.B3n-pURl.js +0 -2
- streamlit/static/static/js/index.B9jJp9aE.js +0 -1
- streamlit/static/static/js/index.BBVtld-D.js +0 -1
- streamlit/static/static/js/index.BKHPnvYd.js +0 -1
- streamlit/static/static/js/index.B_1CXynz.js +0 -1
- streamlit/static/static/js/index.Bm1LklYO.js +0 -1
- streamlit/static/static/js/index.Byi6__iF.js +0 -1
- streamlit/static/static/js/index.BzJeMpQ-.js +0 -197
- streamlit/static/static/js/index.C4tw7-Cl.js +0 -2
- streamlit/static/static/js/index.CG0C49ex.js +0 -1
- streamlit/static/static/js/index.CMuSJPv-.js +0 -1
- streamlit/static/static/js/index.COPFcr_K.js +0 -3855
- streamlit/static/static/js/index.CU3TLDlu.js +0 -1
- streamlit/static/static/js/index.CVKKDwaf.js +0 -1
- streamlit/static/static/js/index.CWZeK3mV.js +0 -12
- streamlit/static/static/js/index.CWbiNJQl.js +0 -1
- streamlit/static/static/js/index.CWxefYP6.js +0 -73
- streamlit/static/static/js/index.DOdWa88b.js +0 -1
- streamlit/static/static/js/index.DXtnaPua.js +0 -1
- streamlit/static/static/js/index.DatDwFl3.js +0 -3
- streamlit/static/static/js/index.DeWYPvQR.js +0 -1
- streamlit/static/static/js/index.Do4vzIvK.js +0 -1
- streamlit/static/static/js/index.Dvrstlh8.js +0 -1
- streamlit/static/static/js/index.DvznfdF_.js +0 -1
- streamlit/static/static/js/index.U1vvXeGn.js +0 -1
- streamlit/static/static/js/index._WAuWRkp.js +0 -7
- streamlit/static/static/js/index.bwA9_eWC.js +0 -1
- streamlit/static/static/js/index.m1njXuKl.js +0 -1
- streamlit/static/static/js/index.mihWZKb1.js +0 -1
- streamlit/static/static/js/useOnInputChange.CDZx-L6q.js +0 -1
- streamlit/static/static/js/useTextInputAutoExpand.BuE9l5TG.js +0 -2
- streamlit/static/static/js/withFullScreenWrapper.DWXejOhQ.js +0 -1
- {streamlit-1.47.1.data → streamlit-1.48.1.data}/scripts/streamlit.cmd +0 -0
- {streamlit-1.47.1.dist-info → streamlit-1.48.1.dist-info}/WHEEL +0 -0
- {streamlit-1.47.1.dist-info → streamlit-1.48.1.dist-info}/entry_points.txt +0 -0
- {streamlit-1.47.1.dist-info → streamlit-1.48.1.dist-info}/top_level.txt +0 -0
streamlit/elements/layouts.py
CHANGED
|
@@ -21,20 +21,30 @@ from typing_extensions import TypeAlias
|
|
|
21
21
|
|
|
22
22
|
from streamlit.delta_generator_singletons import get_dg_singleton_instance
|
|
23
23
|
from streamlit.elements.lib.layout_utils import (
|
|
24
|
+
Gap,
|
|
25
|
+
Height,
|
|
26
|
+
HorizontalAlignment,
|
|
27
|
+
VerticalAlignment,
|
|
28
|
+
Width,
|
|
24
29
|
WidthWithoutContent,
|
|
30
|
+
get_align,
|
|
31
|
+
get_gap_size,
|
|
32
|
+
get_height_config,
|
|
33
|
+
get_justify,
|
|
25
34
|
get_width_config,
|
|
35
|
+
validate_height,
|
|
36
|
+
validate_horizontal_alignment,
|
|
37
|
+
validate_vertical_alignment,
|
|
26
38
|
validate_width,
|
|
27
39
|
)
|
|
28
40
|
from streamlit.elements.lib.utils import Key, compute_and_register_element_id, to_key
|
|
29
41
|
from streamlit.errors import (
|
|
30
42
|
StreamlitAPIException,
|
|
31
|
-
StreamlitInvalidColumnGapError,
|
|
32
43
|
StreamlitInvalidColumnSpecError,
|
|
33
44
|
StreamlitInvalidVerticalAlignmentError,
|
|
34
45
|
)
|
|
35
46
|
from streamlit.proto.Block_pb2 import Block as BlockProto
|
|
36
|
-
from streamlit.proto.GapSize_pb2 import GapConfig
|
|
37
|
-
from streamlit.proto.HeightConfig_pb2 import HeightConfig
|
|
47
|
+
from streamlit.proto.GapSize_pb2 import GapConfig
|
|
38
48
|
from streamlit.runtime.metrics_util import gather_metrics
|
|
39
49
|
from streamlit.string_util import validate_icon_or_emoji
|
|
40
50
|
|
|
@@ -42,6 +52,7 @@ if TYPE_CHECKING:
|
|
|
42
52
|
from streamlit.delta_generator import DeltaGenerator
|
|
43
53
|
from streamlit.elements.lib.dialog import Dialog
|
|
44
54
|
from streamlit.elements.lib.mutable_status_container import StatusContainer
|
|
55
|
+
from streamlit.runtime.state import WidgetCallback
|
|
45
56
|
|
|
46
57
|
SpecType: TypeAlias = Union[int, Sequence[Union[int, float]]]
|
|
47
58
|
|
|
@@ -51,9 +62,14 @@ class LayoutsMixin:
|
|
|
51
62
|
def container(
|
|
52
63
|
self,
|
|
53
64
|
*,
|
|
54
|
-
height: int | None = None,
|
|
55
65
|
border: bool | None = None,
|
|
56
66
|
key: Key | None = None,
|
|
67
|
+
width: WidthWithoutContent = "stretch",
|
|
68
|
+
height: Height = "content",
|
|
69
|
+
horizontal: bool = False,
|
|
70
|
+
horizontal_alignment: HorizontalAlignment = "left",
|
|
71
|
+
vertical_alignment: VerticalAlignment = "top",
|
|
72
|
+
gap: Gap | None = "small",
|
|
57
73
|
) -> DeltaGenerator:
|
|
58
74
|
"""Insert a multi-element container.
|
|
59
75
|
|
|
@@ -61,25 +77,12 @@ class LayoutsMixin:
|
|
|
61
77
|
multiple elements. This allows you to, for example, insert multiple
|
|
62
78
|
elements into your app out of order.
|
|
63
79
|
|
|
64
|
-
To add elements to the returned container, you can use the ``with``
|
|
65
|
-
(preferred) or just call
|
|
66
|
-
examples below.
|
|
80
|
+
To add elements to the returned container, you can use the ``with``
|
|
81
|
+
notation (preferred) or just call commands directly on the returned
|
|
82
|
+
object. See examples below.
|
|
67
83
|
|
|
68
84
|
Parameters
|
|
69
85
|
----------
|
|
70
|
-
height : int or None
|
|
71
|
-
Desired height of the container expressed in pixels. If ``None`` (default)
|
|
72
|
-
the container grows to fit its content. If a fixed height, scrolling is
|
|
73
|
-
enabled for large content and a grey border is shown around the container
|
|
74
|
-
to visually separate its scroll surface from the rest of the app.
|
|
75
|
-
|
|
76
|
-
.. note::
|
|
77
|
-
Use scrolling containers sparingly. If you use scrolling
|
|
78
|
-
containers, avoid heights that exceed 500 pixels. Otherwise,
|
|
79
|
-
the scroll surface of the container might cover the majority of
|
|
80
|
-
the screen on mobile devices, which makes it hard to scroll the
|
|
81
|
-
rest of the app.
|
|
82
|
-
|
|
83
86
|
border : bool or None
|
|
84
87
|
Whether to show a border around the container. If ``None`` (default), a
|
|
85
88
|
border is shown if the container is set to a fixed height and not
|
|
@@ -91,10 +94,102 @@ class LayoutsMixin:
|
|
|
91
94
|
Additionally, if ``key`` is provided, it will be used as CSS
|
|
92
95
|
class name prefixed with ``st-key-``.
|
|
93
96
|
|
|
97
|
+
width : "stretch" or int
|
|
98
|
+
The width of the container. This can be one of the following:
|
|
99
|
+
|
|
100
|
+
- ``"stretch"`` (default): The width of the container matches the
|
|
101
|
+
width of the parent container.
|
|
102
|
+
- An integer specifying the width in pixels: The container has a
|
|
103
|
+
fixed width. If the specified width is greater than the width of
|
|
104
|
+
the parent container, the width of the container matches the width
|
|
105
|
+
of the parent container.
|
|
106
|
+
|
|
107
|
+
height : "content", "stretch", or int
|
|
108
|
+
The height of the container. This can be one of the following:
|
|
109
|
+
|
|
110
|
+
- ``"content"`` (default): The height of the container matches the
|
|
111
|
+
height of its content.
|
|
112
|
+
- ``"stretch"``: The height of the container matches the height of
|
|
113
|
+
its content or the height of the parent container, whichever is
|
|
114
|
+
larger. If the container is not in a parent container, the height
|
|
115
|
+
of the container matches the height of its content.
|
|
116
|
+
- An integer specifying the height in pixels: The container has a
|
|
117
|
+
fixed height. If the content is larger than the specified
|
|
118
|
+
height, scrolling is enabled.
|
|
119
|
+
|
|
120
|
+
.. note::
|
|
121
|
+
Use scrolling containers sparingly. If you use scrolling
|
|
122
|
+
containers, avoid heights that exceed 500 pixels. Otherwise,
|
|
123
|
+
the scroll surface of the container might cover the majority of
|
|
124
|
+
the screen on mobile devices, which makes it hard to scroll the
|
|
125
|
+
rest of the app.
|
|
126
|
+
|
|
127
|
+
horizontal : bool
|
|
128
|
+
Whether to use horizontal flexbox layout. If this is ``False``
|
|
129
|
+
(default), the container's elements are laid out vertically. If
|
|
130
|
+
this is ``True``, the container's elements are laid out
|
|
131
|
+
horizontally and will overflow to the next line if they don't fit
|
|
132
|
+
within the container's width.
|
|
133
|
+
|
|
134
|
+
horizontal_alignment : "left", "center", "right", or "distribute"
|
|
135
|
+
The horizontal alignment of the elements inside the container. This
|
|
136
|
+
can be one of the following:
|
|
137
|
+
|
|
138
|
+
- ``"left"`` (default): Elements are aligned to the left side of
|
|
139
|
+
the container.
|
|
140
|
+
- ``"center"``: Elements are horizontally centered inside the
|
|
141
|
+
container.
|
|
142
|
+
- ``"right"``: Elements are aligned to the right side of the
|
|
143
|
+
container.
|
|
144
|
+
- ``"distribute"``: Elements are distributed evenly in the
|
|
145
|
+
container. This increases the horizontal gap between elements to
|
|
146
|
+
fill the width of the container. A standalone element is aligned
|
|
147
|
+
to the left.
|
|
148
|
+
|
|
149
|
+
When ``horizontal`` is ``False``, ``"distribute"`` aligns the
|
|
150
|
+
elements the same as ``"left"``.
|
|
151
|
+
|
|
152
|
+
vertical_alignment : "top", "center", "bottom", or "distribute"
|
|
153
|
+
The vertical alignment of the elements inside the container. This
|
|
154
|
+
can be one of the following:
|
|
155
|
+
|
|
156
|
+
- ``"top"`` (default): Elements are aligned to the top of the
|
|
157
|
+
container.
|
|
158
|
+
- ``"center"``: Elements are vertically centered inside the
|
|
159
|
+
container.
|
|
160
|
+
- ``"bottom"``: Elements are aligned to the bottom of the
|
|
161
|
+
container.
|
|
162
|
+
- ``"distribute"``: Elements are distributed evenly in the
|
|
163
|
+
container. This increases the vertical gap between elements to
|
|
164
|
+
fill the height of the container. A standalone element is aligned
|
|
165
|
+
to the top.
|
|
166
|
+
|
|
167
|
+
When ``horizontal`` is ``True``, ``"distribute"`` aligns the
|
|
168
|
+
elements the same as ``"top"``.
|
|
169
|
+
|
|
170
|
+
gap : "small", "medium", "large", or None
|
|
171
|
+
The minimum gap size between the elements inside the container.
|
|
172
|
+
This can be one of the following:
|
|
173
|
+
|
|
174
|
+
- ``"small"`` (default): 1rem gap between the elements.
|
|
175
|
+
- ``"medium"``: 2rem gap between the elements.
|
|
176
|
+
- ``"large"``: 4rem gap between the elements.
|
|
177
|
+
- ``None``: No gap between the elements.
|
|
178
|
+
|
|
179
|
+
The rem unit is relative to the ``theme.baseFontSize``
|
|
180
|
+
configuration option.
|
|
181
|
+
|
|
182
|
+
The minimum gap applies to both the vertical and horizontal gaps
|
|
183
|
+
between the elements. Elements may have larger gaps in one
|
|
184
|
+
direction if you use a distributed horizontal alignment or fixed
|
|
185
|
+
height.
|
|
94
186
|
|
|
95
187
|
Examples
|
|
96
188
|
--------
|
|
97
|
-
Inserting elements using ``with`` notation
|
|
189
|
+
**Example 1: Inserting elements using ``with`` notation**
|
|
190
|
+
|
|
191
|
+
You can use the ``with`` statement to insert any element into a
|
|
192
|
+
container.
|
|
98
193
|
|
|
99
194
|
>>> import streamlit as st
|
|
100
195
|
>>>
|
|
@@ -110,7 +205,12 @@ class LayoutsMixin:
|
|
|
110
205
|
https://doc-container1.streamlit.app/
|
|
111
206
|
height: 520px
|
|
112
207
|
|
|
113
|
-
Inserting elements out of order
|
|
208
|
+
**Example 2: Inserting elements out of order**
|
|
209
|
+
|
|
210
|
+
When you create a container, its position in the app remains fixed and
|
|
211
|
+
you can add elements to it at any time. This allows you to insert
|
|
212
|
+
elements out of order in your app. You can also write to the container
|
|
213
|
+
by calling commands directly on the container object.
|
|
114
214
|
|
|
115
215
|
>>> import streamlit as st
|
|
116
216
|
>>>
|
|
@@ -118,14 +218,16 @@ class LayoutsMixin:
|
|
|
118
218
|
>>> container.write("This is inside the container")
|
|
119
219
|
>>> st.write("This is outside the container")
|
|
120
220
|
>>>
|
|
121
|
-
>>> # Now insert some more in the container
|
|
122
221
|
>>> container.write("This is inside too")
|
|
123
222
|
|
|
124
223
|
.. output ::
|
|
125
224
|
https://doc-container2.streamlit.app/
|
|
126
225
|
height: 300px
|
|
127
226
|
|
|
128
|
-
|
|
227
|
+
**Example 3: Grid layout with columns and containers**
|
|
228
|
+
|
|
229
|
+
You can create a grid with a fixed number of elements per row by using
|
|
230
|
+
columns and containers.
|
|
129
231
|
|
|
130
232
|
>>> import streamlit as st
|
|
131
233
|
>>>
|
|
@@ -140,7 +242,10 @@ class LayoutsMixin:
|
|
|
140
242
|
https://doc-container3.streamlit.app/
|
|
141
243
|
height: 350px
|
|
142
244
|
|
|
143
|
-
|
|
245
|
+
**Example 4: Vertically scrolling container**
|
|
246
|
+
|
|
247
|
+
You can create a vertically scrolling container by setting a fixed
|
|
248
|
+
height.
|
|
144
249
|
|
|
145
250
|
>>> import streamlit as st
|
|
146
251
|
>>>
|
|
@@ -153,28 +258,63 @@ class LayoutsMixin:
|
|
|
153
258
|
https://doc-container4.streamlit.app/
|
|
154
259
|
height: 400px
|
|
155
260
|
|
|
261
|
+
**Example 5: Horizontal container**
|
|
262
|
+
|
|
263
|
+
You can create a row of widgets using a horizontal container. Use
|
|
264
|
+
``horizontal_alignment`` to specify the alignment of the elements.
|
|
265
|
+
|
|
266
|
+
>>> import streamlit as st
|
|
267
|
+
>>>
|
|
268
|
+
>>> flex = st.container(horizontal=True, horizontal_alignment="right")
|
|
269
|
+
>>>
|
|
270
|
+
>>> for card in range(3):
|
|
271
|
+
>>> flex.button(f"Button {card + 1}")
|
|
272
|
+
|
|
273
|
+
.. output ::
|
|
274
|
+
https://doc-container5.streamlit.app/
|
|
275
|
+
height: 250px
|
|
276
|
+
|
|
156
277
|
"""
|
|
157
278
|
key = to_key(key)
|
|
158
279
|
block_proto = BlockProto()
|
|
159
280
|
block_proto.allow_empty = False
|
|
160
281
|
block_proto.flex_container.border = border or False
|
|
161
|
-
block_proto.flex_container.
|
|
282
|
+
block_proto.flex_container.gap_config.gap_size = get_gap_size(
|
|
283
|
+
gap, "st.container"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
validate_horizontal_alignment(horizontal_alignment)
|
|
287
|
+
validate_vertical_alignment(vertical_alignment)
|
|
288
|
+
if horizontal:
|
|
289
|
+
block_proto.flex_container.wrap = True
|
|
290
|
+
block_proto.flex_container.direction = (
|
|
291
|
+
BlockProto.FlexContainer.Direction.HORIZONTAL
|
|
292
|
+
)
|
|
293
|
+
block_proto.flex_container.justify = get_justify(horizontal_alignment)
|
|
294
|
+
block_proto.flex_container.align = get_align(vertical_alignment)
|
|
295
|
+
else:
|
|
296
|
+
block_proto.flex_container.wrap = False
|
|
297
|
+
block_proto.flex_container.direction = (
|
|
298
|
+
BlockProto.FlexContainer.Direction.VERTICAL
|
|
299
|
+
)
|
|
300
|
+
block_proto.flex_container.justify = get_justify(vertical_alignment)
|
|
301
|
+
block_proto.flex_container.align = get_align(horizontal_alignment)
|
|
302
|
+
|
|
303
|
+
validate_width(width)
|
|
304
|
+
block_proto.width_config.CopyFrom(get_width_config(width))
|
|
162
305
|
|
|
163
306
|
if isinstance(height, int) or border:
|
|
164
307
|
block_proto.allow_empty = True
|
|
165
308
|
|
|
166
|
-
if
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
block_proto.
|
|
309
|
+
if border is not None:
|
|
310
|
+
block_proto.flex_container.border = border
|
|
311
|
+
elif isinstance(height, int):
|
|
312
|
+
block_proto.flex_container.border = True
|
|
313
|
+
else:
|
|
314
|
+
block_proto.flex_container.border = False
|
|
172
315
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# border as default setting for scrolling
|
|
176
|
-
# containers.
|
|
177
|
-
block_proto.flex_container.border = True
|
|
316
|
+
validate_height(height, allow_content=True)
|
|
317
|
+
block_proto.height_config.CopyFrom(get_height_config(height))
|
|
178
318
|
|
|
179
319
|
if key:
|
|
180
320
|
# At the moment, the ID is only used for extracting the
|
|
@@ -193,9 +333,10 @@ class LayoutsMixin:
|
|
|
193
333
|
self,
|
|
194
334
|
spec: SpecType,
|
|
195
335
|
*,
|
|
196
|
-
gap:
|
|
336
|
+
gap: Gap | None = "small",
|
|
197
337
|
vertical_alignment: Literal["top", "center", "bottom"] = "top",
|
|
198
338
|
border: bool = False,
|
|
339
|
+
width: WidthWithoutContent = "stretch",
|
|
199
340
|
) -> list[DeltaGenerator]:
|
|
200
341
|
"""Insert containers laid out as side-by-side columns.
|
|
201
342
|
|
|
@@ -244,6 +385,14 @@ class LayoutsMixin:
|
|
|
244
385
|
``False`` (default), no border is shown. If this is ``True``, a
|
|
245
386
|
border is shown around each column.
|
|
246
387
|
|
|
388
|
+
width : int or "stretch"
|
|
389
|
+
The desired width of the columns expressed in pixels. If this is
|
|
390
|
+
``"stretch"`` (default), Streamlit sets the width of the columns to
|
|
391
|
+
match the width of the parent container. Otherwise, this must be an
|
|
392
|
+
integer. If the specified width is greater than the width of the
|
|
393
|
+
parent container, Streamlit sets the width of the columns to match
|
|
394
|
+
the width of the parent container.
|
|
395
|
+
|
|
247
396
|
Returns
|
|
248
397
|
-------
|
|
249
398
|
list of containers
|
|
@@ -281,16 +430,16 @@ class LayoutsMixin:
|
|
|
281
430
|
You can just call methods directly on the returned objects:
|
|
282
431
|
|
|
283
432
|
>>> import streamlit as st
|
|
284
|
-
>>>
|
|
433
|
+
>>> from numpy.random import default_rng as rng
|
|
285
434
|
>>>
|
|
435
|
+
>>> df = rng(0).standard_normal((10, 1))
|
|
286
436
|
>>> col1, col2 = st.columns([3, 1])
|
|
287
|
-
>>> data = np.random.randn(10, 1)
|
|
288
437
|
>>>
|
|
289
438
|
>>> col1.subheader("A wide column with a chart")
|
|
290
|
-
>>> col1.line_chart(
|
|
439
|
+
>>> col1.line_chart(df)
|
|
291
440
|
>>>
|
|
292
441
|
>>> col2.subheader("A narrow column with the data")
|
|
293
|
-
>>> col2.write(
|
|
442
|
+
>>> col2.write(df)
|
|
294
443
|
|
|
295
444
|
.. output ::
|
|
296
445
|
https://doc-columns2.streamlit.app/
|
|
@@ -317,7 +466,6 @@ class LayoutsMixin:
|
|
|
317
466
|
Adjust vertical alignment to customize your grid layouts.
|
|
318
467
|
|
|
319
468
|
>>> import streamlit as st
|
|
320
|
-
>>> import numpy as np
|
|
321
469
|
>>>
|
|
322
470
|
>>> vertical_alignment = st.selectbox(
|
|
323
471
|
>>> "Vertical alignment", ["top", "center", "bottom"], index=2
|
|
@@ -370,28 +518,11 @@ class LayoutsMixin:
|
|
|
370
518
|
|
|
371
519
|
if vertical_alignment not in vertical_alignment_mapping:
|
|
372
520
|
raise StreamlitInvalidVerticalAlignmentError(
|
|
373
|
-
vertical_alignment=vertical_alignment
|
|
521
|
+
vertical_alignment=vertical_alignment,
|
|
522
|
+
element_type="st.columns",
|
|
374
523
|
)
|
|
375
524
|
|
|
376
|
-
|
|
377
|
-
gap_mapping = {
|
|
378
|
-
"small": GapSize.SMALL,
|
|
379
|
-
"medium": GapSize.MEDIUM,
|
|
380
|
-
"large": GapSize.LARGE,
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if isinstance(gap, str):
|
|
384
|
-
gap_size = gap.lower()
|
|
385
|
-
valid_sizes = gap_mapping.keys()
|
|
386
|
-
|
|
387
|
-
if gap_size in valid_sizes:
|
|
388
|
-
return gap_mapping[gap_size]
|
|
389
|
-
elif gap is None:
|
|
390
|
-
return GapSize.NONE
|
|
391
|
-
|
|
392
|
-
raise StreamlitInvalidColumnGapError(gap=gap)
|
|
393
|
-
|
|
394
|
-
gap_size = column_gap(gap)
|
|
525
|
+
gap_size = get_gap_size(gap, "st.columns")
|
|
395
526
|
gap_config = GapConfig()
|
|
396
527
|
gap_config.gap_size = gap_size
|
|
397
528
|
|
|
@@ -413,6 +544,11 @@ class LayoutsMixin:
|
|
|
413
544
|
block_proto.flex_container.wrap = True
|
|
414
545
|
block_proto.flex_container.gap_config.CopyFrom(gap_config)
|
|
415
546
|
block_proto.flex_container.scale = 1
|
|
547
|
+
block_proto.flex_container.align = BlockProto.FlexContainer.Align.STRETCH
|
|
548
|
+
|
|
549
|
+
validate_width(width=width)
|
|
550
|
+
block_proto.width_config.CopyFrom(get_width_config(width=width))
|
|
551
|
+
|
|
416
552
|
row = self.dg._block(block_proto)
|
|
417
553
|
total_weight = sum(weights)
|
|
418
554
|
return [row._block(column_proto(w / total_weight)) for w in weights]
|
|
@@ -502,17 +638,17 @@ class LayoutsMixin:
|
|
|
502
638
|
Or you can just call methods directly on the returned objects:
|
|
503
639
|
|
|
504
640
|
>>> import streamlit as st
|
|
505
|
-
>>>
|
|
641
|
+
>>> from numpy.random import default_rng as rng
|
|
642
|
+
>>>
|
|
643
|
+
>>> df = rng(0).standard_normal((10, 1))
|
|
506
644
|
>>>
|
|
507
645
|
>>> tab1, tab2 = st.tabs(["📈 Chart", "🗃 Data"])
|
|
508
|
-
>>> data = np.random.randn(10, 1)
|
|
509
646
|
>>>
|
|
510
647
|
>>> tab1.subheader("A tab with a chart")
|
|
511
|
-
>>> tab1.line_chart(
|
|
648
|
+
>>> tab1.line_chart(df)
|
|
512
649
|
>>>
|
|
513
650
|
>>> tab2.subheader("A tab with the data")
|
|
514
|
-
>>> tab2.write(
|
|
515
|
-
|
|
651
|
+
>>> tab2.write(df)
|
|
516
652
|
|
|
517
653
|
.. output ::
|
|
518
654
|
https://doc-tabs2.streamlit.app/
|
|
@@ -682,7 +818,8 @@ class LayoutsMixin:
|
|
|
682
818
|
help: str | None = None,
|
|
683
819
|
icon: str | None = None,
|
|
684
820
|
disabled: bool = False,
|
|
685
|
-
use_container_width: bool =
|
|
821
|
+
use_container_width: bool | None = None,
|
|
822
|
+
width: Width = "content",
|
|
686
823
|
) -> DeltaGenerator:
|
|
687
824
|
r"""Insert a popover container.
|
|
688
825
|
|
|
@@ -752,16 +889,39 @@ class LayoutsMixin:
|
|
|
752
889
|
use_container_width : bool
|
|
753
890
|
Whether to expand the button's width to fill its parent container.
|
|
754
891
|
If ``use_container_width`` is ``False`` (default), Streamlit sizes
|
|
755
|
-
the button to fit its
|
|
892
|
+
the button to fit its content. If ``use_container_width`` is
|
|
756
893
|
``True``, the width of the button matches its parent container.
|
|
757
894
|
|
|
758
|
-
In both cases, if the
|
|
759
|
-
parent container, the
|
|
895
|
+
In both cases, if the content of the button is wider than the
|
|
896
|
+
parent container, the content will line wrap.
|
|
760
897
|
|
|
761
|
-
The popover
|
|
898
|
+
The popover container's minimum width matches the width of its
|
|
899
|
+
button. The popover container may be wider than its button to fit
|
|
900
|
+
the container's content.
|
|
901
|
+
|
|
902
|
+
width : int, "stretch", or "content"
|
|
903
|
+
The width of the button. This can be one of the following:
|
|
904
|
+
|
|
905
|
+
- ``"content"`` (default): The width of the button matches the
|
|
906
|
+
width of its content, but doesn't exceed the width of the parent
|
|
907
|
+
container.
|
|
908
|
+
- ``"stretch"``: The width of the button matches the width of the
|
|
909
|
+
parent container.
|
|
910
|
+
- An integer specifying the width in pixels: The button has a
|
|
911
|
+
fixed width. If the specified width is greater than the width of
|
|
912
|
+
the parent container, the width of the button matches the width
|
|
913
|
+
of the parent container.
|
|
914
|
+
|
|
915
|
+
The popover container's minimum width matches the width of its
|
|
762
916
|
button. The popover container may be wider than its button to fit
|
|
763
917
|
the container's contents.
|
|
764
918
|
|
|
919
|
+
.. deprecated::
|
|
920
|
+
``use_container_width`` is deprecated and will be removed in a
|
|
921
|
+
future release. For ``use_container_width=True``, use
|
|
922
|
+
``width="stretch"``. For ``use_container_width=False``, use
|
|
923
|
+
``width="content"``.
|
|
924
|
+
|
|
765
925
|
Examples
|
|
766
926
|
--------
|
|
767
927
|
You can use the ``with`` notation to insert any element into a popover:
|
|
@@ -799,9 +959,11 @@ class LayoutsMixin:
|
|
|
799
959
|
if label is None:
|
|
800
960
|
raise StreamlitAPIException("A label is required for a popover")
|
|
801
961
|
|
|
962
|
+
if use_container_width is not None:
|
|
963
|
+
width = "stretch" if use_container_width else "content"
|
|
964
|
+
|
|
802
965
|
popover_proto = BlockProto.Popover()
|
|
803
966
|
popover_proto.label = label
|
|
804
|
-
popover_proto.use_container_width = use_container_width
|
|
805
967
|
popover_proto.disabled = disabled
|
|
806
968
|
if help:
|
|
807
969
|
popover_proto.help = str(help)
|
|
@@ -812,6 +974,9 @@ class LayoutsMixin:
|
|
|
812
974
|
block_proto.allow_empty = True
|
|
813
975
|
block_proto.popover.CopyFrom(popover_proto)
|
|
814
976
|
|
|
977
|
+
validate_width(width, allow_content=True)
|
|
978
|
+
block_proto.width_config.CopyFrom(get_width_config(width))
|
|
979
|
+
|
|
815
980
|
return self.dg._block(block_proto=block_proto)
|
|
816
981
|
|
|
817
982
|
@gather_metrics("status")
|
|
@@ -948,6 +1113,7 @@ class LayoutsMixin:
|
|
|
948
1113
|
*,
|
|
949
1114
|
dismissible: bool = True,
|
|
950
1115
|
width: Literal["small", "large"] = "small",
|
|
1116
|
+
on_dismiss: Literal["ignore", "rerun"] | WidgetCallback = "ignore",
|
|
951
1117
|
) -> Dialog:
|
|
952
1118
|
"""Inserts the dialog container.
|
|
953
1119
|
|
|
@@ -955,7 +1121,7 @@ class LayoutsMixin:
|
|
|
955
1121
|
The dialog_decorator also has a more descriptive docstring since it is user-facing.
|
|
956
1122
|
"""
|
|
957
1123
|
return get_dg_singleton_instance().dialog_container_cls._create(
|
|
958
|
-
self.dg, title, dismissible=dismissible, width=width
|
|
1124
|
+
self.dg, title, dismissible=dismissible, width=width, on_dismiss=on_dismiss
|
|
959
1125
|
)
|
|
960
1126
|
|
|
961
1127
|
@property
|
|
@@ -356,7 +356,13 @@ def prep_chart_data_for_add_rows(
|
|
|
356
356
|
df.index = pd.RangeIndex(start=start, stop=stop, step=old_step)
|
|
357
357
|
add_rows_metadata.last_index = stop - 1
|
|
358
358
|
|
|
359
|
-
out_data, *_ = _prep_data(
|
|
359
|
+
out_data, *_ = _prep_data(
|
|
360
|
+
df,
|
|
361
|
+
x_column=add_rows_metadata.columns["x_column"],
|
|
362
|
+
y_column_list=add_rows_metadata.columns["y_column_list"],
|
|
363
|
+
color_column=add_rows_metadata.columns["color_column"],
|
|
364
|
+
size_column=add_rows_metadata.columns["size_column"],
|
|
365
|
+
)
|
|
360
366
|
|
|
361
367
|
return out_data, add_rows_metadata
|
|
362
368
|
|
|
@@ -597,7 +603,7 @@ def _maybe_reset_index_in_place(
|
|
|
597
603
|
x_column = _SEPARATED_INDEX_COLUMN_NAME
|
|
598
604
|
else:
|
|
599
605
|
# Reuse index's name for the new column.
|
|
600
|
-
x_column = df.index.name
|
|
606
|
+
x_column = str(df.index.name)
|
|
601
607
|
|
|
602
608
|
df.index.name = x_column
|
|
603
609
|
df.reset_index(inplace=True) # noqa: PD002
|
|
@@ -988,7 +994,9 @@ def _get_color_encoding(
|
|
|
988
994
|
return alt.ColorValue(to_css_color(cast("Any", color_value[0])))
|
|
989
995
|
return alt.Color(
|
|
990
996
|
field=color_column if color_column is not None else alt.Undefined,
|
|
991
|
-
scale=alt.Scale(
|
|
997
|
+
scale=alt.Scale(
|
|
998
|
+
domain=y_column_list, range=[to_css_color(c) for c in color_values]
|
|
999
|
+
),
|
|
992
1000
|
legend=_COLOR_LEGEND_SETTINGS,
|
|
993
1001
|
type="nominal",
|
|
994
1002
|
title=" ",
|
streamlit/elements/lib/dialog.py
CHANGED
|
@@ -19,6 +19,7 @@ from typing import TYPE_CHECKING, Literal, cast
|
|
|
19
19
|
from typing_extensions import Self, TypeAlias
|
|
20
20
|
|
|
21
21
|
from streamlit.delta_generator import DeltaGenerator
|
|
22
|
+
from streamlit.elements.lib.utils import compute_and_register_element_id
|
|
22
23
|
from streamlit.errors import StreamlitAPIException
|
|
23
24
|
from streamlit.proto.Block_pb2 import Block as BlockProto
|
|
24
25
|
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
|
@@ -26,11 +27,13 @@ from streamlit.runtime.scriptrunner_utils.script_run_context import (
|
|
|
26
27
|
enqueue_message,
|
|
27
28
|
get_script_run_ctx,
|
|
28
29
|
)
|
|
30
|
+
from streamlit.runtime.state import register_widget
|
|
29
31
|
|
|
30
32
|
if TYPE_CHECKING:
|
|
31
33
|
from types import TracebackType
|
|
32
34
|
|
|
33
35
|
from streamlit.cursor import Cursor
|
|
36
|
+
from streamlit.runtime.state import WidgetCallback
|
|
34
37
|
|
|
35
38
|
DialogWidth: TypeAlias = Literal["small", "large"]
|
|
36
39
|
|
|
@@ -81,12 +84,51 @@ class Dialog(DeltaGenerator):
|
|
|
81
84
|
*,
|
|
82
85
|
dismissible: bool = True,
|
|
83
86
|
width: DialogWidth = "small",
|
|
87
|
+
on_dismiss: Literal["ignore", "rerun"] | WidgetCallback = "ignore",
|
|
84
88
|
) -> Dialog:
|
|
89
|
+
# Validation for on_dismiss parameter
|
|
90
|
+
if on_dismiss not in ["ignore", "rerun"] and not callable(on_dismiss):
|
|
91
|
+
raise StreamlitAPIException(
|
|
92
|
+
f"You have passed {on_dismiss} to `on_dismiss`. But only 'ignore', "
|
|
93
|
+
"'rerun', or a callable is supported."
|
|
94
|
+
)
|
|
95
|
+
|
|
85
96
|
block_proto = BlockProto()
|
|
86
97
|
block_proto.dialog.title = title
|
|
87
98
|
block_proto.dialog.dismissible = dismissible
|
|
88
99
|
block_proto.dialog.width = _process_dialog_width_input(width)
|
|
89
100
|
|
|
101
|
+
# Handle on_dismiss functionality
|
|
102
|
+
is_dismiss_activated = on_dismiss != "ignore"
|
|
103
|
+
element_id = None
|
|
104
|
+
|
|
105
|
+
if is_dismiss_activated:
|
|
106
|
+
# Register as widget when on_dismiss is activated
|
|
107
|
+
|
|
108
|
+
ctx = get_script_run_ctx()
|
|
109
|
+
|
|
110
|
+
element_id = compute_and_register_element_id(
|
|
111
|
+
"dialog",
|
|
112
|
+
user_key=None,
|
|
113
|
+
# Dialogs within forms still trigger a normal rerun:
|
|
114
|
+
form_id="",
|
|
115
|
+
dg=parent,
|
|
116
|
+
title=title,
|
|
117
|
+
dismissible=dismissible,
|
|
118
|
+
width=width,
|
|
119
|
+
on_dismiss=str(on_dismiss) if not callable(on_dismiss) else "callback",
|
|
120
|
+
)
|
|
121
|
+
block_proto.dialog.id = element_id
|
|
122
|
+
|
|
123
|
+
register_widget(
|
|
124
|
+
element_id,
|
|
125
|
+
on_change_handler=on_dismiss if callable(on_dismiss) else None,
|
|
126
|
+
deserializer=lambda x: x, # Simple passthrough for trigger values
|
|
127
|
+
serializer=lambda x: x, # Simple passthrough for trigger values
|
|
128
|
+
ctx=ctx,
|
|
129
|
+
value_type="trigger_value",
|
|
130
|
+
)
|
|
131
|
+
|
|
90
132
|
# We store the delta path here, because in _update we enqueue a new proto
|
|
91
133
|
# message to update the open status. Without this, the dialog content is gone
|
|
92
134
|
# when the _update message is sent
|
|
@@ -97,6 +139,7 @@ class Dialog(DeltaGenerator):
|
|
|
97
139
|
|
|
98
140
|
dialog._delta_path = delta_path
|
|
99
141
|
dialog._current_proto = block_proto
|
|
142
|
+
|
|
100
143
|
return dialog
|
|
101
144
|
|
|
102
145
|
def __init__(
|
|
@@ -31,6 +31,18 @@ TYPE_PAIRS = [
|
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def _get_main_filename_and_extension(filename: str) -> tuple[str, str]:
|
|
35
|
+
"""Returns the main part of a filename and its extension."""
|
|
36
|
+
# Handle NTFS Alternate Data Streams (ADS) on Windows, e.g: "file.txt:ads" -> ("file.txt", ".txt")
|
|
37
|
+
if os.name == "nt" and ":" in filename:
|
|
38
|
+
main_filename, ads_part = filename.split(":", 1)
|
|
39
|
+
# We only treat it as an ADS if the part after the colon has an extension.
|
|
40
|
+
if os.path.splitext(ads_part)[1]:
|
|
41
|
+
return main_filename, os.path.splitext(main_filename)[1]
|
|
42
|
+
|
|
43
|
+
return filename, os.path.splitext(filename)[1]
|
|
44
|
+
|
|
45
|
+
|
|
34
46
|
def normalize_upload_file_type(file_type: str | Sequence[str]) -> Sequence[str]:
|
|
35
47
|
if isinstance(file_type, str):
|
|
36
48
|
file_type = [file_type]
|
|
@@ -59,8 +71,15 @@ def enforce_filename_restriction(filename: str, allowed_types: Sequence[str]) ->
|
|
|
59
71
|
enforce file type check by extension on the frontend, but we check it on backend
|
|
60
72
|
before returning file to the user to protect ourselves.
|
|
61
73
|
"""
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
|
|
75
|
+
# Ensure that there isn't a null byte in a filename
|
|
76
|
+
# since this could be a workaround to bypass the file type check.
|
|
77
|
+
if "\0" in filename:
|
|
78
|
+
raise StreamlitAPIException("Filename cannot contain null bytes.")
|
|
79
|
+
|
|
80
|
+
main_filename, extension = _get_main_filename_and_extension(filename)
|
|
81
|
+
normalized_filename = main_filename.lower()
|
|
82
|
+
|
|
64
83
|
normalized_allowed_types = [allowed_type.lower() for allowed_type in allowed_types]
|
|
65
84
|
|
|
66
85
|
if not any(
|