streamlit-nightly 1.53.1.dev20260115__py3-none-any.whl → 1.53.1.dev20260116__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 (73) hide show
  1. streamlit/auth_util.py +90 -1
  2. streamlit/cli_util.py +2 -1
  3. streamlit/commands/echo.py +2 -2
  4. streamlit/commands/execution_control.py +1 -1
  5. streamlit/commands/navigation.py +1 -1
  6. streamlit/components/types/base_custom_component.py +0 -2
  7. streamlit/components/v1/custom_component.py +0 -2
  8. streamlit/components/v2/component_path_utils.py +1 -1
  9. streamlit/components/v2/manifest_scanner.py +8 -3
  10. streamlit/components/v2/presentation.py +1 -1
  11. streamlit/config.py +15 -13
  12. streamlit/config_util.py +5 -5
  13. streamlit/dataframe_util.py +5 -5
  14. streamlit/elements/arrow.py +11 -6
  15. streamlit/elements/deck_gl_json_chart.py +1 -1
  16. streamlit/elements/exception.py +4 -2
  17. streamlit/elements/form.py +1 -1
  18. streamlit/elements/layouts.py +1 -1
  19. streamlit/elements/lib/built_in_chart_utils.py +7 -7
  20. streamlit/elements/lib/column_config_utils.py +6 -6
  21. streamlit/elements/lib/dialog.py +1 -1
  22. streamlit/elements/lib/image_utils.py +4 -4
  23. streamlit/elements/lib/layout_utils.py +1 -1
  24. streamlit/elements/lib/policies.py +1 -1
  25. streamlit/elements/lib/utils.py +1 -1
  26. streamlit/elements/map.py +6 -6
  27. streamlit/elements/plotly_chart.py +2 -2
  28. streamlit/elements/toast.py +1 -1
  29. streamlit/elements/vega_charts.py +2 -2
  30. streamlit/elements/widgets/button.py +3 -3
  31. streamlit/elements/widgets/button_group.py +3 -3
  32. streamlit/elements/widgets/chat.py +1 -1
  33. streamlit/elements/widgets/data_editor.py +6 -6
  34. streamlit/elements/widgets/number_input.py +1 -1
  35. streamlit/elements/widgets/slider.py +5 -5
  36. streamlit/elements/widgets/time_widgets.py +91 -10
  37. streamlit/elements/write.py +1 -1
  38. streamlit/env_util.py +1 -1
  39. streamlit/errors.py +0 -14
  40. streamlit/runtime/app_session.py +0 -1
  41. streamlit/runtime/caching/cache_data_api.py +3 -3
  42. streamlit/runtime/caching/cache_errors.py +0 -2
  43. streamlit/runtime/caching/cache_resource_api.py +1 -1
  44. streamlit/runtime/caching/cache_utils.py +2 -2
  45. streamlit/runtime/caching/hashing.py +1 -3
  46. streamlit/runtime/caching/storage/cache_storage_protocol.py +0 -3
  47. streamlit/runtime/connection_factory.py +1 -1
  48. streamlit/runtime/runtime.py +6 -6
  49. streamlit/runtime/scriptrunner_utils/exceptions.py +0 -4
  50. streamlit/runtime/secrets.py +2 -3
  51. streamlit/runtime/state/session_state.py +1 -1
  52. streamlit/runtime/stats.py +0 -7
  53. streamlit/string_util.py +2 -2
  54. streamlit/testing/v1/element_tree.py +8 -10
  55. streamlit/type_util.py +2 -2
  56. streamlit/url_util.py +2 -2
  57. streamlit/user_info.py +2 -2
  58. streamlit/util.py +1 -1
  59. streamlit/watcher/path_watcher.py +1 -1
  60. streamlit/web/cli.py +1 -4
  61. streamlit/web/server/app_discovery.py +2 -1
  62. streamlit/web/server/oauth_authlib_routes.py +14 -42
  63. streamlit/web/server/server.py +1 -1
  64. streamlit/web/server/server_util.py +1 -1
  65. streamlit/web/server/starlette/starlette_auth_routes.py +94 -16
  66. streamlit/web/server/starlette/starlette_routes.py +9 -3
  67. streamlit/web/server/starlette/starlette_server.py +2 -2
  68. {streamlit_nightly-1.53.1.dev20260115.dist-info → streamlit_nightly-1.53.1.dev20260116.dist-info}/METADATA +1 -1
  69. {streamlit_nightly-1.53.1.dev20260115.dist-info → streamlit_nightly-1.53.1.dev20260116.dist-info}/RECORD +73 -73
  70. {streamlit_nightly-1.53.1.dev20260115.data → streamlit_nightly-1.53.1.dev20260116.data}/scripts/streamlit.cmd +0 -0
  71. {streamlit_nightly-1.53.1.dev20260115.dist-info → streamlit_nightly-1.53.1.dev20260116.dist-info}/WHEEL +0 -0
  72. {streamlit_nightly-1.53.1.dev20260115.dist-info → streamlit_nightly-1.53.1.dev20260116.dist-info}/entry_points.txt +0 -0
  73. {streamlit_nightly-1.53.1.dev20260115.dist-info → streamlit_nightly-1.53.1.dev20260116.dist-info}/top_level.txt +0 -0
streamlit/auth_util.py CHANGED
@@ -19,7 +19,7 @@ import re
19
19
  from collections.abc import Callable, Mapping
20
20
  from datetime import datetime, timedelta, timezone
21
21
  from typing import TYPE_CHECKING, Any, Final, TypedDict, cast
22
- from urllib.parse import urlparse
22
+ from urllib.parse import urlencode, urlparse
23
23
 
24
24
  from streamlit import config
25
25
  from streamlit.errors import StreamlitAuthError
@@ -148,6 +148,95 @@ def get_redirect_uri(auth_section: AttrDict) -> str | None:
148
148
  return redirect_uri_parsed.geturl()
149
149
 
150
150
 
151
+ def get_validated_redirect_uri() -> str | None:
152
+ """Get the redirect_uri from secrets, validating it ends with /oauth2callback.
153
+
154
+ This is used for logout flows where we need a validated redirect URI
155
+ that matches the OAuth callback path.
156
+
157
+ Returns
158
+ -------
159
+ str | None
160
+ The validated redirect URI, or None if not configured or invalid.
161
+ """
162
+ auth_section = get_secrets_auth_section()
163
+ if not auth_section:
164
+ return None
165
+
166
+ redirect_uri = get_redirect_uri(auth_section)
167
+ if not redirect_uri:
168
+ return None
169
+
170
+ if not redirect_uri.endswith("/oauth2callback"):
171
+ _LOGGER.warning("Redirect URI does not end with /oauth2callback")
172
+ return None
173
+
174
+ return redirect_uri
175
+
176
+
177
+ def get_origin_from_redirect_uri() -> str | None:
178
+ """Extract the origin (scheme + host) from the configured redirect_uri.
179
+
180
+ Returns
181
+ -------
182
+ str | None
183
+ The origin in format "scheme://host:port", or None if not configured.
184
+ """
185
+ auth_section = get_secrets_auth_section()
186
+ if not auth_section:
187
+ return None
188
+
189
+ redirect_uri = get_redirect_uri(auth_section)
190
+ if not redirect_uri:
191
+ return None
192
+
193
+ redirect_uri_parsed = urlparse(redirect_uri)
194
+ return f"{redirect_uri_parsed.scheme}://{redirect_uri_parsed.netloc}"
195
+
196
+
197
+ def build_logout_url(
198
+ end_session_endpoint: str,
199
+ client_id: str,
200
+ post_logout_redirect_uri: str,
201
+ id_token: str | None = None,
202
+ ) -> str:
203
+ """Build an OIDC logout URL with the required parameters.
204
+
205
+ Parameters
206
+ ----------
207
+ end_session_endpoint
208
+ The OIDC provider's end_session_endpoint URL.
209
+ client_id
210
+ The OAuth client ID.
211
+ post_logout_redirect_uri
212
+ The URI to redirect to after logout.
213
+ id_token
214
+ Optional ID token to include as id_token_hint for the logout request.
215
+
216
+ Returns
217
+ -------
218
+ str
219
+ The complete logout URL with query parameters.
220
+ """
221
+ from urllib.parse import parse_qsl
222
+
223
+ logout_params: dict[str, str] = {
224
+ "client_id": client_id,
225
+ "post_logout_redirect_uri": post_logout_redirect_uri,
226
+ }
227
+
228
+ if id_token:
229
+ logout_params["id_token_hint"] = id_token
230
+
231
+ # Per OIDC spec, end_session_endpoint should be a clean URL without query params,
232
+ # but we handle existing params defensively for non-standard providers.
233
+ parsed = urlparse(end_session_endpoint)
234
+ existing_params = dict(parse_qsl(parsed.query))
235
+ merged_params = {**existing_params, **logout_params}
236
+ new_query = urlencode(merged_params)
237
+ return parsed._replace(query=new_query).geturl()
238
+
239
+
151
240
  def encode_provider_token(provider: str) -> str:
152
241
  """Returns a signed JWT token with the provider and expiration time."""
153
242
  try:
streamlit/cli_util.py CHANGED
@@ -17,7 +17,6 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  import os
20
- import subprocess
21
20
  from typing import Any
22
21
 
23
22
  from streamlit import env_util, errors
@@ -61,6 +60,8 @@ def _open_browser_with_webbrowser(url: str) -> None:
61
60
  def _open_browser_with_command(command: str, url: str) -> None:
62
61
  cmd_line = [command, url]
63
62
  with open(os.devnull, "w", encoding="utf-8") as devnull:
63
+ import subprocess # noqa: S404
64
+
64
65
  subprocess.Popen(cmd_line, stdout=devnull, stderr=subprocess.STDOUT) # noqa: S603
65
66
 
66
67
 
@@ -26,8 +26,8 @@ from streamlit.runtime.metrics_util import gather_metrics
26
26
  if TYPE_CHECKING:
27
27
  from collections.abc import Generator, Iterable
28
28
 
29
- _SPACES_RE = re.compile("\\s*")
30
- _EMPTY_LINE_RE = re.compile("\\s*\n")
29
+ _SPACES_RE = re.compile(r"\s*")
30
+ _EMPTY_LINE_RE = re.compile(r"\s*\n")
31
31
 
32
32
 
33
33
  @gather_metrics("echo")
@@ -158,7 +158,7 @@ def rerun( # type: ignore[misc]
158
158
 
159
159
  """
160
160
 
161
- if scope not in ["app", "fragment"]:
161
+ if scope not in {"app", "fragment"}:
162
162
  raise StreamlitAPIException(
163
163
  f"'{scope}'is not a valid rerun scope. Valid scopes are 'app' and 'fragment'."
164
164
  )
@@ -292,7 +292,7 @@ def navigation(
292
292
 
293
293
  """
294
294
  # Validate position parameter
295
- if not isinstance(position, str) or position not in ["sidebar", "hidden", "top"]:
295
+ if not isinstance(position, str) or position not in {"sidebar", "hidden", "top"}:
296
296
  raise StreamlitAPIException(
297
297
  f'Invalid position "{position}". '
298
298
  'The position parameter must be one of "sidebar", "hidden", or "top".'
@@ -28,8 +28,6 @@ if TYPE_CHECKING:
28
28
  class MarshallComponentException(StreamlitAPIException):
29
29
  """Class for exceptions generated during custom component marshalling."""
30
30
 
31
- pass
32
-
33
31
 
34
32
  class BaseCustomComponent(ABC):
35
33
  """Interface for CustomComponents."""
@@ -40,8 +40,6 @@ if TYPE_CHECKING:
40
40
  class MarshallComponentException(StreamlitAPIException):
41
41
  """Class for exceptions generated during custom component marshalling."""
42
42
 
43
- pass
44
-
45
43
 
46
44
  class CustomComponent(BaseCustomComponent):
47
45
  """A Custom Component declaration."""
@@ -146,7 +146,7 @@ class ComponentPathUtils:
146
146
  len(path) >= 3
147
147
  and path[0].isalpha()
148
148
  and path[1] == ":"
149
- and path[2] in ("/", "\\")
149
+ and path[2] in {"/", "\\"}
150
150
  )
151
151
  is_unc_abs = path.startswith("\\\\")
152
152
 
@@ -187,8 +187,13 @@ def _is_likely_streamlit_component_package(
187
187
  bool
188
188
  True if the package might contain streamlit components, False otherwise.
189
189
  """
190
- # Get package metadata
191
- name = dist.name.lower()
190
+ dist_name = dist.name
191
+ if not isinstance(dist_name, str) or not dist_name:
192
+ # We do not expect a distribution to be missing its name, be defensive
193
+ # and fail closed to prevent runtime issues.
194
+ return False
195
+
196
+ name = dist_name.lower()
192
197
  summary = dist.metadata["Summary"].lower() if "Summary" in dist.metadata else ""
193
198
 
194
199
  # Filter 1: Package name suggests streamlit component
@@ -386,7 +391,7 @@ def _validate_pyproject_for_package(
386
391
  canonical_package = packaging_utils.canonicalize_name(package_name)
387
392
 
388
393
  # Check if project name matches either the distribution name or the package name
389
- return canonical_project in (canonical_dist, canonical_package)
394
+ return canonical_project in {canonical_dist, canonical_package}
390
395
 
391
396
  # If we can't determine ownership, be conservative and reject it
392
397
  return False
@@ -127,7 +127,7 @@ def make_bidi_component_presenter(
127
127
  # st.session_state[component_user_key][name] = value. Using a
128
128
  # dict subclass ensures pretty-printing and JSON serialization
129
129
  # behave as expected for st.write and logs.
130
- class _WriteThrough(dict[str, object]):
130
+ class _WriteThrough(dict[str, object]): # noqa: FURB189
131
131
  def __init__(self, data: dict[str, object]) -> None:
132
132
  super().__init__(data)
133
133
 
streamlit/config.py CHANGED
@@ -99,11 +99,11 @@ class ShowErrorDetailsConfigOptions(str, Enum):
99
99
 
100
100
  @staticmethod
101
101
  def is_true_variation(val: str | bool) -> bool:
102
- return val in ["true", "True", True]
102
+ return val in {"true", "True", True}
103
103
 
104
104
  @staticmethod
105
105
  def is_false_variation(val: str | bool) -> bool:
106
- return val in ["false", "False", False]
106
+ return val in {"false", "False", False}
107
107
 
108
108
  # Config options can be set from several places including the command-line and
109
109
  # the user's script. Legacy config options (true/false) will have type string
@@ -1077,9 +1077,11 @@ def _browser_server_port() -> int:
1077
1077
 
1078
1078
 
1079
1079
  _SSL_PRODUCTION_WARNING = [
1080
- "DO NOT USE THIS OPTION IN A PRODUCTION ENVIRONMENT. It has not gone through "
1081
- "security audits or performance tests. For a production environment, we "
1082
- "recommend performing SSL termination through a load balancer or reverse proxy."
1080
+ (
1081
+ "DO NOT USE THIS OPTION IN A PRODUCTION ENVIRONMENT. It has not gone through "
1082
+ "security audits or performance tests. For a production environment, we "
1083
+ "recommend performing SSL termination through a load balancer or reverse proxy."
1084
+ )
1083
1085
  ]
1084
1086
 
1085
1087
  _create_option(
@@ -2376,10 +2378,10 @@ def is_manually_set(option_name: str) -> bool:
2376
2378
  True if the option has been set by the user.
2377
2379
 
2378
2380
  """
2379
- return get_where_defined(option_name) not in (
2381
+ return get_where_defined(option_name) not in {
2380
2382
  ConfigOption.DEFAULT_DEFINITION,
2381
2383
  ConfigOption.STREAMLIT_DEFINITION,
2382
- )
2384
+ }
2383
2385
 
2384
2386
 
2385
2387
  def show_config() -> None:
@@ -2462,19 +2464,19 @@ def _is_valid_theme_section(section_path: str) -> bool:
2462
2464
 
2463
2465
  # theme.sidebar/light/dark is valid (2 parts: "theme" + section)
2464
2466
  if len(parts) == 2:
2465
- return parts[1] in [
2467
+ return parts[1] in {
2466
2468
  CustomThemeCategories.SIDEBAR.value,
2467
2469
  CustomThemeCategories.LIGHT.value,
2468
2470
  CustomThemeCategories.DARK.value,
2469
- ]
2471
+ }
2470
2472
 
2471
2473
  # theme.light.sidebar/theme.dark.sidebar are the only valid 3-part patterns
2472
2474
  if len(parts) == 3:
2473
2475
  # Only allow light/dark as the middle level, with sidebar as the final level
2474
- if parts[1] in [
2476
+ if parts[1] in {
2475
2477
  CustomThemeCategories.LIGHT.value,
2476
2478
  CustomThemeCategories.DARK.value,
2477
- ]:
2479
+ }:
2478
2480
  return parts[2] == CustomThemeCategories.SIDEBAR.value
2479
2481
  # sidebar cannot have nested sections (theme.sidebar.light/dark)
2480
2482
  return False
@@ -2546,11 +2548,11 @@ def _update_config_with_toml(raw_toml: str, where_defined: str) -> None:
2546
2548
  for name, value in section_data.items():
2547
2549
  option_name = f"{section_path}.{name}"
2548
2550
  # Only check for nested sections when we're already in a theme section
2549
- if section_path.startswith("theme") and name in [
2551
+ if section_path.startswith("theme") and name in {
2550
2552
  CustomThemeCategories.SIDEBAR.value,
2551
2553
  CustomThemeCategories.LIGHT.value,
2552
2554
  CustomThemeCategories.DARK.value,
2553
- ]:
2555
+ }:
2554
2556
  # Validate the theme section before processing
2555
2557
  if not _is_valid_theme_section(option_name):
2556
2558
  raise StreamlitInvalidThemeSectionError(
streamlit/config_util.py CHANGED
@@ -194,7 +194,7 @@ def _clean(txt: str) -> str:
194
194
 
195
195
  Preserves leading and trailing spaces, and does not modify spaces in between lines.
196
196
  """
197
- return re.sub(" +", " ", txt)
197
+ return re.sub(r" +", " ", txt)
198
198
 
199
199
 
200
200
  def _clean_paragraphs(txt: str) -> list[str]:
@@ -741,7 +741,7 @@ def process_theme_inheritance(
741
741
  base_value = base_option.value
742
742
 
743
743
  # Check if it's a file path or URL (not just "light" or "dark")
744
- if base_value in ("light", "dark"):
744
+ if base_value in {"light", "dark"}:
745
745
  return
746
746
 
747
747
  def _raise_invalid_nested_base() -> None:
@@ -756,7 +756,7 @@ def process_theme_inheritance(
756
756
 
757
757
  # Validate that theme.base of the referenced theme file doesn't reference another file
758
758
  theme_base = theme_file_content.get("theme", {}).get("base")
759
- if theme_base and theme_base not in ("light", "dark"):
759
+ if theme_base and theme_base not in {"light", "dark"}:
760
760
  _raise_invalid_nested_base()
761
761
 
762
762
  # Get current theme options from main config.toml
@@ -777,10 +777,10 @@ def process_theme_inheritance(
777
777
  opt_name.startswith("theme.")
778
778
  and opt_name != "theme.base"
779
779
  and opt_config.where_defined
780
- in (
780
+ in {
781
781
  "environment variable",
782
782
  "command-line argument or environment variable",
783
- )
783
+ }
784
784
  ):
785
785
  high_precedence_theme_options[opt_name] = {
786
786
  "value": opt_config.value,
@@ -464,7 +464,7 @@ def _is_list_of_scalars(data: Iterable[Any]) -> bool:
464
464
 
465
465
  # Overview on all value that are interpreted as scalar:
466
466
  # https://pandas.pydata.org/docs/reference/api/pandas.api.types.is_scalar.html
467
- return infer_dtype(data, skipna=True) not in ["mixed", "unknown-array"]
467
+ return infer_dtype(data, skipna=True) not in {"mixed", "unknown-array"}
468
468
 
469
469
 
470
470
  def _iterable_to_list(
@@ -1077,10 +1077,10 @@ def is_colum_type_arrow_incompatible(column: Series[Any] | Index[Any]) -> bool:
1077
1077
  # https://pandas.pydata.org/docs/reference/api/pandas.api.types.infer_dtype.html
1078
1078
  inferred_type = infer_dtype(column, skipna=True)
1079
1079
 
1080
- if inferred_type in [
1080
+ if inferred_type in {
1081
1081
  "mixed-integer",
1082
1082
  "complex",
1083
- ]:
1083
+ }:
1084
1084
  return True
1085
1085
  if inferred_type == "mixed":
1086
1086
  # This includes most of the more complex/custom types (objects, dicts,
@@ -1403,11 +1403,11 @@ def convert_pandas_df_to_data_format(
1403
1403
  return _unify_missing_values(df).to_dict(orient="list")
1404
1404
  if data_format == DataFormat.COLUMN_SERIES_MAPPING:
1405
1405
  return df.to_dict(orient="series")
1406
- if data_format in [
1406
+ if data_format in {
1407
1407
  DataFormat.LIST_OF_VALUES,
1408
1408
  DataFormat.TUPLE_OF_VALUES,
1409
1409
  DataFormat.SET_OF_VALUES,
1410
- ]:
1410
+ }:
1411
1411
  df = _unify_missing_values(df)
1412
1412
  return_list = []
1413
1413
  if len(df.columns) == 1:
@@ -647,7 +647,7 @@ class ArrowMixin:
647
647
  """
648
648
  import pyarrow as pa
649
649
 
650
- if on_select not in ["ignore", "rerun"] and not callable(on_select):
650
+ if on_select not in {"ignore", "rerun"} and not callable(on_select):
651
651
  raise StreamlitAPIException(
652
652
  f"You have passed {on_select} to `on_select`. But only 'ignore', "
653
653
  "'rerun', or a callable is supported."
@@ -744,13 +744,18 @@ class ArrowMixin:
744
744
  elif (
745
745
  # Hide index column if row selections are activated and the dataframe has a range index.
746
746
  # The range index usually does not add a lot of value.
747
- is_selection_activated
748
- and selection_mode in ["multi-row", "single-row"]
749
- and has_range_index
747
+ is_selection_activated and has_range_index
750
748
  ):
751
- update_column_config(
752
- column_config_mapping, INDEX_IDENTIFIER, {"hidden": True}
749
+ # Normalize selection_mode to a set to check for row selection modes
750
+ mode_set = (
751
+ {selection_mode}
752
+ if isinstance(selection_mode, str)
753
+ else set(selection_mode)
753
754
  )
755
+ if mode_set & {"multi-row", "single-row"}:
756
+ update_column_config(
757
+ column_config_mapping, INDEX_IDENTIFIER, {"hidden": True}
758
+ )
754
759
 
755
760
  marshall_column_config(proto, column_config_mapping)
756
761
 
@@ -531,7 +531,7 @@ class PydeckMixin:
531
531
  key = to_key(key)
532
532
  is_selection_activated = on_select != "ignore"
533
533
 
534
- if on_select not in ["ignore", "rerun"] and not callable(on_select):
534
+ if on_select not in {"ignore", "rerun"} and not callable(on_select):
535
535
  raise StreamlitAPIException(
536
536
  f"You have passed {on_select} to `on_select`. "
537
537
  "But only 'ignore', 'rerun', or a callable is supported."
@@ -278,8 +278,10 @@ def _get_stack_trace_str_list(exception: BaseException) -> list[str]:
278
278
  # Format the extracted traceback and add it to the protobuf element.
279
279
  if extracted_traceback is None:
280
280
  trace_str_list = [
281
- "Cannot extract the stack trace for this exception. "
282
- "Try calling exception() within the `catch` block."
281
+ (
282
+ "Cannot extract the stack trace for this exception. "
283
+ "Try calling exception() within the `catch` block."
284
+ )
283
285
  ]
284
286
  else:
285
287
  internal_frames, external_frames = _split_internal_streamlit_frames(
@@ -413,7 +413,7 @@ class FormMixin:
413
413
  width = "stretch" if use_container_width else "content"
414
414
 
415
415
  # Checks whether the entered button type is one of the allowed options
416
- if type not in ["primary", "secondary", "tertiary"]:
416
+ if type not in {"primary", "secondary", "tertiary"}:
417
417
  raise StreamlitAPIException(
418
418
  'The type argument to st.form_submit_button must be "primary", "secondary", or "tertiary". \n'
419
419
  f'The argument passed was "{type}".'
@@ -1036,7 +1036,7 @@ class LayoutsMixin:
1036
1036
  width = "stretch" if use_container_width else "content"
1037
1037
 
1038
1038
  # Checks whether the entered button type is one of the allowed options
1039
- if type not in ["primary", "secondary", "tertiary"]:
1039
+ if type not in {"primary", "secondary", "tertiary"}:
1040
1040
  raise StreamlitAPIException(
1041
1041
  'The type argument to st.popover must be "primary", "secondary", or "tertiary". '
1042
1042
  f'\nThe argument passed was "{type}".'
@@ -136,7 +136,7 @@ def maybe_raise_stack_warning(
136
136
  stack: bool | ChartStackType | None, command: str | None, docs_link: str
137
137
  ) -> None:
138
138
  # Check that the stack parameter is valid, raise more informative error if not
139
- if stack not in (None, True, False, "normalize", "center", "layered"):
139
+ if stack not in {None, True, False, "normalize", "center", "layered"}:
140
140
  raise StreamlitAPIException(
141
141
  f"Invalid value for stack parameter: {stack}. Stack must be one of True, "
142
142
  'False, "normalize", "center", "layered" or None. See documentation '
@@ -425,13 +425,13 @@ def _infer_vegalite_type(
425
425
  # requires Pandas 1.3.
426
426
  typ = infer_dtype(data)
427
427
 
428
- if typ in [
428
+ if typ in {
429
429
  "floating",
430
430
  "mixed-integer-float",
431
431
  "integer",
432
432
  "mixed-integer",
433
433
  "complex",
434
- ]:
434
+ }:
435
435
  return "quantitative"
436
436
 
437
437
  if typ == "categorical" and data.cat.ordered:
@@ -442,9 +442,9 @@ def _infer_vegalite_type(
442
442
  # Altair already extracts the correct sort order somewhere else.
443
443
  # More info about the issue here: https://github.com/streamlit/streamlit/issues/7776
444
444
  return "ordinal"
445
- if typ in ["string", "bytes", "categorical", "boolean", "mixed", "unicode"]:
445
+ if typ in {"string", "bytes", "categorical", "boolean", "mixed", "unicode"}:
446
446
  return "nominal"
447
- if typ in [
447
+ if typ in {
448
448
  "datetime",
449
449
  "datetime64",
450
450
  "timedelta",
@@ -452,7 +452,7 @@ def _infer_vegalite_type(
452
452
  "date",
453
453
  "time",
454
454
  "period",
455
- ]:
455
+ }:
456
456
  return "temporal"
457
457
  # STREAMLIT MOD: I commented this out since Streamlit doesn't use warnings.warn.
458
458
  # > warnings.warn(
@@ -913,7 +913,7 @@ def _get_axis_encodings(
913
913
  _update_encoding_with_stack(stack, stack_encoding)
914
914
 
915
915
  # Handle sorting - only relevant for bar charts
916
- if chart_type in (ChartType.VERTICAL_BAR, ChartType.HORIZONTAL_BAR):
916
+ if chart_type in {ChartType.VERTICAL_BAR, ChartType.HORIZONTAL_BAR}:
917
917
  _update_encoding_with_sort(sort_from_user, sort_encoding)
918
918
 
919
919
  return x_encoding, y_encoding
@@ -289,7 +289,7 @@ def _determine_data_kind_via_inferred_type(
289
289
  if inferred_type == "bytes":
290
290
  return ColumnDataKind.BYTES
291
291
 
292
- if inferred_type in ["floating", "mixed-integer-float"]:
292
+ if inferred_type in {"floating", "mixed-integer-float"}:
293
293
  return ColumnDataKind.FLOAT
294
294
 
295
295
  if inferred_type == "integer":
@@ -304,13 +304,13 @@ def _determine_data_kind_via_inferred_type(
304
304
  if inferred_type == "boolean":
305
305
  return ColumnDataKind.BOOLEAN
306
306
 
307
- if inferred_type in ["datetime64", "datetime"]:
307
+ if inferred_type in {"datetime64", "datetime"}:
308
308
  return ColumnDataKind.DATETIME
309
309
 
310
310
  if inferred_type == "date":
311
311
  return ColumnDataKind.DATE
312
312
 
313
- if inferred_type in ["timedelta64", "timedelta"]:
313
+ if inferred_type in {"timedelta64", "timedelta"}:
314
314
  return ColumnDataKind.TIMEDELTA
315
315
 
316
316
  if inferred_type == "time":
@@ -411,7 +411,7 @@ ColumnConfigMappingInput: TypeAlias = Mapping[
411
411
  # allowing int here leads mypy to complain about simple dict[str, ...]
412
412
  # as input -> which seems like a mypy bug.
413
413
  IndexIdentifierType | str,
414
- ColumnConfig | None | str,
414
+ ColumnConfig | str | None,
415
415
  ]
416
416
 
417
417
 
@@ -499,7 +499,7 @@ def apply_data_specific_configs(
499
499
  # Pandas adds a range index as default to all datastructures
500
500
  # but for most of the non-pandas data objects it is unnecessary
501
501
  # to show this index to the user. Therefore, we will hide it as default.
502
- if data_format in [
502
+ if data_format in {
503
503
  DataFormat.SET_OF_VALUES,
504
504
  DataFormat.TUPLE_OF_VALUES,
505
505
  DataFormat.LIST_OF_VALUES,
@@ -516,7 +516,7 @@ def apply_data_specific_configs(
516
516
  DataFormat.POLARS_LAZYFRAME,
517
517
  DataFormat.PYARROW_ARRAY,
518
518
  DataFormat.RAY_DATASET,
519
- ]:
519
+ }:
520
520
  update_column_config(columns_config, INDEX_IDENTIFIER, {"hidden": True})
521
521
 
522
522
 
@@ -91,7 +91,7 @@ class Dialog(DeltaGenerator):
91
91
  on_dismiss: Literal["ignore", "rerun"] | WidgetCallback = "ignore",
92
92
  ) -> Dialog:
93
93
  # Validation for on_dismiss parameter
94
- if on_dismiss not in ["ignore", "rerun"] and not callable(on_dismiss):
94
+ if on_dismiss not in {"ignore", "rerun"} and not callable(on_dismiss):
95
95
  raise StreamlitAPIException(
96
96
  f"You have passed {on_dismiss} to `on_dismiss`. But only 'ignore', "
97
97
  "'rerun', or a callable is supported."
@@ -80,7 +80,7 @@ its column width"""
80
80
 
81
81
 
82
82
  def _image_may_have_alpha_channel(image: PILImage) -> bool:
83
- return image.mode in ("RGBA", "LA", "P")
83
+ return image.mode in {"RGBA", "LA", "P"}
84
84
 
85
85
 
86
86
  def _image_is_gif(image: PILImage) -> bool:
@@ -155,9 +155,9 @@ def _np_array_to_bytes(array: npt.NDArray[Any], output_format: str = "JPEG") ->
155
155
 
156
156
  def _verify_np_shape(array: npt.NDArray[Any]) -> npt.NDArray[Any]:
157
157
  shape: NumpyShape = array.shape
158
- if len(shape) not in (2, 3):
158
+ if len(shape) not in {2, 3}:
159
159
  raise StreamlitAPIException("Numpy shape has to be of length 2 or 3.")
160
- if len(shape) == 3 and shape[-1] not in (1, 3, 4):
160
+ if len(shape) == 3 and shape[-1] not in {1, 3, 4}:
161
161
  raise StreamlitAPIException(
162
162
  f"Channel can only be 1, 3, or 4 got {shape[-1]}. Shape is {shape}"
163
163
  )
@@ -224,7 +224,7 @@ def _clip_image(image: npt.NDArray[Any], clamp: bool) -> npt.NDArray[Any]:
224
224
  data = np.clip(image, 0, 1.0)
225
225
  elif np.amin(image) < 0.0 or np.amax(image) > 1.0:
226
226
  raise RuntimeError("Data is outside [0.0, 1.0] and clamp is not set.")
227
- data = data * 255
227
+ data = data * 255 # noqa: PLR6104
228
228
  elif clamp:
229
229
  data = np.clip(image, 0, 255)
230
230
  elif np.amin(image) < 0 or np.amax(image) > 255:
@@ -273,7 +273,7 @@ def get_justify(
273
273
  justify = map_to_flex_terminology[alignment]
274
274
  if justify not in valid_justify:
275
275
  return Block.FlexContainer.Justify.JUSTIFY_UNDEFINED
276
- if justify in ["start", "end", "center"]:
276
+ if justify in {"start", "end", "center"}:
277
277
  return cast(
278
278
  "Block.FlexContainer.Justify.ValueType",
279
279
  getattr(Block.FlexContainer.Justify, f"JUSTIFY_{justify.upper()}"),
@@ -188,7 +188,7 @@ def maybe_raise_label_warnings(label: str | None, label_visibility: str | None)
188
188
  "if needed.",
189
189
  stack_info=True,
190
190
  )
191
- if label_visibility not in ("visible", "hidden", "collapsed"):
191
+ if label_visibility not in {"visible", "hidden", "collapsed"}:
192
192
  raise errors.StreamlitAPIException(
193
193
  f"Unsupported label_visibility option '{label_visibility}'. "
194
194
  f"Valid values are 'visible', 'hidden' or 'collapsed'."
@@ -59,10 +59,10 @@ SAFE_VALUES: TypeAlias = Union[
59
59
  time,
60
60
  datetime,
61
61
  timedelta,
62
- None,
63
62
  "ellipsis",
64
63
  Message,
65
64
  PROTO_SCALAR_VALUE,
65
+ None,
66
66
  ]
67
67
 
68
68