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.
- streamlit/__init__.py +0 -7
- streamlit/elements/arrow.py +7 -264
- streamlit/elements/data_editor.py +109 -100
- streamlit/elements/file_uploader.py +17 -0
- streamlit/elements/layouts.py +0 -5
- streamlit/elements/lib/column_config_utils.py +371 -0
- streamlit/elements/lib/pandas_styler_utils.py +275 -0
- streamlit/runtime/connection_factory.py +5 -5
- streamlit/static/asset-manifest.json +20 -20
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/{14.a19a6cd8.chunk.js → 14.9399e424.chunk.js} +1 -1
- streamlit/static/static/js/{227.087adf66.chunk.js → 227.9ccac1d5.chunk.js} +1 -1
- streamlit/static/static/js/{242.0daf8b47.chunk.js → 242.1b3289e0.chunk.js} +1 -1
- streamlit/static/static/js/{279.fdac58fc.chunk.js → 279.35b01780.chunk.js} +1 -1
- streamlit/static/static/js/{289.481fd42d.chunk.js → 289.e6157e40.chunk.js} +1 -1
- streamlit/static/static/js/{467.242e14ff.chunk.js → 467.50ac84df.chunk.js} +1 -1
- streamlit/static/static/js/{491.d0b710e9.chunk.js → 491.5a33a8ce.chunk.js} +1 -1
- streamlit/static/static/js/503.15864587.chunk.js +1 -0
- streamlit/static/static/js/{511.9f04ae9e.chunk.js → 511.e6ca580f.chunk.js} +1 -1
- streamlit/static/static/js/{578.ceaadcd5.chunk.js → 578.a65fcea0.chunk.js} +1 -1
- streamlit/static/static/js/{619.365611c8.chunk.js → 619.0325af0e.chunk.js} +1 -1
- streamlit/static/static/js/{628.7f41e2de.chunk.js → 628.9c70196b.chunk.js} +1 -1
- streamlit/static/static/js/{681.a2ba76c7.chunk.js → 681.9e30a8cd.chunk.js} +1 -1
- streamlit/static/static/js/{745.e2bcf16d.chunk.js → 745.e75ba963.chunk.js} +1 -1
- streamlit/static/static/js/{807.6789990f.chunk.js → 807.122f8b05.chunk.js} +1 -1
- streamlit/static/static/js/{828.096c1ad3.chunk.js → 828.0fde3da8.chunk.js} +1 -1
- streamlit/static/static/js/{871.ba625aee.chunk.js → 871.90a7dbae.chunk.js} +1 -1
- streamlit/static/static/js/{main.5e4731c6.js → main.ff35bd72.js} +2 -2
- streamlit/testing/element_tree.py +426 -548
- streamlit/type_util.py +19 -7
- {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/RECORD +38 -37
- streamlit/elements/show.py +0 -105
- streamlit/static/static/js/728.82770810.chunk.js +0 -1
- /streamlit/static/static/css/{728.23fa976d.chunk.css → 503.23fa976d.chunk.css} +0 -0
- /streamlit/static/static/js/{main.5e4731c6.js.LICENSE.txt → main.ff35bd72.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.21.1.dev20230423.data → streamlit_nightly-1.21.1.dev20230425.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.21.1.dev20230423.dist-info → streamlit_nightly-1.21.1.dev20230425.dist-info}/entry_points.txt +0 -0
- {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 []
|
streamlit/elements/layouts.py
CHANGED
@@ -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
|