streamlit-nightly 1.36.1.dev20240713__py2.py3-none-any.whl → 1.36.1.dev20240715__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 +27 -6
- streamlit/commands/execution_control.py +71 -10
- streamlit/delta_generator.py +3 -3
- streamlit/elements/dialog_decorator.py +59 -25
- streamlit/elements/json.py +11 -1
- streamlit/elements/widgets/chat.py +3 -2
- streamlit/elements/write.py +11 -2
- streamlit/errors.py +15 -0
- streamlit/runtime/app_session.py +15 -13
- streamlit/runtime/context.py +157 -0
- streamlit/runtime/forward_msg_queue.py +1 -0
- streamlit/runtime/fragment.py +129 -44
- streamlit/runtime/metrics_util.py +3 -1
- streamlit/runtime/scriptrunner/exec_code.py +16 -36
- streamlit/runtime/scriptrunner/script_requests.py +63 -22
- streamlit/runtime/scriptrunner/script_run_context.py +3 -3
- streamlit/runtime/scriptrunner/script_runner.py +27 -6
- streamlit/runtime/state/session_state.py +6 -2
- streamlit/runtime/state/widgets.py +20 -8
- streamlit/web/server/websocket_headers.py +9 -0
- {streamlit_nightly-1.36.1.dev20240713.dist-info → streamlit_nightly-1.36.1.dev20240715.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.36.1.dev20240713.dist-info → streamlit_nightly-1.36.1.dev20240715.dist-info}/RECORD +26 -25
- {streamlit_nightly-1.36.1.dev20240713.data → streamlit_nightly-1.36.1.dev20240715.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.36.1.dev20240713.dist-info → streamlit_nightly-1.36.1.dev20240715.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.36.1.dev20240713.dist-info → streamlit_nightly-1.36.1.dev20240715.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.36.1.dev20240713.dist-info → streamlit_nightly-1.36.1.dev20240715.dist-info}/top_level.txt +0 -0
streamlit/__init__.py
CHANGED
@@ -72,7 +72,10 @@ from streamlit.delta_generator import (
|
|
72
72
|
bottom_dg as _bottom_dg,
|
73
73
|
)
|
74
74
|
|
75
|
-
from streamlit.elements.dialog_decorator import
|
75
|
+
from streamlit.elements.dialog_decorator import (
|
76
|
+
dialog_decorator as _dialog_decorator,
|
77
|
+
experimental_dialog_decorator as _experimental_dialog_decorator,
|
78
|
+
)
|
76
79
|
from streamlit.runtime.caching import (
|
77
80
|
cache_resource as _cache_resource,
|
78
81
|
cache_data as _cache_data,
|
@@ -81,9 +84,13 @@ from streamlit.runtime.caching import (
|
|
81
84
|
from streamlit.runtime.connection_factory import (
|
82
85
|
connection_factory as _connection,
|
83
86
|
)
|
84
|
-
from streamlit.runtime.fragment import
|
87
|
+
from streamlit.runtime.fragment import (
|
88
|
+
experimental_fragment as _experimental_fragment,
|
89
|
+
fragment as _fragment,
|
90
|
+
)
|
85
91
|
from streamlit.runtime.metrics_util import gather_metrics as _gather_metrics
|
86
92
|
from streamlit.runtime.secrets import secrets_singleton as _secrets_singleton
|
93
|
+
from streamlit.runtime.context import ContextProxy as _ContextProxy
|
87
94
|
from streamlit.runtime.state import (
|
88
95
|
SessionStateProxy as _SessionStateProxy,
|
89
96
|
QueryParamsProxy as _QueryParamsProxy,
|
@@ -96,7 +103,6 @@ from streamlit.commands.experimental_query_params import (
|
|
96
103
|
|
97
104
|
import streamlit.column_config as _column_config
|
98
105
|
|
99
|
-
|
100
106
|
# Modules that the user should have access to. These are imported with the "as" syntax and the same name; note that renaming the import with "as" does not make it an explicit export.
|
101
107
|
# In this case, you should import it with an underscore to make clear that it is internal and then assign it to a variable with the new intended name.
|
102
108
|
# You can check the export behavior by running 'mypy --strict example_app.py', which disables implicit_reexport, where you use the respective command in the example_app.py Streamlit app.
|
@@ -126,7 +132,6 @@ def _update_logger() -> None:
|
|
126
132
|
# in an alternative config.
|
127
133
|
_config.on_config_parsed(_update_logger, True)
|
128
134
|
|
129
|
-
|
130
135
|
secrets = _secrets_singleton
|
131
136
|
|
132
137
|
# DeltaGenerator methods:
|
@@ -222,6 +227,8 @@ session_state = _SessionStateProxy()
|
|
222
227
|
|
223
228
|
query_params = _QueryParamsProxy()
|
224
229
|
|
230
|
+
context = _ContextProxy()
|
231
|
+
|
225
232
|
# Caching
|
226
233
|
cache_data = _cache_data
|
227
234
|
cache_resource = _cache_resource
|
@@ -234,9 +241,23 @@ column_config = _column_config
|
|
234
241
|
# Connection
|
235
242
|
connection = _connection
|
236
243
|
|
244
|
+
# Fragment and dialog
|
245
|
+
dialog = _dialog_decorator
|
246
|
+
fragment = _fragment
|
247
|
+
|
237
248
|
# Experimental APIs
|
238
|
-
experimental_dialog =
|
239
|
-
|
249
|
+
experimental_dialog = _deprecate_func_name(
|
250
|
+
_experimental_dialog_decorator,
|
251
|
+
"experimental_dialog",
|
252
|
+
"2025-01-01",
|
253
|
+
name_override="dialog",
|
254
|
+
)
|
255
|
+
experimental_fragment = _deprecate_func_name(
|
256
|
+
_experimental_fragment,
|
257
|
+
"experimental_fragment",
|
258
|
+
"2025-01-01",
|
259
|
+
name_override="fragment",
|
260
|
+
)
|
240
261
|
experimental_user = _UserInfoProxy()
|
241
262
|
|
242
263
|
_EXPERIMENTAL_QUERY_PARAMS_DEPRECATE_MSG = "Refer to our [docs page](https://docs.streamlit.io/develop/api-reference/caching-and-state/st.query_params) for more information."
|
@@ -15,7 +15,8 @@
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
17
|
import os
|
18
|
-
from
|
18
|
+
from itertools import dropwhile
|
19
|
+
from typing import Final, Literal, NoReturn
|
19
20
|
|
20
21
|
import streamlit as st
|
21
22
|
from streamlit.errors import NoSessionContext, StreamlitAPIException
|
@@ -23,7 +24,11 @@ from streamlit.file_util import get_main_script_directory, normalize_path_join
|
|
23
24
|
from streamlit.logger import get_logger
|
24
25
|
from streamlit.navigation.page import StreamlitPage
|
25
26
|
from streamlit.runtime.metrics_util import gather_metrics
|
26
|
-
from streamlit.runtime.scriptrunner import
|
27
|
+
from streamlit.runtime.scriptrunner import (
|
28
|
+
RerunData,
|
29
|
+
ScriptRunContext,
|
30
|
+
get_script_run_ctx,
|
31
|
+
)
|
27
32
|
|
28
33
|
_LOGGER: Final = get_logger(__name__)
|
29
34
|
|
@@ -54,22 +59,76 @@ def stop() -> NoReturn: # type: ignore[misc]
|
|
54
59
|
st.empty()
|
55
60
|
|
56
61
|
|
62
|
+
def _new_fragment_id_queue(
|
63
|
+
ctx: ScriptRunContext,
|
64
|
+
scope: Literal["app", "fragment"],
|
65
|
+
) -> list[str]:
|
66
|
+
if scope == "app":
|
67
|
+
return []
|
68
|
+
|
69
|
+
else: # scope == "fragment"
|
70
|
+
curr_queue = (
|
71
|
+
ctx.script_requests.fragment_id_queue if ctx.script_requests else []
|
72
|
+
)
|
73
|
+
|
74
|
+
# If st.rerun(scope="fragment") is called during a full script run, we raise an
|
75
|
+
# exception. This occurs, of course, if st.rerun(scope="fragment") is called
|
76
|
+
# outside of a fragment, but it somewhat surprisingly occurs if it gets called
|
77
|
+
# from within a fragment during a run of the full script. While this behvior may
|
78
|
+
# be surprising, it seems somewhat reasonable given that the correct behavior of
|
79
|
+
# calling st.rerun(scope="fragment") in this situation is unclear to me:
|
80
|
+
# * Rerunning just the fragment immediately may cause weirdness down the line
|
81
|
+
# as any part of the script that occurs after the fragment will not be
|
82
|
+
# executed.
|
83
|
+
# * Waiting until the full script run completes before rerunning the fragment
|
84
|
+
# seems odd (even if we normally do this before running a fragment not
|
85
|
+
# triggered by st.rerun()) because it defers the execution of st.rerun().
|
86
|
+
# * Rerunning the full app feels incorrect as we're seemingly ignoring the
|
87
|
+
# `scope` argument.
|
88
|
+
# With these issues and given that it seems pretty unnatural to have a
|
89
|
+
# fragment-scoped rerun happen during a full script run to begin with, it seems
|
90
|
+
# reasonable to just disallow this completely for now.
|
91
|
+
if not curr_queue:
|
92
|
+
raise StreamlitAPIException(
|
93
|
+
'scope="fragment" can only be specified from `@st.fragment`-decorated '
|
94
|
+
"functions during fragment reruns."
|
95
|
+
)
|
96
|
+
|
97
|
+
assert (
|
98
|
+
new_queue := list(
|
99
|
+
dropwhile(lambda x: x != ctx.current_fragment_id, curr_queue)
|
100
|
+
)
|
101
|
+
), "Could not find current_fragment_id in fragment_id_queue. This should never happen."
|
102
|
+
|
103
|
+
return new_queue
|
104
|
+
|
105
|
+
|
57
106
|
@gather_metrics("rerun")
|
58
|
-
def rerun(
|
107
|
+
def rerun( # type: ignore[misc]
|
108
|
+
*, # The scope argument can only be passed via keyword.
|
109
|
+
scope: Literal["app", "fragment"] = "app",
|
110
|
+
) -> NoReturn:
|
59
111
|
"""Rerun the script immediately.
|
60
112
|
|
61
113
|
When ``st.rerun()`` is called, the script is halted - no more statements will
|
62
|
-
be run, and the script will be queued to re-run
|
114
|
+
be run, and the script will be queued to re-run.
|
115
|
+
|
116
|
+
Parameters
|
117
|
+
----------
|
118
|
+
scope : Literal["app", "fragment"]
|
119
|
+
Specifies what part of the app should rerun. Setting scope="app" reruns the
|
120
|
+
full script; scope="fragment" limits the rerun to only the fragment from which
|
121
|
+
this function is called. If unspecified, defaults to "app".
|
122
|
+
|
123
|
+
Note that setting scope="fragment" is only valid
|
124
|
+
* inside of a fragment
|
125
|
+
* *not* during a full script run.
|
126
|
+
Passing this argument to ``st.rerun()`` from outside of a fragment or within a
|
127
|
+
fragment but when the full script is running raises a ``StreamlitAPIException``.
|
63
128
|
"""
|
64
129
|
|
65
130
|
ctx = get_script_run_ctx()
|
66
131
|
|
67
|
-
# TODO: (rerun[scope] project): in order to make it a fragment-scoped rerun, pass
|
68
|
-
# the fragment_id_queue to the RerunData object and add the following line:
|
69
|
-
# fragment_id_queue=[ctx.current_fragment_id] if scope == "fragment" else []
|
70
|
-
# The script_runner RerunException is checking for the fragment_id_queue to decide
|
71
|
-
# whether or not to reset the dg_stack.
|
72
|
-
|
73
132
|
if ctx and ctx.script_requests:
|
74
133
|
query_string = ctx.query_string
|
75
134
|
page_script_hash = ctx.page_script_hash
|
@@ -78,6 +137,8 @@ def rerun() -> NoReturn: # type: ignore[misc]
|
|
78
137
|
RerunData(
|
79
138
|
query_string=query_string,
|
80
139
|
page_script_hash=page_script_hash,
|
140
|
+
fragment_id_queue=_new_fragment_id_queue(ctx, scope),
|
141
|
+
is_fragment_scoped_rerun=True,
|
81
142
|
)
|
82
143
|
)
|
83
144
|
# Force a yield point so the runner can do the rerun
|
streamlit/delta_generator.py
CHANGED
@@ -441,9 +441,9 @@ class DeltaGenerator(
|
|
441
441
|
ctx = get_script_run_ctx()
|
442
442
|
if ctx and ctx.current_fragment_id and _writes_directly_to_sidebar(dg):
|
443
443
|
raise StreamlitAPIException(
|
444
|
-
"Calling `st.sidebar` in a function wrapped with `st.
|
445
|
-
"
|
446
|
-
"
|
444
|
+
"Calling `st.sidebar` in a function wrapped with `st.fragment` is not "
|
445
|
+
"supported. To write elements to the sidebar with a fragment, call your "
|
446
|
+
"fragment function inside a `with st.sidebar` context manager."
|
447
447
|
)
|
448
448
|
|
449
449
|
# Warn if an element is being changed but the user isn't running the streamlit server.
|
@@ -28,11 +28,15 @@ if TYPE_CHECKING:
|
|
28
28
|
|
29
29
|
def _assert_no_nested_dialogs() -> None:
|
30
30
|
"""Check the current stack for existing DeltaGenerator's of type 'dialog'.
|
31
|
-
Note that the check like this only works when Dialog is called as a context manager,
|
31
|
+
Note that the check like this only works when Dialog is called as a context manager,
|
32
|
+
as this populates the dg_stack in delta_generator correctly.
|
32
33
|
|
33
|
-
This does not detect the edge case in which someone calls, for example,
|
34
|
-
|
35
|
-
|
34
|
+
This does not detect the edge case in which someone calls, for example,
|
35
|
+
`with st.sidebar` inside of a dialog function and opens a dialog in there, as
|
36
|
+
`with st.sidebar` pushes the new DeltaGenerator to the stack. In order to check for
|
37
|
+
that edge case, we could try to check all DeltaGenerators in the stack, and not only
|
38
|
+
the last one. Since we deem this to be an edge case, we lean towards simplicity
|
39
|
+
here.
|
36
40
|
|
37
41
|
Raises
|
38
42
|
------
|
@@ -54,25 +58,34 @@ def _dialog_decorator(
|
|
54
58
|
) -> F:
|
55
59
|
if title is None or title == "":
|
56
60
|
raise StreamlitAPIException(
|
57
|
-
|
61
|
+
"A non-empty `title` argument has to be provided for dialogs, for example "
|
62
|
+
'`@st.dialog("Example Title")`.'
|
58
63
|
)
|
59
64
|
|
60
65
|
@wraps(non_optional_func)
|
61
66
|
def wrap(*args, **kwargs) -> None:
|
62
67
|
_assert_no_nested_dialogs()
|
63
68
|
# Call the Dialog on the event_dg because it lives outside of the normal
|
64
|
-
# Streamlit UI flow. For example, if it is called from the sidebar, it should
|
65
|
-
# inherit the sidebar theming.
|
69
|
+
# Streamlit UI flow. For example, if it is called from the sidebar, it should
|
70
|
+
# not inherit the sidebar theming.
|
66
71
|
dialog = event_dg._dialog(title=title, dismissible=True, width=width)
|
67
72
|
dialog.open()
|
68
73
|
|
69
74
|
def dialog_content() -> None:
|
70
|
-
# if the dialog should be closed, st.rerun() has to be called
|
75
|
+
# if the dialog should be closed, st.rerun() has to be called
|
76
|
+
# (same behavior as with st.fragment)
|
71
77
|
_ = non_optional_func(*args, **kwargs)
|
72
78
|
return None
|
73
79
|
|
74
|
-
# the fragment decorator has multiple return types so that you can pass
|
75
|
-
|
80
|
+
# the fragment decorator has multiple return types so that you can pass
|
81
|
+
# arguments to it. Here we know the return type, so we cast
|
82
|
+
fragmented_dialog_content = cast(
|
83
|
+
Callable[[], None],
|
84
|
+
_fragment(
|
85
|
+
dialog_content, additional_hash_info=non_optional_func.__qualname__
|
86
|
+
),
|
87
|
+
)
|
88
|
+
|
76
89
|
with dialog:
|
77
90
|
fragmented_dialog_content()
|
78
91
|
return None
|
@@ -86,21 +99,23 @@ def dialog_decorator(
|
|
86
99
|
) -> Callable[[F], F]: ...
|
87
100
|
|
88
101
|
|
89
|
-
# 'title' can be a function since `dialog_decorator` is a decorator.
|
90
|
-
# to make the user-doc more friendly as
|
91
|
-
#
|
92
|
-
#
|
102
|
+
# 'title' can be a function since `dialog_decorator` is a decorator.
|
103
|
+
# We just call it 'title' here though to make the user-doc more friendly as
|
104
|
+
# we want the user to pass a title, not a function. The user is supposed to
|
105
|
+
# call it like @st.dialog("my_title") , which makes 'title' a positional arg, hence
|
106
|
+
# this 'trick'. The overload is required to have a good type hint for the decorated
|
107
|
+
# function args.
|
93
108
|
@overload
|
94
109
|
def dialog_decorator(title: F, *, width: DialogWidth = "small") -> F: ...
|
95
110
|
|
96
111
|
|
97
|
-
@gather_metrics("
|
112
|
+
@gather_metrics("dialog")
|
98
113
|
def dialog_decorator(
|
99
114
|
title: F | str, *, width: DialogWidth = "small"
|
100
115
|
) -> F | Callable[[F], F]:
|
101
116
|
"""Function decorator to create a modal dialog.
|
102
117
|
|
103
|
-
A function decorated with ``@st.
|
118
|
+
A function decorated with ``@st.dialog`` becomes a dialog
|
104
119
|
function. When you call a dialog function, Streamlit inserts a modal dialog
|
105
120
|
into your app. Streamlit element commands called within the dialog function
|
106
121
|
render inside the modal dialog.
|
@@ -115,7 +130,7 @@ def dialog_decorator(
|
|
115
130
|
dialog programmatically, call ``st.rerun()`` explicitly inside of the
|
116
131
|
dialog function.
|
117
132
|
|
118
|
-
``st.
|
133
|
+
``st.dialog`` inherits behavior from |st.fragment|_.
|
119
134
|
When a user interacts with an input widget created inside a dialog function,
|
120
135
|
Streamlit only reruns the dialog function instead of the full script.
|
121
136
|
|
@@ -128,13 +143,10 @@ def dialog_decorator(
|
|
128
143
|
|
129
144
|
.. warning::
|
130
145
|
Only one dialog function may be called in a script run, which means
|
131
|
-
that only one dialog can be open at any given time.
|
132
|
-
also a fragment, all fragment limitations apply. Dialogs can't contain
|
133
|
-
fragments, and fragments can't contain dialogs. Using dialogs in widget
|
134
|
-
callback functions is not supported.
|
146
|
+
that only one dialog can be open at any given time.
|
135
147
|
|
136
|
-
.. |st.
|
137
|
-
.. _st.
|
148
|
+
.. |st.fragment| replace:: ``st.fragment``
|
149
|
+
.. _st.fragment: https://docs.streamlit.io/develop/api-reference/execution-flow/st.fragment
|
138
150
|
|
139
151
|
Parameters
|
140
152
|
----------
|
@@ -147,7 +159,7 @@ def dialog_decorator(
|
|
147
159
|
|
148
160
|
Examples
|
149
161
|
--------
|
150
|
-
The following example demonstrates the basic usage of ``@st.
|
162
|
+
The following example demonstrates the basic usage of ``@st.dialog``.
|
151
163
|
In this app, clicking "**A**" or "**B**" will open a modal dialog and prompt you
|
152
164
|
to enter a reason for your vote. In the modal dialog, click "**Submit**" to record
|
153
165
|
your vote into Session State and rerun the app. This will close the modal dialog
|
@@ -155,7 +167,7 @@ def dialog_decorator(
|
|
155
167
|
|
156
168
|
>>> import streamlit as st
|
157
169
|
>>>
|
158
|
-
>>> @st.
|
170
|
+
>>> @st.dialog("Cast your vote")
|
159
171
|
>>> def vote(item):
|
160
172
|
>>> st.write(f"Why is {item} your favorite?")
|
161
173
|
>>> reason = st.text_input("Because...")
|
@@ -189,3 +201,25 @@ def dialog_decorator(
|
|
189
201
|
|
190
202
|
func: F = func_or_title
|
191
203
|
return _dialog_decorator(func, "", width=width)
|
204
|
+
|
205
|
+
|
206
|
+
@overload
|
207
|
+
def experimental_dialog_decorator(
|
208
|
+
title: str, *, width: DialogWidth = "small"
|
209
|
+
) -> Callable[[F], F]: ...
|
210
|
+
|
211
|
+
|
212
|
+
# 'title' can be a function since `dialog_decorator` is a decorator. We just call it
|
213
|
+
# 'title' here though to make the user-doc more friendly as we want the user to pass a
|
214
|
+
# title, not a function. The user is supposed to call it like @st.dialog("my_title"),
|
215
|
+
# which makes 'title' a positional arg, hence this 'trick'. The overload is required to
|
216
|
+
# have a good type hint for the decorated function args.
|
217
|
+
@overload
|
218
|
+
def experimental_dialog_decorator(title: F, *, width: DialogWidth = "small") -> F: ...
|
219
|
+
|
220
|
+
|
221
|
+
@gather_metrics("experimental_dialog")
|
222
|
+
def experimental_dialog_decorator(
|
223
|
+
title: F | str, *, width: DialogWidth = "small"
|
224
|
+
) -> F | Callable[[F], F]:
|
225
|
+
return dialog_decorator(title, width=width)
|
streamlit/elements/json.py
CHANGED
@@ -18,6 +18,7 @@ import json
|
|
18
18
|
from typing import TYPE_CHECKING, Any, cast
|
19
19
|
|
20
20
|
from streamlit.proto.Json_pb2 import Json as JsonProto
|
21
|
+
from streamlit.runtime.context import StreamlitCookies, StreamlitHeaders
|
21
22
|
from streamlit.runtime.metrics_util import gather_metrics
|
22
23
|
from streamlit.runtime.state import QueryParamsProxy, SessionStateProxy
|
23
24
|
from streamlit.user_info import UserInfoProxy
|
@@ -78,7 +79,16 @@ class JsonMixin:
|
|
78
79
|
"""
|
79
80
|
import streamlit as st
|
80
81
|
|
81
|
-
if isinstance(
|
82
|
+
if isinstance(
|
83
|
+
body,
|
84
|
+
(
|
85
|
+
SessionStateProxy,
|
86
|
+
UserInfoProxy,
|
87
|
+
QueryParamsProxy,
|
88
|
+
StreamlitHeaders,
|
89
|
+
StreamlitCookies,
|
90
|
+
),
|
91
|
+
):
|
82
92
|
body = body.to_dict()
|
83
93
|
|
84
94
|
if not isinstance(body, str):
|
@@ -293,8 +293,9 @@ class ChatMixin:
|
|
293
293
|
height: 350px
|
294
294
|
|
295
295
|
The chat input can also be used inline by nesting it inside any layout
|
296
|
-
container (container, columns, tabs, sidebar, etc). Create
|
297
|
-
interfaces embedded next to other content or have multiple
|
296
|
+
container (container, columns, tabs, sidebar, etc) or fragment. Create
|
297
|
+
chat interfaces embedded next to other content or have multiple
|
298
|
+
chatbots!
|
298
299
|
|
299
300
|
>>> import streamlit as st
|
300
301
|
>>>
|
streamlit/elements/write.py
CHANGED
@@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Any, Callable, Final, Generator, Iterable, Lis
|
|
24
24
|
from streamlit import dataframe_util, type_util
|
25
25
|
from streamlit.errors import StreamlitAPIException
|
26
26
|
from streamlit.logger import get_logger
|
27
|
+
from streamlit.runtime.context import StreamlitCookies, StreamlitHeaders
|
27
28
|
from streamlit.runtime.metrics_util import gather_metrics
|
28
29
|
from streamlit.runtime.state import QueryParamsProxy, SessionStateProxy
|
29
30
|
from streamlit.string_util import (
|
@@ -36,7 +37,6 @@ from streamlit.user_info import UserInfoProxy
|
|
36
37
|
if TYPE_CHECKING:
|
37
38
|
from streamlit.delta_generator import DeltaGenerator
|
38
39
|
|
39
|
-
|
40
40
|
# Special methods:
|
41
41
|
HELP_TYPES: Final[tuple[type[Any], ...]] = (
|
42
42
|
types.BuiltinFunctionType,
|
@@ -441,7 +441,16 @@ class WriteMixin:
|
|
441
441
|
dot = vis_utils.model_to_dot(arg)
|
442
442
|
self.dg.graphviz_chart(dot.to_string())
|
443
443
|
elif isinstance(
|
444
|
-
arg,
|
444
|
+
arg,
|
445
|
+
(
|
446
|
+
dict,
|
447
|
+
list,
|
448
|
+
SessionStateProxy,
|
449
|
+
UserInfoProxy,
|
450
|
+
QueryParamsProxy,
|
451
|
+
StreamlitHeaders,
|
452
|
+
StreamlitCookies,
|
453
|
+
),
|
445
454
|
):
|
446
455
|
flush_buffer()
|
447
456
|
self.dg.json(arg)
|
streamlit/errors.py
CHANGED
@@ -38,6 +38,21 @@ class DeprecationError(Error):
|
|
38
38
|
pass
|
39
39
|
|
40
40
|
|
41
|
+
class FragmentStorageKeyError(Error, KeyError):
|
42
|
+
"""A KeyError raised when a KeyError is encountered during a FragmentStorage
|
43
|
+
operation."""
|
44
|
+
|
45
|
+
pass
|
46
|
+
|
47
|
+
|
48
|
+
class FragmentHandledException(Exception):
|
49
|
+
"""An exception that is raised by the fragment
|
50
|
+
when it has handled the exception itself.
|
51
|
+
"""
|
52
|
+
|
53
|
+
pass
|
54
|
+
|
55
|
+
|
41
56
|
class NoStaticFiles(Error):
|
42
57
|
pass
|
43
58
|
|
streamlit/runtime/app_session.py
CHANGED
@@ -122,6 +122,7 @@ class AppSession:
|
|
122
122
|
service that a Streamlit Runtime is running in wants to tie the lifecycle of
|
123
123
|
a Streamlit session to some other session-like object that it manages.
|
124
124
|
"""
|
125
|
+
|
125
126
|
# Each AppSession has a unique string ID.
|
126
127
|
self.id = session_id_override or str(uuid.uuid4())
|
127
128
|
|
@@ -361,7 +362,7 @@ class AppSession:
|
|
361
362
|
client_state.widget_states,
|
362
363
|
client_state.page_script_hash,
|
363
364
|
client_state.page_name,
|
364
|
-
|
365
|
+
fragment_id=fragment_id if fragment_id else None,
|
365
366
|
)
|
366
367
|
else:
|
367
368
|
rerun_data = RerunData()
|
@@ -369,7 +370,7 @@ class AppSession:
|
|
369
370
|
if self._scriptrunner is not None:
|
370
371
|
if (
|
371
372
|
bool(config.get_option("runner.fastReruns"))
|
372
|
-
and not rerun_data.
|
373
|
+
and not rerun_data.fragment_id
|
373
374
|
):
|
374
375
|
# If fastReruns is enabled and this is *not* a rerun of a fragment,
|
375
376
|
# we don't send rerun requests to our existing ScriptRunner. Instead, we
|
@@ -477,8 +478,9 @@ class AppSession:
|
|
477
478
|
exception: BaseException | None = None,
|
478
479
|
client_state: ClientState | None = None,
|
479
480
|
page_script_hash: str | None = None,
|
480
|
-
fragment_ids_this_run:
|
481
|
+
fragment_ids_this_run: list[str] | None = None,
|
481
482
|
pages: dict[PageHash, PageInfo] | None = None,
|
483
|
+
clear_forward_msg_queue: bool = True,
|
482
484
|
) -> None:
|
483
485
|
"""Called when our ScriptRunner emits an event.
|
484
486
|
|
@@ -496,6 +498,7 @@ class AppSession:
|
|
496
498
|
page_script_hash,
|
497
499
|
fragment_ids_this_run,
|
498
500
|
pages,
|
501
|
+
clear_forward_msg_queue,
|
499
502
|
)
|
500
503
|
)
|
501
504
|
|
@@ -507,8 +510,9 @@ class AppSession:
|
|
507
510
|
exception: BaseException | None = None,
|
508
511
|
client_state: ClientState | None = None,
|
509
512
|
page_script_hash: str | None = None,
|
510
|
-
fragment_ids_this_run:
|
513
|
+
fragment_ids_this_run: list[str] | None = None,
|
511
514
|
pages: dict[PageHash, PageInfo] | None = None,
|
515
|
+
clear_forward_msg_queue: bool = True,
|
512
516
|
) -> None:
|
513
517
|
"""Handle a ScriptRunner event.
|
514
518
|
|
@@ -540,10 +544,14 @@ class AppSession:
|
|
540
544
|
A hash of the script path corresponding to the page currently being
|
541
545
|
run. Set only for the SCRIPT_STARTED event.
|
542
546
|
|
543
|
-
fragment_ids_this_run :
|
547
|
+
fragment_ids_this_run : list[str] | None
|
544
548
|
The fragment IDs of the fragments being executed in this script run. Only
|
545
549
|
set for the SCRIPT_STARTED event. If this value is falsy, this script run
|
546
550
|
must be for the full script.
|
551
|
+
|
552
|
+
clear_forward_msg_queue : bool
|
553
|
+
If set (the default), clears the queue of forward messages to be sent to the
|
554
|
+
browser. Set only for the SCRIPT_STARTED event.
|
547
555
|
"""
|
548
556
|
|
549
557
|
assert (
|
@@ -569,13 +577,7 @@ class AppSession:
|
|
569
577
|
page_script_hash is not None
|
570
578
|
), "page_script_hash must be set for the SCRIPT_STARTED event"
|
571
579
|
|
572
|
-
|
573
|
-
# anything from a previous script run that has yet to be sent to the browser
|
574
|
-
# will be overwritten. For fragment runs, however, we don't want to do this
|
575
|
-
# as the ForwardMsgs in the queue may not correspond to the running
|
576
|
-
# fragment, so dropping the messages may result in the app missing
|
577
|
-
# information.
|
578
|
-
if not fragment_ids_this_run:
|
580
|
+
if clear_forward_msg_queue:
|
579
581
|
self._clear_queue()
|
580
582
|
|
581
583
|
self._enqueue_forward_msg(
|
@@ -677,7 +679,7 @@ class AppSession:
|
|
677
679
|
def _create_new_session_message(
|
678
680
|
self,
|
679
681
|
page_script_hash: str,
|
680
|
-
fragment_ids_this_run:
|
682
|
+
fragment_ids_this_run: list[str] | None = None,
|
681
683
|
pages: dict[PageHash, PageInfo] | None = None,
|
682
684
|
) -> ForwardMsg:
|
683
685
|
"""Create and return a new_session ForwardMsg."""
|