streamlit-nightly 1.31.1.dev20240211__py2.py3-none-any.whl → 1.31.2.dev20240212__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/cli_util.py +44 -0
- streamlit/config.py +36 -34
- streamlit/config_util.py +18 -16
- streamlit/delta_generator.py +20 -16
- streamlit/elements/image.py +30 -18
- streamlit/elements/write.py +5 -6
- streamlit/runtime/credentials.py +67 -55
- streamlit/runtime/secrets.py +6 -4
- streamlit/static/asset-manifest.json +3 -3
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/4666.ad225eae.chunk.js +1 -0
- streamlit/static/static/js/{main.603949f7.js → main.043d802e.js} +2 -2
- streamlit/url_util.py +0 -8
- streamlit/watcher/path_watcher.py +3 -6
- streamlit/web/bootstrap.py +48 -46
- streamlit/web/cli.py +8 -5
- streamlit/web/server/routes.py +8 -0
- streamlit/web/server/server.py +2 -3
- {streamlit_nightly-1.31.1.dev20240211.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.31.1.dev20240211.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/RECORD +25 -24
- streamlit/static/static/js/4666.3a6efd97.chunk.js +0 -1
- /streamlit/static/static/js/{main.603949f7.js.LICENSE.txt → main.043d802e.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.31.1.dev20240211.data → streamlit_nightly-1.31.2.dev20240212.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.31.1.dev20240211.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.31.1.dev20240211.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.31.1.dev20240211.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/top_level.txt +0 -0
streamlit/cli_util.py
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
""" Utilities related to the CLI."""
|
16
|
+
|
17
|
+
|
18
|
+
def print_to_cli(message: str, **kwargs) -> None:
|
19
|
+
"""Print a message to the terminal using click if available, else print
|
20
|
+
using the built-in print function.
|
21
|
+
|
22
|
+
You can provide any keyword arguments that click.secho supports.
|
23
|
+
"""
|
24
|
+
try:
|
25
|
+
import click
|
26
|
+
|
27
|
+
click.secho(message, **kwargs)
|
28
|
+
except ImportError:
|
29
|
+
print(message, flush=True)
|
30
|
+
|
31
|
+
|
32
|
+
def style_for_cli(message: str, **kwargs) -> str:
|
33
|
+
"""Style a message using click if available, else return the message
|
34
|
+
unchanged.
|
35
|
+
|
36
|
+
You can provide any keyword arguments that click.style supports.
|
37
|
+
"""
|
38
|
+
|
39
|
+
try:
|
40
|
+
import click
|
41
|
+
|
42
|
+
return click.style(message, **kwargs)
|
43
|
+
except ImportError:
|
44
|
+
return message
|
streamlit/config.py
CHANGED
@@ -14,14 +14,15 @@
|
|
14
14
|
|
15
15
|
"""Loads the configuration data."""
|
16
16
|
|
17
|
+
from __future__ import annotations
|
18
|
+
|
17
19
|
import copy
|
18
20
|
import os
|
19
21
|
import secrets
|
20
22
|
import threading
|
21
23
|
from collections import OrderedDict
|
22
|
-
from typing import Any, Callable
|
24
|
+
from typing import Any, Callable
|
23
25
|
|
24
|
-
import toml
|
25
26
|
from blinker import Signal
|
26
27
|
|
27
28
|
from streamlit import config_util, development, env_util, file_util, util
|
@@ -33,7 +34,7 @@ from streamlit.errors import StreamlitAPIException
|
|
33
34
|
# Descriptions of each of the possible config sections.
|
34
35
|
# (We use OrderedDict to make the order in which sections are declared in this
|
35
36
|
# file be the same order as the sections appear with `streamlit config show`)
|
36
|
-
_section_descriptions:
|
37
|
+
_section_descriptions: dict[str, str] = OrderedDict(
|
37
38
|
_test="Special test section just used for unit tests."
|
38
39
|
)
|
39
40
|
|
@@ -46,10 +47,10 @@ _config_lock = threading.RLock()
|
|
46
47
|
# to `streamlit run`, etc. Note that this and _config_options below are
|
47
48
|
# OrderedDicts to ensure stable ordering when printed using
|
48
49
|
# `streamlit config show`.
|
49
|
-
_config_options_template:
|
50
|
+
_config_options_template: dict[str, ConfigOption] = OrderedDict()
|
50
51
|
|
51
52
|
# Stores the current state of config options.
|
52
|
-
_config_options:
|
53
|
+
_config_options: dict[str, ConfigOption] | None = None
|
53
54
|
|
54
55
|
|
55
56
|
# Indicates that a config option was defined by the user.
|
@@ -147,7 +148,7 @@ def get_option(key: str) -> Any:
|
|
147
148
|
return config_options[key].value
|
148
149
|
|
149
150
|
|
150
|
-
def get_options_for_section(section: str) ->
|
151
|
+
def get_options_for_section(section: str) -> dict[str, Any]:
|
151
152
|
"""Get all of the config options for the given section.
|
152
153
|
|
153
154
|
Run `streamlit config show` in the terminal to see all available options.
|
@@ -159,7 +160,7 @@ def get_options_for_section(section: str) -> Dict[str, Any]:
|
|
159
160
|
|
160
161
|
Returns
|
161
162
|
-------
|
162
|
-
|
163
|
+
dict[str, Any]
|
163
164
|
A dict mapping the names of the options in the given section (without
|
164
165
|
the section name as a prefix) to their values.
|
165
166
|
"""
|
@@ -183,14 +184,14 @@ def _create_section(section: str, description: str) -> None:
|
|
183
184
|
|
184
185
|
def _create_option(
|
185
186
|
key: str,
|
186
|
-
description:
|
187
|
-
default_val:
|
187
|
+
description: str | None = None,
|
188
|
+
default_val: Any | None = None,
|
188
189
|
scriptable: bool = False,
|
189
190
|
visibility: str = "visible",
|
190
191
|
deprecated: bool = False,
|
191
|
-
deprecation_text:
|
192
|
-
expiration_date:
|
193
|
-
replaced_by:
|
192
|
+
deprecation_text: str | None = None,
|
193
|
+
expiration_date: str | None = None,
|
194
|
+
replaced_by: str | None = None,
|
194
195
|
type_: type = str,
|
195
196
|
sensitive: bool = False,
|
196
197
|
) -> ConfigOption:
|
@@ -260,7 +261,10 @@ def _delete_option(key: str) -> None:
|
|
260
261
|
"""
|
261
262
|
try:
|
262
263
|
del _config_options_template[key]
|
263
|
-
|
264
|
+
assert (
|
265
|
+
_config_options is not None
|
266
|
+
), "_config_options should always be populated here."
|
267
|
+
del _config_options[key]
|
264
268
|
except Exception:
|
265
269
|
# We don't care if the option already doesn't exist.
|
266
270
|
pass
|
@@ -707,7 +711,7 @@ _create_option(
|
|
707
711
|
|
708
712
|
|
709
713
|
@_create_option("server.address")
|
710
|
-
def _server_address() ->
|
714
|
+
def _server_address() -> str | None:
|
711
715
|
"""The address where the server will listen for client and browser
|
712
716
|
connections. Use this if you want to bind the server to a specific address.
|
713
717
|
If set, the server will only be accessible from this address, and not from
|
@@ -858,24 +862,19 @@ _create_option(
|
|
858
862
|
)
|
859
863
|
|
860
864
|
|
861
|
-
@_create_option(
|
862
|
-
"browser.serverPort",
|
863
|
-
visibility="hidden",
|
864
|
-
deprecated=True,
|
865
|
-
deprecation_text="browser.serverPort has been deprecated. It will be removed in a future version.",
|
866
|
-
expiration_date="2024-04-01",
|
867
|
-
type_=int,
|
868
|
-
)
|
865
|
+
@_create_option("browser.serverPort", type_=int)
|
869
866
|
def _browser_server_port() -> int:
|
870
867
|
"""Port where users should point their browsers in order to connect to the
|
871
868
|
app.
|
872
869
|
|
873
870
|
This is used to:
|
874
|
-
- Set the correct URL for
|
875
|
-
- Show the URL on the terminal
|
876
|
-
- Open the browser
|
871
|
+
- Set the correct URL for XSRF protection purposes.
|
872
|
+
- Show the URL on the terminal (part of `streamlit run`).
|
873
|
+
- Open the browser automatically (part of `streamlit run`).
|
877
874
|
|
878
|
-
|
875
|
+
This option is for advanced use cases. To change the port of your app, use
|
876
|
+
`server.Port` instead. Don't use port 3000 which is reserved for internal
|
877
|
+
development.
|
879
878
|
|
880
879
|
Default: whatever value is set in server.port.
|
881
880
|
"""
|
@@ -1121,9 +1120,10 @@ def is_manually_set(option_name: str) -> bool:
|
|
1121
1120
|
def show_config() -> None:
|
1122
1121
|
"""Print all config options to the terminal."""
|
1123
1122
|
with _config_lock:
|
1124
|
-
|
1125
|
-
|
1126
|
-
)
|
1123
|
+
assert (
|
1124
|
+
_config_options is not None
|
1125
|
+
), "_config_options should always be populated here."
|
1126
|
+
config_util.show_config(_section_descriptions, _config_options)
|
1127
1127
|
|
1128
1128
|
|
1129
1129
|
# Load Config Files #
|
@@ -1162,7 +1162,7 @@ def _set_option(key: str, value: Any, where_defined: str) -> None:
|
|
1162
1162
|
_config_options[key].set_value(value, where_defined)
|
1163
1163
|
|
1164
1164
|
|
1165
|
-
def _update_config_with_sensitive_env_var(config_options:
|
1165
|
+
def _update_config_with_sensitive_env_var(config_options: dict[str, ConfigOption]):
|
1166
1166
|
"""Update the config system by parsing the environment variable.
|
1167
1167
|
|
1168
1168
|
This should only be called from get_config_options.
|
@@ -1189,6 +1189,8 @@ def _update_config_with_toml(raw_toml: str, where_defined: str) -> None:
|
|
1189
1189
|
Tells the config system where this was set.
|
1190
1190
|
|
1191
1191
|
"""
|
1192
|
+
import toml
|
1193
|
+
|
1192
1194
|
parsed_config_file = toml.loads(raw_toml)
|
1193
1195
|
|
1194
1196
|
for section, options in parsed_config_file.items():
|
@@ -1257,8 +1259,8 @@ CONFIG_FILENAMES = [
|
|
1257
1259
|
|
1258
1260
|
|
1259
1261
|
def get_config_options(
|
1260
|
-
force_reparse=False, options_from_flags:
|
1261
|
-
) ->
|
1262
|
+
force_reparse=False, options_from_flags: dict[str, Any] | None = None
|
1263
|
+
) -> dict[str, ConfigOption]:
|
1262
1264
|
"""Create and return a dict mapping config option names to their values,
|
1263
1265
|
returning a cached dict if possible.
|
1264
1266
|
|
@@ -1275,12 +1277,12 @@ def get_config_options(
|
|
1275
1277
|
force_reparse : bool
|
1276
1278
|
Force config files to be parsed so that we pick up any changes to them.
|
1277
1279
|
|
1278
|
-
options_from_flags :
|
1280
|
+
options_from_flags : dict[str, any] or None
|
1279
1281
|
Config options that we received via CLI flag.
|
1280
1282
|
|
1281
1283
|
Returns
|
1282
1284
|
-------
|
1283
|
-
|
1285
|
+
dict[str, ConfigOption]
|
1284
1286
|
An ordered dict that maps config option names to their values.
|
1285
1287
|
"""
|
1286
1288
|
global _config_options
|
streamlit/config_util.py
CHANGED
@@ -12,17 +12,16 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
import
|
16
|
-
from typing import Dict
|
15
|
+
from __future__ import annotations
|
17
16
|
|
18
|
-
import
|
19
|
-
import toml
|
17
|
+
import re
|
20
18
|
|
19
|
+
from streamlit import cli_util
|
21
20
|
from streamlit.config_option import ConfigOption
|
22
21
|
|
23
22
|
|
24
23
|
def server_option_changed(
|
25
|
-
old_options:
|
24
|
+
old_options: dict[str, ConfigOption], new_options: dict[str, ConfigOption]
|
26
25
|
) -> bool:
|
27
26
|
"""Return True if and only if an option in the server section differs
|
28
27
|
between old_options and new_options.
|
@@ -40,10 +39,11 @@ def server_option_changed(
|
|
40
39
|
|
41
40
|
|
42
41
|
def show_config(
|
43
|
-
section_descriptions:
|
44
|
-
config_options:
|
42
|
+
section_descriptions: dict[str, str],
|
43
|
+
config_options: dict[str, ConfigOption],
|
45
44
|
) -> None:
|
46
45
|
"""Print the given config sections/options to the terminal."""
|
46
|
+
|
47
47
|
out = []
|
48
48
|
out.append(
|
49
49
|
_clean(
|
@@ -55,16 +55,16 @@ def show_config(
|
|
55
55
|
)
|
56
56
|
|
57
57
|
def append_desc(text):
|
58
|
-
out.append("# " +
|
58
|
+
out.append("# " + cli_util.style_for_cli(text, bold=True))
|
59
59
|
|
60
60
|
def append_comment(text):
|
61
|
-
out.append("# " +
|
61
|
+
out.append("# " + cli_util.style_for_cli(text))
|
62
62
|
|
63
63
|
def append_section(text):
|
64
|
-
out.append(
|
64
|
+
out.append(cli_util.style_for_cli(text, bold=True, fg="green"))
|
65
65
|
|
66
66
|
def append_setting(text):
|
67
|
-
out.append(
|
67
|
+
out.append(cli_util.style_for_cli(text, fg="green"))
|
68
68
|
|
69
69
|
for section, _ in section_descriptions.items():
|
70
70
|
# We inject a fake config section used for unit tests that we exclude here as
|
@@ -89,7 +89,7 @@ def show_config(
|
|
89
89
|
|
90
90
|
for key, option in section_options.items():
|
91
91
|
key = option.key.split(".")[1]
|
92
|
-
description_paragraphs = _clean_paragraphs(option.description)
|
92
|
+
description_paragraphs = _clean_paragraphs(option.description or "")
|
93
93
|
|
94
94
|
last_paragraph_idx = len(description_paragraphs) - 1
|
95
95
|
|
@@ -114,6 +114,8 @@ def show_config(
|
|
114
114
|
if i != last_paragraph_idx:
|
115
115
|
out.append("")
|
116
116
|
|
117
|
+
import toml
|
118
|
+
|
117
119
|
toml_default = toml.dumps({"default": option.default_val})
|
118
120
|
toml_default = toml_default[10:].strip()
|
119
121
|
|
@@ -128,7 +130,7 @@ def show_config(
|
|
128
130
|
pass
|
129
131
|
|
130
132
|
if option.deprecated:
|
131
|
-
append_comment(
|
133
|
+
append_comment(cli_util.style_for_cli("DEPRECATED.", fg="yellow"))
|
132
134
|
for line in _clean_paragraphs(option.deprecation_text):
|
133
135
|
append_comment(line)
|
134
136
|
append_comment(
|
@@ -152,10 +154,10 @@ def show_config(
|
|
152
154
|
|
153
155
|
append_setting(toml_setting)
|
154
156
|
|
155
|
-
|
157
|
+
cli_util.print_to_cli("\n".join(out))
|
156
158
|
|
157
159
|
|
158
|
-
def _clean(txt):
|
160
|
+
def _clean(txt: str) -> str:
|
159
161
|
"""Replace sequences of multiple spaces with a single space, excluding newlines.
|
160
162
|
|
161
163
|
Preserves leading and trailing spaces, and does not modify spaces in between lines.
|
@@ -163,7 +165,7 @@ def _clean(txt):
|
|
163
165
|
return re.sub(" +", " ", txt)
|
164
166
|
|
165
167
|
|
166
|
-
def _clean_paragraphs(txt):
|
168
|
+
def _clean_paragraphs(txt: str) -> list[str]:
|
167
169
|
"""Split the text into paragraphs, preserve newlines within the paragraphs."""
|
168
170
|
# Strip both leading and trailing newlines.
|
169
171
|
txt = txt.strip("\n")
|
streamlit/delta_generator.py
CHANGED
@@ -22,20 +22,27 @@ from typing import (
|
|
22
22
|
TYPE_CHECKING,
|
23
23
|
Any,
|
24
24
|
Callable,
|
25
|
+
Final,
|
25
26
|
Hashable,
|
26
27
|
Iterable,
|
28
|
+
Literal,
|
27
29
|
NoReturn,
|
28
|
-
Optional,
|
29
30
|
Type,
|
30
31
|
TypeVar,
|
31
32
|
cast,
|
32
33
|
overload,
|
33
34
|
)
|
34
35
|
|
35
|
-
import
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
from streamlit import (
|
37
|
+
cli_util,
|
38
|
+
config,
|
39
|
+
cursor,
|
40
|
+
env_util,
|
41
|
+
logger,
|
42
|
+
runtime,
|
43
|
+
type_util,
|
44
|
+
util,
|
45
|
+
)
|
39
46
|
from streamlit.cursor import Cursor
|
40
47
|
from streamlit.elements.alert import AlertMixin
|
41
48
|
from streamlit.elements.altair_utils import AddRowsMetadata
|
@@ -83,7 +90,6 @@ from streamlit.elements.widgets.text_widgets import TextWidgetsMixin
|
|
83
90
|
from streamlit.elements.widgets.time_widgets import TimeWidgetsMixin
|
84
91
|
from streamlit.elements.write import WriteMixin
|
85
92
|
from streamlit.errors import NoSessionContext, StreamlitAPIException
|
86
|
-
from streamlit.logger import get_logger
|
87
93
|
from streamlit.proto import Block_pb2, ForwardMsg_pb2
|
88
94
|
from streamlit.proto.RootContainer_pb2 import RootContainer
|
89
95
|
from streamlit.runtime import caching, legacy_caching
|
@@ -99,8 +105,6 @@ if TYPE_CHECKING:
|
|
99
105
|
from streamlit.elements.arrow import Data
|
100
106
|
|
101
107
|
|
102
|
-
LOGGER: Final = get_logger(__name__)
|
103
|
-
|
104
108
|
MAX_DELTA_BYTES: Final[int] = 14 * 1024 * 1024 # 14MB
|
105
109
|
|
106
110
|
# List of Streamlit commands that perform a Pandas "melt" operation on
|
@@ -132,7 +136,7 @@ def _maybe_print_use_warning() -> None:
|
|
132
136
|
if not _use_warning_has_been_displayed:
|
133
137
|
_use_warning_has_been_displayed = True
|
134
138
|
|
135
|
-
warning =
|
139
|
+
warning = cli_util.style_for_cli("Warning:", bold=True, fg="yellow")
|
136
140
|
|
137
141
|
if env_util.is_repl():
|
138
142
|
logger.get_logger("root").warning(
|
@@ -275,7 +279,7 @@ class DeltaGenerator(
|
|
275
279
|
# Change the module of all mixin'ed functions to be st.delta_generator,
|
276
280
|
# instead of the original module (e.g. st.elements.markdown)
|
277
281
|
for mixin in self.__class__.__bases__:
|
278
|
-
for
|
282
|
+
for _, func in mixin.__dict__.items():
|
279
283
|
if callable(func):
|
280
284
|
func.__module__ = self.__module__
|
281
285
|
|
@@ -417,7 +421,7 @@ class DeltaGenerator(
|
|
417
421
|
delta_type: str,
|
418
422
|
element_proto: Message,
|
419
423
|
return_value: None,
|
420
|
-
add_rows_metadata:
|
424
|
+
add_rows_metadata: AddRowsMetadata | None = None,
|
421
425
|
element_width: int | None = None,
|
422
426
|
element_height: int | None = None,
|
423
427
|
) -> DeltaGenerator:
|
@@ -429,7 +433,7 @@ class DeltaGenerator(
|
|
429
433
|
delta_type: str,
|
430
434
|
element_proto: Message,
|
431
435
|
return_value: Type[NoValue],
|
432
|
-
add_rows_metadata:
|
436
|
+
add_rows_metadata: AddRowsMetadata | None = None,
|
433
437
|
element_width: int | None = None,
|
434
438
|
element_height: int | None = None,
|
435
439
|
) -> None:
|
@@ -441,7 +445,7 @@ class DeltaGenerator(
|
|
441
445
|
delta_type: str,
|
442
446
|
element_proto: Message,
|
443
447
|
return_value: Value,
|
444
|
-
add_rows_metadata:
|
448
|
+
add_rows_metadata: AddRowsMetadata | None = None,
|
445
449
|
element_width: int | None = None,
|
446
450
|
element_height: int | None = None,
|
447
451
|
) -> Value:
|
@@ -453,7 +457,7 @@ class DeltaGenerator(
|
|
453
457
|
delta_type: str,
|
454
458
|
element_proto: Message,
|
455
459
|
return_value: None = None,
|
456
|
-
add_rows_metadata:
|
460
|
+
add_rows_metadata: AddRowsMetadata | None = None,
|
457
461
|
element_width: int | None = None,
|
458
462
|
element_height: int | None = None,
|
459
463
|
) -> DeltaGenerator:
|
@@ -465,7 +469,7 @@ class DeltaGenerator(
|
|
465
469
|
delta_type: str,
|
466
470
|
element_proto: Message,
|
467
471
|
return_value: Type[NoValue] | Value | None = None,
|
468
|
-
add_rows_metadata:
|
472
|
+
add_rows_metadata: AddRowsMetadata | None = None,
|
469
473
|
element_width: int | None = None,
|
470
474
|
element_height: int | None = None,
|
471
475
|
) -> DeltaGenerator | Value | None:
|
@@ -476,7 +480,7 @@ class DeltaGenerator(
|
|
476
480
|
delta_type: str,
|
477
481
|
element_proto: Message,
|
478
482
|
return_value: Type[NoValue] | Value | None = None,
|
479
|
-
add_rows_metadata:
|
483
|
+
add_rows_metadata: AddRowsMetadata | None = None,
|
480
484
|
element_width: int | None = None,
|
481
485
|
element_height: int | None = None,
|
482
486
|
) -> DeltaGenerator | Value | None:
|
streamlit/elements/image.py
CHANGED
@@ -19,21 +19,18 @@
|
|
19
19
|
|
20
20
|
"""Image marshalling."""
|
21
21
|
|
22
|
-
import
|
22
|
+
from __future__ import annotations
|
23
|
+
|
23
24
|
import io
|
24
|
-
import mimetypes
|
25
25
|
import os
|
26
26
|
import re
|
27
27
|
from enum import IntEnum
|
28
|
-
from typing import TYPE_CHECKING, List,
|
28
|
+
from typing import TYPE_CHECKING, Final, List, Literal, Sequence, Union, cast
|
29
29
|
|
30
|
-
|
31
|
-
from PIL import GifImagePlugin, Image, ImageFile
|
32
|
-
from typing_extensions import Final, Literal, TypeAlias
|
30
|
+
from typing_extensions import TypeAlias
|
33
31
|
|
34
32
|
from streamlit import runtime, url_util
|
35
33
|
from streamlit.errors import StreamlitAPIException
|
36
|
-
from streamlit.logger import get_logger
|
37
34
|
from streamlit.proto.Image_pb2 import ImageList as ImageListProto
|
38
35
|
from streamlit.runtime import caching
|
39
36
|
from streamlit.runtime.metrics_util import gather_metrics
|
@@ -42,11 +39,10 @@ if TYPE_CHECKING:
|
|
42
39
|
from typing import Any
|
43
40
|
|
44
41
|
import numpy.typing as npt
|
42
|
+
from PIL import GifImagePlugin, Image, ImageFile
|
45
43
|
|
46
44
|
from streamlit.delta_generator import DeltaGenerator
|
47
45
|
|
48
|
-
LOGGER: Final = get_logger(__name__)
|
49
|
-
|
50
46
|
# This constant is related to the frontend maximum content width specified
|
51
47
|
# in App.jsx main container
|
52
48
|
# 730 is the max width of element-container in the frontend, and 2x is for high
|
@@ -54,11 +50,11 @@ LOGGER: Final = get_logger(__name__)
|
|
54
50
|
MAXIMUM_CONTENT_WIDTH: Final[int] = 2 * 730
|
55
51
|
|
56
52
|
PILImage: TypeAlias = Union[
|
57
|
-
ImageFile.ImageFile, Image.Image, GifImagePlugin.GifImageFile
|
53
|
+
"ImageFile.ImageFile", "Image.Image", "GifImagePlugin.GifImageFile"
|
58
54
|
]
|
59
55
|
AtomicImage: TypeAlias = Union[PILImage, "npt.NDArray[Any]", io.BytesIO, str]
|
60
56
|
ImageOrImageList: TypeAlias = Union[AtomicImage, List[AtomicImage]]
|
61
|
-
UseColumnWith: TypeAlias =
|
57
|
+
UseColumnWith: TypeAlias = Union[Literal["auto", "always", "never"], bool, None]
|
62
58
|
Channels: TypeAlias = Literal["RGB", "BGR"]
|
63
59
|
ImageFormat: TypeAlias = Literal["JPEG", "PNG", "GIF"]
|
64
60
|
ImageFormatOrAuto: TypeAlias = Literal[ImageFormat, "auto"]
|
@@ -91,8 +87,8 @@ class ImageMixin:
|
|
91
87
|
image: ImageOrImageList,
|
92
88
|
# TODO: Narrow type of caption, dependent on type of image,
|
93
89
|
# by way of overload
|
94
|
-
caption:
|
95
|
-
width:
|
90
|
+
caption: str | List[str] | None = None,
|
91
|
+
width: int | None = None,
|
96
92
|
use_column_width: UseColumnWith = None,
|
97
93
|
clamp: bool = False,
|
98
94
|
channels: Channels = "RGB",
|
@@ -192,7 +188,7 @@ def _image_is_gif(image: PILImage) -> bool:
|
|
192
188
|
|
193
189
|
|
194
190
|
def _validate_image_format_string(
|
195
|
-
image_data:
|
191
|
+
image_data: bytes | PILImage, format: str
|
196
192
|
) -> ImageFormat:
|
197
193
|
"""Return either "JPEG", "PNG", or "GIF", based on the input `format` string.
|
198
194
|
|
@@ -210,6 +206,8 @@ def _validate_image_format_string(
|
|
210
206
|
return "JPEG"
|
211
207
|
|
212
208
|
if isinstance(image_data, bytes):
|
209
|
+
from PIL import Image
|
210
|
+
|
213
211
|
pil_image = Image.open(io.BytesIO(image_data))
|
214
212
|
else:
|
215
213
|
pil_image = image_data
|
@@ -246,6 +244,9 @@ def _BytesIO_to_bytes(data: io.BytesIO) -> bytes:
|
|
246
244
|
|
247
245
|
|
248
246
|
def _np_array_to_bytes(array: "npt.NDArray[Any]", output_format="JPEG") -> bytes:
|
247
|
+
import numpy as np
|
248
|
+
from PIL import Image
|
249
|
+
|
249
250
|
img = Image.fromarray(array.astype(np.uint8))
|
250
251
|
format = _validate_image_format_string(img, output_format)
|
251
252
|
|
@@ -284,6 +285,8 @@ def _ensure_image_size_and_format(
|
|
284
285
|
MAXIMUM_CONTENT_WIDTH. Ensure the image's format corresponds to the given
|
285
286
|
ImageFormat. Return the (possibly resized and reformatted) image bytes.
|
286
287
|
"""
|
288
|
+
from PIL import Image
|
289
|
+
|
287
290
|
pil_image = Image.open(io.BytesIO(image_data))
|
288
291
|
actual_width, actual_height = pil_image.size
|
289
292
|
|
@@ -305,6 +308,8 @@ def _ensure_image_size_and_format(
|
|
305
308
|
|
306
309
|
|
307
310
|
def _clip_image(image: "npt.NDArray[Any]", clamp: bool) -> "npt.NDArray[Any]":
|
311
|
+
import numpy as np
|
312
|
+
|
308
313
|
data = image
|
309
314
|
if issubclass(image.dtype.type, np.floating):
|
310
315
|
if clamp:
|
@@ -337,12 +342,13 @@ def image_to_url(
|
|
337
342
|
(When running in "raw" mode, we won't actually load data into the
|
338
343
|
MediaFileManager, and we'll return an empty URL.)
|
339
344
|
"""
|
345
|
+
import numpy as np
|
346
|
+
from PIL import Image, ImageFile
|
340
347
|
|
341
348
|
image_data: bytes
|
342
349
|
|
343
350
|
# Strings
|
344
351
|
if isinstance(image, str):
|
345
|
-
|
346
352
|
if not os.path.isfile(image) and url_util.is_url(
|
347
353
|
image, allowed_schemas=("http", "https", "data")
|
348
354
|
):
|
@@ -364,6 +370,8 @@ def image_to_url(
|
|
364
370
|
"<svg", '<svg xmlns="http://www.w3.org/2000/svg" ', 1
|
365
371
|
)
|
366
372
|
# Convert to base64 to prevent issues with encoding:
|
373
|
+
import base64
|
374
|
+
|
367
375
|
image_b64_encoded = base64.b64encode(image.encode("utf-8")).decode("utf-8")
|
368
376
|
# Return SVG as data URI:
|
369
377
|
return f"data:image/svg+xml;base64,{image_b64_encoded}"
|
@@ -376,6 +384,8 @@ def image_to_url(
|
|
376
384
|
# When we aren't able to open the image file, we still pass the path to
|
377
385
|
# the MediaFileManager - its storage backend may have access to files
|
378
386
|
# that Streamlit does not.
|
387
|
+
import mimetypes
|
388
|
+
|
379
389
|
mimetype, _ = mimetypes.guess_type(image)
|
380
390
|
if mimetype is None:
|
381
391
|
mimetype = "application/octet-stream"
|
@@ -441,8 +451,8 @@ def image_to_url(
|
|
441
451
|
def marshall_images(
|
442
452
|
coordinates: str,
|
443
453
|
image: ImageOrImageList,
|
444
|
-
caption:
|
445
|
-
width:
|
454
|
+
caption: str | "npt.NDArray[Any]" | List[str] | None,
|
455
|
+
width: int | WidthBehaviour,
|
446
456
|
proto_imgs: ImageListProto,
|
447
457
|
clamp: bool,
|
448
458
|
channels: Channels = "RGB",
|
@@ -486,6 +496,8 @@ def marshall_images(
|
|
486
496
|
Defaults to 'auto' which identifies the compression type based
|
487
497
|
on the type and format of the image argument.
|
488
498
|
"""
|
499
|
+
import numpy as np
|
500
|
+
|
489
501
|
channels = cast(Channels, channels.upper())
|
490
502
|
|
491
503
|
# Turn single image and caption into one element list.
|
@@ -498,7 +510,7 @@ def marshall_images(
|
|
498
510
|
images = [image]
|
499
511
|
|
500
512
|
if type(caption) is list:
|
501
|
-
captions: Sequence[
|
513
|
+
captions: Sequence[str | None] = caption
|
502
514
|
else:
|
503
515
|
if isinstance(caption, str):
|
504
516
|
captions = [caption]
|
streamlit/elements/write.py
CHANGED
@@ -16,13 +16,14 @@ from __future__ import annotations
|
|
16
16
|
|
17
17
|
import dataclasses
|
18
18
|
import inspect
|
19
|
-
import json
|
19
|
+
import json
|
20
20
|
import types
|
21
21
|
from io import StringIO
|
22
22
|
from typing import (
|
23
23
|
TYPE_CHECKING,
|
24
24
|
Any,
|
25
25
|
Callable,
|
26
|
+
Final,
|
26
27
|
Generator,
|
27
28
|
Iterable,
|
28
29
|
List,
|
@@ -31,10 +32,6 @@ from typing import (
|
|
31
32
|
cast,
|
32
33
|
)
|
33
34
|
|
34
|
-
import numpy as np
|
35
|
-
from PIL import Image, ImageFile
|
36
|
-
from typing_extensions import Final
|
37
|
-
|
38
35
|
from streamlit import type_util
|
39
36
|
from streamlit.errors import StreamlitAPIException
|
40
37
|
from streamlit.logger import get_logger
|
@@ -390,6 +387,8 @@ class WriteMixin:
|
|
390
387
|
flush_buffer()
|
391
388
|
self.dg.dataframe(arg)
|
392
389
|
elif type_util.is_dataframe_like(arg):
|
390
|
+
import numpy as np
|
391
|
+
|
393
392
|
flush_buffer()
|
394
393
|
if len(np.shape(arg)) > 2:
|
395
394
|
self.dg.text(arg)
|
@@ -416,7 +415,7 @@ class WriteMixin:
|
|
416
415
|
elif type_util.is_sympy_expession(arg):
|
417
416
|
flush_buffer()
|
418
417
|
self.dg.latex(arg)
|
419
|
-
elif
|
418
|
+
elif type_util.is_pillow_image(arg):
|
420
419
|
flush_buffer()
|
421
420
|
self.dg.image(arg)
|
422
421
|
elif type_util.is_keras_model(arg):
|