streamlit-nightly 1.32.3.dev20240331__py2.py3-none-any.whl → 1.32.3.dev20240403__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 (23) hide show
  1. streamlit/delta_generator.py +40 -16
  2. streamlit/elements/arrow_altair.py +2 -16
  3. streamlit/elements/html.py +7 -3
  4. streamlit/elements/image.py +4 -2
  5. streamlit/elements/media.py +73 -21
  6. streamlit/elements/spinner.py +1 -1
  7. streamlit/elements/widgets/chat.py +2 -2
  8. streamlit/elements/widgets/slider.py +8 -0
  9. streamlit/runtime/fragment.py +102 -31
  10. streamlit/runtime/runtime_util.py +1 -42
  11. streamlit/static/asset-manifest.json +3 -3
  12. streamlit/static/index.html +1 -1
  13. streamlit/static/static/js/{43.76c54963.chunk.js → 43.9ae03282.chunk.js} +1 -1
  14. streamlit/static/static/js/{main.356407e8.js → main.9c129b72.js} +2 -2
  15. streamlit/testing/v1/app_test.py +10 -4
  16. streamlit/testing/v1/element_tree.py +4 -0
  17. {streamlit_nightly-1.32.3.dev20240331.dist-info → streamlit_nightly-1.32.3.dev20240403.dist-info}/METADATA +1 -1
  18. {streamlit_nightly-1.32.3.dev20240331.dist-info → streamlit_nightly-1.32.3.dev20240403.dist-info}/RECORD +23 -23
  19. /streamlit/static/static/js/{main.356407e8.js.LICENSE.txt → main.9c129b72.js.LICENSE.txt} +0 -0
  20. {streamlit_nightly-1.32.3.dev20240331.data → streamlit_nightly-1.32.3.dev20240403.data}/scripts/streamlit.cmd +0 -0
  21. {streamlit_nightly-1.32.3.dev20240331.dist-info → streamlit_nightly-1.32.3.dev20240403.dist-info}/WHEEL +0 -0
  22. {streamlit_nightly-1.32.3.dev20240331.dist-info → streamlit_nightly-1.32.3.dev20240403.dist-info}/entry_points.txt +0 -0
  23. {streamlit_nightly-1.32.3.dev20240331.dist-info → streamlit_nightly-1.32.3.dev20240403.dist-info}/top_level.txt +0 -0
@@ -119,9 +119,9 @@ ARROW_DELTA_TYPES_THAT_MELT_DATAFRAMES: Final = (
119
119
  Value = TypeVar("Value")
120
120
  DG = TypeVar("DG", bound="DeltaGenerator")
121
121
 
122
- # Type aliases for Parent Block Types
122
+ # Type aliases for Ancestor Block Types
123
123
  BlockType = str
124
- ParentBlockTypes = Iterable[BlockType]
124
+ AncestorBlockTypes = Iterable[BlockType]
125
125
 
126
126
 
127
127
  _use_warning_has_been_displayed: bool = False
@@ -348,7 +348,7 @@ class DeltaGenerator(
348
348
  message = (
349
349
  f"Method `{name}()` does not exist for "
350
350
  "`DeltaGenerator` objects. Did you mean "
351
- "`st.{name}()`?"
351
+ f"`st.{name}()`?"
352
352
  )
353
353
  else:
354
354
  message = f"`{name}()` is not a valid Streamlit command."
@@ -368,18 +368,27 @@ class DeltaGenerator(
368
368
  return dg
369
369
 
370
370
  @property
371
- def _parent_block_types(self) -> ParentBlockTypes:
372
- """Iterate all the block types used by this DeltaGenerator and all
373
- its ancestor DeltaGenerators.
374
- """
371
+ def _ancestors(self) -> Iterable["DeltaGenerator"]:
375
372
  current_dg: DeltaGenerator | None = self
376
373
  while current_dg is not None:
377
- if current_dg._block_type is not None:
378
- yield current_dg._block_type
374
+ yield current_dg
379
375
  current_dg = current_dg._parent
380
376
 
381
- def _count_num_of_parent_columns(self, parent_block_types: ParentBlockTypes) -> int:
382
- return sum(1 for parent_block in parent_block_types if parent_block == "column")
377
+ @property
378
+ def _ancestor_block_types(self) -> AncestorBlockTypes:
379
+ """Iterate all the block types used by this DeltaGenerator and all
380
+ its ancestor DeltaGenerators.
381
+ """
382
+ for a in self._ancestors:
383
+ if a._block_type is not None:
384
+ yield a._block_type
385
+
386
+ def _count_num_of_parent_columns(
387
+ self, ancestor_block_types: AncestorBlockTypes
388
+ ) -> int:
389
+ return sum(
390
+ 1 for ancestor_block in ancestor_block_types if ancestor_block == "column"
391
+ )
383
392
 
384
393
  @property
385
394
  def _cursor(self) -> Cursor | None:
@@ -509,6 +518,15 @@ class DeltaGenerator(
509
518
  """
510
519
  # Operate on the active DeltaGenerator, in case we're in a `with` block.
511
520
  dg = self._active_dg
521
+
522
+ ctx = get_script_run_ctx()
523
+ if ctx and ctx.current_fragment_id and _writes_directly_to_sidebar(dg):
524
+ raise StreamlitAPIException(
525
+ "Calling `st.sidebar` in a function wrapped with `st.experimental_fragment` "
526
+ "is not supported. To write elements to the sidebar with a fragment, "
527
+ "call your fragment function inside a `with st.sidebar` context manager."
528
+ )
529
+
512
530
  # Warn if we're called from within a legacy @st.cache function
513
531
  legacy_caching.maybe_show_cached_st_function_warning(dg, delta_type)
514
532
  # Warn if we're called from within @st.memo or @st.singleton
@@ -587,11 +605,11 @@ class DeltaGenerator(
587
605
  # Prevent nested columns & expanders by checking all parents.
588
606
  block_type = block_proto.WhichOneof("type")
589
607
  # Convert the generator to a list, so we can use it multiple times.
590
- parent_block_types = list(dg._parent_block_types)
608
+ ancestor_block_types = list(dg._ancestor_block_types)
591
609
 
592
610
  if block_type == "column":
593
611
  num_of_parent_columns = self._count_num_of_parent_columns(
594
- parent_block_types
612
+ ancestor_block_types
595
613
  )
596
614
  if (
597
615
  self._root_container == RootContainer.SIDEBAR
@@ -604,15 +622,15 @@ class DeltaGenerator(
604
622
  raise StreamlitAPIException(
605
623
  "Columns can only be placed inside other columns up to one level of nesting."
606
624
  )
607
- if block_type == "chat_message" and block_type in frozenset(parent_block_types):
625
+ if block_type == "chat_message" and block_type in ancestor_block_types:
608
626
  raise StreamlitAPIException(
609
627
  "Chat messages cannot nested inside other chat messages."
610
628
  )
611
- if block_type == "expandable" and block_type in frozenset(parent_block_types):
629
+ if block_type == "expandable" and block_type in ancestor_block_types:
612
630
  raise StreamlitAPIException(
613
631
  "Expanders may not be nested inside other expanders."
614
632
  )
615
- if block_type == "popover" and block_type in frozenset(parent_block_types):
633
+ if block_type == "popover" and block_type in ancestor_block_types:
616
634
  raise StreamlitAPIException(
617
635
  "Popovers may not be nested inside other popovers."
618
636
  )
@@ -903,3 +921,9 @@ def _enqueue_message(msg: ForwardMsg_pb2.ForwardMsg) -> None:
903
921
  msg.delta.fragment_id = ctx.current_fragment_id
904
922
 
905
923
  ctx.enqueue(msg)
924
+
925
+
926
+ def _writes_directly_to_sidebar(dg: DG) -> bool:
927
+ in_sidebar = any(a._root_container == RootContainer.SIDEBAR for a in dg._ancestors)
928
+ has_container = bool(len(list(dg._ancestor_block_types)))
929
+ return in_sidebar and not has_container
@@ -1171,20 +1171,6 @@ def _get_opacity_encoding(
1171
1171
  return None
1172
1172
 
1173
1173
 
1174
- def _get_scale(df: pd.DataFrame, column_name: str | None) -> alt.Scale:
1175
- import altair as alt
1176
-
1177
- # Set the X and Y axes' scale to "utc" if they contain date values.
1178
- # This causes time data to be displayed in UTC, rather the user's local
1179
- # time zone. (By default, vega-lite displays time data in the browser's
1180
- # local time zone, regardless of which time zone the data specifies:
1181
- # https://vega.github.io/vega-lite/docs/timeunit.html#output).
1182
- if _is_date_column(df, column_name):
1183
- return alt.Scale(type="utc")
1184
-
1185
- return alt.Scale()
1186
-
1187
-
1188
1174
  def _get_axis_config(df: pd.DataFrame, column_name: str | None, grid: bool) -> alt.Axis:
1189
1175
  import altair as alt
1190
1176
  from pandas.api.types import is_integer_dtype
@@ -1266,7 +1252,7 @@ def _get_x_encoding(
1266
1252
  x_field,
1267
1253
  title=x_title,
1268
1254
  type=_get_x_encoding_type(df, chart_type, x_column),
1269
- scale=_get_scale(df, x_column),
1255
+ scale=alt.Scale(),
1270
1256
  axis=_get_axis_config(df, x_column, grid=False),
1271
1257
  )
1272
1258
 
@@ -1305,7 +1291,7 @@ def _get_y_encoding(
1305
1291
  field=y_field,
1306
1292
  title=y_title,
1307
1293
  type=_get_y_encoding_type(df, y_column),
1308
- scale=_get_scale(df, y_column),
1294
+ scale=alt.Scale(),
1309
1295
  axis=_get_axis_config(df, y_column, grid=True),
1310
1296
  )
1311
1297
 
@@ -34,10 +34,14 @@ class HtmlMixin:
34
34
  """Insert HTML into your app.
35
35
 
36
36
  Adding custom HTML to your app impacts safety, styling, and
37
- maintainability. By using this command, you may be compromising your
38
- users' security. ``st.html`` content is **not** iframed.
37
+ maintainability. We sanitize HTML with `DOMPurify
38
+ <https://github.com/cure53/DOMPurify>`_, but inserting HTML remains a
39
+ developer risk. Passing untrusted code to ``st.html`` or dynamically
40
+ loading external code can increase the risk of vulnerabilities in your
41
+ app.
39
42
 
40
- Executing JavaScript is not supported at this time.
43
+ ``st.html`` content is **not** iframed. Executing JavaScript is not
44
+ supported at this time.
41
45
 
42
46
  Parameters
43
47
  ----------
@@ -52,7 +52,7 @@ MAXIMUM_CONTENT_WIDTH: Final[int] = 2 * 730
52
52
  PILImage: TypeAlias = Union[
53
53
  "ImageFile.ImageFile", "Image.Image", "GifImagePlugin.GifImageFile"
54
54
  ]
55
- AtomicImage: TypeAlias = Union[PILImage, "npt.NDArray[Any]", io.BytesIO, str]
55
+ AtomicImage: TypeAlias = Union[PILImage, "npt.NDArray[Any]", io.BytesIO, str, bytes]
56
56
  ImageOrImageList: TypeAlias = Union[AtomicImage, List[AtomicImage]]
57
57
  UseColumnWith: TypeAlias = Union[Literal["auto", "always", "never"], bool, None]
58
58
  Channels: TypeAlias = Literal["RGB", "BGR"]
@@ -296,7 +296,9 @@ def _ensure_image_size_and_format(
296
296
  if width > 0 and actual_width > width:
297
297
  # We need to resize the image.
298
298
  new_height = int(1.0 * actual_height * width / actual_width)
299
- pil_image = pil_image.resize((width, new_height), resample=Image.BILINEAR)
299
+ pil_image = pil_image.resize(
300
+ (width, new_height), resample=Image.Resampling.BILINEAR
301
+ )
300
302
  return _PIL_to_bytes(pil_image, format=image_format, quality=90)
301
303
 
302
304
  if pil_image.format != image_format:
@@ -30,7 +30,7 @@ from streamlit.proto.Audio_pb2 import Audio as AudioProto
30
30
  from streamlit.proto.Video_pb2 import Video as VideoProto
31
31
  from streamlit.runtime import caching
32
32
  from streamlit.runtime.metrics_util import gather_metrics
33
- from streamlit.runtime.runtime_util import duration_to_seconds
33
+ from streamlit.time_util import time_to_seconds
34
34
 
35
35
  if TYPE_CHECKING:
36
36
  from typing import Any
@@ -84,29 +84,63 @@ class MediaMixin:
84
84
  http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
85
85
 
86
86
  format : str
87
- The mime type for the audio file. Defaults to 'audio/wav'.
87
+ The mime type for the audio file. Defaults to ``"audio/wav"``.
88
88
  See https://tools.ietf.org/html/rfc4281 for more info.
89
89
 
90
- start_time: int
91
- The time from which this element should start playing.
92
-
90
+ start_time: int, float, timedelta, str, or None
91
+ The time from which the element should start playing. This can be
92
+ one of the following:
93
+
94
+ * ``None`` (default): The element plays from the beginning.
95
+ * An``int`` or ``float`` specifying the time in seconds. ``float``
96
+ values are rounded down to whole seconds.
97
+ * A string specifying the time in a format supported by `Pandas'
98
+ Timedelta constructor <https://pandas.pydata.org/docs/reference/api/pandas.Timedelta.html>`_,
99
+ e.g. ``"2 minute"``, ``"20s"``, or ``"1m14s"``.
100
+ * A ``timedelta`` object from `Python's built-in datetime library
101
+ <https://docs.python.org/3/library/datetime.html#timedelta-objects>`_,
102
+ e.g. ``timedelta(seconds=70)``.
93
103
  sample_rate: int or None
94
104
  The sample rate of the audio data in samples per second. Only required if
95
105
  ``data`` is a numpy array.
96
- end_time: int
97
- The time at which this element should stop playing.
106
+ end_time: int, float, timedelta, str, or None
107
+ The time at which the element should stop playing. This can be
108
+ one of the following:
109
+
110
+ * ``None`` (default): The element plays through to the end.
111
+ * An ``int`` or ``float`` specifying the time in seconds. ``float``
112
+ values are rounded down to whole seconds.
113
+ * A string specifying the time in a format supported by `Pandas'
114
+ Timedelta constructor <https://pandas.pydata.org/docs/reference/api/pandas.Timedelta.html>`_,
115
+ e.g. ``"2 minute"``, ``"20s"``, or ``"1m14s"``.
116
+ * A ``timedelta`` object from `Python's built-in datetime library
117
+ <https://docs.python.org/3/library/datetime.html#timedelta-objects>`_,
118
+ e.g. ``timedelta(seconds=70)``.
98
119
  loop: bool
99
120
  Whether the audio should loop playback.
100
121
 
101
- Example
102
- -------
122
+ Examples
123
+ --------
124
+ To display an audio player for a local file, specify the file's string
125
+ path and format.
126
+
127
+ >>> import streamlit as st
128
+ >>>
129
+ >>> st.audio("cat-purr.mp3", format="audio/mpeg", loop=True)
130
+
131
+ .. output::
132
+ https://doc-audio-purr.streamlit.app/
133
+ height: 250px
134
+
135
+ You can also pass ``bytes`` or ``numpy.ndarray`` objects to ``st.audio``.
136
+
103
137
  >>> import streamlit as st
104
138
  >>> import numpy as np
105
139
  >>>
106
- >>> audio_file = open('myaudio.ogg', 'rb')
140
+ >>> audio_file = open("myaudio.ogg", "rb")
107
141
  >>> audio_bytes = audio_file.read()
108
142
  >>>
109
- >>> st.audio(audio_bytes, format='audio/ogg')
143
+ >>> st.audio(audio_bytes, format="audio/ogg")
110
144
  >>>
111
145
  >>> sample_rate = 44100 # 44100 samples per second
112
146
  >>> seconds = 2 # Note duration of 2 seconds
@@ -177,9 +211,19 @@ class MediaMixin:
177
211
  The mime type for the video file. Defaults to ``"video/mp4"``.
178
212
  See https://tools.ietf.org/html/rfc4281 for more info.
179
213
 
180
- start_time: int
181
- The time from which this element should start playing.
182
-
214
+ start_time: int, float, timedelta, str, or None
215
+ The time from which the element should start playing. This can be
216
+ one of the following:
217
+
218
+ * ``None`` (default): The element plays from the beginning.
219
+ * An``int`` or ``float`` specifying the time in seconds. ``float``
220
+ values are rounded down to whole seconds.
221
+ * A string specifying the time in a format supported by `Pandas'
222
+ Timedelta constructor <https://pandas.pydata.org/docs/reference/api/pandas.Timedelta.html>`_,
223
+ e.g. ``"2 minute"``, ``"20s"``, or ``"1m14s"``.
224
+ * A ``timedelta`` object from `Python's built-in datetime library
225
+ <https://docs.python.org/3/library/datetime.html#timedelta-objects>`_,
226
+ e.g. ``timedelta(seconds=70)``.
183
227
  subtitles: str, bytes, Path, io.BytesIO, or dict
184
228
  Optional subtitle data for the video, supporting several input types:
185
229
 
@@ -204,9 +248,19 @@ class MediaMixin:
204
248
  in a dictrionary's first pair: ``{"None": "", "English": "path/to/english.vtt"}``
205
249
 
206
250
  Not supported for YouTube videos.
207
-
208
- end_time: int or None
209
- The time at which this element should stop playing
251
+ end_time: int, float, timedelta, str, or None
252
+ The time at which the element should stop playing. This can be
253
+ one of the following:
254
+
255
+ * ``None`` (default): The element plays through to the end.
256
+ * An ``int`` or ``float`` specifying the time in seconds. ``float``
257
+ values are rounded down to whole seconds.
258
+ * A string specifying the time in a format supported by `Pandas'
259
+ Timedelta constructor <https://pandas.pydata.org/docs/reference/api/pandas.Timedelta.html>`_,
260
+ e.g. ``"2 minute"``, ``"20s"``, or ``"1m14s"``.
261
+ * A ``timedelta`` object from `Python's built-in datetime library
262
+ <https://docs.python.org/3/library/datetime.html#timedelta-objects>`_,
263
+ e.g. ``timedelta(seconds=70)``.
210
264
  loop: bool
211
265
  Whether the video should loop playback.
212
266
 
@@ -484,7 +538,7 @@ def _parse_start_time_end_time(
484
538
  """Parse start_time and end_time and return them as int."""
485
539
 
486
540
  try:
487
- maybe_start_time = duration_to_seconds(start_time, coerce_none_to_inf=False)
541
+ maybe_start_time = time_to_seconds(start_time, coerce_none_to_inf=False)
488
542
  if maybe_start_time is None:
489
543
  raise ValueError
490
544
  start_time = int(maybe_start_time)
@@ -495,9 +549,7 @@ def _parse_start_time_end_time(
495
549
  raise StreamlitAPIException(error_msg) from None
496
550
 
497
551
  try:
498
- # TODO[kajarenc]: Replace `duration_to_seconds` with `time_to_seconds`
499
- # when PR #8343 is merged.
500
- end_time = duration_to_seconds(end_time, coerce_none_to_inf=False)
552
+ end_time = time_to_seconds(end_time, coerce_none_to_inf=False)
501
553
  if end_time is not None:
502
554
  end_time = int(end_time)
503
555
  except StreamlitAPIException:
@@ -85,7 +85,7 @@ def spinner(text: str = "In progress...", *, _cache: bool = False) -> Iterator[N
85
85
  display_message = False
86
86
  with legacy_caching.suppress_cached_st_function_warning():
87
87
  with caching.suppress_cached_st_function_warning():
88
- if "chat_message" in set(message._active_dg._parent_block_types):
88
+ if "chat_message" in set(message._active_dg._ancestor_block_types):
89
89
  # Temporary stale element fix:
90
90
  # For chat messages, we are resetting the spinner placeholder to an
91
91
  # empty container instead of an empty placeholder (st.empty) to have
@@ -316,10 +316,10 @@ class ChatMixin:
316
316
  # Use bottom position if chat input is within the main container
317
317
  # either directly or within a vertical container. If it has any
318
318
  # other container types as parents, we use inline position.
319
- parent_block_types = set(self.dg._active_dg._parent_block_types)
319
+ ancestor_block_types = set(self.dg._active_dg._ancestor_block_types)
320
320
  if (
321
321
  self.dg._active_dg._root_container == RootContainer.MAIN
322
- and not parent_block_types
322
+ and not ancestor_block_types
323
323
  ):
324
324
  position = "bottom"
325
325
  else:
@@ -604,6 +604,14 @@ class SliderMixin:
604
604
  min_value = _date_to_datetime(min_value)
605
605
  max_value = _date_to_datetime(max_value)
606
606
 
607
+ # The frontend will error if the values are equal, so checking here
608
+ # lets us produce a nicer python error message and stack trace.
609
+ if min_value == max_value:
610
+ raise StreamlitAPIException(
611
+ "Slider `min_value` must be less than the `max_value`."
612
+ f"\nThe values were {min_value} and {max_value}."
613
+ )
614
+
607
615
  # Now, convert to microseconds (so we can serialize datetime to a long)
608
616
  if data_type in TIMELIKE_TYPES:
609
617
  # Restore times/datetimes to original timezone (dates are always naive)
@@ -119,43 +119,114 @@ def fragment(
119
119
  *,
120
120
  run_every: int | float | timedelta | str | None = None,
121
121
  ) -> Callable[[F], F] | F:
122
- """Allow a function to be run independently of the full script.
123
-
124
- Functions decorated with ``@st.experimental_fragment`` are handled specially within
125
- an app: when a widget created within an invocation of the function (a fragment) is
126
- interacted with, then only that fragment is rerun rather than the full streamlit app.
122
+ """Decorator to turn a function into a fragment which can rerun independently\
123
+ of the full script.
124
+
125
+ When a user interacts with an input widget created by a fragment, Streamlit
126
+ only reruns the fragment instead of the full script. If ``run_every`` is set,
127
+ Streamlit will also rerun the fragment at the specified interval while the
128
+ session is active, even if the user is not interacting with your app.
129
+
130
+ To trigger a full script rerun from inside a fragment, call ``st.rerun()``
131
+ directly. Any values from the fragment that need to be accessed from
132
+ the wider app should generally be stored in Session State.
133
+
134
+ When Streamlit element commands are called directly in a fragment, the
135
+ elements are cleared and redrawn on each fragment rerun, just like all
136
+ elements are redrawn on each full-script rerun. The rest of the app is
137
+ persisted during a fragment rerun. When a fragment renders elements into
138
+ externally created containers, the elements will not be cleared with each
139
+ fragment rerun. In this case, elements will accumulate in those containers
140
+ with each fragment rerun, until the next full-script rerun.
141
+
142
+ Calling `st.sidebar` in a fragment is not supported. To write elements to
143
+ the sidebar with a fragment, call your fragment funciton inside a
144
+ `with st.sidebar` context manager.
145
+
146
+ Fragment code can interact with Session State, imported modules, and
147
+ other Streamlit elements created outside the fragment. Note that these
148
+ interactions are additive across multiple fragment reruns. You are
149
+ responsible for handling any side effects of that behavior.
127
150
 
128
151
  Parameters
129
152
  ----------
130
- run_every: int, float, timedelta, str, or None
131
- If set, fragments created from this function rerun periodically at the specified
132
- time interval.
153
+ func: callable
154
+ The function to turn into a fragment.
133
155
 
134
- Example
135
- -------
156
+ run_every: int, float, timedelta, str, or None
157
+ The time interval between automatic fragment reruns. This can be one of
158
+ the following:
159
+
160
+ * ``None`` (default).
161
+ * An ``int`` or ``float`` specifying the interval in seconds.
162
+ * A string specifying the time in a format supported by `Pandas'
163
+ Timedelta constructor <https://pandas.pydata.org/docs/reference/api/pandas.Timedelta.html>`_,
164
+ e.g. ``"1d"``, ``"1.5 days"``, or ``"1h23s"``.
165
+ * A ``timedelta`` object from `Python's built-in datetime library
166
+ <https://docs.python.org/3/library/datetime.html#timedelta-objects>`_,
167
+ e.g. ``timedelta(days=1)``.
168
+
169
+ If ``run_every`` is ``None``, the fragment will only rerun from
170
+ user-triggered events.
171
+
172
+ Examples
173
+ --------
136
174
  The following example demonstrates basic usage of ``@st.experimental_fragment``. In
137
- this app, clicking on the "rerun full script" button will increment both counters,
138
- but the "rerun fragment" button will only increment the counter within the fragment.
139
-
140
- ```python3
141
- import streamlit as st
142
-
143
- if "script_runs" not in st.session_state:
144
- st.session_state.script_runs = 0
145
- st.session_state.fragment_runs = 0
146
-
147
- @st.experimental_fragment
148
- def fragment():
149
- st.button("rerun fragment")
150
- st.write(f"fragment runs: {st.session_state.fragment_runs}")
151
- st.session_state.fragment_runs += 1
152
-
153
- fragment()
175
+ this app, clicking "Rerun full script" will increment both counters and
176
+ update all values displayed in the app. In contrast, clicking "Rerun fragment"
177
+ will only increment the counter within the fragment. In this case, the
178
+ ``st.write`` command inside the fragment will update the app's frontend,
179
+ but the two ``st.write`` commands outside the fragment will not update the
180
+ frontend.
181
+
182
+ >>> import streamlit as st
183
+ >>>
184
+ >>> if "script_runs" not in st.session_state:
185
+ >>> st.session_state.script_runs = 0
186
+ >>> st.session_state.fragment_runs = 0
187
+ >>>
188
+ >>> @st.experimental_fragment
189
+ >>> def fragment():
190
+ >>> st.session_state.fragment_runs += 1
191
+ >>> st.button("Rerun fragment")
192
+ >>> st.write(f"Fragment says it ran {st.session_state.fragment_runs} times.")
193
+ >>>
194
+ >>> st.session_state.script_runs += 1
195
+ >>> fragment()
196
+ >>> st.button("Rerun full script")
197
+ >>> st.write(f"Full script says it ran {st.session_state.script_runs} times.")
198
+ >>> st.write(f"Full script sees that fragment ran {st.session_state.fragment_runs} times.")
199
+
200
+ .. output::
201
+ https://doc-fragment.streamlit.app/
202
+ height: 400px
203
+
204
+ You can also trigger a full-script rerun from inside a fragment by calling
205
+ ``st.rerun``.
206
+
207
+ >>> import streamlit as st
208
+ >>>
209
+ >>> if "clicks" not in st.session_state:
210
+ >>> st.session_state.clicks = 0
211
+ >>>
212
+ >>> @st.experimental_fragment
213
+ >>> def count_to_five():
214
+ >>> if st.button("Plus one!"):
215
+ >>> st.session_state.clicks += 1
216
+ >>> if st.session_state.clicks % 5 == 0:
217
+ >>> st.rerun()
218
+ >>> return
219
+ >>>
220
+ >>> count_to_five()
221
+ >>> st.header(f"Multiples of five clicks: {st.session_state.clicks // 5}")
222
+ >>>
223
+ >>> if st.button("Check click count"):
224
+ >>> st.toast(f"## Total clicks: {st.session_state.clicks}")
225
+
226
+ .. output::
227
+ https://doc-fragment-rerun.streamlit.app/
228
+ height: 400px
154
229
 
155
- st.button("rerun full script")
156
- st.write(f"full script runs: {st.session_state.script_runs}")
157
- st.session_state.script_runs += 1
158
- ```
159
230
  """
160
231
 
161
232
  if func is None:
@@ -16,9 +16,7 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- import math
20
- from datetime import timedelta
21
- from typing import Any, Literal, overload
19
+ from typing import Any
22
20
 
23
21
  from streamlit import config
24
22
  from streamlit.errors import MarkdownFormattedException, StreamlitAPIException
@@ -75,45 +73,6 @@ def is_cacheable_msg(msg: ForwardMsg) -> bool:
75
73
  return msg.ByteSize() >= int(config.get_option("global.minCachedMessageSize"))
76
74
 
77
75
 
78
- @overload
79
- def duration_to_seconds(
80
- ttl: float | timedelta | str | None, *, coerce_none_to_inf: Literal[False]
81
- ) -> float | None:
82
- ...
83
-
84
-
85
- @overload
86
- def duration_to_seconds(ttl: float | timedelta | str | None) -> float:
87
- ...
88
-
89
-
90
- def duration_to_seconds(
91
- ttl: float | timedelta | str | None, *, coerce_none_to_inf: bool = True
92
- ) -> float | None:
93
- """
94
- Convert a ttl value to a float representing "number of seconds".
95
- """
96
- if coerce_none_to_inf and ttl is None:
97
- return math.inf
98
- if isinstance(ttl, timedelta):
99
- return ttl.total_seconds()
100
- if isinstance(ttl, str):
101
- import numpy as np
102
- import pandas as pd
103
-
104
- try:
105
- out: float = pd.Timedelta(ttl).total_seconds()
106
- except ValueError as ex:
107
- raise BadDurationStringError(ttl) from ex
108
-
109
- if np.isnan(out):
110
- raise BadDurationStringError(ttl)
111
-
112
- return out
113
-
114
- return ttl
115
-
116
-
117
76
  def serialize_forward_msg(msg: ForwardMsg) -> bytes:
118
77
  """Serialize a ForwardMsg to send to a client.
119
78
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "files": {
3
3
  "main.css": "./static/css/main.bf304093.css",
4
- "main.js": "./static/js/main.356407e8.js",
4
+ "main.js": "./static/js/main.9c129b72.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",
@@ -9,7 +9,7 @@
9
9
  "static/css/3092.95a45cfe.chunk.css": "./static/css/3092.95a45cfe.chunk.css",
10
10
  "static/js/3092.ad569cc8.chunk.js": "./static/js/3092.ad569cc8.chunk.js",
11
11
  "static/css/43.e3b876c5.chunk.css": "./static/css/43.e3b876c5.chunk.css",
12
- "static/js/43.76c54963.chunk.js": "./static/js/43.76c54963.chunk.js",
12
+ "static/js/43.9ae03282.chunk.js": "./static/js/43.9ae03282.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",
15
15
  "static/js/4185.78230b2a.chunk.js": "./static/js/4185.78230b2a.chunk.js",
@@ -152,6 +152,6 @@
152
152
  },
153
153
  "entrypoints": [
154
154
  "static/css/main.bf304093.css",
155
- "static/js/main.356407e8.js"
155
+ "static/js/main.9c129b72.js"
156
156
  ]
157
157
  }
@@ -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.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>
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.9c129b72.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>