streamlit-nightly 1.33.1.dev20240409__py2.py3-none-any.whl → 1.33.1.dev20240414__py2.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/__init__.py +4 -1
- streamlit/delta_generator.py +54 -36
- streamlit/elements/dialog_decorator.py +166 -0
- streamlit/elements/image.py +5 -3
- streamlit/elements/layouts.py +19 -0
- streamlit/elements/lib/dialog.py +148 -0
- streamlit/proto/Block_pb2.py +26 -22
- streamlit/proto/Block_pb2.pyi +43 -3
- streamlit/proto/Common_pb2.py +1 -1
- streamlit/runtime/scriptrunner/script_run_context.py +3 -0
- streamlit/runtime/scriptrunner/script_runner.py +16 -0
- streamlit/runtime/state/query_params.py +28 -11
- streamlit/runtime/state/query_params_proxy.py +51 -3
- streamlit/runtime/state/session_state.py +3 -0
- streamlit/static/asset-manifest.json +5 -5
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/{1168.3029456a.chunk.js → 1168.1d6408e6.chunk.js} +1 -1
- streamlit/static/static/js/{4666.b694c5a9.chunk.js → 4666.492dcf72.chunk.js} +1 -1
- streamlit/static/static/js/8427.d30dffe1.chunk.js +1 -0
- streamlit/static/static/js/main.a41ffabd.js +2 -0
- {streamlit_nightly-1.33.1.dev20240409.dist-info → streamlit_nightly-1.33.1.dev20240414.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.33.1.dev20240409.dist-info → streamlit_nightly-1.33.1.dev20240414.dist-info}/RECORD +27 -25
- streamlit/static/static/js/8427.b0ed496b.chunk.js +0 -1
- streamlit/static/static/js/main.37ad7d13.js +0 -2
- /streamlit/static/static/js/{main.37ad7d13.js.LICENSE.txt → main.a41ffabd.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.33.1.dev20240409.data → streamlit_nightly-1.33.1.dev20240414.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.33.1.dev20240409.dist-info → streamlit_nightly-1.33.1.dev20240414.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.33.1.dev20240409.dist-info → streamlit_nightly-1.33.1.dev20240414.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.33.1.dev20240409.dist-info → streamlit_nightly-1.33.1.dev20240414.dist-info}/top_level.txt +0 -0
streamlit/__init__.py
CHANGED
@@ -72,6 +72,10 @@ from streamlit.delta_generator import (
|
|
72
72
|
event_dg as _event_dg,
|
73
73
|
bottom_dg as _bottom_dg,
|
74
74
|
)
|
75
|
+
from streamlit.elements.dialog_decorator import (
|
76
|
+
# rename so that it is available as st.dialog
|
77
|
+
dialog_decorator as experimental_dialog,
|
78
|
+
)
|
75
79
|
from streamlit.runtime.caching import (
|
76
80
|
cache_resource as _cache_resource,
|
77
81
|
cache_data as _cache_data,
|
@@ -129,7 +133,6 @@ _config.on_config_parsed(_update_logger, True)
|
|
129
133
|
secrets = _secrets_singleton
|
130
134
|
|
131
135
|
# DeltaGenerator methods:
|
132
|
-
|
133
136
|
_main = _main_dg
|
134
137
|
sidebar = _sidebar_dg
|
135
138
|
_event = _event_dg
|
streamlit/delta_generator.py
CHANGED
@@ -26,6 +26,7 @@ from typing import (
|
|
26
26
|
Final,
|
27
27
|
Hashable,
|
28
28
|
Iterable,
|
29
|
+
List,
|
29
30
|
Literal,
|
30
31
|
NoReturn,
|
31
32
|
TypeVar,
|
@@ -314,9 +315,9 @@ class DeltaGenerator(
|
|
314
315
|
if self == self._main_dg:
|
315
316
|
# We're being invoked via an `st.foo` pattern - use the current
|
316
317
|
# `with` dg (aka the top of the stack).
|
317
|
-
|
318
|
-
if
|
319
|
-
return
|
318
|
+
last_context_stack_dg = get_last_dg_added_to_context_stack()
|
319
|
+
if last_context_stack_dg is not None:
|
320
|
+
return last_context_stack_dg
|
320
321
|
|
321
322
|
# We're being invoked via an `st.sidebar.foo` pattern - ignore the
|
322
323
|
# current `with` dg.
|
@@ -606,34 +607,7 @@ class DeltaGenerator(
|
|
606
607
|
block_type = block_proto.WhichOneof("type")
|
607
608
|
# Convert the generator to a list, so we can use it multiple times.
|
608
609
|
ancestor_block_types = list(dg._ancestor_block_types)
|
609
|
-
|
610
|
-
if block_type == "column":
|
611
|
-
num_of_parent_columns = self._count_num_of_parent_columns(
|
612
|
-
ancestor_block_types
|
613
|
-
)
|
614
|
-
if (
|
615
|
-
self._root_container == RootContainer.SIDEBAR
|
616
|
-
and num_of_parent_columns > 0
|
617
|
-
):
|
618
|
-
raise StreamlitAPIException(
|
619
|
-
"Columns cannot be placed inside other columns in the sidebar. This is only possible in the main area of the app."
|
620
|
-
)
|
621
|
-
if num_of_parent_columns > 1:
|
622
|
-
raise StreamlitAPIException(
|
623
|
-
"Columns can only be placed inside other columns up to one level of nesting."
|
624
|
-
)
|
625
|
-
if block_type == "chat_message" and block_type in ancestor_block_types:
|
626
|
-
raise StreamlitAPIException(
|
627
|
-
"Chat messages cannot nested inside other chat messages."
|
628
|
-
)
|
629
|
-
if block_type == "expandable" and block_type in ancestor_block_types:
|
630
|
-
raise StreamlitAPIException(
|
631
|
-
"Expanders may not be nested inside other expanders."
|
632
|
-
)
|
633
|
-
if block_type == "popover" and block_type in ancestor_block_types:
|
634
|
-
raise StreamlitAPIException(
|
635
|
-
"Popovers may not be nested inside other popovers."
|
636
|
-
)
|
610
|
+
_check_nested_element_violation(self, block_type, ancestor_block_types)
|
637
611
|
|
638
612
|
if dg._root_container is None or dg._cursor is None:
|
639
613
|
return dg
|
@@ -684,11 +658,9 @@ class DeltaGenerator(
|
|
684
658
|
def _arrow_add_rows(
|
685
659
|
self: DG,
|
686
660
|
data: Data = None,
|
687
|
-
**kwargs:
|
688
|
-
|
689
|
-
|
690
|
-
| dict[Hashable, Any]
|
691
|
-
| None,
|
661
|
+
**kwargs: (
|
662
|
+
DataFrame | npt.NDArray[Any] | Iterable[Any] | dict[Hashable, Any] | None
|
663
|
+
),
|
692
664
|
) -> DG | None:
|
693
665
|
"""Concatenate a dataframe to the bottom of the current one.
|
694
666
|
|
@@ -814,6 +786,20 @@ dg_stack: ContextVar[tuple[DeltaGenerator, ...]] = ContextVar(
|
|
814
786
|
)
|
815
787
|
|
816
788
|
|
789
|
+
def get_last_dg_added_to_context_stack() -> DeltaGenerator | None:
|
790
|
+
"""Get the last added DeltaGenerator of the stack in the current context.
|
791
|
+
|
792
|
+
Returns None if the stack has only one element or is empty for whatever reason.
|
793
|
+
"""
|
794
|
+
current_stack = dg_stack.get()
|
795
|
+
# If set to "> 0" and thus return the only delta generator in the stack - which logically makes more sense -, some unit tests
|
796
|
+
# fail. It looks like the reason is that they create their own main delta generator but do not populate the dg_stack correctly. However, to be on the safe-side,
|
797
|
+
# we keep the logic but leave the comment as shared knowledge for whoever will look into this in the future.
|
798
|
+
if len(current_stack) > 1:
|
799
|
+
return current_stack[-1]
|
800
|
+
return None
|
801
|
+
|
802
|
+
|
817
803
|
def _prep_data_for_add_rows(
|
818
804
|
data: Data,
|
819
805
|
delta_type: str,
|
@@ -927,3 +913,35 @@ def _writes_directly_to_sidebar(dg: DG) -> bool:
|
|
927
913
|
in_sidebar = any(a._root_container == RootContainer.SIDEBAR for a in dg._ancestors)
|
928
914
|
has_container = bool(len(list(dg._ancestor_block_types)))
|
929
915
|
return in_sidebar and not has_container
|
916
|
+
|
917
|
+
|
918
|
+
def _check_nested_element_violation(
|
919
|
+
dg: DeltaGenerator, block_type: str | None, ancestor_block_types: List[BlockType]
|
920
|
+
) -> None:
|
921
|
+
"""Check if elements are nested in a forbidden way.
|
922
|
+
|
923
|
+
Raises
|
924
|
+
------
|
925
|
+
StreamlitAPIException: throw if an invalid element nesting is detected.
|
926
|
+
"""
|
927
|
+
|
928
|
+
if block_type == "column":
|
929
|
+
num_of_parent_columns = dg._count_num_of_parent_columns(ancestor_block_types)
|
930
|
+
if dg._root_container == RootContainer.SIDEBAR and num_of_parent_columns > 0:
|
931
|
+
raise StreamlitAPIException(
|
932
|
+
"Columns cannot be placed inside other columns in the sidebar. This is only possible in the main area of the app."
|
933
|
+
)
|
934
|
+
if num_of_parent_columns > 1:
|
935
|
+
raise StreamlitAPIException(
|
936
|
+
"Columns can only be placed inside other columns up to one level of nesting."
|
937
|
+
)
|
938
|
+
if block_type == "chat_message" and block_type in ancestor_block_types:
|
939
|
+
raise StreamlitAPIException(
|
940
|
+
"Chat messages cannot nested inside other chat messages."
|
941
|
+
)
|
942
|
+
if block_type == "expandable" and block_type in ancestor_block_types:
|
943
|
+
raise StreamlitAPIException(
|
944
|
+
"Expanders may not be nested inside other expanders."
|
945
|
+
)
|
946
|
+
if block_type == "popover" and block_type in ancestor_block_types:
|
947
|
+
raise StreamlitAPIException("Popovers may not be nested inside other popovers.")
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from __future__ import annotations
|
16
|
+
|
17
|
+
from functools import wraps
|
18
|
+
from typing import Callable, TypeVar, cast, overload
|
19
|
+
|
20
|
+
from streamlit.delta_generator import event_dg, get_last_dg_added_to_context_stack
|
21
|
+
from streamlit.elements.lib.dialog import DialogWidth
|
22
|
+
from streamlit.errors import StreamlitAPIException
|
23
|
+
from streamlit.runtime.fragment import fragment as _fragment
|
24
|
+
from streamlit.runtime.metrics_util import gather_metrics
|
25
|
+
|
26
|
+
|
27
|
+
def _assert_no_nested_dialogs() -> None:
|
28
|
+
"""Check the current stack for existing DeltaGenerator's of type 'dialog'.
|
29
|
+
Note that the check like this only works when Dialog is called as a context manager, as this populates the dg_stack in delta_generator correctly.
|
30
|
+
|
31
|
+
This does not detect the edge case in which someone calls, for example, `with st.sidebar` inside of a dialog function and opens a dialog in there,
|
32
|
+
as `with st.sidebar` pushes the new DeltaGenerator to the stack. In order to check for that edge case, we could try to check all DeltaGenerators in the stack,
|
33
|
+
and not only the last one. Since we deem this to be an edge case, we lean towards simplicity here.
|
34
|
+
|
35
|
+
Raises
|
36
|
+
------
|
37
|
+
StreamlitAPIException
|
38
|
+
Raised if the user tries to nest dialogs inside of each other.
|
39
|
+
"""
|
40
|
+
last_dg_in_current_context = get_last_dg_added_to_context_stack()
|
41
|
+
if last_dg_in_current_context and "dialog" in set(
|
42
|
+
last_dg_in_current_context._ancestor_block_types
|
43
|
+
):
|
44
|
+
raise StreamlitAPIException("Dialogs may not be nested inside other dialogs.")
|
45
|
+
|
46
|
+
|
47
|
+
F = TypeVar("F", bound=Callable[..., None])
|
48
|
+
|
49
|
+
|
50
|
+
def _dialog_decorator(
|
51
|
+
non_optional_func: F, title: str, *, width: DialogWidth = "small"
|
52
|
+
) -> F:
|
53
|
+
if title is None or title == "":
|
54
|
+
raise StreamlitAPIException(
|
55
|
+
'A non-empty `title` argument has to be provided for dialogs, for example `@st.experimental_dialog("Example Title")`.'
|
56
|
+
)
|
57
|
+
|
58
|
+
@wraps(non_optional_func)
|
59
|
+
def wrap(*args, **kwargs) -> None:
|
60
|
+
_assert_no_nested_dialogs()
|
61
|
+
# Call the Dialog on the event_dg because it lives outside of the normal
|
62
|
+
# Streamlit UI flow. For example, if it is called from the sidebar, it should not
|
63
|
+
# inherit the sidebar theming.
|
64
|
+
dialog = event_dg._dialog(title=title, dismissible=True, width=width)
|
65
|
+
dialog.open()
|
66
|
+
|
67
|
+
@_fragment
|
68
|
+
def dialog_content() -> None:
|
69
|
+
# if the dialog should be closed, st.rerun() has to be called (same behavior as with st.fragment)
|
70
|
+
_ = non_optional_func(*args, **kwargs)
|
71
|
+
return None
|
72
|
+
|
73
|
+
with dialog:
|
74
|
+
return dialog_content()
|
75
|
+
|
76
|
+
return cast(F, wrap)
|
77
|
+
|
78
|
+
|
79
|
+
@overload
|
80
|
+
def dialog_decorator(title: str, *, width: DialogWidth = "small") -> Callable[[F], F]:
|
81
|
+
...
|
82
|
+
|
83
|
+
|
84
|
+
# 'title' can be a function since `dialog_decorator` is a decorator. We just call it 'title' here though
|
85
|
+
# to make the user-doc more friendly as we want the user to pass a title, not a function.
|
86
|
+
# The user is supposed to call it like @st.dialog("my_title") , which makes 'title' a positional arg, hence
|
87
|
+
# this 'trick'. The overload is required to have a good type hint for the decorated function args.
|
88
|
+
@overload
|
89
|
+
def dialog_decorator(title: F | None, *, width: DialogWidth = "small") -> F:
|
90
|
+
...
|
91
|
+
|
92
|
+
|
93
|
+
@gather_metrics("experimental_dialog")
|
94
|
+
def dialog_decorator(
|
95
|
+
title: F | None | str = "", *, width: DialogWidth = "small"
|
96
|
+
) -> F | Callable[[F], F]:
|
97
|
+
r"""Decorate a function to mark it as a Streamlit dialog. When the decorated function is called, a dialog element is inserted with the function's body as the content.
|
98
|
+
|
99
|
+
The decorated function can hold multiple elements which are rendered inside of a modal when the decorated function is called.
|
100
|
+
The decorated function is using `st.experimental_fragment`, which means that interacting with elements inside of the dialog will
|
101
|
+
only re-run the dialog function.
|
102
|
+
|
103
|
+
The decorated function can accept arguments that can be passed when it is called.
|
104
|
+
|
105
|
+
Dismissing a dialog does not cause an app re-run.
|
106
|
+
You can close the dialog programmatically by executing `st.rerun()` explicitly inside of the decorated function.
|
107
|
+
|
108
|
+
In order to pass state from dialog widgets to the app, you can leverage `st.session_state`.
|
109
|
+
|
110
|
+
.. warning::
|
111
|
+
Currently, a dialog may not open another dialog.
|
112
|
+
Also, only one dialog-decorated function may be called in a script run, which means that only one dialog can be open at any given time.
|
113
|
+
|
114
|
+
Parameters
|
115
|
+
----------
|
116
|
+
title : str
|
117
|
+
A string that will be used as the dialog's title. It cannot be empty.
|
118
|
+
width : "small", "large"
|
119
|
+
The width of the dialog. Defaults to "small".
|
120
|
+
|
121
|
+
Returns
|
122
|
+
-------
|
123
|
+
A decorated function that, when called, inserts a dialog element context container. The container itself contains the decorated function's elements.
|
124
|
+
|
125
|
+
Examples
|
126
|
+
--------
|
127
|
+
You can annotate a function to mark it as a Streamlit dialog function and pass arguments to it. You can either dismiss the dialog via the ESC-key or the X-button or close it programmatically and trigger a re-run by using `st.rerun()`.
|
128
|
+
Leverage `st.session_state` if you want to pass dialog widget states to the overall app:
|
129
|
+
|
130
|
+
>>> import streamlit as st
|
131
|
+
>>>
|
132
|
+
>>> @st.experimental_dialog("Streamlit Example Dialog")
|
133
|
+
>>> def example_dialog(some_arg: str, some_other_arg: int):
|
134
|
+
>>> st.write(f"You passed following args: {some_arg} | {some_other_arg}")
|
135
|
+
>>> # interacting with the text_input only re-runs `example_dialog`
|
136
|
+
>>> some_text_input = st.text_input("Type something:", key="example_dialog_some_text_input")
|
137
|
+
>>> # following write is updated when chaning the text_input inside the dialog
|
138
|
+
>>> st.write(f"You wrote '{some_text_input}' in the dialog")
|
139
|
+
>>> if st.button("Close the dialog"):
|
140
|
+
>>> st.rerun()
|
141
|
+
>>>
|
142
|
+
>>> if st.button("Open dialog"):
|
143
|
+
>>> example_dialog("Some string arg", 42)
|
144
|
+
>>>
|
145
|
+
>>> # following write is updated with the dialog's text input when the dialog was opened, the text input was interacted with and a re-run was triggered, e.g. by clicking the Close-button defined in `example_dialog`
|
146
|
+
>>> st.write(f"You wrote '{st.session_state.get('example_dialog_some_text_input', '')}' in the dialog")
|
147
|
+
|
148
|
+
"""
|
149
|
+
|
150
|
+
func_or_title = title
|
151
|
+
if func_or_title is None:
|
152
|
+
# Support passing the params via function decorator
|
153
|
+
def wrapper(f: F) -> F:
|
154
|
+
return _dialog_decorator(non_optional_func=f, title="", width=width)
|
155
|
+
|
156
|
+
return wrapper
|
157
|
+
elif type(func_or_title) is str:
|
158
|
+
# Support passing the params via function decorator
|
159
|
+
def wrapper(f: F) -> F:
|
160
|
+
title: str = func_or_title
|
161
|
+
return _dialog_decorator(non_optional_func=f, title=title, width=width)
|
162
|
+
|
163
|
+
return wrapper
|
164
|
+
|
165
|
+
func: F = cast(F, func_or_title)
|
166
|
+
return _dialog_decorator(func, "", width=width)
|
streamlit/elements/image.py
CHANGED
@@ -296,9 +296,11 @@ def _ensure_image_size_and_format(
|
|
296
296
|
if width > 0 and actual_width > width:
|
297
297
|
# We need to resize the image.
|
298
298
|
new_height = int(1.0 * actual_height * width / actual_width)
|
299
|
-
|
300
|
-
|
301
|
-
|
299
|
+
# pillow reexports Image.Resampling.BILINEAR as Image.BILINEAR for backwards
|
300
|
+
# compatibility reasons, so we use the reexport to support older pillow
|
301
|
+
# versions. The types don't seem to reflect this, though, hence the type: ignore
|
302
|
+
# below.
|
303
|
+
pil_image = pil_image.resize((width, new_height), resample=Image.BILINEAR) # type: ignore[attr-defined]
|
302
304
|
return _PIL_to_bytes(pil_image, format=image_format, quality=90)
|
303
305
|
|
304
306
|
if pil_image.format != image_format:
|
streamlit/elements/layouts.py
CHANGED
@@ -24,6 +24,7 @@ from streamlit.runtime.metrics_util import gather_metrics
|
|
24
24
|
|
25
25
|
if TYPE_CHECKING:
|
26
26
|
from streamlit.delta_generator import DeltaGenerator
|
27
|
+
from streamlit.elements.lib.dialog import Dialog
|
27
28
|
from streamlit.elements.lib.mutable_status_container import StatusContainer
|
28
29
|
|
29
30
|
SpecType: TypeAlias = Union[int, Sequence[Union[int, float]]]
|
@@ -710,6 +711,24 @@ class LayoutsMixin:
|
|
710
711
|
self.dg, label=label, expanded=expanded, state=state
|
711
712
|
)
|
712
713
|
|
714
|
+
def _dialog(
|
715
|
+
self,
|
716
|
+
title: str,
|
717
|
+
*,
|
718
|
+
dismissible: bool = True,
|
719
|
+
width: Literal["small", "large"] = "small",
|
720
|
+
) -> "Dialog":
|
721
|
+
"""Inserts the dialog container.
|
722
|
+
|
723
|
+
Marked as internal because it is used by the dialog_decorator and is not supposed to be used directly.
|
724
|
+
The dialog_decorator also has a more descriptive docstring since it is user-facing.
|
725
|
+
"""
|
726
|
+
|
727
|
+
# We need to import Dialog here to avoid a circular import
|
728
|
+
from streamlit.elements.lib.dialog import Dialog
|
729
|
+
|
730
|
+
return Dialog._create(self.dg, title, dismissible=dismissible, width=width)
|
731
|
+
|
713
732
|
@property
|
714
733
|
def dg(self) -> DeltaGenerator:
|
715
734
|
"""Get our DeltaGenerator."""
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from __future__ import annotations
|
16
|
+
|
17
|
+
import time
|
18
|
+
from types import TracebackType
|
19
|
+
from typing import Literal, cast
|
20
|
+
|
21
|
+
from typing_extensions import TypeAlias
|
22
|
+
|
23
|
+
from streamlit.cursor import Cursor
|
24
|
+
from streamlit.delta_generator import DeltaGenerator, _enqueue_message
|
25
|
+
from streamlit.errors import StreamlitAPIException
|
26
|
+
from streamlit.proto.Block_pb2 import Block as BlockProto
|
27
|
+
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
28
|
+
from streamlit.runtime.scriptrunner import get_script_run_ctx
|
29
|
+
|
30
|
+
DialogWidth: TypeAlias = Literal["small", "large"]
|
31
|
+
|
32
|
+
|
33
|
+
def _process_dialog_width_input(
|
34
|
+
width: DialogWidth,
|
35
|
+
) -> BlockProto.Dialog.DialogWidth.ValueType:
|
36
|
+
"""Maps the user-provided literal to a value of the DialogWidth proto enum.
|
37
|
+
|
38
|
+
Returns the mapped enum field for "small" by default and otherwise the mapped type.
|
39
|
+
"""
|
40
|
+
if width == "large":
|
41
|
+
return BlockProto.Dialog.DialogWidth.LARGE
|
42
|
+
|
43
|
+
return BlockProto.Dialog.DialogWidth.SMALL
|
44
|
+
|
45
|
+
|
46
|
+
def _assert_first_dialog_to_be_opened(should_open: bool) -> None:
|
47
|
+
"""Check whether a dialog has already been opened in the same script run.
|
48
|
+
|
49
|
+
Only one dialog is supposed to be opened. The check is implemented in a way
|
50
|
+
that for a script run, the open function can only be called once.
|
51
|
+
One dialog at a time is a product decision and not a technical one.
|
52
|
+
|
53
|
+
Raises
|
54
|
+
------
|
55
|
+
StreamlitAPIException
|
56
|
+
Raised when a dialog has already been opened in the current script run.
|
57
|
+
"""
|
58
|
+
script_run_ctx = get_script_run_ctx()
|
59
|
+
# We don't reset the ctx.has_dialog_opened when the flag is False because
|
60
|
+
# it is reset in a new scriptrun anyways. If the execution model ever changes,
|
61
|
+
# this might need to change.
|
62
|
+
if should_open and script_run_ctx:
|
63
|
+
if script_run_ctx.has_dialog_opened:
|
64
|
+
raise StreamlitAPIException(
|
65
|
+
"Only one dialog is allowed to be opened at the same time. Please make sure to not call a dialog-decorated function more than once in a script run."
|
66
|
+
)
|
67
|
+
script_run_ctx.has_dialog_opened = True
|
68
|
+
|
69
|
+
|
70
|
+
class Dialog(DeltaGenerator):
|
71
|
+
@staticmethod
|
72
|
+
def _create(
|
73
|
+
parent: DeltaGenerator,
|
74
|
+
title: str,
|
75
|
+
*,
|
76
|
+
dismissible: bool = True,
|
77
|
+
width: DialogWidth = "small",
|
78
|
+
) -> Dialog:
|
79
|
+
block_proto = BlockProto()
|
80
|
+
block_proto.dialog.title = title
|
81
|
+
block_proto.dialog.dismissible = dismissible
|
82
|
+
block_proto.dialog.width = _process_dialog_width_input(width)
|
83
|
+
|
84
|
+
# We store the delta path here, because in _update we enqueue a new proto message to update the
|
85
|
+
# open status. Without this, the dialog content is gone when the _update message is sent
|
86
|
+
delta_path: list[int] = (
|
87
|
+
parent._active_dg._cursor.delta_path if parent._active_dg._cursor else []
|
88
|
+
)
|
89
|
+
dialog = cast(Dialog, parent._block(block_proto=block_proto, dg_type=Dialog))
|
90
|
+
|
91
|
+
dialog._delta_path = delta_path
|
92
|
+
dialog._current_proto = block_proto
|
93
|
+
# We add a sleep here to give the web app time to react to the update. Otherwise,
|
94
|
+
# we might run into issues where the dialog cannot be opened again after closing
|
95
|
+
time.sleep(0.05)
|
96
|
+
return dialog
|
97
|
+
|
98
|
+
def __init__(
|
99
|
+
self,
|
100
|
+
root_container: int | None,
|
101
|
+
cursor: Cursor | None,
|
102
|
+
parent: DeltaGenerator | None,
|
103
|
+
block_type: str | None,
|
104
|
+
):
|
105
|
+
super().__init__(root_container, cursor, parent, block_type)
|
106
|
+
|
107
|
+
# Initialized in `_create()`:
|
108
|
+
self._current_proto: BlockProto | None = None
|
109
|
+
self._delta_path: list[int] | None = None
|
110
|
+
|
111
|
+
def _update(self, should_open: bool):
|
112
|
+
"""Send an updated proto message to indicate the open-status for the dialog."""
|
113
|
+
|
114
|
+
assert self._current_proto is not None, "Dialog not correctly initialized!"
|
115
|
+
assert self._delta_path is not None, "Dialog not correctly initialized!"
|
116
|
+
_assert_first_dialog_to_be_opened(should_open)
|
117
|
+
msg = ForwardMsg()
|
118
|
+
msg.metadata.delta_path[:] = self._delta_path
|
119
|
+
msg.delta.add_block.CopyFrom(self._current_proto)
|
120
|
+
msg.delta.add_block.dialog.is_open = should_open
|
121
|
+
|
122
|
+
self._current_proto = msg.delta.add_block
|
123
|
+
|
124
|
+
# We add a sleep here to give the web app time to react to the update. Otherwise,
|
125
|
+
# we might run into issues where the dialog cannot be opened again after closing
|
126
|
+
time.sleep(0.05)
|
127
|
+
_enqueue_message(msg)
|
128
|
+
|
129
|
+
def open(self) -> None:
|
130
|
+
self._update(True)
|
131
|
+
|
132
|
+
def close(self) -> None:
|
133
|
+
self._update(False)
|
134
|
+
|
135
|
+
def __enter__(self) -> Dialog: # type: ignore[override]
|
136
|
+
# This is a little dubious: we're returning a different type than
|
137
|
+
# our superclass' `__enter__` function. Maybe DeltaGenerator.__enter__
|
138
|
+
# should always return `self`?
|
139
|
+
super().__enter__()
|
140
|
+
return self
|
141
|
+
|
142
|
+
def __exit__(
|
143
|
+
self,
|
144
|
+
exc_type: type[BaseException] | None,
|
145
|
+
exc_val: BaseException | None,
|
146
|
+
exc_tb: TracebackType | None,
|
147
|
+
) -> Literal[False]:
|
148
|
+
return super().__exit__(exc_type, exc_val, exc_tb)
|
streamlit/proto/Block_pb2.py
CHANGED
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
|
13
13
|
|
14
14
|
|
15
15
|
|
16
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bstreamlit/proto/Block.proto\"\
|
16
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bstreamlit/proto/Block.proto\"\xbe\x08\n\x05\x42lock\x12#\n\x08vertical\x18\x01 \x01(\x0b\x32\x0f.Block.VerticalH\x00\x12\'\n\nhorizontal\x18\x02 \x01(\x0b\x32\x11.Block.HorizontalH\x00\x12\x1f\n\x06\x63olumn\x18\x03 \x01(\x0b\x32\r.Block.ColumnH\x00\x12\'\n\nexpandable\x18\x04 \x01(\x0b\x32\x11.Block.ExpandableH\x00\x12\x1b\n\x04\x66orm\x18\x05 \x01(\x0b\x32\x0b.Block.FormH\x00\x12,\n\rtab_container\x18\x06 \x01(\x0b\x32\x13.Block.TabContainerH\x00\x12\x19\n\x03tab\x18\x07 \x01(\x0b\x32\n.Block.TabH\x00\x12*\n\x0c\x63hat_message\x18\t \x01(\x0b\x32\x12.Block.ChatMessageH\x00\x12!\n\x07popover\x18\n \x01(\x0b\x32\x0e.Block.PopoverH\x00\x12\x1f\n\x06\x64ialog\x18\x0b \x01(\x0b\x32\r.Block.DialogH\x00\x12\x13\n\x0b\x61llow_empty\x18\x08 \x01(\x08\x1a*\n\x08Vertical\x12\x0e\n\x06\x62order\x18\x01 \x01(\x08\x12\x0e\n\x06height\x18\x02 \x01(\r\x1a\x19\n\nHorizontal\x12\x0b\n\x03gap\x18\x01 \x01(\t\x1a%\n\x06\x43olumn\x12\x0e\n\x06weight\x18\x01 \x01(\x01\x12\x0b\n\x03gap\x18\x02 \x01(\t\x1aM\n\nExpandable\x12\r\n\x05label\x18\x01 \x01(\t\x12\x15\n\x08\x65xpanded\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12\x0c\n\x04icon\x18\x03 \x01(\tB\x0b\n\t_expanded\x1a\x9d\x01\n\x06\x44ialog\x12\r\n\x05title\x18\x01 \x01(\t\x12\x13\n\x0b\x64ismissible\x18\x02 \x01(\x08\x12(\n\x05width\x18\x03 \x01(\x0e\x32\x19.Block.Dialog.DialogWidth\x12\x14\n\x07is_open\x18\x04 \x01(\x08H\x00\x88\x01\x01\"#\n\x0b\x44ialogWidth\x12\t\n\x05SMALL\x10\x00\x12\t\n\x05LARGE\x10\x01\x42\n\n\x08_is_open\x1a@\n\x04\x46orm\x12\x0f\n\x07\x66orm_id\x18\x01 \x01(\t\x12\x17\n\x0f\x63lear_on_submit\x18\x02 \x01(\x08\x12\x0e\n\x06\x62order\x18\x03 \x01(\x08\x1a\x0e\n\x0cTabContainer\x1a\x14\n\x03Tab\x12\r\n\x05label\x18\x01 \x01(\t\x1aU\n\x07Popover\x12\r\n\x05label\x18\x01 \x01(\t\x12\x1b\n\x13use_container_width\x18\x02 \x01(\x08\x12\x0c\n\x04help\x18\x03 \x01(\t\x12\x10\n\x08\x64isabled\x18\x04 \x01(\x08\x1a\x8d\x01\n\x0b\x43hatMessage\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06\x61vatar\x18\x02 \x01(\t\x12\x32\n\x0b\x61vatar_type\x18\x03 \x01(\x0e\x32\x1d.Block.ChatMessage.AvatarType\",\n\nAvatarType\x12\t\n\x05IMAGE\x10\x00\x12\t\n\x05\x45MOJI\x10\x01\x12\x08\n\x04ICON\x10\x02\x42\x06\n\x04typeB*\n\x1c\x63om.snowflake.apps.streamlitB\nBlockProtob\x06proto3')
|
17
17
|
|
18
18
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
19
19
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'streamlit.proto.Block_pb2', globals())
|
@@ -22,25 +22,29 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
22
22
|
DESCRIPTOR._options = None
|
23
23
|
DESCRIPTOR._serialized_options = b'\n\034com.snowflake.apps.streamlitB\nBlockProto'
|
24
24
|
_BLOCK._serialized_start=32
|
25
|
-
_BLOCK._serialized_end=
|
26
|
-
_BLOCK_VERTICAL._serialized_start=
|
27
|
-
_BLOCK_VERTICAL._serialized_end=
|
28
|
-
_BLOCK_HORIZONTAL._serialized_start=
|
29
|
-
_BLOCK_HORIZONTAL._serialized_end=
|
30
|
-
_BLOCK_COLUMN._serialized_start=
|
31
|
-
_BLOCK_COLUMN._serialized_end=
|
32
|
-
_BLOCK_EXPANDABLE._serialized_start=
|
33
|
-
_BLOCK_EXPANDABLE._serialized_end=
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
25
|
+
_BLOCK._serialized_end=1118
|
26
|
+
_BLOCK_VERTICAL._serialized_start=428
|
27
|
+
_BLOCK_VERTICAL._serialized_end=470
|
28
|
+
_BLOCK_HORIZONTAL._serialized_start=472
|
29
|
+
_BLOCK_HORIZONTAL._serialized_end=497
|
30
|
+
_BLOCK_COLUMN._serialized_start=499
|
31
|
+
_BLOCK_COLUMN._serialized_end=536
|
32
|
+
_BLOCK_EXPANDABLE._serialized_start=538
|
33
|
+
_BLOCK_EXPANDABLE._serialized_end=615
|
34
|
+
_BLOCK_DIALOG._serialized_start=618
|
35
|
+
_BLOCK_DIALOG._serialized_end=775
|
36
|
+
_BLOCK_DIALOG_DIALOGWIDTH._serialized_start=728
|
37
|
+
_BLOCK_DIALOG_DIALOGWIDTH._serialized_end=763
|
38
|
+
_BLOCK_FORM._serialized_start=777
|
39
|
+
_BLOCK_FORM._serialized_end=841
|
40
|
+
_BLOCK_TABCONTAINER._serialized_start=843
|
41
|
+
_BLOCK_TABCONTAINER._serialized_end=857
|
42
|
+
_BLOCK_TAB._serialized_start=859
|
43
|
+
_BLOCK_TAB._serialized_end=879
|
44
|
+
_BLOCK_POPOVER._serialized_start=881
|
45
|
+
_BLOCK_POPOVER._serialized_end=966
|
46
|
+
_BLOCK_CHATMESSAGE._serialized_start=969
|
47
|
+
_BLOCK_CHATMESSAGE._serialized_end=1110
|
48
|
+
_BLOCK_CHATMESSAGE_AVATARTYPE._serialized_start=1066
|
49
|
+
_BLOCK_CHATMESSAGE_AVATARTYPE._serialized_end=1110
|
46
50
|
# @@protoc_insertion_point(module_scope)
|
streamlit/proto/Block_pb2.pyi
CHANGED
@@ -96,6 +96,42 @@ class Block(google.protobuf.message.Message):
|
|
96
96
|
def ClearField(self, field_name: typing_extensions.Literal["_expanded", b"_expanded", "expanded", b"expanded", "icon", b"icon", "label", b"label"]) -> None: ...
|
97
97
|
def WhichOneof(self, oneof_group: typing_extensions.Literal["_expanded", b"_expanded"]) -> typing_extensions.Literal["expanded"] | None: ...
|
98
98
|
|
99
|
+
class Dialog(google.protobuf.message.Message):
|
100
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
101
|
+
|
102
|
+
class _DialogWidth:
|
103
|
+
ValueType = typing.NewType("ValueType", builtins.int)
|
104
|
+
V: typing_extensions.TypeAlias = ValueType
|
105
|
+
|
106
|
+
class _DialogWidthEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Block.Dialog._DialogWidth.ValueType], builtins.type): # noqa: F821
|
107
|
+
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
108
|
+
SMALL: Block.Dialog._DialogWidth.ValueType # 0
|
109
|
+
LARGE: Block.Dialog._DialogWidth.ValueType # 1
|
110
|
+
|
111
|
+
class DialogWidth(_DialogWidth, metaclass=_DialogWidthEnumTypeWrapper): ...
|
112
|
+
SMALL: Block.Dialog.DialogWidth.ValueType # 0
|
113
|
+
LARGE: Block.Dialog.DialogWidth.ValueType # 1
|
114
|
+
|
115
|
+
TITLE_FIELD_NUMBER: builtins.int
|
116
|
+
DISMISSIBLE_FIELD_NUMBER: builtins.int
|
117
|
+
WIDTH_FIELD_NUMBER: builtins.int
|
118
|
+
IS_OPEN_FIELD_NUMBER: builtins.int
|
119
|
+
title: builtins.str
|
120
|
+
dismissible: builtins.bool
|
121
|
+
width: global___Block.Dialog.DialogWidth.ValueType
|
122
|
+
is_open: builtins.bool
|
123
|
+
def __init__(
|
124
|
+
self,
|
125
|
+
*,
|
126
|
+
title: builtins.str = ...,
|
127
|
+
dismissible: builtins.bool = ...,
|
128
|
+
width: global___Block.Dialog.DialogWidth.ValueType = ...,
|
129
|
+
is_open: builtins.bool | None = ...,
|
130
|
+
) -> None: ...
|
131
|
+
def HasField(self, field_name: typing_extensions.Literal["_is_open", b"_is_open", "is_open", b"is_open"]) -> builtins.bool: ...
|
132
|
+
def ClearField(self, field_name: typing_extensions.Literal["_is_open", b"_is_open", "dismissible", b"dismissible", "is_open", b"is_open", "title", b"title", "width", b"width"]) -> None: ...
|
133
|
+
def WhichOneof(self, oneof_group: typing_extensions.Literal["_is_open", b"_is_open"]) -> typing_extensions.Literal["is_open"] | None: ...
|
134
|
+
|
99
135
|
class Form(google.protobuf.message.Message):
|
100
136
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
101
137
|
|
@@ -196,6 +232,7 @@ class Block(google.protobuf.message.Message):
|
|
196
232
|
TAB_FIELD_NUMBER: builtins.int
|
197
233
|
CHAT_MESSAGE_FIELD_NUMBER: builtins.int
|
198
234
|
POPOVER_FIELD_NUMBER: builtins.int
|
235
|
+
DIALOG_FIELD_NUMBER: builtins.int
|
199
236
|
ALLOW_EMPTY_FIELD_NUMBER: builtins.int
|
200
237
|
@property
|
201
238
|
def vertical(self) -> global___Block.Vertical: ...
|
@@ -215,6 +252,8 @@ class Block(google.protobuf.message.Message):
|
|
215
252
|
def chat_message(self) -> global___Block.ChatMessage: ...
|
216
253
|
@property
|
217
254
|
def popover(self) -> global___Block.Popover: ...
|
255
|
+
@property
|
256
|
+
def dialog(self) -> global___Block.Dialog: ...
|
218
257
|
allow_empty: builtins.bool
|
219
258
|
def __init__(
|
220
259
|
self,
|
@@ -228,10 +267,11 @@ class Block(google.protobuf.message.Message):
|
|
228
267
|
tab: global___Block.Tab | None = ...,
|
229
268
|
chat_message: global___Block.ChatMessage | None = ...,
|
230
269
|
popover: global___Block.Popover | None = ...,
|
270
|
+
dialog: global___Block.Dialog | None = ...,
|
231
271
|
allow_empty: builtins.bool = ...,
|
232
272
|
) -> None: ...
|
233
|
-
def HasField(self, field_name: typing_extensions.Literal["chat_message", b"chat_message", "column", b"column", "expandable", b"expandable", "form", b"form", "horizontal", b"horizontal", "popover", b"popover", "tab", b"tab", "tab_container", b"tab_container", "type", b"type", "vertical", b"vertical"]) -> builtins.bool: ...
|
234
|
-
def ClearField(self, field_name: typing_extensions.Literal["allow_empty", b"allow_empty", "chat_message", b"chat_message", "column", b"column", "expandable", b"expandable", "form", b"form", "horizontal", b"horizontal", "popover", b"popover", "tab", b"tab", "tab_container", b"tab_container", "type", b"type", "vertical", b"vertical"]) -> None: ...
|
235
|
-
def WhichOneof(self, oneof_group: typing_extensions.Literal["type", b"type"]) -> typing_extensions.Literal["vertical", "horizontal", "column", "expandable", "form", "tab_container", "tab", "chat_message", "popover"] | None: ...
|
273
|
+
def HasField(self, field_name: typing_extensions.Literal["chat_message", b"chat_message", "column", b"column", "dialog", b"dialog", "expandable", b"expandable", "form", b"form", "horizontal", b"horizontal", "popover", b"popover", "tab", b"tab", "tab_container", b"tab_container", "type", b"type", "vertical", b"vertical"]) -> builtins.bool: ...
|
274
|
+
def ClearField(self, field_name: typing_extensions.Literal["allow_empty", b"allow_empty", "chat_message", b"chat_message", "column", b"column", "dialog", b"dialog", "expandable", b"expandable", "form", b"form", "horizontal", b"horizontal", "popover", b"popover", "tab", b"tab", "tab_container", b"tab_container", "type", b"type", "vertical", b"vertical"]) -> None: ...
|
275
|
+
def WhichOneof(self, oneof_group: typing_extensions.Literal["type", b"type"]) -> typing_extensions.Literal["vertical", "horizontal", "column", "expandable", "form", "tab_container", "tab", "chat_message", "popover", "dialog"] | None: ...
|
236
276
|
|
237
277
|
global___Block = Block
|