reflex 0.7.2.dev1__py3-none-any.whl → 0.7.3__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.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (49) hide show
  1. reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +6 -3
  2. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -1
  3. reflex/.templates/web/components/shiki/code.js +26 -21
  4. reflex/.templates/web/postcss.config.js +1 -1
  5. reflex/.templates/web/utils/client_side_routing.js +18 -16
  6. reflex/.templates/web/utils/helpers/dataeditor.js +1 -1
  7. reflex/.templates/web/utils/helpers/range.js +30 -30
  8. reflex/.templates/web/utils/state.js +44 -22
  9. reflex/app_mixins/middleware.py +9 -10
  10. reflex/compiler/compiler.py +7 -0
  11. reflex/components/core/banner.py +1 -1
  12. reflex/components/core/foreach.py +3 -2
  13. reflex/components/datadisplay/logo.py +1 -1
  14. reflex/components/el/__init__.pyi +22 -0
  15. reflex/components/el/elements/__init__.py +20 -1
  16. reflex/components/el/elements/__init__.pyi +42 -1
  17. reflex/components/el/elements/media.py +11 -0
  18. reflex/components/el/elements/media.pyi +11 -0
  19. reflex/components/lucide/icon.py +8 -6
  20. reflex/components/lucide/icon.pyi +0 -1
  21. reflex/components/radix/themes/components/slider.py +2 -1
  22. reflex/config.py +13 -7
  23. reflex/custom_components/custom_components.py +23 -25
  24. reflex/event.py +7 -2
  25. reflex/istate/data.py +59 -7
  26. reflex/reflex.py +69 -30
  27. reflex/state.py +3 -3
  28. reflex/testing.py +4 -10
  29. reflex/utils/exceptions.py +31 -1
  30. reflex/utils/exec.py +4 -8
  31. reflex/utils/export.py +2 -2
  32. reflex/vars/base.py +23 -1
  33. reflex/vars/number.py +22 -21
  34. reflex/vars/object.py +29 -0
  35. reflex/vars/sequence.py +37 -0
  36. {reflex-0.7.2.dev1.dist-info → reflex-0.7.3.dist-info}/METADATA +42 -42
  37. {reflex-0.7.2.dev1.dist-info → reflex-0.7.3.dist-info}/RECORD +61 -70
  38. {reflex-0.7.2.dev1.dist-info → reflex-0.7.3.dist-info}/WHEEL +1 -1
  39. {reflex-0.7.2.dev1.dist-info → reflex-0.7.3.dist-info}/entry_points.txt +0 -3
  40. benchmarks/__init__.py +0 -3
  41. benchmarks/benchmark_compile_times.py +0 -147
  42. benchmarks/benchmark_imports.py +0 -128
  43. benchmarks/benchmark_lighthouse.py +0 -75
  44. benchmarks/benchmark_package_size.py +0 -135
  45. benchmarks/benchmark_web_size.py +0 -106
  46. benchmarks/conftest.py +0 -20
  47. benchmarks/lighthouse.sh +0 -77
  48. benchmarks/utils.py +0 -74
  49. {reflex-0.7.2.dev1.dist-info → reflex-0.7.3.dist-info}/licenses/LICENSE +0 -0
reflex/reflex.py CHANGED
@@ -22,10 +22,6 @@ typer.core.rich = None # pyright: ignore [reportPrivateImportUsage]
22
22
  cli = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
23
23
 
24
24
 
25
- # Get the config.
26
- config = get_config()
27
-
28
-
29
25
  def version(value: bool):
30
26
  """Get the Reflex version.
31
27
 
@@ -58,13 +54,19 @@ def main(
58
54
  def _init(
59
55
  name: str,
60
56
  template: str | None = None,
61
- loglevel: constants.LogLevel = config.loglevel,
57
+ loglevel: constants.LogLevel | None = None,
62
58
  ai: bool = False,
63
59
  ):
64
60
  """Initialize a new Reflex app in the given directory."""
65
61
  from reflex.utils import exec, prerequisites
66
62
 
63
+ if loglevel is not None:
64
+ console.set_log_level(loglevel)
65
+
66
+ config = get_config()
67
+
67
68
  # Set the log level.
69
+ loglevel = loglevel or config.loglevel
68
70
  console.set_log_level(loglevel)
69
71
 
70
72
  # Show system info
@@ -109,8 +111,8 @@ def init(
109
111
  None,
110
112
  help="The template to initialize the app with.",
111
113
  ),
112
- loglevel: constants.LogLevel = typer.Option(
113
- config.loglevel, help="The log level to use."
114
+ loglevel: constants.LogLevel | None = typer.Option(
115
+ None, help="The log level to use."
114
116
  ),
115
117
  ai: bool = typer.Option(
116
118
  False,
@@ -127,12 +129,20 @@ def _run(
127
129
  backend: bool = True,
128
130
  frontend_port: int | None = None,
129
131
  backend_port: int | None = None,
130
- backend_host: str = config.backend_host,
131
- loglevel: constants.LogLevel = config.loglevel,
132
+ backend_host: str | None = None,
133
+ loglevel: constants.LogLevel | None = None,
132
134
  ):
133
135
  """Run the app in the given directory."""
134
136
  from reflex.utils import build, exec, prerequisites, processes
135
137
 
138
+ if loglevel is not None:
139
+ console.set_log_level(loglevel)
140
+
141
+ config = get_config()
142
+
143
+ loglevel = loglevel or config.loglevel
144
+ backend_host = backend_host or config.backend_host
145
+
136
146
  # Set the log level.
137
147
  console.set_log_level(loglevel)
138
148
 
@@ -274,21 +284,19 @@ def run(
274
284
  help="Execute only backend.",
275
285
  envvar=environment.REFLEX_BACKEND_ONLY.name,
276
286
  ),
277
- frontend_port: int = typer.Option(
278
- config.frontend_port,
287
+ frontend_port: int | None = typer.Option(
288
+ None,
279
289
  help="Specify a different frontend port.",
280
290
  envvar=environment.REFLEX_FRONTEND_PORT.name,
281
291
  ),
282
- backend_port: int = typer.Option(
283
- config.backend_port,
292
+ backend_port: int | None = typer.Option(
293
+ None,
284
294
  help="Specify a different backend port.",
285
295
  envvar=environment.REFLEX_BACKEND_PORT.name,
286
296
  ),
287
- backend_host: str = typer.Option(
288
- config.backend_host, help="Specify the backend host."
289
- ),
290
- loglevel: constants.LogLevel = typer.Option(
291
- config.loglevel, help="The log level to use."
297
+ backend_host: str | None = typer.Option(None, help="Specify the backend host."),
298
+ loglevel: constants.LogLevel | None = typer.Option(
299
+ None, help="The log level to use."
292
300
  ),
293
301
  ):
294
302
  """Run the app in the current directory."""
@@ -296,6 +304,16 @@ def run(
296
304
  console.error("Cannot use both --frontend-only and --backend-only options.")
297
305
  raise typer.Exit(1)
298
306
 
307
+ if loglevel is not None:
308
+ console.set_log_level(loglevel)
309
+
310
+ config = get_config()
311
+
312
+ frontend_port = frontend_port or config.frontend_port
313
+ backend_port = backend_port or config.backend_port
314
+ backend_host = backend_host or config.backend_host
315
+ loglevel = loglevel or config.loglevel
316
+
299
317
  environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.RUN)
300
318
  environment.REFLEX_BACKEND_ONLY.set(backend)
301
319
  environment.REFLEX_FRONTEND_ONLY.set(frontend)
@@ -335,8 +353,8 @@ def export(
335
353
  env: constants.Env = typer.Option(
336
354
  constants.Env.PROD, help="The environment to export the app in."
337
355
  ),
338
- loglevel: constants.LogLevel = typer.Option(
339
- config.loglevel, help="The log level to use."
356
+ loglevel: constants.LogLevel | None = typer.Option(
357
+ None, help="The log level to use."
340
358
  ),
341
359
  ):
342
360
  """Export the app to a zip file."""
@@ -347,8 +365,11 @@ def export(
347
365
 
348
366
  frontend, backend = prerequisites.check_running_mode(frontend, backend)
349
367
 
368
+ loglevel = loglevel or get_config().loglevel
369
+ console.set_log_level(loglevel)
370
+
350
371
  if prerequisites.needs_reinit(frontend=frontend or not backend):
351
- _init(name=config.app_name, loglevel=loglevel)
372
+ _init(name=get_config().app_name, loglevel=loglevel)
352
373
 
353
374
  export_utils.export(
354
375
  zipping=zipping,
@@ -362,10 +383,14 @@ def export(
362
383
 
363
384
 
364
385
  @cli.command()
365
- def login(loglevel: constants.LogLevel = typer.Option(config.loglevel)):
386
+ def login(loglevel: constants.LogLevel | None = typer.Option(None)):
366
387
  """Authenticate with experimental Reflex hosting service."""
367
388
  from reflex_cli.v2 import cli as hosting_cli
368
389
 
390
+ loglevel = loglevel or get_config().loglevel
391
+
392
+ console.set_log_level(loglevel)
393
+
369
394
  check_version()
370
395
 
371
396
  validated_info = hosting_cli.login()
@@ -376,8 +401,8 @@ def login(loglevel: constants.LogLevel = typer.Option(config.loglevel)):
376
401
 
377
402
  @cli.command()
378
403
  def logout(
379
- loglevel: constants.LogLevel = typer.Option(
380
- config.loglevel, help="The log level to use."
404
+ loglevel: constants.LogLevel | None = typer.Option(
405
+ None, help="The log level to use."
381
406
  ),
382
407
  ):
383
408
  """Log out of access to Reflex hosting service."""
@@ -385,6 +410,8 @@ def logout(
385
410
 
386
411
  check_version()
387
412
 
413
+ loglevel = loglevel or get_config().loglevel
414
+
388
415
  logout(loglevel) # pyright: ignore [reportArgumentType]
389
416
 
390
417
 
@@ -403,6 +430,8 @@ def db_init():
403
430
  from reflex import model
404
431
  from reflex.utils import prerequisites
405
432
 
433
+ config = get_config()
434
+
406
435
  # Check the database url.
407
436
  if config.db_url is None:
408
437
  console.error("db_url is not configured, cannot initialize.")
@@ -470,8 +499,8 @@ def makemigrations(
470
499
 
471
500
  @cli.command()
472
501
  def deploy(
473
- app_name: str = typer.Option(
474
- config.app_name,
502
+ app_name: str | None = typer.Option(
503
+ None,
475
504
  "--app-name",
476
505
  help="The name of the App to deploy under.",
477
506
  ),
@@ -510,8 +539,8 @@ def deploy(
510
539
  "--envfile",
511
540
  help="The path to an env file to use. Will override any envs set manually.",
512
541
  ),
513
- loglevel: constants.LogLevel = typer.Option(
514
- config.loglevel, help="The log level to use."
542
+ loglevel: constants.LogLevel | None = typer.Option(
543
+ None, help="The log level to use."
515
544
  ),
516
545
  project: str | None = typer.Option(
517
546
  None,
@@ -542,6 +571,14 @@ def deploy(
542
571
  from reflex.utils import export as export_utils
543
572
  from reflex.utils import prerequisites
544
573
 
574
+ if loglevel is not None:
575
+ console.set_log_level(loglevel)
576
+
577
+ config = get_config()
578
+
579
+ loglevel = loglevel or config.loglevel
580
+ app_name = app_name or config.app_name
581
+
545
582
  check_version()
546
583
 
547
584
  environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.DEPLOY)
@@ -608,13 +645,15 @@ def deploy(
608
645
  @cli.command()
609
646
  def rename(
610
647
  new_name: str = typer.Argument(..., help="The new name for the app."),
611
- loglevel: constants.LogLevel = typer.Option(
612
- config.loglevel, help="The log level to use."
648
+ loglevel: constants.LogLevel | None = typer.Option(
649
+ None, help="The log level to use."
613
650
  ),
614
651
  ):
615
652
  """Rename the app in the current directory."""
616
653
  from reflex.utils import prerequisites
617
654
 
655
+ loglevel = loglevel or get_config().loglevel
656
+
618
657
  prerequisites.validate_app_name(new_name)
619
658
  prerequisites.rename_app(new_name, loglevel)
620
659
 
reflex/state.py CHANGED
@@ -1013,9 +1013,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1013
1013
 
1014
1014
  if not types.is_valid_var_type(prop._var_type):
1015
1015
  raise VarTypeError(
1016
- "State vars must be primitive Python types, "
1017
- "Plotly figures, Pandas dataframes, "
1018
- "or subclasses of rx.Base. "
1016
+ "State vars must be of a serializable type. "
1017
+ "Valid types include strings, numbers, booleans, lists, "
1018
+ "dictionaries, dataclasses, datetime objects, and pydantic models. "
1019
1019
  f'Found var "{prop._js_expr}" with type {prop._var_type}.'
1020
1020
  )
1021
1021
  cls._set_var(prop)
reflex/testing.py CHANGED
@@ -54,18 +54,12 @@ from reflex.state import (
54
54
  from reflex.utils import console
55
55
 
56
56
  try:
57
- from selenium import webdriver # pyright: ignore [reportMissingImports]
58
- from selenium.webdriver.remote.webdriver import ( # pyright: ignore [reportMissingImports]
59
- WebDriver,
60
- )
57
+ from selenium import webdriver
58
+ from selenium.webdriver.remote.webdriver import WebDriver
61
59
 
62
60
  if TYPE_CHECKING:
63
- from selenium.webdriver.common.options import (
64
- ArgOptions, # pyright: ignore [reportMissingImports]
65
- )
66
- from selenium.webdriver.remote.webelement import ( # pyright: ignore [reportMissingImports]
67
- WebElement,
68
- )
61
+ from selenium.webdriver.common.options import ArgOptions
62
+ from selenium.webdriver.remote.webelement import WebElement
69
63
 
70
64
  has_selenium = True
71
65
  except ImportError:
@@ -1,6 +1,11 @@
1
1
  """Custom Exceptions."""
2
2
 
3
- from typing import Any
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from reflex.vars import Var
4
9
 
5
10
 
6
11
  class ReflexError(Exception):
@@ -78,6 +83,31 @@ class VarAttributeError(ReflexError, AttributeError):
78
83
  class UntypedVarError(ReflexError, TypeError):
79
84
  """Custom TypeError for untyped var errors."""
80
85
 
86
+ def __init__(self, var: Var, action: str, doc_link: str = ""):
87
+ """Create an UntypedVarError from a var.
88
+
89
+ Args:
90
+ var: The var.
91
+ action: The action that caused the error.
92
+ doc_link: The link to the documentation.
93
+ """
94
+ var_data = var._get_all_var_data()
95
+ is_state_var = (
96
+ var_data
97
+ and var_data.state
98
+ and var_data.field_name
99
+ and var_data.state + "." + var_data.field_name == str(var)
100
+ )
101
+ super().__init__(
102
+ f"Cannot {action} on untyped var '{var!s}' of type '{var._var_type!s}'."
103
+ + (
104
+ " Please add a type annotation to the var in the state class."
105
+ if is_state_var
106
+ else " You can call the var's .to(desired_type) method to convert it to the desired type."
107
+ )
108
+ + (f" See {doc_link}" if doc_link else "")
109
+ )
110
+
81
111
 
82
112
  class UntypedComputedVarError(ReflexError, TypeError):
83
113
  """Custom TypeError for untyped computed var errors."""
reflex/utils/exec.py CHANGED
@@ -332,11 +332,9 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
332
332
  """
333
333
  console.debug("Using Granian for backend")
334
334
  try:
335
- from granian import Granian # pyright: ignore [reportMissingImports]
336
- from granian.constants import ( # pyright: ignore [reportMissingImports]
337
- Interfaces,
338
- )
339
- from granian.log import LogLevels # pyright: ignore [reportMissingImports]
335
+ from granian.constants import Interfaces
336
+ from granian.log import LogLevels
337
+ from granian.server import Server as Granian
340
338
 
341
339
  Granian(
342
340
  target=get_granian_target(),
@@ -466,9 +464,7 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
466
464
  from reflex.utils import processes
467
465
 
468
466
  try:
469
- from granian.constants import ( # pyright: ignore [reportMissingImports]
470
- Interfaces,
471
- )
467
+ from granian.constants import Interfaces
472
468
 
473
469
  command = [
474
470
  "granian",
reflex/utils/export.py CHANGED
@@ -6,8 +6,6 @@ from reflex import constants
6
6
  from reflex.config import environment, get_config
7
7
  from reflex.utils import build, console, exec, prerequisites, telemetry
8
8
 
9
- config = get_config()
10
-
11
9
 
12
10
  def export(
13
11
  zipping: bool = True,
@@ -33,6 +31,8 @@ def export(
33
31
  env: The environment to use. Defaults to constants.Env.PROD.
34
32
  loglevel: The log level to use. Defaults to console._LOG_LEVEL.
35
33
  """
34
+ config = get_config()
35
+
36
36
  # Set the log level.
37
37
  console.set_log_level(loglevel)
38
38
 
reflex/vars/base.py CHANGED
@@ -1256,6 +1256,27 @@ class Var(Generic[VAR_TYPE]):
1256
1256
 
1257
1257
  if not TYPE_CHECKING:
1258
1258
 
1259
+ def __getitem__(self, key: Any) -> Var:
1260
+ """Get the item from the var.
1261
+
1262
+ Args:
1263
+ key: The key to get.
1264
+
1265
+ Raises:
1266
+ UntypedVarError: If the var type is Any.
1267
+ TypeError: If the var type is Any.
1268
+
1269
+ # noqa: DAR101 self
1270
+ """
1271
+ if self._var_type is Any:
1272
+ raise exceptions.UntypedVarError(
1273
+ self,
1274
+ f"access the item '{key}'",
1275
+ )
1276
+ raise TypeError(
1277
+ f"Var of type {self._var_type} does not support item access."
1278
+ )
1279
+
1259
1280
  def __getattr__(self, name: str):
1260
1281
  """Get an attribute of the var.
1261
1282
 
@@ -1281,7 +1302,8 @@ class Var(Generic[VAR_TYPE]):
1281
1302
 
1282
1303
  if self._var_type is Any:
1283
1304
  raise exceptions.UntypedVarError(
1284
- f"You must provide an annotation for the state var `{self!s}`. Annotation cannot be `{self._var_type}`."
1305
+ self,
1306
+ f"access the attribute '{name}'",
1285
1307
  )
1286
1308
 
1287
1309
  raise VarAttributeError(
reflex/vars/number.py CHANGED
@@ -23,6 +23,7 @@ from reflex.utils.exceptions import (
23
23
  VarValueError,
24
24
  )
25
25
  from reflex.utils.imports import ImportDict, ImportVar
26
+ from reflex.utils.types import safe_issubclass
26
27
 
27
28
  from .base import (
28
29
  CustomVarOperationReturn,
@@ -524,7 +525,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)):
524
525
  Returns:
525
526
  bool: True if the number is a float.
526
527
  """
527
- return issubclass(self._var_type, float)
528
+ return safe_issubclass(self._var_type, float)
528
529
 
529
530
  def _is_strict_int(self) -> bool:
530
531
  """Check if the number is an int.
@@ -532,7 +533,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)):
532
533
  Returns:
533
534
  bool: True if the number is an int.
534
535
  """
535
- return issubclass(self._var_type, int)
536
+ return safe_issubclass(self._var_type, int)
536
537
 
537
538
  def __format__(self, format_spec: str) -> str:
538
539
  """Format the number.
@@ -546,6 +547,20 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)):
546
547
  Raises:
547
548
  VarValueError: If the format specifier is not supported.
548
549
  """
550
+ from .sequence import (
551
+ get_decimal_string_operation,
552
+ get_decimal_string_separator_operation,
553
+ )
554
+
555
+ separator = ""
556
+
557
+ if format_spec and format_spec[:1] == ",":
558
+ separator = ","
559
+ format_spec = format_spec[1:]
560
+ elif format_spec and format_spec[:1] == "_":
561
+ separator = "_"
562
+ format_spec = format_spec[1:]
563
+
549
564
  if (
550
565
  format_spec
551
566
  and format_spec[-1] == "f"
@@ -553,14 +568,17 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)):
553
568
  and format_spec[1:-1].isdigit()
554
569
  ):
555
570
  how_many_decimals = int(format_spec[1:-1])
571
+ return f"{get_decimal_string_operation(self, Var.create(how_many_decimals), Var.create(separator))}"
572
+
573
+ if not format_spec and separator:
556
574
  return (
557
- f"{get_decimal_string_operation(self, Var.create(how_many_decimals))}"
575
+ f"{get_decimal_string_separator_operation(self, Var.create(separator))}"
558
576
  )
559
577
 
560
578
  if format_spec:
561
579
  raise VarValueError(
562
580
  (
563
- "Unknown format code '{}' for object of type 'NumberVar'. It is only supported to use '.f' for float numbers."
581
+ "Unknown format code '{}' for object of type 'NumberVar'. It is only supported to use ',', '_', and '.f' for float numbers."
564
582
  "If possible, use computed variables instead: https://reflex.dev/docs/vars/computed-vars/"
565
583
  ).format(format_spec)
566
584
  )
@@ -568,23 +586,6 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)):
568
586
  return super().__format__(format_spec)
569
587
 
570
588
 
571
- @var_operation
572
- def get_decimal_string_operation(value: NumberVar, decimals: NumberVar):
573
- """Get the decimal string of the number.
574
-
575
- Args:
576
- value: The number.
577
- decimals: The number of decimals.
578
-
579
- Returns:
580
- The decimal string of the number.
581
- """
582
- return var_operation_return(
583
- js_expression=f"({value}.toFixed({decimals}))",
584
- var_type=str,
585
- )
586
-
587
-
588
589
  def binary_number_operation(
589
590
  func: Callable[[NumberVar, NumberVar], str],
590
591
  ) -> Callable[[number_types, number_types], NumberVar]:
reflex/vars/object.py CHANGED
@@ -202,6 +202,12 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping):
202
202
  key: Var | Any,
203
203
  ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
204
204
 
205
+ @overload
206
+ def __getitem__(
207
+ self: ObjectVar[Mapping[Any, VALUE_TYPE]],
208
+ key: Var | Any,
209
+ ) -> Var[VALUE_TYPE]: ...
210
+
205
211
  def __getitem__(self, key: Var | Any) -> Var:
206
212
  """Get an item from the object.
207
213
 
@@ -221,6 +227,29 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping):
221
227
  return self.__getattr__(key)
222
228
  return ObjectItemOperation.create(self, key).guess_type()
223
229
 
230
+ def get(self, key: Var | Any, default: Var | Any | None = None) -> Var:
231
+ """Get an item from the object.
232
+
233
+ Args:
234
+ key: The key to get from the object.
235
+ default: The default value if the key is not found.
236
+
237
+ Returns:
238
+ The item from the object.
239
+ """
240
+ from reflex.components.core.cond import cond
241
+
242
+ if default is None:
243
+ default = Var.create(None)
244
+
245
+ value = self.__getitem__(key) # pyright: ignore[reportUnknownVariableType,reportAttributeAccessIssue,reportUnknownMemberType]
246
+
247
+ return cond( # pyright: ignore[reportUnknownVariableType]
248
+ value,
249
+ value,
250
+ default,
251
+ )
252
+
224
253
  # NoReturn is used here to catch when key value is Any
225
254
  @overload
226
255
  def __getattr__( # pyright: ignore [reportOverlappingOverload]
reflex/vars/sequence.py CHANGED
@@ -1202,6 +1202,43 @@ def string_replace_operation(
1202
1202
  )
1203
1203
 
1204
1204
 
1205
+ @var_operation
1206
+ def get_decimal_string_separator_operation(value: NumberVar, separator: StringVar):
1207
+ """Get the decimal string separator.
1208
+
1209
+ Args:
1210
+ value: The number.
1211
+ separator: The separator.
1212
+
1213
+ Returns:
1214
+ The decimal string separator.
1215
+ """
1216
+ return var_operation_return(
1217
+ js_expression=f"({value}.toLocaleString('en-US').replaceAll(',', {separator}))",
1218
+ var_type=str,
1219
+ )
1220
+
1221
+
1222
+ @var_operation
1223
+ def get_decimal_string_operation(
1224
+ value: NumberVar, decimals: NumberVar, separator: StringVar
1225
+ ):
1226
+ """Get the decimal string of the number.
1227
+
1228
+ Args:
1229
+ value: The number.
1230
+ decimals: The number of decimals.
1231
+ separator: The separator.
1232
+
1233
+ Returns:
1234
+ The decimal string of the number.
1235
+ """
1236
+ return var_operation_return(
1237
+ js_expression=f"({value}.toLocaleString('en-US', ((decimals) => ({{minimumFractionDigits: decimals, maximumFractionDigits: decimals}}))({decimals})).replaceAll(',', {separator}))",
1238
+ var_type=str,
1239
+ )
1240
+
1241
+
1205
1242
  # Compile regex for finding reflex var tags.
1206
1243
  _decode_var_pattern_re = (
1207
1244
  rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"