streamlit-nightly 1.37.2.dev20240814__py2.py3-none-any.whl → 1.37.2.dev20240816__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.
Files changed (85) hide show
  1. streamlit/commands/execution_control.py +1 -4
  2. streamlit/commands/experimental_query_params.py +1 -1
  3. streamlit/commands/logo.py +1 -1
  4. streamlit/commands/navigation.py +1 -1
  5. streamlit/commands/page_config.py +1 -1
  6. streamlit/components/v1/component_arrow.py +2 -2
  7. streamlit/components/v1/component_registry.py +1 -1
  8. streamlit/components/v1/custom_component.py +1 -1
  9. streamlit/cursor.py +1 -1
  10. streamlit/dataframe_util.py +184 -25
  11. streamlit/elements/arrow.py +1 -1
  12. streamlit/elements/json.py +13 -7
  13. streamlit/elements/lib/dialog.py +1 -1
  14. streamlit/elements/lib/mutable_status_container.py +1 -1
  15. streamlit/elements/lib/policies.py +1 -1
  16. streamlit/elements/media.py +3 -5
  17. streamlit/elements/plotly_chart.py +1 -1
  18. streamlit/elements/vega_charts.py +1 -1
  19. streamlit/elements/widgets/button.py +7 -4
  20. streamlit/elements/widgets/button_group.py +28 -9
  21. streamlit/elements/widgets/chat.py +1 -1
  22. streamlit/elements/widgets/data_editor.py +1 -1
  23. streamlit/elements/write.py +18 -3
  24. streamlit/error_util.py +10 -8
  25. streamlit/navigation/page.py +1 -1
  26. streamlit/platform.py +1 -1
  27. streamlit/runtime/caching/cache_data_api.py +1 -1
  28. streamlit/runtime/caching/cache_resource_api.py +1 -1
  29. streamlit/runtime/caching/cached_message_replay.py +1 -1
  30. streamlit/runtime/context.py +6 -5
  31. streamlit/runtime/forward_msg_queue.py +18 -1
  32. streamlit/runtime/fragment.py +7 -5
  33. streamlit/runtime/media_file_manager.py +3 -1
  34. streamlit/runtime/metrics_util.py +2 -7
  35. streamlit/runtime/scriptrunner/__init__.py +7 -4
  36. streamlit/runtime/scriptrunner/exec_code.py +6 -3
  37. streamlit/runtime/scriptrunner/script_runner.py +10 -9
  38. streamlit/runtime/scriptrunner_utils/__init__.py +19 -0
  39. streamlit/runtime/{scriptrunner → scriptrunner_utils}/exceptions.py +1 -1
  40. streamlit/runtime/{scriptrunner → scriptrunner_utils}/script_requests.py +61 -6
  41. streamlit/runtime/{scriptrunner → scriptrunner_utils}/script_run_context.py +1 -1
  42. streamlit/runtime/state/__init__.py +0 -2
  43. streamlit/runtime/state/common.py +1 -1
  44. streamlit/runtime/state/query_params.py +1 -6
  45. streamlit/runtime/state/session_state.py +1 -5
  46. streamlit/runtime/state/session_state_proxy.py +3 -1
  47. streamlit/runtime/state/widgets.py +0 -59
  48. streamlit/static/asset-manifest.json +17 -17
  49. streamlit/static/index.html +1 -1
  50. streamlit/static/static/js/{1168.2a7e18da.chunk.js → 1168.2a9806f0.chunk.js} +1 -1
  51. streamlit/static/static/js/{1451.3d44ca81.chunk.js → 1451.d93e956f.chunk.js} +1 -1
  52. streamlit/static/static/js/{178.7bea8c5d.chunk.js → 178.ddebe26b.chunk.js} +1 -1
  53. streamlit/static/static/js/2469.6217c5c3.chunk.js +1 -0
  54. streamlit/static/static/js/{2634.1249dc7a.chunk.js → 2634.4e2535ee.chunk.js} +1 -1
  55. streamlit/static/static/js/{2736.dcbc9141.chunk.js → 2736.3d50ec7f.chunk.js} +1 -1
  56. streamlit/static/static/js/{3301.45709e64.chunk.js → 3301.7379a9fd.chunk.js} +5 -5
  57. streamlit/static/static/js/4113.786b0142.chunk.js +1 -0
  58. streamlit/static/static/js/4500.d884c792.chunk.js +1 -0
  59. streamlit/static/static/js/6853.a1c4fa00.chunk.js +1 -0
  60. streamlit/static/static/js/{7602.2331daf7.chunk.js → 7602.33571c14.chunk.js} +1 -1
  61. streamlit/static/static/js/7805.ba32ae70.chunk.js +1 -0
  62. streamlit/static/static/js/8148.b905db99.chunk.js +1 -0
  63. streamlit/static/static/js/8427.b1a68937.chunk.js +1 -0
  64. streamlit/static/static/js/{9330.2b4c99e0.chunk.js → 9330.32e8a53a.chunk.js} +1 -1
  65. streamlit/static/static/js/main.90c4efd0.js +28 -0
  66. streamlit/testing/v1/local_script_runner.py +1 -1
  67. streamlit/type_util.py +17 -47
  68. streamlit/user_info.py +4 -2
  69. streamlit/web/server/stats_request_handler.py +1 -3
  70. streamlit/web/server/websocket_headers.py +1 -1
  71. {streamlit_nightly-1.37.2.dev20240814.dist-info → streamlit_nightly-1.37.2.dev20240816.dist-info}/METADATA +1 -1
  72. {streamlit_nightly-1.37.2.dev20240814.dist-info → streamlit_nightly-1.37.2.dev20240816.dist-info}/RECORD +77 -76
  73. streamlit/static/static/js/2469.4bb197dd.chunk.js +0 -1
  74. streamlit/static/static/js/4113.ca4d2d7b.chunk.js +0 -1
  75. streamlit/static/static/js/4500.c007e274.chunk.js +0 -1
  76. streamlit/static/static/js/6853.5d19f25b.chunk.js +0 -1
  77. streamlit/static/static/js/7805.f7c8d475.chunk.js +0 -1
  78. streamlit/static/static/js/8148.7805e73f.chunk.js +0 -1
  79. streamlit/static/static/js/8427.69ce2c45.chunk.js +0 -1
  80. streamlit/static/static/js/main.b519dd78.js +0 -28
  81. /streamlit/static/static/js/{main.b519dd78.js.LICENSE.txt → main.90c4efd0.js.LICENSE.txt} +0 -0
  82. {streamlit_nightly-1.37.2.dev20240814.data → streamlit_nightly-1.37.2.dev20240816.data}/scripts/streamlit.cmd +0 -0
  83. {streamlit_nightly-1.37.2.dev20240814.dist-info → streamlit_nightly-1.37.2.dev20240816.dist-info}/WHEEL +0 -0
  84. {streamlit_nightly-1.37.2.dev20240814.dist-info → streamlit_nightly-1.37.2.dev20240816.dist-info}/entry_points.txt +0 -0
  85. {streamlit_nightly-1.37.2.dev20240814.dist-info → streamlit_nightly-1.37.2.dev20240816.dist-info}/top_level.txt +0 -0
@@ -23,11 +23,9 @@ from typing import (
23
23
  Sequence,
24
24
  TypeVar,
25
25
  cast,
26
- get_args,
26
+ overload,
27
27
  )
28
28
 
29
- from typing_extensions import TypeAlias
30
-
31
29
  from streamlit.elements.form_utils import current_form_id
32
30
  from streamlit.elements.lib.options_selector_utils import (
33
31
  convert_to_sequence_and_check_comparable,
@@ -43,7 +41,7 @@ from streamlit.elements.widgets.multiselect import MultiSelectSerde
43
41
  from streamlit.errors import StreamlitAPIException
44
42
  from streamlit.proto.ButtonGroup_pb2 import ButtonGroup as ButtonGroupProto
45
43
  from streamlit.runtime.metrics_util import gather_metrics
46
- from streamlit.runtime.scriptrunner import get_script_run_ctx
44
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
47
45
  from streamlit.runtime.state import register_widget
48
46
  from streamlit.runtime.state.common import (
49
47
  RegisterWidgetResult,
@@ -80,8 +78,6 @@ _STAR_ICON: Final = ":material/star:"
80
78
  # in base64 format and send it over the wire as an image.
81
79
  _SELECTED_STAR_ICON: Final = ":material/star_filled:"
82
80
 
83
- _FeedbackOptions: TypeAlias = Literal["thumbs", "faces", "stars"]
84
-
85
81
 
86
82
  class FeedbackSerde:
87
83
  """Uses the MultiSelectSerde under-the-hood, but accepts a single index value
@@ -114,7 +110,7 @@ class FeedbackSerde:
114
110
 
115
111
 
116
112
  def get_mapped_options(
117
- feedback_option: _FeedbackOptions,
113
+ feedback_option: Literal["thumbs", "faces", "stars"],
118
114
  ) -> tuple[list[ButtonGroupProto.Option], list[int]]:
119
115
  # options object understandable by the web app
120
116
  options: list[ButtonGroupProto.Option] = []
@@ -168,10 +164,33 @@ def _build_proto(
168
164
 
169
165
 
170
166
  class ButtonGroupMixin:
167
+ @overload # These overloads are not documented in the docstring, at least not at this time, on the theory that most people won't know what it means. And the Literals here are a subclass of int anyway.
168
+ # Usually, we would make a type alias for Literal["thumbs", "faces", "stars"]; but, in this case, we don't use it in too many other places, and it's a more helpful autocomplete if we just enumerate the values explicitly, so a decision has been made to keep it as not an alias.
169
+ def feedback(
170
+ self,
171
+ options: Literal["thumbs"] = ...,
172
+ *,
173
+ key: str | None = None,
174
+ disabled: bool = False,
175
+ on_change: WidgetCallback | None = None,
176
+ args: Any | None = None,
177
+ kwargs: Any | None = None,
178
+ ) -> Literal[0, 1] | None: ...
179
+ @overload
180
+ def feedback(
181
+ self,
182
+ options: Literal["faces", "stars"] = ...,
183
+ *,
184
+ key: str | None = None,
185
+ disabled: bool = False,
186
+ on_change: WidgetCallback | None = None,
187
+ args: Any | None = None,
188
+ kwargs: Any | None = None,
189
+ ) -> Literal[0, 1, 2, 3, 4] | None: ...
171
190
  @gather_metrics("feedback")
172
191
  def feedback(
173
192
  self,
174
- options: _FeedbackOptions = "thumbs",
193
+ options: Literal["thumbs", "faces", "stars"] = "thumbs",
175
194
  *,
176
195
  key: str | None = None,
177
196
  disabled: bool = False,
@@ -262,7 +281,7 @@ class ButtonGroupMixin:
262
281
 
263
282
  """
264
283
 
265
- if not isinstance(options, list) and options not in get_args(_FeedbackOptions):
284
+ if options not in ["thumbs", "faces", "stars"]:
266
285
  raise StreamlitAPIException(
267
286
  "The options argument to st.feedback must be one of "
268
287
  "['thumbs', 'faces', 'stars']. "
@@ -30,7 +30,7 @@ from streamlit.proto.ChatInput_pb2 import ChatInput as ChatInputProto
30
30
  from streamlit.proto.Common_pb2 import StringTriggerValue as StringTriggerValueProto
31
31
  from streamlit.proto.RootContainer_pb2 import RootContainer
32
32
  from streamlit.runtime.metrics_util import gather_metrics
33
- from streamlit.runtime.scriptrunner import get_script_run_ctx
33
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
34
34
  from streamlit.runtime.state import (
35
35
  WidgetArgs,
36
36
  WidgetCallback,
@@ -59,7 +59,7 @@ from streamlit.elements.lib.utils import Key, to_key
59
59
  from streamlit.errors import StreamlitAPIException
60
60
  from streamlit.proto.Arrow_pb2 import Arrow as ArrowProto
61
61
  from streamlit.runtime.metrics_util import gather_metrics
62
- from streamlit.runtime.scriptrunner import get_script_run_ctx
62
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
63
63
  from streamlit.runtime.state import (
64
64
  WidgetArgs,
65
65
  WidgetCallback,
@@ -18,6 +18,7 @@ import dataclasses
18
18
  import inspect
19
19
  import types
20
20
  from collections import ChainMap, UserDict, UserList
21
+ from collections.abc import ItemsView, KeysView, ValuesView
21
22
  from io import StringIO
22
23
  from typing import (
23
24
  TYPE_CHECKING,
@@ -258,13 +259,13 @@ class WriteMixin:
258
259
  - write(string) : Prints the formatted Markdown string, with
259
260
  support for LaTeX expression, emoji shortcodes, and colored text.
260
261
  See docs for st.markdown for more.
261
- - write(data_frame) : Displays any dataframe-compatible value
262
- as read-only table.
262
+ - write(dataframe) : Displays any dataframe-like object in a table.
263
+ - write(dict) : Displays dict-like in an interactive viewer.
264
+ - write(list) : Displays list-like in an interactive viewer.
263
265
  - write(error) : Prints an exception specially.
264
266
  - write(func) : Displays information about a function.
265
267
  - write(module) : Displays information about the module.
266
268
  - write(class) : Displays information about a class.
267
- - write(dict) : Displays dict in an interactive widget.
268
269
  - write(mpl_fig) : Displays a Matplotlib figure.
269
270
  - write(generator) : Streams the output of a generator.
270
271
  - write(openai.Stream) : Streams the output of an OpenAI stream.
@@ -276,8 +277,10 @@ class WriteMixin:
276
277
  - write(bokeh_fig) : Displays a Bokeh figure.
277
278
  - write(sympy_expr) : Prints SymPy expression using LaTeX.
278
279
  - write(htmlable) : Prints _repr_html_() for the object if available.
280
+ - write(db_cursor) : Displays DB API 2.0 cursor results in a table.
279
281
  - write(obj) : Prints str(obj) if otherwise unknown.
280
282
 
283
+
281
284
  unsafe_allow_html : bool
282
285
  Whether to render HTML within ``*args``. This only applies to
283
286
  strings or objects falling back on ``_repr_html_()``. If this is
@@ -457,10 +460,14 @@ class WriteMixin:
457
460
  UserDict,
458
461
  ChainMap,
459
462
  UserList,
463
+ ItemsView,
464
+ KeysView,
465
+ ValuesView,
460
466
  ),
461
467
  )
462
468
  or type_util.is_custom_dict(arg)
463
469
  or type_util.is_namedtuple(arg)
470
+ or type_util.is_pydantic_model(arg)
464
471
  ):
465
472
  flush_buffer()
466
473
  self.dg.json(arg)
@@ -495,6 +502,14 @@ class WriteMixin:
495
502
  ):
496
503
  # We either explicitly allow HTML or infer it's not HTML
497
504
  self.dg.markdown(repr_html, unsafe_allow_html=unsafe_allow_html)
505
+ elif type_util.has_callable_attr(
506
+ arg, "to_pandas"
507
+ ) or type_util.has_callable_attr(arg, "__dataframe__"):
508
+ # This object can very likely be converted to a DataFrame
509
+ # using the to_pandas, to_arrow, or the dataframe interchange
510
+ # protocol.
511
+ flush_buffer()
512
+ self.dg.dataframe(arg)
498
513
  else:
499
514
  stringified_arg = str(arg)
500
515
 
streamlit/error_util.py CHANGED
@@ -16,7 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  from typing import Final
18
18
 
19
- import streamlit as st
19
+ import streamlit
20
20
  from streamlit import config
21
21
  from streamlit.errors import UncaughtAppException
22
22
  from streamlit.logger import get_logger
@@ -52,9 +52,6 @@ def _print_rich_exception(e: BaseException) -> None:
52
52
  tab_size=8,
53
53
  )
54
54
 
55
- # Import script_runner here to prevent circular import
56
- import streamlit.runtime.scriptrunner.script_runner as script_runner
57
-
58
55
  # Print exception via rich
59
56
  console.print(
60
57
  rich_traceback.Traceback.from_exception(
@@ -66,11 +63,16 @@ def _print_rich_exception(e: BaseException) -> None:
66
63
  max_frames=100,
67
64
  word_wrap=False,
68
65
  extra_lines=3,
69
- suppress=[script_runner], # Ignore script runner
66
+ suppress=[streamlit],
70
67
  )
71
68
  )
72
69
 
73
70
 
71
+ def _show_exception(ex: BaseException) -> None:
72
+ """Show the exception on the frontend."""
73
+ streamlit.exception(ex)
74
+
75
+
74
76
  def handle_uncaught_app_exception(ex: BaseException) -> None:
75
77
  """Handle an exception that originated from a user app.
76
78
 
@@ -78,6 +80,7 @@ def handle_uncaught_app_exception(ex: BaseException) -> None:
78
80
  if the user has disabled client error details, we display a generic
79
81
  warning in the frontend instead.
80
82
  """
83
+
81
84
  error_logged = False
82
85
 
83
86
  if config.get_option("logger.enableRich"):
@@ -95,12 +98,11 @@ def handle_uncaught_app_exception(ex: BaseException) -> None:
95
98
 
96
99
  if config.get_option("client.showErrorDetails"):
97
100
  if not error_logged:
98
- # TODO: Clean up the stack trace, so it doesn't include ScriptRunner.
99
101
  _LOGGER.warning("Uncaught app exception", exc_info=ex)
100
- st.exception(ex)
102
+ _show_exception(ex)
101
103
  else:
102
104
  if not error_logged:
103
105
  # Use LOGGER.error, rather than LOGGER.debug, since we don't
104
106
  # show debug logs by default.
105
107
  _LOGGER.error("Uncaught app exception", exc_info=ex)
106
- st.exception(UncaughtAppException(ex))
108
+ _show_exception(UncaughtAppException(ex))
@@ -20,7 +20,7 @@ from typing import Callable
20
20
 
21
21
  from streamlit.errors import StreamlitAPIException
22
22
  from streamlit.runtime.metrics_util import gather_metrics
23
- from streamlit.runtime.scriptrunner.script_run_context import get_script_run_ctx
23
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
24
24
  from streamlit.source_util import page_icon_and_name
25
25
  from streamlit.string_util import validate_icon_or_emoji
26
26
  from streamlit.util import calc_md5
streamlit/platform.py CHANGED
@@ -17,7 +17,7 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
20
- from streamlit.runtime.scriptrunner import get_script_run_ctx
20
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
21
21
 
22
22
 
23
23
  def post_parent_message(message: str) -> None:
@@ -66,7 +66,7 @@ from streamlit.runtime.caching.storage.dummy_cache_storage import (
66
66
  MemoryCacheStorageManager,
67
67
  )
68
68
  from streamlit.runtime.metrics_util import gather_metrics
69
- from streamlit.runtime.scriptrunner.script_run_context import get_script_run_ctx
69
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
70
70
  from streamlit.runtime.stats import CacheStat, CacheStatsProvider, group_stats
71
71
  from streamlit.time_util import time_to_seconds
72
72
 
@@ -43,7 +43,7 @@ from streamlit.runtime.caching.cached_message_replay import (
43
43
  show_widget_replay_deprecation,
44
44
  )
45
45
  from streamlit.runtime.metrics_util import gather_metrics
46
- from streamlit.runtime.scriptrunner.script_run_context import get_script_run_ctx
46
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
47
47
  from streamlit.runtime.stats import CacheStat, CacheStatsProvider, group_stats
48
48
  from streamlit.time_util import time_to_seconds
49
49
 
@@ -25,7 +25,7 @@ from streamlit import runtime, util
25
25
  from streamlit.deprecation_util import show_deprecation_warning
26
26
  from streamlit.runtime.caching.cache_errors import CacheReplayClosureError
27
27
  from streamlit.runtime.caching.hashing import update_hash
28
- from streamlit.runtime.scriptrunner.script_run_context import (
28
+ from streamlit.runtime.scriptrunner_utils.script_run_context import (
29
29
  ScriptRunContext,
30
30
  get_script_run_ctx,
31
31
  )
@@ -20,8 +20,7 @@ from typing import TYPE_CHECKING, Any, Iterable, Iterator, Mapping, cast
20
20
 
21
21
  from streamlit import runtime
22
22
  from streamlit.runtime.metrics_util import gather_metrics
23
- from streamlit.runtime.scriptrunner import get_script_run_ctx
24
- from streamlit.type_util import is_type
23
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
25
24
 
26
25
  if TYPE_CHECKING:
27
26
  from http.cookies import Morsel
@@ -42,11 +41,13 @@ def _get_request() -> HTTPServerRequest | None:
42
41
  # We return websocket request only if session_client is an instance of
43
42
  # BrowserWebSocketHandler (which is True for the Streamlit open-source
44
43
  # implementation). For any other implementation, we return None.
45
- if not is_type(
46
- session_client,
47
- "streamlit.web.server.browser_websocket_handler.BrowserWebSocketHandler",
44
+ # We are not using `type_util.is_type` here to avoid circular import.
45
+ if (
46
+ f"{type(session_client).__module__}.{type(session_client).__qualname__}"
47
+ != "streamlit.web.server.browser_websocket_handler.BrowserWebSocketHandler"
48
48
  ):
49
49
  return None
50
+
50
51
  return cast("RequestHandler", session_client).request
51
52
 
52
53
 
@@ -94,7 +94,7 @@ class ForwardMsgQueue:
94
94
  self._queue = []
95
95
  else:
96
96
  self._queue = [
97
- msg
97
+ _update_script_finished_message(msg)
98
98
  for msg in self._queue
99
99
  if msg.WhichOneof("type")
100
100
  in {
@@ -166,3 +166,20 @@ def _maybe_compose_deltas(old_delta: Delta, new_delta: Delta) -> Delta | None:
166
166
  return new_delta
167
167
 
168
168
  return None
169
+
170
+
171
+ def _update_script_finished_message(msg: ForwardMsg) -> ForwardMsg:
172
+ """
173
+ When we are here, the message queue is cleared from non-lifecycle messages
174
+ before they were flushed to the browser.
175
+
176
+ If there were no non-lifecycle messages in the queue, changing the type here
177
+ should not matter for the frontend anyways, so we optimistically change the
178
+ `script_finished` message to `FINISHED_EARLY_FOR_RERUN`. This indicates to
179
+ the frontend that the previous run was interrupted by a new script start.
180
+ Otherwise, a `FINISHED_SUCCESSFULLY` message might trigger a reset of widget
181
+ states on the frontend.
182
+ """
183
+ if msg.WhichOneof("type") == "script_finished":
184
+ msg.script_finished = ForwardMsg.ScriptFinishedStatus.FINISHED_EARLY_FOR_RERUN
185
+ return msg
@@ -30,8 +30,11 @@ from streamlit.error_util import handle_uncaught_app_exception
30
30
  from streamlit.errors import FragmentHandledException, FragmentStorageKeyError
31
31
  from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
32
32
  from streamlit.runtime.metrics_util import gather_metrics
33
- from streamlit.runtime.scriptrunner.exceptions import RerunException, StopException
34
- from streamlit.runtime.scriptrunner.script_run_context import get_script_run_ctx
33
+ from streamlit.runtime.scriptrunner_utils.exceptions import (
34
+ RerunException,
35
+ StopException,
36
+ )
37
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
35
38
  from streamlit.time_util import time_to_seconds
36
39
 
37
40
  if TYPE_CHECKING:
@@ -160,7 +163,7 @@ def _fragment(
160
163
 
161
164
  ctx = get_script_run_ctx()
162
165
  if ctx is None:
163
- return
166
+ return None
164
167
 
165
168
  cursors_snapshot = deepcopy(ctx.cursors)
166
169
  dg_stack_snapshot = deepcopy(context_dg_stack.get())
@@ -265,8 +268,7 @@ def _fragment(
265
268
  ctx.current_fragment_id = prev_fragment_id
266
269
  ctx.current_fragment_delta_path = []
267
270
 
268
- if not ctx.fragment_storage.contains(fragment_id):
269
- ctx.fragment_storage.set(fragment_id, wrapped_fragment)
271
+ ctx.fragment_storage.set(fragment_id, wrapped_fragment)
270
272
 
271
273
  if run_every:
272
274
  msg = ForwardMsg()
@@ -28,7 +28,9 @@ _LOGGER: Final = get_logger(__name__)
28
28
 
29
29
  def _get_session_id() -> str:
30
30
  """Get the active AppSession's session_id."""
31
- from streamlit.runtime.scriptrunner import get_script_run_ctx
31
+ from streamlit.runtime.scriptrunner_utils.script_run_context import (
32
+ get_script_run_ctx,
33
+ )
32
34
 
33
35
  ctx = get_script_run_ctx()
34
36
  if ctx is None:
@@ -29,6 +29,8 @@ from streamlit import config, util
29
29
  from streamlit.logger import get_logger
30
30
  from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
31
31
  from streamlit.proto.PageProfile_pb2 import Argument, Command
32
+ from streamlit.runtime.scriptrunner_utils.exceptions import RerunException
33
+ from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
32
34
 
33
35
  _LOGGER: Final = get_logger(__name__)
34
36
 
@@ -364,10 +366,6 @@ def gather_metrics(name: str, func: F | None = None) -> Callable[[F], F] | F:
364
366
  from timeit import default_timer as timer
365
367
 
366
368
  exec_start = timer()
367
- # Local imports to prevent circular dependencies
368
- from streamlit.runtime.scriptrunner import get_script_run_ctx
369
- from streamlit.runtime.scriptrunner.exceptions import RerunException
370
-
371
369
  ctx = get_script_run_ctx(suppress_warning=True)
372
370
 
373
371
  tracking_activated = (
@@ -444,9 +442,6 @@ def create_page_profile_message(
444
442
  uncaught_exception: str | None = None,
445
443
  ) -> ForwardMsg:
446
444
  """Create and return the full PageProfile ForwardMsg."""
447
- # Local import to prevent circular dependencies
448
- from streamlit.runtime.scriptrunner import get_script_run_ctx
449
-
450
445
  msg = ForwardMsg()
451
446
  page_profile = msg.page_profile
452
447
 
@@ -12,15 +12,18 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from streamlit.runtime.scriptrunner.exceptions import RerunException, StopException
16
- from streamlit.runtime.scriptrunner.script_requests import RerunData
17
- from streamlit.runtime.scriptrunner.script_run_context import (
15
+ from streamlit.runtime.scriptrunner.script_runner import ScriptRunner, ScriptRunnerEvent
16
+ from streamlit.runtime.scriptrunner_utils.exceptions import (
17
+ RerunException,
18
+ StopException,
19
+ )
20
+ from streamlit.runtime.scriptrunner_utils.script_requests import RerunData
21
+ from streamlit.runtime.scriptrunner_utils.script_run_context import (
18
22
  ScriptRunContext,
19
23
  add_script_run_ctx,
20
24
  enqueue_message,
21
25
  get_script_run_ctx,
22
26
  )
23
- from streamlit.runtime.scriptrunner.script_runner import ScriptRunner, ScriptRunnerEvent
24
27
 
25
28
  __all__ = [
26
29
  "RerunData",
@@ -22,11 +22,14 @@ from streamlit.delta_generator_singletons import (
22
22
  )
23
23
  from streamlit.error_util import handle_uncaught_app_exception
24
24
  from streamlit.errors import FragmentHandledException
25
- from streamlit.runtime.scriptrunner.exceptions import RerunException, StopException
25
+ from streamlit.runtime.scriptrunner_utils.exceptions import (
26
+ RerunException,
27
+ StopException,
28
+ )
26
29
 
27
30
  if TYPE_CHECKING:
28
- from streamlit.runtime.scriptrunner.script_requests import RerunData
29
- from streamlit.runtime.scriptrunner.script_run_context import ScriptRunContext
31
+ from streamlit.runtime.scriptrunner_utils.script_requests import RerunData
32
+ from streamlit.runtime.scriptrunner_utils.script_run_context import ScriptRunContext
30
33
 
31
34
 
32
35
  def exec_func_with_error_handling(
@@ -30,15 +30,22 @@ from streamlit.errors import FragmentStorageKeyError
30
30
  from streamlit.logger import get_logger
31
31
  from streamlit.proto.ClientState_pb2 import ClientState
32
32
  from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
33
- from streamlit.runtime.scriptrunner.exceptions import RerunException, StopException
33
+ from streamlit.runtime.metrics_util import (
34
+ create_page_profile_message,
35
+ to_microseconds,
36
+ )
34
37
  from streamlit.runtime.scriptrunner.exec_code import exec_func_with_error_handling
35
38
  from streamlit.runtime.scriptrunner.script_cache import ScriptCache
36
- from streamlit.runtime.scriptrunner.script_requests import (
39
+ from streamlit.runtime.scriptrunner_utils.exceptions import (
40
+ RerunException,
41
+ StopException,
42
+ )
43
+ from streamlit.runtime.scriptrunner_utils.script_requests import (
37
44
  RerunData,
38
45
  ScriptRequests,
39
46
  ScriptRequestType,
40
47
  )
41
- from streamlit.runtime.scriptrunner.script_run_context import (
48
+ from streamlit.runtime.scriptrunner_utils.script_run_context import (
42
49
  ScriptRunContext,
43
50
  add_script_run_ctx,
44
51
  get_script_run_ctx,
@@ -612,12 +619,6 @@ class ScriptRunner:
612
619
 
613
620
  if ctx.gather_usage_stats:
614
621
  try:
615
- # Prevent issues with circular import
616
- from streamlit.runtime.metrics_util import (
617
- create_page_profile_message,
618
- to_microseconds,
619
- )
620
-
621
622
  # Create and send page profile information
622
623
  ctx.enqueue(
623
624
  create_page_profile_message(
@@ -0,0 +1,19 @@
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
+ """The modules in this package are separated from
16
+ the scriptrunner-package, because they are more or less
17
+ standalone and other modules import them quite frequently.
18
+ This separation helps us to remove dependency cycles.
19
+ """
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from streamlit.runtime.scriptrunner.script_requests import RerunData
15
+ from streamlit.runtime.scriptrunner_utils.script_requests import RerunData
16
16
  from streamlit.util import repr_
17
17
 
18
18
 
@@ -17,13 +17,11 @@ from __future__ import annotations
17
17
  import threading
18
18
  from dataclasses import dataclass, field, replace
19
19
  from enum import Enum
20
- from typing import TYPE_CHECKING, cast
20
+ from typing import cast
21
21
 
22
22
  from streamlit import util
23
- from streamlit.runtime.state import coalesce_widget_states
24
-
25
- if TYPE_CHECKING:
26
- from streamlit.proto.WidgetStates_pb2 import WidgetStates
23
+ from streamlit.proto.Common_pb2 import StringTriggerValue as StringTriggerValueProto
24
+ from streamlit.proto.WidgetStates_pb2 import WidgetState, WidgetStates
27
25
 
28
26
 
29
27
  class ScriptRequestType(Enum):
@@ -91,6 +89,63 @@ def _fragment_run_should_not_preempt_script(
91
89
  return bool(fragment_id_queue) and not is_fragment_scoped_rerun
92
90
 
93
91
 
92
+ def _coalesce_widget_states(
93
+ old_states: WidgetStates | None, new_states: WidgetStates | None
94
+ ) -> WidgetStates | None:
95
+ """Coalesce an older WidgetStates into a newer one, and return a new
96
+ WidgetStates containing the result.
97
+
98
+ For most widget values, we just take the latest version.
99
+
100
+ However, any trigger_values (which are set by buttons) that are True in
101
+ `old_states` will be set to True in the coalesced result, so that button
102
+ presses don't go missing.
103
+ """
104
+ if not old_states and not new_states:
105
+ return None
106
+ elif not old_states:
107
+ return new_states
108
+ elif not new_states:
109
+ return old_states
110
+
111
+ states_by_id: dict[str, WidgetState] = {
112
+ wstate.id: wstate for wstate in new_states.widgets
113
+ }
114
+
115
+ trigger_value_types = [
116
+ ("trigger_value", False),
117
+ ("string_trigger_value", StringTriggerValueProto(data=None)),
118
+ ]
119
+ for old_state in old_states.widgets:
120
+ for trigger_value_type, unset_value in trigger_value_types:
121
+ if (
122
+ old_state.WhichOneof("value") == trigger_value_type
123
+ and getattr(old_state, trigger_value_type) != unset_value
124
+ ):
125
+ new_trigger_val = states_by_id.get(old_state.id)
126
+ # It should nearly always be the case that new_trigger_val is None
127
+ # here as trigger values are deleted from the client's WidgetStateManager
128
+ # as soon as a rerun_script BackMsg is sent to the server. Since it's
129
+ # impossible to test that the client sends us state in the expected
130
+ # format in a unit test, we test for this behavior in
131
+ # e2e_playwright/test_fragment_queue_test.py
132
+ if not new_trigger_val or (
133
+ # Ensure the corresponding new_state is also a trigger;
134
+ # otherwise, a widget that was previously a button/chat_input but no
135
+ # longer is could get a bad value.
136
+ new_trigger_val.WhichOneof("value") == trigger_value_type
137
+ # We only want to take the value of old_state if new_trigger_val is
138
+ # unset as the old value may be stale if a newer one was entered.
139
+ and getattr(new_trigger_val, trigger_value_type) == unset_value
140
+ ):
141
+ states_by_id[old_state.id] = old_state
142
+
143
+ coalesced = WidgetStates()
144
+ coalesced.widgets.extend(states_by_id.values())
145
+
146
+ return coalesced
147
+
148
+
94
149
  class ScriptRequests:
95
150
  """An interface for communicating with a ScriptRunner. Thread-safe.
96
151
 
@@ -146,7 +201,7 @@ class ScriptRequests:
146
201
  # We already have an existing Rerun request, so we can coalesce the new
147
202
  # rerun request into the existing one.
148
203
 
149
- coalesced_states = coalesce_widget_states(
204
+ coalesced_states = _coalesce_widget_states(
150
205
  self._rerun_data.widget_states, new_data.widget_states
151
206
  )
152
207
 
@@ -31,7 +31,7 @@ if TYPE_CHECKING:
31
31
  from streamlit.proto.PageProfile_pb2 import Command
32
32
  from streamlit.runtime.fragment import FragmentStorage
33
33
  from streamlit.runtime.pages_manager import PagesManager
34
- from streamlit.runtime.scriptrunner.script_requests import ScriptRequests
34
+ from streamlit.runtime.scriptrunner_utils.script_requests import ScriptRequests
35
35
  from streamlit.runtime.state import SafeSessionState
36
36
  from streamlit.runtime.uploaded_file_manager import UploadedFileManager
37
37
  _LOGGER: Final = get_logger(__name__)
@@ -26,7 +26,6 @@ from streamlit.runtime.state.session_state_proxy import (
26
26
  )
27
27
  from streamlit.runtime.state.widgets import (
28
28
  NoValue,
29
- coalesce_widget_states,
30
29
  register_widget,
31
30
  )
32
31
 
@@ -42,6 +41,5 @@ __all__ = [
42
41
  "SessionStateProxy",
43
42
  "get_session_state",
44
43
  "NoValue",
45
- "coalesce_widget_states",
46
44
  "register_widget",
47
45
  ]
@@ -66,7 +66,7 @@ from streamlit.util import HASHLIB_KWARGS
66
66
  if TYPE_CHECKING:
67
67
  from builtins import ellipsis
68
68
 
69
- from streamlit.runtime.scriptrunner.script_run_context import ScriptRunContext
69
+ from streamlit.runtime.scriptrunner_utils.script_run_context import ScriptRunContext
70
70
  from streamlit.runtime.state.widgets import NoValue
71
71
 
72
72