streamlit-nightly 1.38.1.dev20240905__py2.py3-none-any.whl → 1.38.1.dev20240907__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 (23) hide show
  1. streamlit/commands/page_config.py +12 -14
  2. streamlit/elements/layouts.py +10 -14
  3. streamlit/elements/lib/policies.py +11 -12
  4. streamlit/elements/widgets/button.py +16 -9
  5. streamlit/elements/widgets/multiselect.py +5 -15
  6. streamlit/elements/widgets/number_input.py +22 -26
  7. streamlit/errors.py +274 -2
  8. streamlit/runtime/pages_manager.py +4 -0
  9. streamlit/runtime/scriptrunner_utils/script_run_context.py +6 -7
  10. streamlit/runtime/state/query_params_proxy.py +1 -1
  11. streamlit/static/asset-manifest.json +3 -3
  12. streamlit/static/index.html +1 -1
  13. streamlit/static/static/js/5180.5e064ef1.chunk.js +1 -0
  14. streamlit/static/static/js/{main.7feaefcb.js → main.abc0ee04.js} +2 -2
  15. streamlit/testing/v1/local_script_runner.py +1 -1
  16. {streamlit_nightly-1.38.1.dev20240905.dist-info → streamlit_nightly-1.38.1.dev20240907.dist-info}/METADATA +1 -1
  17. {streamlit_nightly-1.38.1.dev20240905.dist-info → streamlit_nightly-1.38.1.dev20240907.dist-info}/RECORD +22 -22
  18. streamlit/static/static/js/5281.1a0ea2eb.chunk.js +0 -1
  19. /streamlit/static/static/js/{main.7feaefcb.js.LICENSE.txt → main.abc0ee04.js.LICENSE.txt} +0 -0
  20. {streamlit_nightly-1.38.1.dev20240905.data → streamlit_nightly-1.38.1.dev20240907.data}/scripts/streamlit.cmd +0 -0
  21. {streamlit_nightly-1.38.1.dev20240905.dist-info → streamlit_nightly-1.38.1.dev20240907.dist-info}/WHEEL +0 -0
  22. {streamlit_nightly-1.38.1.dev20240905.dist-info → streamlit_nightly-1.38.1.dev20240907.dist-info}/entry_points.txt +0 -0
  23. {streamlit_nightly-1.38.1.dev20240905.dist-info → streamlit_nightly-1.38.1.dev20240907.dist-info}/top_level.txt +0 -0
@@ -21,7 +21,12 @@ from typing import TYPE_CHECKING, Final, Literal, Mapping, Union, cast
21
21
  from typing_extensions import TypeAlias
22
22
 
23
23
  from streamlit.elements import image
24
- from streamlit.errors import StreamlitAPIException
24
+ from streamlit.errors import (
25
+ StreamlitInvalidMenuItemKeyError,
26
+ StreamlitInvalidPageLayoutError,
27
+ StreamlitInvalidSidebarStateError,
28
+ StreamlitInvalidURLError,
29
+ )
25
30
  from streamlit.proto.ForwardMsg_pb2 import ForwardMsg as ForwardProto
26
31
  from streamlit.proto.PageConfig_pb2 import PageConfig as PageConfigProto
27
32
  from streamlit.runtime.metrics_util import gather_metrics
@@ -227,9 +232,8 @@ def set_page_config(
227
232
  elif layout == "wide":
228
233
  pb_layout = PageConfigProto.WIDE
229
234
  else:
230
- raise StreamlitAPIException(
231
- f'`layout` must be "centered" or "wide" (got "{layout}")'
232
- )
235
+ raise StreamlitInvalidPageLayoutError(layout=layout)
236
+
233
237
  msg.page_config_changed.layout = pb_layout
234
238
 
235
239
  pb_sidebar_state: PageConfigProto.SidebarState.ValueType
@@ -240,10 +244,8 @@ def set_page_config(
240
244
  elif initial_sidebar_state == "collapsed":
241
245
  pb_sidebar_state = PageConfigProto.COLLAPSED
242
246
  else:
243
- raise StreamlitAPIException(
244
- "`initial_sidebar_state` must be "
245
- '"auto" or "expanded" or "collapsed" '
246
- f'(got "{initial_sidebar_state}")'
247
+ raise StreamlitInvalidSidebarStateError(
248
+ initial_sidebar_state=initial_sidebar_state
247
249
  )
248
250
 
249
251
  msg.page_config_changed.initial_sidebar_state = pb_sidebar_state
@@ -287,15 +289,11 @@ def set_menu_items_proto(lowercase_menu_items, menu_items_proto) -> None:
287
289
  def validate_menu_items(menu_items: MenuItems) -> None:
288
290
  for k, v in menu_items.items():
289
291
  if not valid_menu_item_key(k):
290
- raise StreamlitAPIException(
291
- "We only accept the keys: "
292
- '"Get help", "Report a bug", and "About" '
293
- f'("{k}" is not a valid key.)'
294
- )
292
+ raise StreamlitInvalidMenuItemKeyError(key=k)
295
293
  if v is not None and (
296
294
  not is_url(v, ("http", "https", "mailto")) and k != ABOUT_KEY
297
295
  ):
298
- raise StreamlitAPIException(f'"{v}" is a not a valid URL!')
296
+ raise StreamlitInvalidURLError(url=v)
299
297
 
300
298
 
301
299
  def valid_menu_item_key(key: str) -> TypeGuard[MenuKey]:
@@ -20,7 +20,12 @@ from typing_extensions import TypeAlias
20
20
 
21
21
  from streamlit.delta_generator_singletons import get_dg_singleton_instance
22
22
  from streamlit.elements.lib.utils import Key, compute_and_register_element_id, to_key
23
- from streamlit.errors import StreamlitAPIException
23
+ from streamlit.errors import (
24
+ StreamlitAPIException,
25
+ StreamlitInvalidColumnGapError,
26
+ StreamlitInvalidColumnSpecError,
27
+ StreamlitInvalidVerticalAlignmentError,
28
+ )
24
29
  from streamlit.proto.Block_pb2 import Block as BlockProto
25
30
  from streamlit.runtime.metrics_util import gather_metrics
26
31
  from streamlit.string_util import validate_icon_or_emoji
@@ -294,12 +299,7 @@ class LayoutsMixin:
294
299
  weights = (1,) * weights
295
300
 
296
301
  if len(weights) == 0 or any(weight <= 0 for weight in weights):
297
- raise StreamlitAPIException(
298
- "The input argument to st.columns must be either a "
299
- "positive integer or a list of positive numeric weights. "
300
- "See [documentation](https://docs.streamlit.io/develop/api-reference/layout/st.columns) "
301
- "for more information."
302
- )
302
+ raise StreamlitInvalidColumnSpecError()
303
303
 
304
304
  vertical_alignment_mapping: dict[
305
305
  str, BlockProto.Column.VerticalAlignment.ValueType
@@ -310,9 +310,8 @@ class LayoutsMixin:
310
310
  }
311
311
 
312
312
  if vertical_alignment not in vertical_alignment_mapping:
313
- raise StreamlitAPIException(
314
- 'The `vertical_alignment` argument to st.columns must be "top", "center", or "bottom". \n'
315
- f"The argument passed was {vertical_alignment}."
313
+ raise StreamlitInvalidVerticalAlignmentError(
314
+ vertical_alignment=vertical_alignment
316
315
  )
317
316
 
318
317
  def column_gap(gap):
@@ -323,10 +322,7 @@ class LayoutsMixin:
323
322
  if gap_size in valid_sizes:
324
323
  return gap_size
325
324
 
326
- raise StreamlitAPIException(
327
- 'The gap argument to st.columns must be "small", "medium", or "large". \n'
328
- f"The argument passed was {gap}."
329
- )
325
+ raise StreamlitInvalidColumnGapError(gap=gap)
330
326
 
331
327
  gap_size = column_gap(gap)
332
328
 
@@ -18,7 +18,12 @@ from typing import TYPE_CHECKING, Any, Final, Sequence
18
18
 
19
19
  from streamlit import config, errors, logger, runtime
20
20
  from streamlit.elements.form_utils import is_in_form
21
- from streamlit.errors import StreamlitAPIException, StreamlitAPIWarning
21
+ from streamlit.errors import (
22
+ StreamlitAPIWarning,
23
+ StreamlitFragmentWidgetsNotAllowedOutsideError,
24
+ StreamlitInvalidFormCallbackError,
25
+ StreamlitValueAssignmentNotAllowedError,
26
+ )
22
27
  from streamlit.runtime.scriptrunner_utils.script_run_context import (
23
28
  get_script_run_ctx,
24
29
  in_cached_function,
@@ -38,15 +43,12 @@ def check_callback_rules(dg: DeltaGenerator, on_change: WidgetCallback | None) -
38
43
 
39
44
  Raises
40
45
  ------
41
- StreamlitAPIException:
46
+ StreamlitInvalidFormCallbackError:
42
47
  Raised when the described rule is violated.
43
48
  """
44
49
 
45
50
  if runtime.exists() and is_in_form(dg) and on_change is not None:
46
- raise StreamlitAPIException(
47
- "With forms, callbacks can only be defined on the `st.form_submit_button`."
48
- " Defining callbacks on other widgets inside a form is not allowed."
49
- )
51
+ raise StreamlitInvalidFormCallbackError()
50
52
 
51
53
 
52
54
  _shown_default_value_warning: bool = False
@@ -76,10 +78,7 @@ def check_session_state_rules(
76
78
  return
77
79
 
78
80
  if not writes_allowed:
79
- raise StreamlitAPIException(
80
- f"Values for the widget with key '{key}' cannot be set using"
81
- " `st.session_state`."
82
- )
81
+ raise StreamlitValueAssignmentNotAllowedError(key=key)
83
82
 
84
83
  if (
85
84
  default_value is not None
@@ -155,13 +154,13 @@ def check_fragment_path_policy(dg: DeltaGenerator):
155
154
  # the elements delta path cannot be smaller than the fragment's delta path if it is
156
155
  # inside of the fragment
157
156
  if len(current_cursor_delta_path) < len(current_fragment_delta_path):
158
- raise StreamlitAPIException(_fragment_writes_widget_to_outside_error)
157
+ raise StreamlitFragmentWidgetsNotAllowedOutsideError()
159
158
 
160
159
  # all path indices of the fragment-path must occur in the inner-elements delta path,
161
160
  # otherwise it is outside of the fragment container
162
161
  for index, path_index in enumerate(current_fragment_delta_path):
163
162
  if current_cursor_delta_path[index] != path_index:
164
- raise StreamlitAPIException(_fragment_writes_widget_to_outside_error)
163
+ raise StreamlitFragmentWidgetsNotAllowedOutsideError()
165
164
 
166
165
 
167
166
  def check_widget_policies(
@@ -34,7 +34,11 @@ from streamlit import runtime
34
34
  from streamlit.elements.form_utils import current_form_id, is_in_form
35
35
  from streamlit.elements.lib.policies import check_widget_policies
36
36
  from streamlit.elements.lib.utils import Key, compute_and_register_element_id, to_key
37
- from streamlit.errors import StreamlitAPIException
37
+ from streamlit.errors import (
38
+ StreamlitAPIException,
39
+ StreamlitMissingPageLabelError,
40
+ StreamlitPageNotFoundError,
41
+ )
38
42
  from streamlit.file_util import get_main_script_directory, normalize_path_join
39
43
  from streamlit.navigation.page import StreamlitPage
40
44
  from streamlit.proto.Button_pb2 import Button as ButtonProto
@@ -776,9 +780,7 @@ class ButtonMixin:
776
780
  # Handle external links:
777
781
  if is_url(page):
778
782
  if label is None or label == "":
779
- raise StreamlitAPIException(
780
- "The label param is required for external links used with st.page_link - please provide a label."
781
- )
783
+ raise StreamlitMissingPageLabelError()
782
784
  else:
783
785
  page_link_proto.page = page
784
786
  page_link_proto.external = True
@@ -808,11 +810,16 @@ class ButtonMixin:
808
810
  break
809
811
 
810
812
  if page_link_proto.page_script_hash == "":
811
- raise StreamlitAPIException(
812
- f"Could not find page: `{page}`. Must be the file path relative to "
813
- "the main script, from the directory: "
814
- f"`{os.path.basename(main_script_directory)}`. Only the main app "
815
- "file and files in the `pages/` directory are supported."
813
+ is_mpa_v2 = (
814
+ ctx is not None
815
+ and ctx.pages_manager is not None
816
+ and ctx.pages_manager.mpa_version == 2
817
+ )
818
+
819
+ raise StreamlitPageNotFoundError(
820
+ is_mpa_v2=is_mpa_v2,
821
+ page=page,
822
+ main_script_directory=main_script_directory,
816
823
  )
817
824
 
818
825
  return self.dg._enqueue("page_link", page_link_proto)
@@ -37,7 +37,9 @@ from streamlit.elements.lib.utils import (
37
37
  maybe_coerce_enum_sequence,
38
38
  to_key,
39
39
  )
40
- from streamlit.errors import StreamlitAPIException
40
+ from streamlit.errors import (
41
+ StreamlitSelectionCountExceedsMaxError,
42
+ )
41
43
  from streamlit.proto.MultiSelect_pb2 import MultiSelect as MultiSelectProto
42
44
  from streamlit.runtime.metrics_util import gather_metrics
43
45
  from streamlit.runtime.scriptrunner import ScriptRunContext, get_script_run_ctx
@@ -80,18 +82,6 @@ class MultiSelectSerde(Generic[T]):
80
82
  return [self.options[i] for i in current_value]
81
83
 
82
84
 
83
- def _get_over_max_options_message(current_selections: int, max_selections: int):
84
- curr_selections_noun = "option" if current_selections == 1 else "options"
85
- max_selections_noun = "option" if max_selections == 1 else "options"
86
- return f"""
87
- Multiselect has {current_selections} {curr_selections_noun} selected but `max_selections`
88
- is set to {max_selections}. This happened because you either gave too many options to `default`
89
- or you manipulated the widget's state through `st.session_state`. Note that
90
- the latter can happen before the line indicated in the traceback.
91
- Please select at most {max_selections} {max_selections_noun}.
92
- """
93
-
94
-
95
85
  def _get_default_count(default: Sequence[Any] | Any | None) -> int:
96
86
  if default is None:
97
87
  return 0
@@ -108,8 +98,8 @@ def _check_max_selections(
108
98
 
109
99
  default_count = _get_default_count(selections)
110
100
  if default_count > max_selections:
111
- raise StreamlitAPIException(
112
- _get_over_max_options_message(default_count, max_selections)
101
+ raise StreamlitSelectionCountExceedsMaxError(
102
+ current_selections_count=default_count, max_selections_count=max_selections
113
103
  )
114
104
 
115
105
 
@@ -33,7 +33,13 @@ from streamlit.elements.lib.utils import (
33
33
  get_label_visibility_proto_value,
34
34
  to_key,
35
35
  )
36
- from streamlit.errors import StreamlitAPIException
36
+ from streamlit.errors import (
37
+ StreamlitInvalidNumberFormatError,
38
+ StreamlitJSNumberBoundsError,
39
+ StreamlitMixedNumericTypesError,
40
+ StreamlitValueAboveMaxError,
41
+ StreamlitValueBelowMinError,
42
+ )
37
43
  from streamlit.js_number import JSNumber, JSNumberBoundsException
38
44
  from streamlit.proto.NumberInput_pb2 import NumberInput as NumberInputProto
39
45
  from streamlit.runtime.metrics_util import gather_metrics
@@ -369,23 +375,17 @@ class NumberInputMixin:
369
375
  # Ensure that all arguments are of the same type.
370
376
  number_input_args = [min_value, max_value, value, step]
371
377
 
372
- int_args = all(
378
+ all_int_args = all(
373
379
  isinstance(a, (numbers.Integral, type(None), str))
374
380
  for a in number_input_args
375
381
  )
376
382
 
377
- float_args = all(
383
+ all_float_args = all(
378
384
  isinstance(a, (float, type(None), str)) for a in number_input_args
379
385
  )
380
386
 
381
- if not int_args and not float_args:
382
- raise StreamlitAPIException(
383
- "All numerical arguments must be of the same type."
384
- f"\n`value` has {type(value).__name__} type."
385
- f"\n`min_value` has {type(min_value).__name__} type."
386
- f"\n`max_value` has {type(max_value).__name__} type."
387
- f"\n`step` has {type(step).__name__} type."
388
- )
387
+ if not all_int_args and not all_float_args:
388
+ raise StreamlitMixedNumericTypesError(value=value, min_value=min_value, max_value=max_value, step=step)
389
389
 
390
390
  session_state = get_session_state().filtered_state
391
391
  if key is not None and key in session_state and session_state[key] is None:
@@ -394,9 +394,9 @@ class NumberInputMixin:
394
394
  if value == "min":
395
395
  if min_value is not None:
396
396
  value = min_value
397
- elif int_args and float_args:
397
+ elif all_int_args and all_float_args:
398
398
  value = 0.0 # if no values are provided, defaults to float
399
- elif int_args:
399
+ elif all_int_args:
400
400
  value = 0
401
401
  else:
402
402
  value = 0.0
@@ -405,7 +405,7 @@ class NumberInputMixin:
405
405
  float_value = isinstance(value, float)
406
406
 
407
407
  if value is None:
408
- if int_args and not float_args:
408
+ if all_int_args and not all_float_args:
409
409
  # Select int type if all relevant args are ints:
410
410
  int_value = True
411
411
  else:
@@ -437,22 +437,18 @@ class NumberInputMixin:
437
437
  try:
438
438
  float(format % 2)
439
439
  except (TypeError, ValueError):
440
- raise StreamlitAPIException(
441
- "Format string for st.number_input contains invalid characters: %s"
442
- % format
443
- )
440
+ raise StreamlitInvalidNumberFormatError(format)
441
+
444
442
 
445
443
  # Ensure that the value matches arguments' types.
446
- all_ints = int_value and int_args
444
+ all_ints = int_value and all_int_args
447
445
 
448
446
  if min_value is not None and value is not None and min_value > value:
449
- raise StreamlitAPIException(
450
- f"The default `value` {value} must be greater than or equal to the `min_value` {min_value}"
451
- )
447
+ raise StreamlitValueBelowMinError(value=value, min_value=min_value)
448
+
449
+
452
450
  if max_value is not None and value is not None and max_value < value:
453
- raise StreamlitAPIException(
454
- f"The default `value` {value} must be less than or equal to the `max_value` {max_value}"
455
- )
451
+ raise StreamlitValueAboveMaxError(value=value, max_value=max_value)
456
452
 
457
453
  # Bounds checks. JSNumber produces human-readable exceptions that
458
454
  # we simply re-package as StreamlitAPIExceptions.
@@ -476,7 +472,7 @@ class NumberInputMixin:
476
472
  if value is not None:
477
473
  JSNumber.validate_float_bounds(value, "`value`")
478
474
  except JSNumberBoundsException as e:
479
- raise StreamlitAPIException(str(e))
475
+ raise StreamlitJSNumberBoundsError(str(e))
480
476
 
481
477
  data_type = NumberInputProto.INT if all_ints else NumberInputProto.FLOAT
482
478
 
streamlit/errors.py CHANGED
@@ -14,6 +14,9 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import os
18
+ from typing import Any, Literal
19
+
17
20
  from streamlit import util
18
21
 
19
22
 
@@ -154,7 +157,276 @@ class StreamlitModuleNotFoundError(StreamlitAPIWarning):
154
157
 
155
158
  def __init__(self, module_name, *args):
156
159
  message = (
157
- f'This Streamlit command requires module "{module_name}" to be '
158
- "installed."
160
+ f'This Streamlit command requires module "{module_name}" '
161
+ "to be installed."
159
162
  )
160
163
  super().__init__(message, *args)
164
+
165
+
166
+ class LocalizableStreamlitException(StreamlitAPIException):
167
+ def __init__(self, message: str, **kwargs):
168
+ super().__init__((message).format(**kwargs))
169
+ self._exec_kwargs = kwargs
170
+
171
+ @property
172
+ def exec_kwargs(self) -> dict[str, Any]:
173
+ return self._exec_kwargs
174
+
175
+
176
+ # st.set_page_config
177
+ class StreamlitSetPageConfigMustBeFirstCommandError(LocalizableStreamlitException):
178
+ """Exception raised when the set_page_config command is not the first executed streamlit command."""
179
+
180
+ def __init__(self):
181
+ super().__init__(
182
+ "`set_page_config()` can only be called once per app page, "
183
+ "and must be called as the first Streamlit command in your script.\n\n"
184
+ "For more information refer to the [docs]"
185
+ "(https://docs.streamlit.io/develop/api-reference/configuration/st.set_page_config)."
186
+ )
187
+
188
+
189
+ class StreamlitInvalidPageLayoutError(LocalizableStreamlitException):
190
+ """Exception raised when an invalid value is specified for layout."""
191
+
192
+ def __init__(self, layout: str):
193
+ super().__init__(
194
+ '`layout` must be `"centered"` or `"wide"` (got `"{layout}"`)',
195
+ layout=layout,
196
+ )
197
+
198
+
199
+ class StreamlitInvalidSidebarStateError(LocalizableStreamlitException):
200
+ """Exception raised when an invalid value is specified for `initial_sidebar_state`."""
201
+
202
+ def __init__(self, initial_sidebar_state: str):
203
+ super().__init__(
204
+ '`initial_sidebar_state` must be `"auto"` or `"expanded"` or `"collapsed"` (got `"{initial_sidebar_state}"`)',
205
+ initial_sidebar_state=initial_sidebar_state,
206
+ )
207
+
208
+
209
+ class StreamlitInvalidMenuItemKeyError(LocalizableStreamlitException):
210
+ """Exception raised when an invalid key is specified."""
211
+
212
+ def __init__(self, key: str):
213
+ super().__init__(
214
+ 'We only accept the keys: `"Get help"`, `"Report a bug"`, and `"About"` (`"{key}"` is not a valid key.)',
215
+ key=key,
216
+ )
217
+
218
+
219
+ class StreamlitInvalidURLError(LocalizableStreamlitException):
220
+ """Exception raised when an invalid URL is specified for any of the menu items except for “About”."""
221
+
222
+ def __init__(self, url: str):
223
+ super().__init__(
224
+ '"{url}" is a not a valid URL. '
225
+ 'You must use a fully qualified domain beginning with "http://", "https://", or "mailto:".',
226
+ url=url,
227
+ )
228
+
229
+
230
+ # st.columns
231
+ class StreamlitInvalidColumnSpecError(LocalizableStreamlitException):
232
+ """Exception raised when no weights are specified, or a negative weight is specified."""
233
+
234
+ def __init__(self):
235
+ super().__init__(
236
+ "The `spec` argument to `st.columns` must be either a "
237
+ "positive integer (number of columns) or a list of positive numbers (width ratios of the columns). "
238
+ "See [documentation](https://docs.streamlit.io/develop/api-reference/layout/st.columns) "
239
+ "for more information."
240
+ )
241
+
242
+
243
+ class StreamlitInvalidVerticalAlignmentError(LocalizableStreamlitException):
244
+ """Exception raised when an invalid value is specified for vertical_alignment."""
245
+
246
+ def __init__(self, vertical_alignment: str):
247
+ super().__init__(
248
+ 'The `vertical_alignment` argument to `st.columns` must be `"top"`, `"center"`, or `"bottom"`. \n'
249
+ "The argument passed was {vertical_alignment}.",
250
+ vertical_alignment=vertical_alignment,
251
+ )
252
+
253
+
254
+ class StreamlitInvalidColumnGapError(LocalizableStreamlitException):
255
+ """Exception raised when an invalid value is specified for gap."""
256
+
257
+ def __init__(self, gap: str):
258
+ super().__init__(
259
+ 'The `gap` argument to `st.columns` must be `"small"`, `"medium"`, or `"large"`. \n'
260
+ "The argument passed was {gap}.",
261
+ gap=gap,
262
+ )
263
+
264
+
265
+ # st.multiselect
266
+ class StreamlitSelectionCountExceedsMaxError(LocalizableStreamlitException):
267
+ """Exception raised when there are more default selections specified than the max allowable selections."""
268
+
269
+ def __init__(self, current_selections_count: int, max_selections_count: int):
270
+ super().__init__(
271
+ "Multiselect has {current_selections_count} {current_selections_noun} "
272
+ "selected but `max_selections` is set to {max_selections_count}. "
273
+ "This happened because you either gave too many options to `default` "
274
+ "or you manipulated the widget's state through `st.session_state`. "
275
+ "Note that the latter can happen before the line indicated in the traceback. "
276
+ "Please select at most {max_selections_count} {options_noun}.",
277
+ current_selections_count=current_selections_count,
278
+ current_selections_noun="option"
279
+ if current_selections_count == 1
280
+ else "options",
281
+ max_selections_count=max_selections_count,
282
+ options_noun="option" if max_selections_count == 1 else "options",
283
+ )
284
+
285
+
286
+ # st.number_input
287
+ class StreamlitMixedNumericTypesError(LocalizableStreamlitException):
288
+ """Exception raised mixing floats and ints in st.number_input."""
289
+
290
+ def __init__(
291
+ self,
292
+ value: int | float | Literal["min"] | None,
293
+ min_value: int | float | None,
294
+ max_value: int | float | None,
295
+ step: int | float | None,
296
+ ):
297
+ value_type = None
298
+ min_value_type = None
299
+ max_value_type = None
300
+ step_type = None
301
+
302
+ error_message = "All numerical arguments must be of the same type."
303
+
304
+ if value:
305
+ value_type = type(value).__name__
306
+ error_message += "\n`value` has {value_type} type."
307
+
308
+ if min_value:
309
+ min_value_type = type(min_value).__name__
310
+ error_message += "\n`min_value` has {min_value_type} type."
311
+
312
+ if max_value:
313
+ max_value_type = type(max_value).__name__
314
+ error_message += "\n`max_value` has {max_value_type} type."
315
+
316
+ if step:
317
+ step_type = type(step).__name__
318
+ error_message += "\n`step` has {step_type} type."
319
+
320
+ super().__init__(
321
+ error_message,
322
+ value_type=value_type,
323
+ min_value_type=min_value_type,
324
+ max_value_type=max_value_type,
325
+ step_type=step_type,
326
+ )
327
+
328
+
329
+ class StreamlitValueBelowMinError(LocalizableStreamlitException):
330
+ """Exception raised when the `min_value` is greater than the `value`."""
331
+
332
+ def __init__(self, value: int | float, min_value: int | float):
333
+ super().__init__(
334
+ "The `value` {value} is less than the `min_value` {min_value}.",
335
+ value=value,
336
+ min_value=min_value,
337
+ )
338
+
339
+
340
+ class StreamlitValueAboveMaxError(LocalizableStreamlitException):
341
+ """Exception raised when the `max_value` is less than the `value`."""
342
+
343
+ def __init__(self, value: int | float, max_value: int | float):
344
+ super().__init__(
345
+ "The `value` {value} is greater than than the `max_value` {max_value}.",
346
+ value=value,
347
+ max_value=max_value,
348
+ )
349
+
350
+
351
+ class StreamlitJSNumberBoundsError(LocalizableStreamlitException):
352
+ """Exception raised when a number exceeds the Javascript limits."""
353
+
354
+ def __init__(self, message: str):
355
+ super().__init__(message)
356
+
357
+
358
+ class StreamlitInvalidNumberFormatError(LocalizableStreamlitException):
359
+ """Exception raised when the format string for `st.number_input` contains
360
+ invalid characters.
361
+ """
362
+
363
+ def __init__(self, format: str):
364
+ super().__init__(
365
+ "Format string for `st.number_input` contains invalid characters: {format}",
366
+ format=format,
367
+ )
368
+
369
+
370
+ # st.page_link
371
+ class StreamlitMissingPageLabelError(LocalizableStreamlitException):
372
+ """Exception raised when a page_link is created without a label."""
373
+
374
+ def __init__(self):
375
+ super().__init__(
376
+ "The `label` param is required for external links used with `st.page_link` - please provide a `label`."
377
+ )
378
+
379
+
380
+ class StreamlitPageNotFoundError(LocalizableStreamlitException):
381
+ """Exception raised the linked page can not be found."""
382
+
383
+ def __init__(self, page: str, main_script_directory: str, is_mpa_v2: bool):
384
+ directory = os.path.basename(main_script_directory)
385
+
386
+ message = (
387
+ "Could not find page: `{page}`. You must provide a file path "
388
+ "relative to the entrypoint file (from the directory `{directory}`). "
389
+ "Only the entrypoint file and files in the `pages/` directory are supported."
390
+ )
391
+
392
+ if is_mpa_v2:
393
+ message = (
394
+ "Could not find page: `{page}`. You must provide a `StreamlitPage` "
395
+ "object or file path relative to the entrypoint file. Only pages "
396
+ "previously defined by `st.Page` and passed to `st.navigation` are "
397
+ "allowed."
398
+ )
399
+
400
+ super().__init__(
401
+ message,
402
+ page=page,
403
+ directory=directory,
404
+ )
405
+
406
+
407
+ # policies
408
+ class StreamlitFragmentWidgetsNotAllowedOutsideError(LocalizableStreamlitException):
409
+ """Exception raised when the fragment attempts to write to an element outside of its container."""
410
+
411
+ def __init__(self):
412
+ super().__init__("Fragments cannot write widgets to outside containers.")
413
+
414
+
415
+ class StreamlitInvalidFormCallbackError(LocalizableStreamlitException):
416
+ """Exception raised a `on_change` callback is set on any element in a form except for the `st.form_submit_button`."""
417
+
418
+ def __init__(self):
419
+ super().__init__(
420
+ "Within a form, callbacks can only be defined on `st.form_submit_button`. "
421
+ "Defining callbacks on other widgets inside a form is not allowed."
422
+ )
423
+
424
+
425
+ class StreamlitValueAssignmentNotAllowedError(LocalizableStreamlitException):
426
+ """Exception raised when trying to set values where writes are not allowed."""
427
+
428
+ def __init__(self, key: str):
429
+ super().__init__(
430
+ "Values for the widget with `key` '{key}' cannot be set using `st.session_state`.",
431
+ key=key,
432
+ )
@@ -269,6 +269,10 @@ class PagesManager:
269
269
  def intended_page_script_hash(self) -> PageHash | None:
270
270
  return self._intended_page_script_hash
271
271
 
272
+ @property
273
+ def mpa_version(self) -> int:
274
+ return 2 if isinstance(self.pages_strategy, PagesStrategyV2) else 1
275
+
272
276
  def get_main_page(self) -> PageInfo:
273
277
  return {
274
278
  "script_path": self._main_script_path,