streamlit-nightly 1.32.3.dev20240326__py2.py3-none-any.whl → 1.32.3.dev20240329__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 +6 -3
- streamlit/components/v1/__init__.py +3 -17
- streamlit/components/v1/{custom_component.py → components.py} +159 -11
- streamlit/delta_generator.py +5 -0
- streamlit/elements/html.py +83 -0
- streamlit/elements/widgets/time_widgets.py +5 -21
- streamlit/errors.py +0 -6
- streamlit/proto/AutoRerun_pb2.py +25 -0
- streamlit/proto/AutoRerun_pb2.pyi +48 -0
- streamlit/proto/ClientState_pb2.py +3 -3
- streamlit/proto/ClientState_pb2.pyi +4 -1
- streamlit/proto/Delta_pb2.py +2 -2
- streamlit/proto/Delta_pb2.pyi +4 -1
- streamlit/proto/Element_pb2.py +4 -3
- streamlit/proto/Element_pb2.pyi +9 -4
- streamlit/proto/ForwardMsg_pb2.py +10 -9
- streamlit/proto/ForwardMsg_pb2.pyi +12 -3
- streamlit/proto/Html_pb2.py +26 -0
- streamlit/proto/Html_pb2.pyi +45 -0
- streamlit/proto/NewSession_pb2.py +24 -24
- streamlit/proto/NewSession_pb2.pyi +8 -1
- streamlit/proto/PageProfile_pb2.py +6 -6
- streamlit/proto/PageProfile_pb2.pyi +4 -1
- streamlit/runtime/app_session.py +67 -24
- streamlit/runtime/caching/cache_data_api.py +3 -3
- streamlit/runtime/caching/cache_resource_api.py +2 -2
- streamlit/runtime/fragment.py +239 -0
- streamlit/runtime/metrics_util.py +17 -9
- streamlit/runtime/runtime.py +6 -12
- streamlit/runtime/scriptrunner/script_requests.py +53 -37
- streamlit/runtime/scriptrunner/script_run_context.py +15 -2
- streamlit/runtime/scriptrunner/script_runner.py +63 -14
- streamlit/runtime/state/common.py +2 -0
- streamlit/runtime/state/session_state.py +51 -7
- streamlit/runtime/state/widgets.py +10 -2
- streamlit/static/asset-manifest.json +26 -24
- streamlit/static/index.html +1 -1
- streamlit/static/static/css/2411.8b8f33d6.chunk.css +1 -0
- streamlit/static/static/css/3092.95a45cfe.chunk.css +1 -0
- streamlit/static/static/css/43.e3b876c5.chunk.css +1 -0
- streamlit/static/static/js/1074.73973756.chunk.js +1 -0
- streamlit/static/static/js/1451.3b0a3e31.chunk.js +1 -0
- streamlit/static/static/js/1792.b8efa879.chunk.js +1 -0
- streamlit/static/static/js/2411.a8823789.chunk.js +2 -0
- streamlit/static/static/js/2634.1249dc7a.chunk.js +1 -0
- streamlit/static/static/js/2736.779ccbc1.chunk.js +2 -0
- streamlit/static/static/js/2736.779ccbc1.chunk.js.LICENSE.txt +60 -0
- streamlit/static/static/js/{3092.3d4df25e.chunk.js → 3092.ad569cc8.chunk.js} +1 -1
- streamlit/static/static/js/3513.e3e7300a.chunk.js +1 -0
- streamlit/static/static/js/4132.49bf3f2c.chunk.js.LICENSE.txt +4 -4
- streamlit/static/static/js/4177.69f9f18d.chunk.js +1 -0
- streamlit/static/static/js/4319.a6745434.chunk.js +1 -0
- streamlit/static/static/js/{4477.2555c11a.chunk.js → 4477.e10e4373.chunk.js} +1 -1
- streamlit/static/static/js/{4666.99f3abc3.chunk.js → 4666.b694c5a9.chunk.js} +1 -1
- streamlit/static/static/js/5106.44f0ff51.chunk.js +1 -0
- streamlit/static/static/js/5379.6571574f.chunk.js +1 -0
- streamlit/static/static/js/6013.8e80e091.chunk.js +1 -0
- streamlit/static/static/js/6718.802da17e.chunk.js +1 -0
- streamlit/static/static/js/7175.be4076bc.chunk.js +1 -0
- streamlit/static/static/js/{7602.f0420392.chunk.js → 7602.6175e969.chunk.js} +1 -1
- streamlit/static/static/js/{8492.e6dab83f.chunk.js → 8492.f56c9d4c.chunk.js} +1 -1
- streamlit/static/static/js/8691.9ccf7f89.chunk.js +1 -0
- streamlit/static/static/js/937.a1248039.chunk.js +2 -0
- streamlit/static/static/js/937.a1248039.chunk.js.LICENSE.txt +1 -0
- streamlit/static/static/js/main.356407e8.js +2 -0
- streamlit/testing/v1/local_script_runner.py +2 -0
- streamlit/time_util.py +88 -0
- streamlit/web/server/component_request_handler.py +2 -2
- streamlit/web/server/server.py +2 -1
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/RECORD +77 -73
- streamlit/components/lib/__init__.py +0 -13
- streamlit/components/lib/local_component_registry.py +0 -82
- streamlit/components/types/__init__.py +0 -13
- streamlit/components/types/base_component_registry.py +0 -98
- streamlit/components/types/base_custom_component.py +0 -137
- streamlit/components/v1/component_registry.py +0 -103
- streamlit/static/static/css/2411.81b3d18f.chunk.css +0 -1
- streamlit/static/static/css/3092.f719e2e6.chunk.css +0 -1
- streamlit/static/static/css/43.c24b25fa.chunk.css +0 -1
- streamlit/static/static/js/1074.71719df6.chunk.js +0 -1
- streamlit/static/static/js/1451.e3be1711.chunk.js +0 -1
- streamlit/static/static/js/1792.16c16498.chunk.js +0 -1
- streamlit/static/static/js/2411.b389bf4e.chunk.js +0 -2
- streamlit/static/static/js/2736.17fbad1a.chunk.js +0 -2
- streamlit/static/static/js/2736.17fbad1a.chunk.js.LICENSE.txt +0 -60
- streamlit/static/static/js/3513.57cff89c.chunk.js +0 -1
- streamlit/static/static/js/4177.ab9a7aa1.chunk.js +0 -1
- streamlit/static/static/js/4319.213fc321.chunk.js +0 -1
- streamlit/static/static/js/5106.22187bfc.chunk.js +0 -1
- streamlit/static/static/js/5379.e466522d.chunk.js +0 -1
- streamlit/static/static/js/6013.75c92264.chunk.js +0 -1
- streamlit/static/static/js/6718.97945fc6.chunk.js +0 -1
- streamlit/static/static/js/7175.8c1b4d38.chunk.js +0 -1
- streamlit/static/static/js/8691.24a5792f.chunk.js +0 -1
- streamlit/static/static/js/main.7fde7092.js +0 -2
- /streamlit/static/static/js/{2411.b389bf4e.chunk.js.LICENSE.txt → 2411.a8823789.chunk.js.LICENSE.txt} +0 -0
- /streamlit/static/static/js/{main.7fde7092.js.LICENSE.txt → main.356407e8.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.32.3.dev20240326.data → streamlit_nightly-1.32.3.dev20240329.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,7 @@
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
17
|
import threading
|
18
|
-
from dataclasses import dataclass
|
18
|
+
from dataclasses import dataclass, field
|
19
19
|
from enum import Enum
|
20
20
|
from typing import cast
|
21
21
|
|
@@ -46,6 +46,7 @@ class RerunData:
|
|
46
46
|
widget_states: WidgetStates | None = None
|
47
47
|
page_script_hash: str = ""
|
48
48
|
page_name: str = ""
|
49
|
+
fragment_id_queue: list[str] = field(default_factory=list)
|
49
50
|
|
50
51
|
def __repr__(self) -> str:
|
51
52
|
return util.repr_(self)
|
@@ -103,41 +104,46 @@ class ScriptRequests:
|
|
103
104
|
return False
|
104
105
|
|
105
106
|
if self._state == ScriptRequestType.CONTINUE:
|
106
|
-
#
|
107
|
-
# unconditionally
|
107
|
+
# The script is currently running, and we haven't received a request to
|
108
|
+
# rerun it as of yet. We can handle a rerun request unconditionally so
|
109
|
+
# just change self._state and set self._rerun_data.
|
108
110
|
self._state = ScriptRequestType.RERUN
|
109
111
|
self._rerun_data = new_data
|
110
112
|
return True
|
111
113
|
|
112
114
|
if self._state == ScriptRequestType.RERUN:
|
113
|
-
#
|
114
|
-
#
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
)
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
115
|
+
# We already have an existing Rerun request, so we can coalesce the new
|
116
|
+
# rerun request into the existing one.
|
117
|
+
|
118
|
+
coalesced_states = coalesce_widget_states(
|
119
|
+
self._rerun_data.widget_states, new_data.widget_states
|
120
|
+
)
|
121
|
+
|
122
|
+
if new_data.fragment_id_queue:
|
123
|
+
# This RERUN request corresponds to a fragment run. We append the
|
124
|
+
# new fragment ID to the end of the current fragment_id_queue if it
|
125
|
+
# isn't already contained in it.
|
126
|
+
fragment_id_queue = [*self._rerun_data.fragment_id_queue]
|
127
|
+
if (
|
128
|
+
# new_data.fragment_id_queue is always a singleton
|
129
|
+
(new_fragment_id := new_data.fragment_id_queue[0])
|
130
|
+
not in fragment_id_queue
|
131
|
+
):
|
132
|
+
fragment_id_queue.append(new_fragment_id)
|
133
|
+
else:
|
134
|
+
# Otherwise, this is a request to rerun the full script, so we want
|
135
|
+
# to clear out any fragments we have queued to run since they'll all
|
136
|
+
# be run with the full script anyway.
|
137
|
+
fragment_id_queue = []
|
138
|
+
|
139
|
+
self._rerun_data = RerunData(
|
140
|
+
query_string=new_data.query_string,
|
141
|
+
widget_states=coalesced_states,
|
142
|
+
page_script_hash=new_data.page_script_hash,
|
143
|
+
page_name=new_data.page_name,
|
144
|
+
fragment_id_queue=fragment_id_queue,
|
145
|
+
)
|
146
|
+
|
141
147
|
return True
|
142
148
|
|
143
149
|
# We'll never get here
|
@@ -146,22 +152,32 @@ class ScriptRequests:
|
|
146
152
|
def on_scriptrunner_yield(self) -> ScriptRequest | None:
|
147
153
|
"""Called by the ScriptRunner when it's at a yield point.
|
148
154
|
|
149
|
-
If we have no request
|
155
|
+
If we have no request or a RERUN request corresponding to one or more fragments,
|
156
|
+
return None.
|
150
157
|
|
151
|
-
If we have a RERUN request, return the request and set our internal
|
158
|
+
If we have a (full script) RERUN request, return the request and set our internal
|
152
159
|
state to CONTINUE.
|
153
160
|
|
154
161
|
If we have a STOP request, return the request and remain stopped.
|
155
162
|
"""
|
156
|
-
if self._state == ScriptRequestType.CONTINUE
|
157
|
-
#
|
158
|
-
#
|
159
|
-
|
163
|
+
if self._state == ScriptRequestType.CONTINUE or (
|
164
|
+
# Reruns corresponding to fragments should *not* cancel the current script
|
165
|
+
# run as doing so will affect elements outside of the fragment.
|
166
|
+
self._state == ScriptRequestType.RERUN
|
167
|
+
and self._rerun_data.fragment_id_queue
|
168
|
+
):
|
169
|
+
# We avoid taking the lock in the common cases of having no request and
|
170
|
+
# having a RERUN request corresponding to >=1 fragments. If a STOP or
|
171
|
+
# (full script) RERUN request is received between the `if` and `return`, it
|
172
|
+
# will be handled at the next `on_scriptrunner_yield`, or when
|
160
173
|
# `on_scriptrunner_ready` is called.
|
161
174
|
return None
|
162
175
|
|
163
176
|
with self._lock:
|
164
177
|
if self._state == ScriptRequestType.RERUN:
|
178
|
+
if self._rerun_data.fragment_id_queue:
|
179
|
+
return None
|
180
|
+
|
165
181
|
self._state = ScriptRequestType.CONTINUE
|
166
182
|
return ScriptRequest(ScriptRequestType.RERUN, self._rerun_data)
|
167
183
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
17
17
|
import collections
|
18
18
|
import threading
|
19
19
|
from dataclasses import dataclass, field
|
20
|
-
from typing import Callable, Counter, Dict, Final, Union
|
20
|
+
from typing import TYPE_CHECKING, Callable, Counter, Dict, Final, Union
|
21
21
|
from urllib import parse
|
22
22
|
|
23
23
|
from typing_extensions import TypeAlias
|
@@ -31,6 +31,9 @@ from streamlit.runtime.scriptrunner.script_requests import ScriptRequests
|
|
31
31
|
from streamlit.runtime.state import SafeSessionState
|
32
32
|
from streamlit.runtime.uploaded_file_manager import UploadedFileManager
|
33
33
|
|
34
|
+
if TYPE_CHECKING:
|
35
|
+
from streamlit.runtime.fragment import FragmentStorage
|
36
|
+
|
34
37
|
_LOGGER: Final = get_logger(__name__)
|
35
38
|
|
36
39
|
UserInfo: TypeAlias = Dict[str, Union[str, None]]
|
@@ -59,6 +62,7 @@ class ScriptRunContext:
|
|
59
62
|
main_script_path: str
|
60
63
|
page_script_hash: str
|
61
64
|
user_info: UserInfo
|
65
|
+
fragment_storage: "FragmentStorage"
|
62
66
|
|
63
67
|
gather_usage_stats: bool = False
|
64
68
|
command_tracking_deactivated: bool = False
|
@@ -71,12 +75,19 @@ class ScriptRunContext:
|
|
71
75
|
form_ids_this_run: set[str] = field(default_factory=set)
|
72
76
|
cursors: dict[int, "streamlit.cursor.RunningCursor"] = field(default_factory=dict)
|
73
77
|
script_requests: ScriptRequests | None = None
|
78
|
+
current_fragment_id: str | None = None
|
79
|
+
fragment_ids_this_run: set[str] | None = None
|
74
80
|
|
75
81
|
# TODO(willhuang1997): Remove this variable when experimental query params are removed
|
76
82
|
_experimental_query_params_used = False
|
77
83
|
_production_query_params_used = False
|
78
84
|
|
79
|
-
def reset(
|
85
|
+
def reset(
|
86
|
+
self,
|
87
|
+
query_string: str = "",
|
88
|
+
page_script_hash: str = "",
|
89
|
+
fragment_ids_this_run: set[str] | None = None,
|
90
|
+
) -> None:
|
80
91
|
self.cursors = {}
|
81
92
|
self.widget_ids_this_run = set()
|
82
93
|
self.widget_user_keys_this_run = set()
|
@@ -89,6 +100,8 @@ class ScriptRunContext:
|
|
89
100
|
self.command_tracking_deactivated: bool = False
|
90
101
|
self.tracked_commands = []
|
91
102
|
self.tracked_commands_counter = collections.Counter()
|
103
|
+
self.current_fragment_id = None
|
104
|
+
self.fragment_ids_this_run = fragment_ids_this_run
|
92
105
|
|
93
106
|
parsed_query_params = parse.parse_qs(query_string, keep_blank_values=True)
|
94
107
|
with self.session_state.query_params() as qp:
|
@@ -21,7 +21,7 @@ import types
|
|
21
21
|
from contextlib import contextmanager
|
22
22
|
from enum import Enum
|
23
23
|
from timeit import default_timer as timer
|
24
|
-
from typing import Callable, Final
|
24
|
+
from typing import TYPE_CHECKING, Callable, Final
|
25
25
|
|
26
26
|
from blinker import Signal
|
27
27
|
|
@@ -49,6 +49,9 @@ from streamlit.runtime.state import (
|
|
49
49
|
from streamlit.runtime.uploaded_file_manager import UploadedFileManager
|
50
50
|
from streamlit.vendor.ipython.modified_sys_path import modified_sys_path
|
51
51
|
|
52
|
+
if TYPE_CHECKING:
|
53
|
+
from streamlit.runtime.fragment import FragmentStorage
|
54
|
+
|
52
55
|
_LOGGER: Final = get_logger(__name__)
|
53
56
|
|
54
57
|
|
@@ -68,6 +71,10 @@ class ScriptRunnerEvent(Enum):
|
|
68
71
|
# The script run stopped in order to start a script run with newer widget state.
|
69
72
|
SCRIPT_STOPPED_FOR_RERUN = "SCRIPT_STOPPED_FOR_RERUN"
|
70
73
|
|
74
|
+
# The script run corresponding to a fragment ran to completion, or was interrupted
|
75
|
+
# by the user.
|
76
|
+
FRAGMENT_STOPPED_WITH_SUCCESS = "FRAGMENT_STOPPED_WITH_SUCCESS"
|
77
|
+
|
71
78
|
# The ScriptRunner is done processing the ScriptEventQueue and
|
72
79
|
# is shut down.
|
73
80
|
SHUTDOWN = "SHUTDOWN"
|
@@ -106,6 +113,7 @@ class ScriptRunner:
|
|
106
113
|
script_cache: ScriptCache,
|
107
114
|
initial_rerun_data: RerunData,
|
108
115
|
user_info: dict[str, str | None],
|
116
|
+
fragment_storage: "FragmentStorage",
|
109
117
|
):
|
110
118
|
"""Initialize the ScriptRunner.
|
111
119
|
|
@@ -119,12 +127,18 @@ class ScriptRunner:
|
|
119
127
|
main_script_path
|
120
128
|
Path to our main app script.
|
121
129
|
|
130
|
+
session_state
|
131
|
+
The AppSession's SessionState instance.
|
132
|
+
|
122
133
|
uploaded_file_mgr
|
123
134
|
The File manager to store the data uploaded by the file_uploader widget.
|
124
135
|
|
125
136
|
script_cache
|
126
137
|
A ScriptCache instance.
|
127
138
|
|
139
|
+
initial_rerun_data
|
140
|
+
RerunData to initialize this ScriptRunner with.
|
141
|
+
|
128
142
|
user_info
|
129
143
|
A dict that contains information about the current user. For now,
|
130
144
|
it only contains the user's email address.
|
@@ -136,16 +150,18 @@ class ScriptRunner:
|
|
136
150
|
Information about the current user is optionally provided when a
|
137
151
|
websocket connection is initialized via the "X-Streamlit-User" header.
|
138
152
|
|
153
|
+
fragment_storage
|
154
|
+
The AppSession's FragmentStorage instance.
|
139
155
|
"""
|
140
156
|
self._session_id = session_id
|
141
157
|
self._main_script_path = main_script_path
|
142
|
-
self._uploaded_file_mgr = uploaded_file_mgr
|
143
|
-
self._script_cache = script_cache
|
144
|
-
self._user_info = user_info
|
145
|
-
|
146
158
|
self._session_state = SafeSessionState(
|
147
159
|
session_state, yield_callback=self._maybe_handle_execution_control_request
|
148
160
|
)
|
161
|
+
self._uploaded_file_mgr = uploaded_file_mgr
|
162
|
+
self._script_cache = script_cache
|
163
|
+
self._user_info = user_info
|
164
|
+
self._fragment_storage = fragment_storage
|
149
165
|
|
150
166
|
self._requests = ScriptRequests()
|
151
167
|
self._requests.request_rerun(initial_rerun_data)
|
@@ -277,6 +293,7 @@ class ScriptRunner:
|
|
277
293
|
page_script_hash="",
|
278
294
|
user_info=self._user_info,
|
279
295
|
gather_usage_stats=bool(config.get_option("browser.gatherUsageStats")),
|
296
|
+
fragment_storage=self._fragment_storage,
|
280
297
|
)
|
281
298
|
add_script_run_ctx(threading.current_thread(), ctx)
|
282
299
|
|
@@ -394,6 +411,9 @@ class ScriptRunner:
|
|
394
411
|
The RerunData to use.
|
395
412
|
|
396
413
|
"""
|
414
|
+
# Avoid circular imports
|
415
|
+
from streamlit.delta_generator import dg_stack
|
416
|
+
|
397
417
|
assert self._is_in_script_thread()
|
398
418
|
|
399
419
|
# An explicit loop instead of recursion to avoid stack overflows
|
@@ -441,16 +461,20 @@ class ScriptRunner:
|
|
441
461
|
else main_page_info["page_script_hash"]
|
442
462
|
)
|
443
463
|
|
464
|
+
fragment_ids_this_run = set(rerun_data.fragment_id_queue)
|
465
|
+
|
444
466
|
ctx = self._get_script_run_ctx()
|
445
467
|
ctx.reset(
|
446
468
|
query_string=rerun_data.query_string,
|
447
469
|
page_script_hash=page_script_hash,
|
470
|
+
fragment_ids_this_run=fragment_ids_this_run,
|
448
471
|
)
|
449
472
|
|
450
473
|
self.on_event.send(
|
451
474
|
self,
|
452
475
|
event=ScriptRunnerEvent.SCRIPT_STARTED,
|
453
476
|
page_script_hash=page_script_hash,
|
477
|
+
fragment_ids_this_run=fragment_ids_this_run,
|
454
478
|
)
|
455
479
|
|
456
480
|
# Compile the script. Any errors thrown here will be surfaced
|
@@ -497,6 +521,16 @@ class ScriptRunner:
|
|
497
521
|
# is interrupted by a RerunException.
|
498
522
|
rerun_exception_data: RerunData | None = None
|
499
523
|
|
524
|
+
# Saving and restoring our original cursors/dg_stack is needed
|
525
|
+
# specifically to handle the case where a RerunException is raised while
|
526
|
+
# running a fragment. In this case, we need to restore both to their states
|
527
|
+
# at the start of the script run to ensure that we write to the correct
|
528
|
+
# places in the app during the rerun (without this, ctx.cursors and dg_stack
|
529
|
+
# will still be set to the snapshots they were restored from when running
|
530
|
+
# the fragment).
|
531
|
+
original_cursors = ctx.cursors
|
532
|
+
original_dg_stack = dg_stack.get()
|
533
|
+
|
500
534
|
# If the script stops early, we don't want to remove unseen widgets,
|
501
535
|
# so we track this to potentially skip session state cleanup later.
|
502
536
|
premature_stop: bool = False
|
@@ -504,14 +538,6 @@ class ScriptRunner:
|
|
504
538
|
try:
|
505
539
|
# Create fake module. This gives us a name global namespace to
|
506
540
|
# execute the code in.
|
507
|
-
# TODO(vdonato): Double-check that we're okay with naming the
|
508
|
-
# module for every page `__main__`. I'm pretty sure this is
|
509
|
-
# necessary given that people will likely often write
|
510
|
-
# ```
|
511
|
-
# if __name__ == "__main__":
|
512
|
-
# ...
|
513
|
-
# ```
|
514
|
-
# in their scripts.
|
515
541
|
module = self._new_module("__main__")
|
516
542
|
|
517
543
|
# Install the fake module as the __main__ module. This allows
|
@@ -539,11 +565,30 @@ class ScriptRunner:
|
|
539
565
|
|
540
566
|
ctx.on_script_start()
|
541
567
|
prep_time = timer() - start_time
|
542
|
-
|
568
|
+
|
569
|
+
if rerun_data.fragment_id_queue:
|
570
|
+
for fragment_id in rerun_data.fragment_id_queue:
|
571
|
+
try:
|
572
|
+
wrapped_fragment = self._fragment_storage.get(
|
573
|
+
fragment_id
|
574
|
+
)
|
575
|
+
ctx.current_fragment_id = fragment_id
|
576
|
+
wrapped_fragment()
|
577
|
+
|
578
|
+
except KeyError:
|
579
|
+
raise RuntimeError(
|
580
|
+
f"Could not find fragment with id {fragment_id}"
|
581
|
+
)
|
582
|
+
else:
|
583
|
+
self._fragment_storage.clear()
|
584
|
+
exec(code, module.__dict__)
|
585
|
+
|
543
586
|
self._session_state.maybe_check_serializable()
|
544
587
|
self._session_state[SCRIPT_RUN_WITHOUT_ERRORS_KEY] = True
|
545
588
|
except RerunException as e:
|
546
589
|
rerun_exception_data = e.rerun_data
|
590
|
+
ctx.cursors = original_cursors
|
591
|
+
dg_stack.set(original_dg_stack)
|
547
592
|
# Interruption due to a rerun is usually from `st.rerun()`, which
|
548
593
|
# we want to count as a script completion so triggers reset.
|
549
594
|
# It is also possible for this to happen if fast reruns is off,
|
@@ -563,7 +608,11 @@ class ScriptRunner:
|
|
563
608
|
|
564
609
|
finally:
|
565
610
|
if rerun_exception_data:
|
611
|
+
# The handling for when a full script run or a fragment is stopped early
|
612
|
+
# is the same, so we only have one ScriptRunnerEvent for this scenario.
|
566
613
|
finished_event = ScriptRunnerEvent.SCRIPT_STOPPED_FOR_RERUN
|
614
|
+
elif rerun_data.fragment_id_queue:
|
615
|
+
finished_event = ScriptRunnerEvent.FRAGMENT_STOPPED_WITH_SUCCESS
|
567
616
|
else:
|
568
617
|
finished_event = ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS
|
569
618
|
|
@@ -182,11 +182,21 @@ class WStates(MutableMapping[str, Any]):
|
|
182
182
|
"""Set a widget's metadata, overwriting any existing metadata it has."""
|
183
183
|
self.widget_metadata[widget_meta.id] = widget_meta
|
184
184
|
|
185
|
-
def remove_stale_widgets(
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
185
|
+
def remove_stale_widgets(
|
186
|
+
self,
|
187
|
+
active_widget_ids: set[str],
|
188
|
+
fragment_ids_this_run: set[str] | None,
|
189
|
+
) -> None:
|
190
|
+
"""Remove widget state for stale widgets."""
|
191
|
+
self.states = {
|
192
|
+
k: v
|
193
|
+
for k, v in self.states.items()
|
194
|
+
if not _is_stale_widget(
|
195
|
+
self.widget_metadata.get(k),
|
196
|
+
active_widget_ids,
|
197
|
+
fragment_ids_this_run,
|
198
|
+
)
|
199
|
+
}
|
190
200
|
|
191
201
|
def get_serialized(self, k: str) -> WidgetStateProto | None:
|
192
202
|
"""Get the serialized value of the widget with the given id.
|
@@ -561,14 +571,31 @@ class SessionState:
|
|
561
571
|
|
562
572
|
def _remove_stale_widgets(self, active_widget_ids: set[str]) -> None:
|
563
573
|
"""Remove widget state for widgets whose ids aren't in `active_widget_ids`."""
|
564
|
-
|
574
|
+
# Avoid circular imports.
|
575
|
+
from streamlit.runtime.scriptrunner import get_script_run_ctx
|
576
|
+
|
577
|
+
ctx = get_script_run_ctx()
|
578
|
+
if ctx is None:
|
579
|
+
return
|
580
|
+
|
581
|
+
self._new_widget_state.remove_stale_widgets(
|
582
|
+
active_widget_ids,
|
583
|
+
ctx.fragment_ids_this_run,
|
584
|
+
)
|
565
585
|
|
566
586
|
# Remove entries from _old_state corresponding to
|
567
587
|
# widgets not in widget_ids.
|
568
588
|
self._old_state = {
|
569
589
|
k: v
|
570
590
|
for k, v in self._old_state.items()
|
571
|
-
if (
|
591
|
+
if (
|
592
|
+
not is_widget_id(k)
|
593
|
+
or not _is_stale_widget(
|
594
|
+
self._new_widget_state.widget_metadata.get(k),
|
595
|
+
active_widget_ids,
|
596
|
+
ctx.fragment_ids_this_run,
|
597
|
+
)
|
598
|
+
)
|
572
599
|
}
|
573
600
|
|
574
601
|
def _set_widget_metadata(self, widget_metadata: WidgetMetadata[Any]) -> None:
|
@@ -671,6 +698,23 @@ def _is_internal_key(key: str) -> bool:
|
|
671
698
|
return key.startswith(STREAMLIT_INTERNAL_KEY_PREFIX)
|
672
699
|
|
673
700
|
|
701
|
+
def _is_stale_widget(
|
702
|
+
metadata: WidgetMetadata[Any] | None,
|
703
|
+
active_widget_ids: set[str],
|
704
|
+
fragment_ids_this_run: set[str] | None,
|
705
|
+
) -> bool:
|
706
|
+
if not metadata:
|
707
|
+
return True
|
708
|
+
elif metadata.id in active_widget_ids:
|
709
|
+
return False
|
710
|
+
# If we're running 1 or more fragments, but this widget is unrelated to any of the
|
711
|
+
# fragments that we're running, then it should not be marked as stale as its value
|
712
|
+
# may still be needed for a future fragment run or full script run.
|
713
|
+
elif fragment_ids_this_run and metadata.fragment_id not in fragment_ids_this_run:
|
714
|
+
return False
|
715
|
+
return True
|
716
|
+
|
717
|
+
|
674
718
|
@dataclass
|
675
719
|
class SessionStateStatProvider(CacheStatsProvider):
|
676
720
|
_session_mgr: SessionManager
|
@@ -159,6 +159,7 @@ def register_widget(
|
|
159
159
|
callback=on_change_handler,
|
160
160
|
callback_args=args,
|
161
161
|
callback_kwargs=kwargs,
|
162
|
+
fragment_id=ctx.current_fragment_id if ctx else None,
|
162
163
|
)
|
163
164
|
return register_widget_from_metadata(metadata, ctx, widget_func_name, element_type)
|
164
165
|
|
@@ -217,8 +218,8 @@ def register_widget_from_metadata(
|
|
217
218
|
|
218
219
|
|
219
220
|
def coalesce_widget_states(
|
220
|
-
old_states: WidgetStates, new_states: WidgetStates
|
221
|
-
) -> WidgetStates:
|
221
|
+
old_states: WidgetStates | None, new_states: WidgetStates | None
|
222
|
+
) -> WidgetStates | None:
|
222
223
|
"""Coalesce an older WidgetStates into a newer one, and return a new
|
223
224
|
WidgetStates containing the result.
|
224
225
|
|
@@ -228,6 +229,13 @@ def coalesce_widget_states(
|
|
228
229
|
`old_states` will be set to True in the coalesced result, so that button
|
229
230
|
presses don't go missing.
|
230
231
|
"""
|
232
|
+
if not old_states and not new_states:
|
233
|
+
return None
|
234
|
+
elif not old_states:
|
235
|
+
return new_states
|
236
|
+
elif not new_states:
|
237
|
+
return old_states
|
238
|
+
|
231
239
|
states_by_id: dict[str, WidgetState] = {
|
232
240
|
wstate.id: wstate for wstate in new_states.widgets
|
233
241
|
}
|
@@ -1,14 +1,14 @@
|
|
1
1
|
{
|
2
2
|
"files": {
|
3
3
|
"main.css": "./static/css/main.bf304093.css",
|
4
|
-
"main.js": "./static/js/main.
|
4
|
+
"main.js": "./static/js/main.356407e8.js",
|
5
5
|
"static/js/9336.2d95d840.chunk.js": "./static/js/9336.2d95d840.chunk.js",
|
6
6
|
"static/js/9330.d29313d4.chunk.js": "./static/js/9330.d29313d4.chunk.js",
|
7
7
|
"static/js/7217.d970c074.chunk.js": "./static/js/7217.d970c074.chunk.js",
|
8
8
|
"static/js/3301.1d1b10bb.chunk.js": "./static/js/3301.1d1b10bb.chunk.js",
|
9
|
-
"static/css/3092.
|
10
|
-
"static/js/3092.
|
11
|
-
"static/css/43.
|
9
|
+
"static/css/3092.95a45cfe.chunk.css": "./static/css/3092.95a45cfe.chunk.css",
|
10
|
+
"static/js/3092.ad569cc8.chunk.js": "./static/js/3092.ad569cc8.chunk.js",
|
11
|
+
"static/css/43.e3b876c5.chunk.css": "./static/css/43.e3b876c5.chunk.css",
|
12
12
|
"static/js/43.76c54963.chunk.js": "./static/js/43.76c54963.chunk.js",
|
13
13
|
"static/js/8427.44d27448.chunk.js": "./static/js/8427.44d27448.chunk.js",
|
14
14
|
"static/js/7323.2808d029.chunk.js": "./static/js/7323.2808d029.chunk.js",
|
@@ -20,24 +20,25 @@
|
|
20
20
|
"static/js/4113.1e7eff4d.chunk.js": "./static/js/4113.1e7eff4d.chunk.js",
|
21
21
|
"static/js/1168.3029456a.chunk.js": "./static/js/1168.3029456a.chunk.js",
|
22
22
|
"static/js/178.b5384fd0.chunk.js": "./static/js/178.b5384fd0.chunk.js",
|
23
|
-
"static/js/1792.
|
24
|
-
"static/js/3513.
|
25
|
-
"static/js/7602.
|
26
|
-
"static/js/6013.
|
27
|
-
"static/js/8492.
|
28
|
-
"static/js/4177.
|
29
|
-
"static/js/1451.
|
30
|
-
"static/js/
|
23
|
+
"static/js/1792.b8efa879.chunk.js": "./static/js/1792.b8efa879.chunk.js",
|
24
|
+
"static/js/3513.e3e7300a.chunk.js": "./static/js/3513.e3e7300a.chunk.js",
|
25
|
+
"static/js/7602.6175e969.chunk.js": "./static/js/7602.6175e969.chunk.js",
|
26
|
+
"static/js/6013.8e80e091.chunk.js": "./static/js/6013.8e80e091.chunk.js",
|
27
|
+
"static/js/8492.f56c9d4c.chunk.js": "./static/js/8492.f56c9d4c.chunk.js",
|
28
|
+
"static/js/4177.69f9f18d.chunk.js": "./static/js/4177.69f9f18d.chunk.js",
|
29
|
+
"static/js/1451.3b0a3e31.chunk.js": "./static/js/1451.3b0a3e31.chunk.js",
|
30
|
+
"static/js/2634.1249dc7a.chunk.js": "./static/js/2634.1249dc7a.chunk.js",
|
31
|
+
"static/js/1074.73973756.chunk.js": "./static/js/1074.73973756.chunk.js",
|
31
32
|
"static/js/8477.e948c092.chunk.js": "./static/js/8477.e948c092.chunk.js",
|
32
33
|
"static/js/6853.d999ac75.chunk.js": "./static/js/6853.d999ac75.chunk.js",
|
33
|
-
"static/js/4477.
|
34
|
-
"static/js/4319.
|
35
|
-
"static/js/5106.
|
36
|
-
"static/js/4666.
|
37
|
-
"static/js/5379.
|
38
|
-
"static/js/8691.
|
39
|
-
"static/js/6718.
|
40
|
-
"static/js/7175.
|
34
|
+
"static/js/4477.e10e4373.chunk.js": "./static/js/4477.e10e4373.chunk.js",
|
35
|
+
"static/js/4319.a6745434.chunk.js": "./static/js/4319.a6745434.chunk.js",
|
36
|
+
"static/js/5106.44f0ff51.chunk.js": "./static/js/5106.44f0ff51.chunk.js",
|
37
|
+
"static/js/4666.b694c5a9.chunk.js": "./static/js/4666.b694c5a9.chunk.js",
|
38
|
+
"static/js/5379.6571574f.chunk.js": "./static/js/5379.6571574f.chunk.js",
|
39
|
+
"static/js/8691.9ccf7f89.chunk.js": "./static/js/8691.9ccf7f89.chunk.js",
|
40
|
+
"static/js/6718.802da17e.chunk.js": "./static/js/6718.802da17e.chunk.js",
|
41
|
+
"static/js/7175.be4076bc.chunk.js": "./static/js/7175.be4076bc.chunk.js",
|
41
42
|
"static/js/5345.65c91ee7.chunk.js": "./static/js/5345.65c91ee7.chunk.js",
|
42
43
|
"static/js/9865.fd93213d.chunk.js": "./static/js/9865.fd93213d.chunk.js",
|
43
44
|
"static/js/6405.ac5a6f23.chunk.js": "./static/js/6405.ac5a6f23.chunk.js",
|
@@ -50,8 +51,8 @@
|
|
50
51
|
"static/js/8570.6de19120.chunk.js": "./static/js/8570.6de19120.chunk.js",
|
51
52
|
"static/js/7142.83028745.chunk.js": "./static/js/7142.83028745.chunk.js",
|
52
53
|
"static/js/3998.01237fcf.chunk.js": "./static/js/3998.01237fcf.chunk.js",
|
53
|
-
"static/css/2411.
|
54
|
-
"static/js/2411.
|
54
|
+
"static/css/2411.8b8f33d6.chunk.css": "./static/css/2411.8b8f33d6.chunk.css",
|
55
|
+
"static/js/2411.a8823789.chunk.js": "./static/js/2411.a8823789.chunk.js",
|
55
56
|
"static/js/656.ae85f8f1.chunk.js": "./static/js/656.ae85f8f1.chunk.js",
|
56
57
|
"static/js/6150.1294fa0d.chunk.js": "./static/js/6150.1294fa0d.chunk.js",
|
57
58
|
"static/js/4253.749d5244.chunk.js": "./static/js/4253.749d5244.chunk.js",
|
@@ -60,7 +61,8 @@
|
|
60
61
|
"static/js/2187.9469f035.chunk.js": "./static/js/2187.9469f035.chunk.js",
|
61
62
|
"static/js/1479.6709db03.chunk.js": "./static/js/1479.6709db03.chunk.js",
|
62
63
|
"static/js/4132.49bf3f2c.chunk.js": "./static/js/4132.49bf3f2c.chunk.js",
|
63
|
-
"static/js/2736.
|
64
|
+
"static/js/2736.779ccbc1.chunk.js": "./static/js/2736.779ccbc1.chunk.js",
|
65
|
+
"static/js/937.a1248039.chunk.js": "./static/js/937.a1248039.chunk.js",
|
64
66
|
"static/media/fireworks.gif": "./static/media/fireworks.0906f02ea43f1018a6d2.gif",
|
65
67
|
"static/media/flake-2.png": "./static/media/flake-2.e3f07d06933dd0e84c24.png",
|
66
68
|
"static/media/flake-1.png": "./static/media/flake-1.8077dc154e0bf900aa73.png",
|
@@ -150,6 +152,6 @@
|
|
150
152
|
},
|
151
153
|
"entrypoints": [
|
152
154
|
"static/css/main.bf304093.css",
|
153
|
-
"static/js/main.
|
155
|
+
"static/js/main.356407e8.js"
|
154
156
|
]
|
155
157
|
}
|
streamlit/static/index.html
CHANGED
@@ -1 +1 @@
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><link rel="shortcut icon" href="./favicon.png"/><link rel="preload" href="./static/media/SourceSansPro-Regular.0d69e5ff5e92ac64a0c9.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="./static/media/SourceSansPro-SemiBold.abed79cd0df1827e18cf.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="./static/media/SourceSansPro-Bold.118dea98980e20a81ced.woff2" as="font" type="font/woff2" crossorigin><title>Streamlit</title><script>window.prerenderReady=!1</script><script defer="defer" src="./static/js/main.
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><link rel="shortcut icon" href="./favicon.png"/><link rel="preload" href="./static/media/SourceSansPro-Regular.0d69e5ff5e92ac64a0c9.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="./static/media/SourceSansPro-SemiBold.abed79cd0df1827e18cf.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="./static/media/SourceSansPro-Bold.118dea98980e20a81ced.woff2" as="font" type="font/woff2" crossorigin><title>Streamlit</title><script>window.prerenderReady=!1</script><script defer="defer" src="./static/js/main.356407e8.js"></script><link href="./static/css/main.bf304093.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|