streamlit-nightly 1.21.1.dev20230423__py2.py3-none-any.whl → 1.21.1.dev20230425__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 (40) hide show
  1. streamlit/__init__.py +0 -7
  2. streamlit/elements/arrow.py +7 -264
  3. streamlit/elements/data_editor.py +109 -100
  4. streamlit/elements/file_uploader.py +17 -0
  5. streamlit/elements/layouts.py +0 -5
  6. streamlit/elements/lib/column_config_utils.py +371 -0
  7. streamlit/elements/lib/pandas_styler_utils.py +275 -0
  8. streamlit/runtime/connection_factory.py +5 -5
  9. streamlit/static/asset-manifest.json +20 -20
  10. streamlit/static/index.html +1 -1
  11. streamlit/static/static/js/{14.a19a6cd8.chunk.js → 14.9399e424.chunk.js} +1 -1
  12. streamlit/static/static/js/{227.087adf66.chunk.js → 227.9ccac1d5.chunk.js} +1 -1
  13. streamlit/static/static/js/{242.0daf8b47.chunk.js → 242.1b3289e0.chunk.js} +1 -1
  14. streamlit/static/static/js/{279.fdac58fc.chunk.js → 279.35b01780.chunk.js} +1 -1
  15. streamlit/static/static/js/{289.481fd42d.chunk.js → 289.e6157e40.chunk.js} +1 -1
  16. streamlit/static/static/js/{467.242e14ff.chunk.js → 467.50ac84df.chunk.js} +1 -1
  17. streamlit/static/static/js/{491.d0b710e9.chunk.js → 491.5a33a8ce.chunk.js} +1 -1
  18. streamlit/static/static/js/503.15864587.chunk.js +1 -0
  19. streamlit/static/static/js/{511.9f04ae9e.chunk.js → 511.e6ca580f.chunk.js} +1 -1
  20. streamlit/static/static/js/{578.ceaadcd5.chunk.js → 578.a65fcea0.chunk.js} +1 -1
  21. streamlit/static/static/js/{619.365611c8.chunk.js → 619.0325af0e.chunk.js} +1 -1
  22. streamlit/static/static/js/{628.7f41e2de.chunk.js → 628.9c70196b.chunk.js} +1 -1
  23. streamlit/static/static/js/{681.a2ba76c7.chunk.js → 681.9e30a8cd.chunk.js} +1 -1
  24. streamlit/static/static/js/{745.e2bcf16d.chunk.js → 745.e75ba963.chunk.js} +1 -1
  25. streamlit/static/static/js/{807.6789990f.chunk.js → 807.122f8b05.chunk.js} +1 -1
  26. streamlit/static/static/js/{828.096c1ad3.chunk.js → 828.0fde3da8.chunk.js} +1 -1
  27. streamlit/static/static/js/{871.ba625aee.chunk.js → 871.90a7dbae.chunk.js} +1 -1
  28. streamlit/static/static/js/{main.5e4731c6.js → main.ff35bd72.js} +2 -2
  29. streamlit/testing/element_tree.py +426 -548
  30. streamlit/type_util.py +19 -7
  31. {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/METADATA +1 -1
  32. {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/RECORD +38 -37
  33. streamlit/elements/show.py +0 -105
  34. streamlit/static/static/js/728.82770810.chunk.js +0 -1
  35. /streamlit/static/static/css/{728.23fa976d.chunk.css → 503.23fa976d.chunk.css} +0 -0
  36. /streamlit/static/static/js/{main.5e4731c6.js.LICENSE.txt → main.ff35bd72.js.LICENSE.txt} +0 -0
  37. {streamlit_nightly-1.21.1.dev20230423.data → streamlit_nightly-1.21.1.dev20230425.data}/scripts/streamlit.cmd +0 -0
  38. {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/WHEEL +0 -0
  39. {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/entry_points.txt +0 -0
  40. {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/top_level.txt +0 -0
@@ -43,6 +43,15 @@ from streamlit.type_util import Key, LabelVisibility, maybe_raise_label_warnings
43
43
  SomeUploadedFiles = Optional[Union[UploadedFile, List[UploadedFile]]]
44
44
 
45
45
 
46
+ TYPE_PAIRS = [
47
+ (".jpg", ".jpeg"),
48
+ (".mpg", ".mpeg"),
49
+ (".mp4", ".mpeg4"),
50
+ (".tif", ".tiff"),
51
+ (".htm", ".html"),
52
+ ]
53
+
54
+
46
55
  def _get_file_recs(
47
56
  widget_id: str, widget_value: Optional[FileUploaderStateProto]
48
57
  ) -> List[UploadedFileRec]:
@@ -390,6 +399,14 @@ class FileUploaderMixin:
390
399
  for file_type in type
391
400
  ]
392
401
 
402
+ type = [t.lower() for t in type]
403
+
404
+ for x, y in TYPE_PAIRS:
405
+ if x in type and y not in type:
406
+ type.append(y)
407
+ if y in type and x not in type:
408
+ type.append(x)
409
+
393
410
  file_uploader_proto = FileUploaderProto()
394
411
  file_uploader_proto.label = label
395
412
  file_uploader_proto.type[:] = type if type is not None else []
@@ -416,8 +416,3 @@ class LayoutsMixin:
416
416
  def dg(self) -> "DeltaGenerator":
417
417
  """Get our DeltaGenerator."""
418
418
  return cast("DeltaGenerator", self)
419
-
420
- # Deprecated beta_ functions
421
- beta_container = deprecate_func_name(container, "beta_container", "2021-11-02")
422
- beta_expander = deprecate_func_name(expander, "beta_expander", "2021-11-02")
423
- beta_columns = deprecate_func_name(columns, "beta_columns", "2021-11-02")
@@ -0,0 +1,371 @@
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
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
+ from __future__ import annotations
16
+
17
+ import json
18
+ from enum import Enum
19
+ from typing import Any, Dict, List, Optional, Union
20
+
21
+ import pandas as pd
22
+ import pyarrow as pa
23
+ from typing_extensions import Literal, TypeAlias, TypedDict
24
+
25
+ from streamlit.proto.Arrow_pb2 import Arrow as ArrowProto
26
+
27
+ # The index identifier can be used to apply configuration options
28
+ IndexIdentifierType = Literal["index"]
29
+ INDEX_IDENTIFIER: IndexIdentifierType = "index"
30
+
31
+ # This is used as prefix for columns that are configured via the numerical position.
32
+ # The integer value is converted into a string key with this prefix.
33
+ # This needs to match with the prefix configured in the frontend.
34
+ _NUMERICAL_POSITION_PREFIX = "col:"
35
+
36
+ ColumnWidth = Literal["small", "medium", "large"]
37
+
38
+ # Type alias that represents all available column types
39
+ # which are configurable by the user.
40
+ ColumnType: TypeAlias = Literal[
41
+ "object", "text", "number", "checkbox", "selectbox", "list"
42
+ ]
43
+
44
+
45
+ # The column data kind is used to describe the type of the data within the column.
46
+ class ColumnDataKind(str, Enum):
47
+ INTEGER = "integer"
48
+ FLOAT = "float"
49
+ DATE = "date"
50
+ TIME = "time"
51
+ DATETIME = "datetime"
52
+ BOOLEAN = "boolean"
53
+ STRING = "string"
54
+ TIMEDELTA = "timedelta"
55
+ PERIOD = "period"
56
+ INTERVAL = "interval"
57
+ BYTES = "bytes"
58
+ DECIMAL = "decimal"
59
+ COMPLEX = "complex"
60
+ LIST = "list"
61
+ DICT = "dict"
62
+ EMPTY = "empty"
63
+ UNKNOWN = "unknown"
64
+
65
+
66
+ # The dataframe schema is just a list of column data kinds
67
+ # based on the order of the columns in the underlying dataframe.
68
+ # The index column(s) are attached at the beginning of the list.
69
+ DataframeSchema: TypeAlias = List[ColumnDataKind]
70
+
71
+
72
+ def _determine_data_kind_via_arrow(field: pa.Field) -> ColumnDataKind:
73
+ """Determine the data kind via the arrow type information.
74
+
75
+ The column data kind refers to the shared data type of the values
76
+ in the column (e.g. integer, float, string, bool).
77
+
78
+ Parameters
79
+ ----------
80
+
81
+ field : pa.Field
82
+ The arrow field from the arrow table schema.
83
+
84
+ Returns
85
+ -------
86
+ ColumnDataKind
87
+ The data kind of the field.
88
+ """
89
+ field_type = field.type
90
+ if pa.types.is_integer(field_type):
91
+ return ColumnDataKind.INTEGER
92
+
93
+ if pa.types.is_floating(field_type):
94
+ return ColumnDataKind.FLOAT
95
+
96
+ if pa.types.is_boolean(field_type):
97
+ return ColumnDataKind.BOOLEAN
98
+
99
+ if pa.types.is_string(field_type):
100
+ return ColumnDataKind.STRING
101
+
102
+ if pa.types.is_date(field_type):
103
+ return ColumnDataKind.DATE
104
+
105
+ if pa.types.is_time(field_type):
106
+ return ColumnDataKind.TIME
107
+
108
+ if pa.types.is_timestamp(field_type):
109
+ return ColumnDataKind.DATETIME
110
+
111
+ if pa.types.is_duration(field_type):
112
+ return ColumnDataKind.TIMEDELTA
113
+
114
+ if pa.types.is_list(field_type):
115
+ return ColumnDataKind.LIST
116
+
117
+ if pa.types.is_decimal(field_type):
118
+ return ColumnDataKind.DECIMAL
119
+
120
+ if pa.types.is_null(field_type):
121
+ return ColumnDataKind.EMPTY
122
+
123
+ # Interval does not seem to work correctly:
124
+ # if pa.types.is_interval(field_type):
125
+ # return ColumnDataKind.INTERVAL
126
+
127
+ if pa.types.is_binary(field_type):
128
+ return ColumnDataKind.BYTES
129
+
130
+ if pa.types.is_struct(field_type):
131
+ return ColumnDataKind.DICT
132
+
133
+ return ColumnDataKind.UNKNOWN
134
+
135
+
136
+ def _determine_data_kind_via_pandas_dtype(
137
+ column: pd.Series | pd.Index,
138
+ ) -> ColumnDataKind:
139
+ """Determine the data kind by using the pandas dtype.
140
+
141
+ The column data kind refers to the shared data type of the values
142
+ in the column (e.g. integer, float, string, bool).
143
+
144
+ Parameters
145
+ ----------
146
+ column : pd.Series, pd.Index
147
+ The column for which the data kind should be determined.
148
+
149
+ Returns
150
+ -------
151
+ ColumnDataKind
152
+ The data kind of the column.
153
+ """
154
+ column_dtype = column.dtype
155
+ if pd.api.types.is_bool_dtype(column_dtype):
156
+ return ColumnDataKind.BOOLEAN
157
+
158
+ if pd.api.types.is_integer_dtype(column_dtype):
159
+ return ColumnDataKind.INTEGER
160
+
161
+ if pd.api.types.is_float_dtype(column_dtype):
162
+ return ColumnDataKind.FLOAT
163
+
164
+ if pd.api.types.is_datetime64_any_dtype(column_dtype):
165
+ return ColumnDataKind.DATETIME
166
+
167
+ if pd.api.types.is_timedelta64_dtype(column_dtype):
168
+ return ColumnDataKind.TIMEDELTA
169
+
170
+ if pd.api.types.is_period_dtype(column_dtype):
171
+ return ColumnDataKind.PERIOD
172
+
173
+ if pd.api.types.is_interval_dtype(column_dtype):
174
+ return ColumnDataKind.INTERVAL
175
+
176
+ if pd.api.types.is_complex_dtype(column_dtype):
177
+ return ColumnDataKind.COMPLEX
178
+
179
+ if pd.api.types.is_object_dtype(
180
+ column_dtype
181
+ ) is False and pd.api.types.is_string_dtype(column_dtype):
182
+ # The is_string_dtype
183
+ return ColumnDataKind.STRING
184
+
185
+ return ColumnDataKind.UNKNOWN
186
+
187
+
188
+ def _determine_data_kind_via_inferred_type(
189
+ column: pd.Series | pd.Index,
190
+ ) -> ColumnDataKind:
191
+ """Determine the data kind by inferring it from the underlying data.
192
+
193
+ The column data kind refers to the shared data type of the values
194
+ in the column (e.g. integer, float, string, bool).
195
+
196
+ Parameters
197
+ ----------
198
+ column : pd.Series, pd.Index
199
+ The column to determine the data kind for.
200
+
201
+ Returns
202
+ -------
203
+ ColumnDataKind
204
+ The data kind of the column.
205
+ """
206
+
207
+ inferred_type = pd.api.types.infer_dtype(column)
208
+
209
+ if inferred_type == "string":
210
+ return ColumnDataKind.STRING
211
+
212
+ if inferred_type == "bytes":
213
+ return ColumnDataKind.BYTES
214
+
215
+ if inferred_type in ["floating", "mixed-integer-float"]:
216
+ return ColumnDataKind.FLOAT
217
+
218
+ if inferred_type == "integer":
219
+ return ColumnDataKind.INTEGER
220
+
221
+ if inferred_type == "decimal":
222
+ return ColumnDataKind.DECIMAL
223
+
224
+ if inferred_type == "complex":
225
+ return ColumnDataKind.COMPLEX
226
+
227
+ if inferred_type == "boolean":
228
+ return ColumnDataKind.BOOLEAN
229
+
230
+ if inferred_type in ["datetime64", "datetime"]:
231
+ return ColumnDataKind.DATETIME
232
+
233
+ if inferred_type == "date":
234
+ return ColumnDataKind.DATE
235
+
236
+ if inferred_type in ["timedelta64", "timedelta"]:
237
+ return ColumnDataKind.TIMEDELTA
238
+
239
+ if inferred_type == "time":
240
+ return ColumnDataKind.TIME
241
+
242
+ if inferred_type == "period":
243
+ return ColumnDataKind.PERIOD
244
+
245
+ if inferred_type == "interval":
246
+ return ColumnDataKind.INTERVAL
247
+
248
+ if inferred_type == "empty":
249
+ return ColumnDataKind.EMPTY
250
+
251
+ # TODO(lukasmasuch): Unused types: mixed, unknown-array, categorical, mixed-integer
252
+ return ColumnDataKind.UNKNOWN
253
+
254
+
255
+ def _determine_data_kind(
256
+ column: pd.Series | pd.Index, field: Optional[pa.Field] = None
257
+ ) -> ColumnDataKind:
258
+ """Determine the data kind of a column.
259
+
260
+ The column data kind refers to the shared data type of the values
261
+ in the column (e.g. integer, float, string, bool).
262
+
263
+ Parameters
264
+ ----------
265
+ column : pd.Series, pd.Index
266
+ The column to determine the data kind for.
267
+ field : pa.Field, optional
268
+ The arrow field from the arrow table schema.
269
+
270
+ Returns
271
+ -------
272
+ ColumnDataKind
273
+ The data kind of the column.
274
+ """
275
+
276
+ if pd.api.types.is_categorical_dtype(column.dtype):
277
+ # Categorical columns can have different underlying data kinds
278
+ # depending on the categories.
279
+ return _determine_data_kind_via_inferred_type(column.dtype.categories)
280
+
281
+ if field is not None:
282
+ data_kind = _determine_data_kind_via_arrow(field)
283
+ if data_kind != ColumnDataKind.UNKNOWN:
284
+ return data_kind
285
+
286
+ if column.dtype.name == "object":
287
+ # If dtype is object, we need to infer the type from the column
288
+ return _determine_data_kind_via_inferred_type(column)
289
+ return _determine_data_kind_via_pandas_dtype(column)
290
+
291
+
292
+ def determine_dataframe_schema(
293
+ data_df: pd.DataFrame, arrow_schema: pa.Schema
294
+ ) -> DataframeSchema:
295
+ """Determine the schema of a dataframe.
296
+
297
+ Parameters
298
+ ----------
299
+ data_df : pd.DataFrame
300
+ The dataframe to determine the schema of.
301
+ arrow_schema : pa.Schema
302
+ The Arrow schema of the dataframe.
303
+
304
+ Returns
305
+ -------
306
+
307
+ DataframeSchema
308
+ A list that contains the detected data type for the index and columns.
309
+ It starts with the index and then contains the columns in the original order.
310
+ """
311
+
312
+ dataframe_schema: DataframeSchema = []
313
+
314
+ # Add type of index:
315
+ dataframe_schema.append(_determine_data_kind(data_df.index))
316
+
317
+ # Add types for all columns:
318
+ for i, column in enumerate(data_df.items()):
319
+ _, column_data = column
320
+ dataframe_schema.append(
321
+ _determine_data_kind(column_data, arrow_schema.field(i))
322
+ )
323
+ return dataframe_schema
324
+
325
+
326
+ class ColumnConfig(TypedDict, total=False):
327
+ title: Optional[str]
328
+ width: Optional[Literal["small", "medium", "large"]]
329
+ hidden: Optional[bool]
330
+ disabled: Optional[bool]
331
+ required: Optional[bool]
332
+ alignment: Optional[Literal["left", "center", "right"]]
333
+ help: Optional[str]
334
+ type: Optional[ColumnType]
335
+ type_options: Optional[Dict[str, Any]]
336
+
337
+
338
+ # A mapping of column names/IDs to column configs.
339
+ ColumnConfigMapping: TypeAlias = Dict[Union[IndexIdentifierType, str], ColumnConfig]
340
+
341
+
342
+ def marshall_column_config(
343
+ proto: ArrowProto, column_config_mapping: ColumnConfigMapping
344
+ ) -> None:
345
+ """Marshall the column config into the Arrow proto.
346
+
347
+ Parameters
348
+ ----------
349
+ proto : ArrowProto
350
+ The proto to marshall into.
351
+
352
+ column_config_mapping : ColumnConfigMapping
353
+ The column config to marshall.
354
+ """
355
+
356
+ # Ignore all None values and prefix columns specified by numerical index
357
+ def remove_none_values(input_dict: Dict[Any, Any]) -> Dict[Any, Any]:
358
+ new_dict = {}
359
+ for key, val in input_dict.items():
360
+ if isinstance(val, dict):
361
+ val = remove_none_values(val)
362
+ if val is not None:
363
+ new_dict[key] = val
364
+ return new_dict
365
+
366
+ proto.columns = json.dumps(
367
+ {
368
+ (f"{_NUMERICAL_POSITION_PREFIX}{str(k)}" if isinstance(k, int) else k): v
369
+ for (k, v) in remove_none_values(column_config_mapping).items()
370
+ }
371
+ )
@@ -0,0 +1,275 @@
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
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
+ from typing import TYPE_CHECKING, Any, List, Mapping, TypeVar
16
+
17
+ from pandas import DataFrame
18
+ from pandas.io.formats.style import Styler
19
+
20
+ from streamlit import type_util
21
+ from streamlit.proto.Arrow_pb2 import Arrow as ArrowProto
22
+
23
+ if TYPE_CHECKING:
24
+ from pandas.io.formats.style import Styler
25
+
26
+
27
+ def marshall_styler(proto: ArrowProto, styler: "Styler", default_uuid: str) -> None:
28
+ """Marshall pandas.Styler into an Arrow proto.
29
+
30
+ Parameters
31
+ ----------
32
+ proto : proto.Arrow
33
+ Output. The protobuf for Streamlit Arrow proto.
34
+
35
+ styler : pandas.Styler
36
+ Helps style a DataFrame or Series according to the data with HTML and CSS.
37
+
38
+ default_uuid : str
39
+ If pandas.Styler uuid is not provided, this value will be used.
40
+
41
+ """
42
+ # pandas.Styler uuid should be set before _compute is called.
43
+ _marshall_uuid(proto, styler, default_uuid)
44
+
45
+ # We're using protected members of pandas.Styler to get styles,
46
+ # which is not ideal and could break if the interface changes.
47
+ styler._compute()
48
+
49
+ # In Pandas 1.3.0, styler._translate() signature was changed.
50
+ # 2 arguments were added: sparse_index and sparse_columns.
51
+ # The functionality that they provide is not yet supported.
52
+ if type_util.is_pandas_version_less_than("1.3.0"):
53
+ pandas_styles = styler._translate()
54
+ else:
55
+ pandas_styles = styler._translate(False, False)
56
+
57
+ _marshall_caption(proto, styler)
58
+ _marshall_styles(proto, styler, pandas_styles)
59
+ _marshall_display_values(proto, styler.data, pandas_styles)
60
+
61
+
62
+ def _marshall_uuid(proto: ArrowProto, styler: "Styler", default_uuid: str) -> None:
63
+ """Marshall pandas.Styler uuid into an Arrow proto.
64
+
65
+ Parameters
66
+ ----------
67
+ proto : proto.Arrow
68
+ Output. The protobuf for Streamlit Arrow proto.
69
+
70
+ styler : pandas.Styler
71
+ Helps style a DataFrame or Series according to the data with HTML and CSS.
72
+
73
+ default_uuid : str
74
+ If pandas.Styler uuid is not provided, this value will be used.
75
+
76
+ """
77
+ if styler.uuid is None:
78
+ styler.set_uuid(default_uuid)
79
+
80
+ proto.styler.uuid = str(styler.uuid)
81
+
82
+
83
+ def _marshall_caption(proto: ArrowProto, styler: "Styler") -> None:
84
+ """Marshall pandas.Styler caption into an Arrow proto.
85
+
86
+ Parameters
87
+ ----------
88
+ proto : proto.Arrow
89
+ Output. The protobuf for Streamlit Arrow proto.
90
+
91
+ styler : pandas.Styler
92
+ Helps style a DataFrame or Series according to the data with HTML and CSS.
93
+
94
+ """
95
+ if styler.caption is not None:
96
+ proto.styler.caption = styler.caption
97
+
98
+
99
+ def _marshall_styles(
100
+ proto: ArrowProto, styler: "Styler", styles: Mapping[str, Any]
101
+ ) -> None:
102
+ """Marshall pandas.Styler styles into an Arrow proto.
103
+
104
+ Parameters
105
+ ----------
106
+ proto : proto.Arrow
107
+ Output. The protobuf for Streamlit Arrow proto.
108
+
109
+ styler : pandas.Styler
110
+ Helps style a DataFrame or Series according to the data with HTML and CSS.
111
+
112
+ styles : dict
113
+ pandas.Styler translated styles.
114
+
115
+ """
116
+ css_rules = []
117
+
118
+ if "table_styles" in styles:
119
+ table_styles = styles["table_styles"]
120
+ table_styles = _trim_pandas_styles(table_styles)
121
+ for style in table_styles:
122
+ # styles in "table_styles" have a space
123
+ # between the uuid and selector.
124
+ rule = _pandas_style_to_css(
125
+ "table_styles", style, styler.uuid, separator=" "
126
+ )
127
+ css_rules.append(rule)
128
+
129
+ if "cellstyle" in styles:
130
+ cellstyle = styles["cellstyle"]
131
+ cellstyle = _trim_pandas_styles(cellstyle)
132
+ for style in cellstyle:
133
+ rule = _pandas_style_to_css("cell_style", style, styler.uuid)
134
+ css_rules.append(rule)
135
+
136
+ if len(css_rules) > 0:
137
+ proto.styler.styles = "\n".join(css_rules)
138
+
139
+
140
+ M = TypeVar("M", bound=Mapping[str, Any])
141
+
142
+
143
+ def _trim_pandas_styles(styles: List[M]) -> List[M]:
144
+ """Filter out empty styles.
145
+
146
+ Every cell will have a class, but the list of props
147
+ may just be [['', '']].
148
+
149
+ Parameters
150
+ ----------
151
+ styles : list
152
+ pandas.Styler translated styles.
153
+
154
+ """
155
+ return [x for x in styles if any(any(y) for y in x["props"])]
156
+
157
+
158
+ def _pandas_style_to_css(
159
+ style_type: str,
160
+ style: Mapping[str, Any],
161
+ uuid: str,
162
+ separator: str = "",
163
+ ) -> str:
164
+ """Convert pandas.Styler translated style to CSS.
165
+
166
+ Parameters
167
+ ----------
168
+ style_type : str
169
+ Either "table_styles" or "cell_style".
170
+
171
+ style : dict
172
+ pandas.Styler translated style.
173
+
174
+ uuid : str
175
+ pandas.Styler uuid.
176
+
177
+ separator : str
178
+ A string separator used between table and cell selectors.
179
+
180
+ """
181
+ declarations = []
182
+ for css_property, css_value in style["props"]:
183
+ declaration = css_property.strip() + ": " + css_value.strip()
184
+ declarations.append(declaration)
185
+
186
+ table_selector = f"#T_{uuid}"
187
+
188
+ # In pandas < 1.1.0
189
+ # translated_style["cellstyle"] has the following shape:
190
+ # [
191
+ # {
192
+ # "props": [["color", " black"], ["background-color", "orange"], ["", ""]],
193
+ # "selector": "row0_col0"
194
+ # }
195
+ # ...
196
+ # ]
197
+ #
198
+ # In pandas >= 1.1.0
199
+ # translated_style["cellstyle"] has the following shape:
200
+ # [
201
+ # {
202
+ # "props": [("color", " black"), ("background-color", "orange"), ("", "")],
203
+ # "selectors": ["row0_col0"]
204
+ # }
205
+ # ...
206
+ # ]
207
+ if style_type == "table_styles" or (
208
+ style_type == "cell_style" and type_util.is_pandas_version_less_than("1.1.0")
209
+ ):
210
+ cell_selectors = [style["selector"]]
211
+ else:
212
+ cell_selectors = style["selectors"]
213
+
214
+ selectors = []
215
+ for cell_selector in cell_selectors:
216
+ selectors.append(table_selector + separator + cell_selector)
217
+ selector = ", ".join(selectors)
218
+
219
+ declaration_block = "; ".join(declarations)
220
+ rule_set = selector + " { " + declaration_block + " }"
221
+
222
+ return rule_set
223
+
224
+
225
+ def _marshall_display_values(
226
+ proto: ArrowProto, df: DataFrame, styles: Mapping[str, Any]
227
+ ) -> None:
228
+ """Marshall pandas.Styler display values into an Arrow proto.
229
+
230
+ Parameters
231
+ ----------
232
+ proto : proto.Arrow
233
+ Output. The protobuf for Streamlit Arrow proto.
234
+
235
+ df : pandas.DataFrame
236
+ A dataframe with original values.
237
+
238
+ styles : dict
239
+ pandas.Styler translated styles.
240
+
241
+ """
242
+ new_df = _use_display_values(df, styles)
243
+ proto.styler.display_values = type_util.data_frame_to_bytes(new_df)
244
+
245
+
246
+ def _use_display_values(df: DataFrame, styles: Mapping[str, Any]) -> DataFrame:
247
+ """Create a new pandas.DataFrame where display values are used instead of original ones.
248
+
249
+ Parameters
250
+ ----------
251
+ df : pandas.DataFrame
252
+ A dataframe with original values.
253
+
254
+ styles : dict
255
+ pandas.Styler translated styles.
256
+
257
+ """
258
+ import re
259
+
260
+ # If values in a column are not of the same type, Arrow
261
+ # serialization would fail. Thus, we need to cast all values
262
+ # of the dataframe to strings before assigning them display values.
263
+ new_df = df.astype(str)
264
+
265
+ cell_selector_regex = re.compile(r"row(\d+)_col(\d+)")
266
+ if "body" in styles:
267
+ rows = styles["body"]
268
+ for row in rows:
269
+ for cell in row:
270
+ match = cell_selector_regex.match(cell["id"])
271
+ if match:
272
+ r, c = map(int, match.groups())
273
+ new_df.iat[r, c] = str(cell["display_value"])
274
+
275
+ return new_df