streamlit-nightly 1.34.1.dev20240512__py2.py3-none-any.whl → 1.34.1.dev20240514__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 (32) hide show
  1. streamlit/delta_generator.py +4 -4
  2. streamlit/elements/arrow.py +250 -3
  3. streamlit/elements/plotly_chart.py +4 -4
  4. streamlit/elements/vega_charts.py +410 -47
  5. streamlit/proto/ArrowVegaLiteChart_pb2.py +2 -2
  6. streamlit/proto/ArrowVegaLiteChart_pb2.pyi +15 -2
  7. streamlit/proto/Arrow_pb2.py +8 -6
  8. streamlit/proto/Arrow_pb2.pyi +33 -1
  9. streamlit/runtime/state/common.py +3 -1
  10. streamlit/runtime/state/widgets.py +7 -5
  11. streamlit/static/asset-manifest.json +7 -7
  12. streamlit/static/index.html +1 -1
  13. streamlit/static/static/css/8148.49dfd2ce.chunk.css +1 -0
  14. streamlit/static/static/js/1168.14f7c6ff.chunk.js +1 -0
  15. streamlit/static/static/js/5441.1b94928f.chunk.js +1 -0
  16. streamlit/static/static/js/6950.70fe55c2.chunk.js +2 -0
  17. streamlit/static/static/js/8148.a5f74d47.chunk.js +1 -0
  18. streamlit/static/static/js/{main.d6101304.js → main.32c71338.js} +2 -2
  19. streamlit/web/bootstrap.py +5 -9
  20. {streamlit_nightly-1.34.1.dev20240512.dist-info → streamlit_nightly-1.34.1.dev20240514.dist-info}/METADATA +1 -1
  21. {streamlit_nightly-1.34.1.dev20240512.dist-info → streamlit_nightly-1.34.1.dev20240514.dist-info}/RECORD +27 -27
  22. streamlit/static/static/css/3092.95a45cfe.chunk.css +0 -1
  23. streamlit/static/static/js/1168.7452e363.chunk.js +0 -1
  24. streamlit/static/static/js/1955.426e67ca.chunk.js +0 -2
  25. streamlit/static/static/js/3092.21bb8f7b.chunk.js +0 -1
  26. streamlit/static/static/js/5441.5bacdeda.chunk.js +0 -1
  27. /streamlit/static/static/js/{1955.426e67ca.chunk.js.LICENSE.txt → 6950.70fe55c2.chunk.js.LICENSE.txt} +0 -0
  28. /streamlit/static/static/js/{main.d6101304.js.LICENSE.txt → main.32c71338.js.LICENSE.txt} +0 -0
  29. {streamlit_nightly-1.34.1.dev20240512.data → streamlit_nightly-1.34.1.dev20240514.data}/scripts/streamlit.cmd +0 -0
  30. {streamlit_nightly-1.34.1.dev20240512.dist-info → streamlit_nightly-1.34.1.dev20240514.dist-info}/WHEEL +0 -0
  31. {streamlit_nightly-1.34.1.dev20240512.dist-info → streamlit_nightly-1.34.1.dev20240514.dist-info}/entry_points.txt +0 -0
  32. {streamlit_nightly-1.34.1.dev20240512.dist-info → streamlit_nightly-1.34.1.dev20240514.dist-info}/top_level.txt +0 -0
@@ -587,7 +587,7 @@ class DeltaGenerator(
587
587
  ... np.random.randn(50, 20),
588
588
  ... columns=('col %d' % i for i in range(20)))
589
589
  ...
590
- >>> my_table._arrow_add_rows(df2)
590
+ >>> my_table.add_rows(df2)
591
591
  >>> # Now the table shown in the Streamlit app contains the data for
592
592
  >>> # df1 followed by the data for df2.
593
593
 
@@ -596,14 +596,14 @@ class DeltaGenerator(
596
596
 
597
597
  >>> # Assuming df1 and df2 from the example above still exist...
598
598
  >>> my_chart = st.line_chart(df1)
599
- >>> my_chart._arrow_add_rows(df2)
599
+ >>> my_chart.add_rows(df2)
600
600
  >>> # Now the chart shown in the Streamlit app contains the data for
601
601
  >>> # df1 followed by the data for df2.
602
602
 
603
603
  And for plots whose datasets are named, you can pass the data with a
604
604
  keyword argument where the key is the name:
605
605
 
606
- >>> my_chart = st._arrow_vega_lite_chart({
606
+ >>> my_chart = st.vega_lite_chart({
607
607
  ... 'mark': 'line',
608
608
  ... 'encoding': {'x': 'a', 'y': 'b'},
609
609
  ... 'datasets': {
@@ -611,7 +611,7 @@ class DeltaGenerator(
611
611
  ... },
612
612
  ... 'data': {'name': 'some_fancy_name'},
613
613
  ... }),
614
- >>> my_chart._arrow_add_rows(some_fancy_name=df2) # <-- name used as keyword
614
+ >>> my_chart.add_rows(some_fancy_name=df2) # <-- name used as keyword
615
615
 
616
616
  """
617
617
  if self._root_container is None or self._cursor is None:
@@ -14,7 +14,22 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Union, cast
17
+ import json
18
+ from dataclasses import dataclass
19
+ from typing import (
20
+ TYPE_CHECKING,
21
+ Any,
22
+ Dict,
23
+ Final,
24
+ Iterable,
25
+ List,
26
+ Literal,
27
+ Set,
28
+ TypedDict,
29
+ Union,
30
+ cast,
31
+ overload,
32
+ )
18
33
 
19
34
  from typing_extensions import TypeAlias
20
35
 
@@ -27,9 +42,15 @@ from streamlit.elements.lib.column_config_utils import (
27
42
  process_config_mapping,
28
43
  update_column_config,
29
44
  )
45
+ from streamlit.elements.lib.event_utils import AttributeDictionary
30
46
  from streamlit.elements.lib.pandas_styler_utils import marshall_styler
47
+ from streamlit.errors import StreamlitAPIException
31
48
  from streamlit.proto.Arrow_pb2 import Arrow as ArrowProto
32
49
  from streamlit.runtime.metrics_util import gather_metrics
50
+ from streamlit.runtime.scriptrunner import get_script_run_ctx
51
+ from streamlit.runtime.state import WidgetCallback, register_widget
52
+ from streamlit.runtime.state.common import compute_widget_id
53
+ from streamlit.type_util import Key, to_key
33
54
 
34
55
  if TYPE_CHECKING:
35
56
  import pyarrow as pa
@@ -51,9 +72,112 @@ Data: TypeAlias = Union[
51
72
  None,
52
73
  ]
53
74
 
75
+ SelectionMode: TypeAlias = Literal[
76
+ "single-row", "multi-row", "single-column", "multi-column"
77
+ ]
78
+ _SELECTION_MODES: Final[set[SelectionMode]] = {
79
+ "single-row",
80
+ "multi-row",
81
+ "single-column",
82
+ "multi-column",
83
+ }
84
+
85
+
86
+ class DataframeSelectionState(TypedDict, total=False):
87
+ """
88
+ A dictionary representing the current selection state of the dataframe.
89
+
90
+ Attributes
91
+ ----------
92
+ rows
93
+ The selected rows (numerical indices).
94
+ columns
95
+ The selected columns (column names).
96
+ """
97
+
98
+ rows: list[int]
99
+ columns: list[str]
100
+
101
+
102
+ class DataframeState(TypedDict, total=False):
103
+ """
104
+ A dictionary representing the current state of the dataframe.
105
+
106
+ Attributes
107
+ ----------
108
+ selection : DataframeSelectionState
109
+ The state of the `on_select` event.
110
+ """
111
+
112
+ selection: DataframeSelectionState
113
+
114
+
115
+ @dataclass
116
+ class DataframeSelectionSerde:
117
+ """DataframeSelectionSerde is used to serialize and deserialize the dataframe selection state."""
118
+
119
+ def deserialize(self, ui_value: str | None, widget_id: str = "") -> DataframeState:
120
+ empty_selection_state: DataframeState = {
121
+ "selection": {
122
+ "rows": [],
123
+ "columns": [],
124
+ },
125
+ }
126
+ selection_state: DataframeState = (
127
+ empty_selection_state if ui_value is None else json.loads(ui_value)
128
+ )
129
+
130
+ if "selection" not in selection_state:
131
+ selection_state = empty_selection_state
132
+
133
+ return cast(DataframeState, AttributeDictionary(selection_state))
134
+
135
+ def serialize(self, editing_state: DataframeState) -> str:
136
+ return json.dumps(editing_state, default=str)
137
+
138
+
139
+ def parse_selection_mode(
140
+ selection_mode: SelectionMode | Iterable[SelectionMode],
141
+ ) -> Set[ArrowProto.SelectionMode.ValueType]:
142
+ """Parse and check the user provided selection modes."""
143
+ if isinstance(selection_mode, str):
144
+ # Only a single selection mode was passed
145
+ selection_mode_set = {selection_mode}
146
+ else:
147
+ # Multiple selection modes were passed
148
+ selection_mode_set = set(selection_mode)
149
+
150
+ if not selection_mode_set.issubset(_SELECTION_MODES):
151
+ raise StreamlitAPIException(
152
+ f"Invalid selection mode: {selection_mode}. "
153
+ f"Valid options are: {_SELECTION_MODES}"
154
+ )
155
+
156
+ if selection_mode_set.issuperset({"single-row", "multi-row"}):
157
+ raise StreamlitAPIException(
158
+ "Only one of `single-row` or `multi-row` can be selected as selection mode."
159
+ )
160
+
161
+ if selection_mode_set.issuperset({"single-column", "multi-column"}):
162
+ raise StreamlitAPIException(
163
+ "Only one of `single-column` or `multi-column` can be selected as selection mode."
164
+ )
165
+
166
+ parsed_selection_modes = []
167
+ for selection_mode in selection_mode_set:
168
+ if selection_mode == "single-row":
169
+ parsed_selection_modes.append(ArrowProto.SelectionMode.SINGLE_ROW)
170
+ elif selection_mode == "multi-row":
171
+ parsed_selection_modes.append(ArrowProto.SelectionMode.MULTI_ROW)
172
+ elif selection_mode == "single-column":
173
+ parsed_selection_modes.append(ArrowProto.SelectionMode.SINGLE_COLUMN)
174
+ elif selection_mode == "multi-column":
175
+ parsed_selection_modes.append(ArrowProto.SelectionMode.MULTI_COLUMN)
176
+ return set(parsed_selection_modes)
177
+
54
178
 
55
179
  class ArrowMixin:
56
- @gather_metrics("dataframe")
180
+ @overload
57
181
  def dataframe(
58
182
  self,
59
183
  data: Data = None,
@@ -64,7 +188,44 @@ class ArrowMixin:
64
188
  hide_index: bool | None = None,
65
189
  column_order: Iterable[str] | None = None,
66
190
  column_config: ColumnConfigMappingInput | None = None,
191
+ key: Key | None = None,
192
+ on_select: Literal["ignore"], # No default value here to make it work with mypy
193
+ selection_mode: SelectionMode | Iterable[SelectionMode] = "multi-row",
67
194
  ) -> DeltaGenerator:
195
+ ...
196
+
197
+ @overload
198
+ def dataframe(
199
+ self,
200
+ data: Data = None,
201
+ width: int | None = None,
202
+ height: int | None = None,
203
+ *,
204
+ use_container_width: bool = False,
205
+ hide_index: bool | None = None,
206
+ column_order: Iterable[str] | None = None,
207
+ column_config: ColumnConfigMappingInput | None = None,
208
+ key: Key | None = None,
209
+ on_select: Literal["rerun"] | WidgetCallback = "rerun",
210
+ selection_mode: SelectionMode | Iterable[SelectionMode] = "multi-row",
211
+ ) -> DataframeState:
212
+ ...
213
+
214
+ @gather_metrics("dataframe")
215
+ def dataframe(
216
+ self,
217
+ data: Data = None,
218
+ width: int | None = None,
219
+ height: int | None = None,
220
+ *,
221
+ use_container_width: bool = False,
222
+ hide_index: bool | None = None,
223
+ column_order: Iterable[str] | None = None,
224
+ column_config: ColumnConfigMappingInput | None = None,
225
+ key: Key | None = None,
226
+ on_select: Literal["ignore", "rerun"] | WidgetCallback = "ignore",
227
+ selection_mode: SelectionMode | Iterable[SelectionMode] = "multi-row",
228
+ ) -> DeltaGenerator | DataframeState:
68
229
  """Display a dataframe as an interactive table.
69
230
 
70
231
  This command works with dataframes from Pandas, PyArrow, Snowpark, and PySpark.
@@ -119,6 +280,30 @@ class ArrowMixin:
119
280
 
120
281
  To configure the index column(s), use ``_index`` as the column name.
121
282
 
283
+ key : str
284
+ An optional string to use as the unique key for this element when used in combination
285
+ with ```on_select```. If this is omitted, a key will be generated for the widget based
286
+ on its content. Multiple widgets of the same type may not share the same key.
287
+
288
+ on_select : "ignore" or "rerun" or callable
289
+ Controls the behavior in response to selection events on the table. Can be one of:
290
+
291
+ - "ignore" (default): Streamlit will not react to any selection events in the chart.
292
+ - "rerun": Streamlit will rerun the app when the user selects rows or columns in the table.
293
+ In this case, ```st.dataframe``` will return the selection data as a dictionary.
294
+ - callable: If a callable is provided, Streamlit will rerun and execute the callable as a
295
+ callback function before the rest of the app. The selection data can be retrieved through
296
+ session state by setting the key parameter.
297
+
298
+ selection_mode : "single-row", "multi-row", single-column", "multi-column", or an iterable of these
299
+ The selection mode of the table. Can be one of:
300
+
301
+ - "multi-row" (default): Multiple rows can be selected at a time.
302
+ - "single-row": Only one row can be selected at a time.
303
+ - "multi-column": Multiple columns can be selected at a time.
304
+ - "single-column": Only one column can be selected at a time.
305
+ - An iterable of the above options: The table will allow selection based on the modes specified.
306
+
122
307
  Examples
123
308
  --------
124
309
  >>> import streamlit as st
@@ -186,6 +371,29 @@ class ArrowMixin:
186
371
  """
187
372
  import pyarrow as pa
188
373
 
374
+ if on_select not in ["ignore", "rerun"] and not callable(on_select):
375
+ raise StreamlitAPIException(
376
+ f"You have passed {on_select} to `on_select`. But only 'ignore', 'rerun', or a callable is supported."
377
+ )
378
+
379
+ key = to_key(key)
380
+ is_selection_activated = on_select != "ignore"
381
+
382
+ if is_selection_activated:
383
+ # Run some checks that are only relevant when selections are activated
384
+
385
+ # Import here to avoid circular imports
386
+ from streamlit.elements.utils import (
387
+ check_cache_replay_rules,
388
+ check_callback_rules,
389
+ check_session_state_rules,
390
+ )
391
+
392
+ check_cache_replay_rules()
393
+ if callable(on_select):
394
+ check_callback_rules(self.dg, on_select)
395
+ check_session_state_rules(default_value=None, key=key, writes_allowed=False)
396
+
189
397
  # Convert the user provided column config into the frontend compatible format:
190
398
  column_config_mapping = process_config_mapping(column_config)
191
399
 
@@ -236,7 +444,46 @@ class ArrowMixin:
236
444
  )
237
445
  marshall_column_config(proto, column_config_mapping)
238
446
 
239
- return self.dg._enqueue("arrow_data_frame", proto)
447
+ if is_selection_activated:
448
+ # Import here to avoid circular imports
449
+ from streamlit.elements.form import current_form_id
450
+
451
+ # If selection events are activated, we need to register the dataframe
452
+ # element as a widget.
453
+ proto.selection_mode.extend(parse_selection_mode(selection_mode))
454
+ proto.form_id = current_form_id(self.dg)
455
+
456
+ ctx = get_script_run_ctx()
457
+ proto.id = compute_widget_id(
458
+ "dataframe",
459
+ user_key=key,
460
+ data=proto.data,
461
+ width=width,
462
+ height=height,
463
+ use_container_width=use_container_width,
464
+ column_order=proto.column_order,
465
+ column_config=proto.columns,
466
+ key=key,
467
+ selection_mode=selection_mode,
468
+ is_selection_activated=is_selection_activated,
469
+ form_id=proto.form_id,
470
+ page=ctx.page_script_hash if ctx else None,
471
+ )
472
+
473
+ serde = DataframeSelectionSerde()
474
+ widget_state = register_widget(
475
+ "dataframe",
476
+ proto,
477
+ user_key=key,
478
+ on_change_handler=on_select if callable(on_select) else None,
479
+ deserializer=serde.deserialize,
480
+ serializer=serde.serialize,
481
+ ctx=ctx,
482
+ )
483
+ self.dg._enqueue("arrow_data_frame", proto)
484
+ return cast(DataframeState, widget_state.value)
485
+ else:
486
+ return self.dg._enqueue("arrow_data_frame", proto)
240
487
 
241
488
  @gather_metrics("table")
242
489
  def table(self, data: Data = None) -> DeltaGenerator:
@@ -114,11 +114,11 @@ class PlotlyState(TypedDict, total=False):
114
114
 
115
115
  Attributes
116
116
  ----------
117
- select : PlotlySelectionState
117
+ selection : PlotlySelectionState
118
118
  The state of the `on_select` event.
119
119
  """
120
120
 
121
- select: PlotlySelectionState
121
+ selection: PlotlySelectionState
122
122
 
123
123
 
124
124
  @dataclass
@@ -127,7 +127,7 @@ class PlotlyChartSelectionSerde:
127
127
 
128
128
  def deserialize(self, ui_value: str | None, widget_id: str = "") -> PlotlyState:
129
129
  empty_selection_state: PlotlyState = {
130
- "select": {
130
+ "selection": {
131
131
  "points": [],
132
132
  "point_indices": [],
133
133
  "box": [],
@@ -141,7 +141,7 @@ class PlotlyChartSelectionSerde:
141
141
  else cast(PlotlyState, AttributeDictionary(json.loads(ui_value)))
142
142
  )
143
143
 
144
- if "select" not in selection_state:
144
+ if "selection" not in selection_state:
145
145
  selection_state = empty_selection_state
146
146
 
147
147
  return cast(PlotlyState, AttributeDictionary(selection_state))