streamlit-nightly 1.31.1.dev20240210__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.
Files changed (26) hide show
  1. streamlit/cli_util.py +44 -0
  2. streamlit/config.py +36 -34
  3. streamlit/config_util.py +18 -16
  4. streamlit/delta_generator.py +20 -16
  5. streamlit/elements/image.py +30 -18
  6. streamlit/elements/write.py +5 -6
  7. streamlit/runtime/credentials.py +67 -55
  8. streamlit/runtime/secrets.py +6 -4
  9. streamlit/static/asset-manifest.json +3 -3
  10. streamlit/static/index.html +1 -1
  11. streamlit/static/static/js/4666.ad225eae.chunk.js +1 -0
  12. streamlit/static/static/js/{main.603949f7.js → main.043d802e.js} +2 -2
  13. streamlit/url_util.py +0 -8
  14. streamlit/watcher/path_watcher.py +3 -6
  15. streamlit/web/bootstrap.py +48 -46
  16. streamlit/web/cli.py +8 -5
  17. streamlit/web/server/routes.py +8 -0
  18. streamlit/web/server/server.py +2 -3
  19. {streamlit_nightly-1.31.1.dev20240210.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/METADATA +1 -1
  20. {streamlit_nightly-1.31.1.dev20240210.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/RECORD +25 -24
  21. streamlit/static/static/js/4666.3a6efd97.chunk.js +0 -1
  22. /streamlit/static/static/js/{main.603949f7.js.LICENSE.txt → main.043d802e.js.LICENSE.txt} +0 -0
  23. {streamlit_nightly-1.31.1.dev20240210.data → streamlit_nightly-1.31.2.dev20240212.data}/scripts/streamlit.cmd +0 -0
  24. {streamlit_nightly-1.31.1.dev20240210.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/WHEEL +0 -0
  25. {streamlit_nightly-1.31.1.dev20240210.dist-info → streamlit_nightly-1.31.2.dev20240212.dist-info}/entry_points.txt +0 -0
  26. {streamlit_nightly-1.31.1.dev20240210.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, Dict, Optional, cast
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: Dict[str, str] = OrderedDict(
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: Dict[str, ConfigOption] = OrderedDict()
50
+ _config_options_template: dict[str, ConfigOption] = OrderedDict()
50
51
 
51
52
  # Stores the current state of config options.
52
- _config_options: Optional[Dict[str, ConfigOption]] = None
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) -> Dict[str, Any]:
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
- Dict[str, Any]
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: Optional[str] = None,
187
- default_val: Optional[Any] = None,
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: Optional[str] = None,
192
- expiration_date: Optional[str] = None,
193
- replaced_by: Optional[str] = None,
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
- del cast(Dict[str, ConfigOption], _config_options)[key]
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() -> Optional[str]:
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 CORS and XSRF protection purposes.
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
- Don't use port 3000 which is reserved for internal development.
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
- config_util.show_config(
1125
- _section_descriptions, cast(Dict[str, ConfigOption], _config_options)
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: Dict[str, ConfigOption]):
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: Optional[Dict[str, Any]] = None
1261
- ) -> Dict[str, ConfigOption]:
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 : Optional[Dict[str, any]
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
- Dict[str, ConfigOption]
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 re
16
- from typing import Dict
15
+ from __future__ import annotations
17
16
 
18
- import click
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: Dict[str, ConfigOption], new_options: Dict[str, ConfigOption]
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: Dict[str, str],
44
- config_options: Dict[str, ConfigOption],
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("# " + click.style(text, bold=True))
58
+ out.append("# " + cli_util.style_for_cli(text, bold=True))
59
59
 
60
60
  def append_comment(text):
61
- out.append("# " + click.style(text))
61
+ out.append("# " + cli_util.style_for_cli(text))
62
62
 
63
63
  def append_section(text):
64
- out.append(click.style(text, bold=True, fg="green"))
64
+ out.append(cli_util.style_for_cli(text, bold=True, fg="green"))
65
65
 
66
66
  def append_setting(text):
67
- out.append(click.style(text, fg="green"))
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(click.style("DEPRECATED.", fg="yellow"))
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
- click.echo("\n".join(out))
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")
@@ -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 click
36
- from typing_extensions import Final, Literal
37
-
38
- from streamlit import config, cursor, env_util, logger, runtime, type_util, util
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 = click.style("Warning:", bold=True, fg="yellow")
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 name, func in mixin.__dict__.items():
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: Optional[AddRowsMetadata] = None,
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: Optional[AddRowsMetadata] = None,
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: Optional[AddRowsMetadata] = None,
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: Optional[AddRowsMetadata] = None,
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: Optional[AddRowsMetadata] = None,
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: Optional[AddRowsMetadata] = None,
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:
@@ -19,21 +19,18 @@
19
19
 
20
20
  """Image marshalling."""
21
21
 
22
- import base64
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, Optional, Sequence, Union, cast
28
+ from typing import TYPE_CHECKING, Final, List, Literal, Sequence, Union, cast
29
29
 
30
- import numpy as np
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 = Optional[Union[Literal["auto", "always", "never"], bool]]
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: Optional[Union[str, List[str]]] = None,
95
- width: Optional[int] = None,
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: Union[bytes, PILImage], format: str
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: Optional[Union[str, "npt.NDArray[Any]", List[str]]],
445
- width: Union[int, WidthBehaviour],
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[Optional[str]] = caption
513
+ captions: Sequence[str | None] = caption
502
514
  else:
503
515
  if isinstance(caption, str):
504
516
  captions = [caption]
@@ -16,13 +16,14 @@ from __future__ import annotations
16
16
 
17
17
  import dataclasses
18
18
  import inspect
19
- import json as 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 isinstance(arg, (ImageFile.ImageFile, Image.Image)):
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):