reflex 0.6.0a4__py3-none-any.whl → 0.6.1a1__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 (56) hide show
  1. reflex/.templates/jinja/web/pages/_app.js.jinja2 +14 -0
  2. reflex/.templates/web/utils/state.js +67 -40
  3. reflex/app.py +9 -5
  4. reflex/app_mixins/lifespan.py +24 -6
  5. reflex/base.py +7 -13
  6. reflex/compiler/utils.py +17 -8
  7. reflex/components/base/bare.py +3 -1
  8. reflex/components/base/meta.py +5 -3
  9. reflex/components/component.py +19 -19
  10. reflex/components/core/cond.py +4 -4
  11. reflex/components/datadisplay/__init__.py +0 -1
  12. reflex/components/datadisplay/__init__.pyi +0 -1
  13. reflex/components/datadisplay/code.py +93 -106
  14. reflex/components/datadisplay/code.pyi +710 -53
  15. reflex/components/datadisplay/logo.py +22 -20
  16. reflex/components/dynamic.py +157 -0
  17. reflex/components/el/elements/forms.py +4 -1
  18. reflex/components/gridjs/datatable.py +2 -1
  19. reflex/components/markdown/markdown.py +10 -6
  20. reflex/components/markdown/markdown.pyi +3 -0
  21. reflex/components/radix/themes/components/progress.py +22 -0
  22. reflex/components/radix/themes/components/progress.pyi +2 -0
  23. reflex/components/radix/themes/components/segmented_control.py +3 -0
  24. reflex/components/radix/themes/components/segmented_control.pyi +2 -0
  25. reflex/components/radix/themes/layout/stack.py +1 -1
  26. reflex/components/recharts/cartesian.py +1 -1
  27. reflex/components/tags/iter_tag.py +5 -1
  28. reflex/config.py +2 -2
  29. reflex/constants/base.py +4 -1
  30. reflex/constants/installer.py +8 -1
  31. reflex/event.py +61 -20
  32. reflex/experimental/assets.py +3 -1
  33. reflex/experimental/client_state.py +5 -1
  34. reflex/experimental/misc.py +5 -3
  35. reflex/middleware/hydrate_middleware.py +1 -2
  36. reflex/page.py +10 -3
  37. reflex/reflex.py +20 -3
  38. reflex/state.py +105 -43
  39. reflex/style.py +12 -2
  40. reflex/testing.py +8 -4
  41. reflex/utils/console.py +1 -1
  42. reflex/utils/exceptions.py +4 -0
  43. reflex/utils/exec.py +170 -18
  44. reflex/utils/format.py +6 -44
  45. reflex/utils/path_ops.py +36 -1
  46. reflex/utils/prerequisites.py +42 -14
  47. reflex/utils/serializers.py +7 -46
  48. reflex/utils/types.py +17 -2
  49. reflex/vars/base.py +303 -43
  50. reflex/vars/number.py +3 -0
  51. reflex/vars/sequence.py +43 -0
  52. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/METADATA +2 -3
  53. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/RECORD +56 -55
  54. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/LICENSE +0 -0
  55. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/WHEEL +0 -0
  56. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/entry_points.txt +0 -0
reflex/utils/exec.py CHANGED
@@ -16,6 +16,7 @@ import psutil
16
16
 
17
17
  from reflex import constants
18
18
  from reflex.config import get_config
19
+ from reflex.constants.base import LogLevel
19
20
  from reflex.utils import console, path_ops
20
21
  from reflex.utils.prerequisites import get_web_dir
21
22
 
@@ -60,6 +61,13 @@ def kill(proc_pid: int):
60
61
  process.kill()
61
62
 
62
63
 
64
+ def notify_backend():
65
+ """Output a string notifying where the backend is running."""
66
+ console.print(
67
+ f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
68
+ )
69
+
70
+
63
71
  # run_process_and_launch_url is assumed to be used
64
72
  # only to launch the frontend
65
73
  # If this is not the case, might have to change the logic
@@ -103,9 +111,7 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True):
103
111
  f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
104
112
  )
105
113
  if backend_present:
106
- console.print(
107
- f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
108
- )
114
+ notify_backend()
109
115
  first_run = False
110
116
  else:
111
117
  console.print("New packages detected: Updating app...")
@@ -172,10 +178,42 @@ def run_frontend_prod(root: Path, port: str, backend_present=True):
172
178
  )
173
179
 
174
180
 
181
+ def should_use_granian():
182
+ """Whether to use Granian for backend.
183
+
184
+ Returns:
185
+ True if Granian should be used.
186
+ """
187
+ return os.getenv("REFLEX_USE_GRANIAN", "0") == "1"
188
+
189
+
190
+ def get_app_module():
191
+ """Get the app module for the backend.
192
+
193
+ Returns:
194
+ The app module for the backend.
195
+ """
196
+ return f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
197
+
198
+
199
+ def get_granian_target():
200
+ """Get the Granian target for the backend.
201
+
202
+ Returns:
203
+ The Granian target for the backend.
204
+ """
205
+ import reflex
206
+
207
+ app_module_path = Path(reflex.__file__).parent / "app_module_for_backend.py"
208
+
209
+ return f"{str(app_module_path)}:{constants.CompileVars.APP}.{constants.CompileVars.API}"
210
+
211
+
175
212
  def run_backend(
176
213
  host: str,
177
214
  port: int,
178
215
  loglevel: constants.LogLevel = constants.LogLevel.ERROR,
216
+ frontend_present: bool = False,
179
217
  ):
180
218
  """Run the backend.
181
219
 
@@ -183,25 +221,82 @@ def run_backend(
183
221
  host: The app host
184
222
  port: The app port
185
223
  loglevel: The log level.
224
+ frontend_present: Whether the frontend is present.
186
225
  """
187
- import uvicorn
188
-
189
- config = get_config()
190
- app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
191
-
192
226
  web_dir = get_web_dir()
193
227
  # Create a .nocompile file to skip compile for backend.
194
228
  if web_dir.exists():
195
229
  (web_dir / constants.NOCOMPILE_FILE).touch()
196
230
 
231
+ if not frontend_present:
232
+ notify_backend()
233
+
197
234
  # Run the backend in development mode.
235
+ if should_use_granian():
236
+ run_granian_backend(host, port, loglevel)
237
+ else:
238
+ run_uvicorn_backend(host, port, loglevel)
239
+
240
+
241
+ def run_uvicorn_backend(host, port, loglevel: LogLevel):
242
+ """Run the backend in development mode using Uvicorn.
243
+
244
+ Args:
245
+ host: The app host
246
+ port: The app port
247
+ loglevel: The log level.
248
+ """
249
+ import uvicorn
250
+
198
251
  uvicorn.run(
199
- app=f"{app_module}.{constants.CompileVars.API}",
252
+ app=f"{get_app_module()}.{constants.CompileVars.API}",
200
253
  host=host,
201
254
  port=port,
202
255
  log_level=loglevel.value,
203
256
  reload=True,
204
- reload_dirs=[config.app_name],
257
+ reload_dirs=[get_config().app_name],
258
+ )
259
+
260
+
261
+ def run_granian_backend(host, port, loglevel: LogLevel):
262
+ """Run the backend in development mode using Granian.
263
+
264
+ Args:
265
+ host: The app host
266
+ port: The app port
267
+ loglevel: The log level.
268
+ """
269
+ console.debug("Using Granian for backend")
270
+ try:
271
+ from granian import Granian # type: ignore
272
+ from granian.constants import Interfaces # type: ignore
273
+ from granian.log import LogLevels # type: ignore
274
+
275
+ Granian(
276
+ target=get_granian_target(),
277
+ address=host,
278
+ port=port,
279
+ interface=Interfaces.ASGI,
280
+ log_level=LogLevels(loglevel.value),
281
+ reload=True,
282
+ reload_paths=[Path(get_config().app_name)],
283
+ reload_ignore_dirs=[".web"],
284
+ ).serve()
285
+ except ImportError:
286
+ console.error(
287
+ 'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)'
288
+ )
289
+ os._exit(1)
290
+
291
+
292
+ def _get_backend_workers():
293
+ from reflex.utils import processes
294
+
295
+ config = get_config()
296
+ return (
297
+ processes.get_num_workers()
298
+ if not config.gunicorn_workers
299
+ else config.gunicorn_workers
205
300
  )
206
301
 
207
302
 
@@ -209,9 +304,28 @@ def run_backend_prod(
209
304
  host: str,
210
305
  port: int,
211
306
  loglevel: constants.LogLevel = constants.LogLevel.ERROR,
307
+ frontend_present: bool = False,
212
308
  ):
213
309
  """Run the backend.
214
310
 
311
+ Args:
312
+ host: The app host
313
+ port: The app port
314
+ loglevel: The log level.
315
+ frontend_present: Whether the frontend is present.
316
+ """
317
+ if not frontend_present:
318
+ notify_backend()
319
+
320
+ if should_use_granian():
321
+ run_granian_backend_prod(host, port, loglevel)
322
+ else:
323
+ run_uvicorn_backend_prod(host, port, loglevel)
324
+
325
+
326
+ def run_uvicorn_backend_prod(host, port, loglevel):
327
+ """Run the backend in production mode using Uvicorn.
328
+
215
329
  Args:
216
330
  host: The app host
217
331
  port: The app port
@@ -220,14 +334,11 @@ def run_backend_prod(
220
334
  from reflex.utils import processes
221
335
 
222
336
  config = get_config()
223
- num_workers = (
224
- processes.get_num_workers()
225
- if not config.gunicorn_workers
226
- else config.gunicorn_workers
227
- )
337
+
338
+ app_module = get_app_module()
339
+
228
340
  RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split()
229
341
  RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split()
230
- app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
231
342
  command = (
232
343
  [
233
344
  *RUN_BACKEND_PROD_WINDOWS,
@@ -243,7 +354,7 @@ def run_backend_prod(
243
354
  "--bind",
244
355
  f"{host}:{port}",
245
356
  "--threads",
246
- str(num_workers),
357
+ str(_get_backend_workers()),
247
358
  f"{app_module}()",
248
359
  ]
249
360
  )
@@ -252,7 +363,7 @@ def run_backend_prod(
252
363
  "--log-level",
253
364
  loglevel.value,
254
365
  "--workers",
255
- str(num_workers),
366
+ str(_get_backend_workers()),
256
367
  ]
257
368
  processes.new_process(
258
369
  command,
@@ -262,6 +373,47 @@ def run_backend_prod(
262
373
  )
263
374
 
264
375
 
376
+ def run_granian_backend_prod(host, port, loglevel):
377
+ """Run the backend in production mode using Granian.
378
+
379
+ Args:
380
+ host: The app host
381
+ port: The app port
382
+ loglevel: The log level.
383
+ """
384
+ from reflex.utils import processes
385
+
386
+ try:
387
+ from granian.constants import Interfaces # type: ignore
388
+
389
+ command = [
390
+ "granian",
391
+ "--workers",
392
+ str(_get_backend_workers()),
393
+ "--log-level",
394
+ "critical",
395
+ "--host",
396
+ host,
397
+ "--port",
398
+ str(port),
399
+ "--interface",
400
+ str(Interfaces.ASGI),
401
+ get_granian_target(),
402
+ ]
403
+ processes.new_process(
404
+ command,
405
+ run=True,
406
+ show_logs=True,
407
+ env={
408
+ constants.SKIP_COMPILE_ENV_VAR: "yes"
409
+ }, # skip compile for prod backend
410
+ )
411
+ except ImportError:
412
+ console.error(
413
+ 'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)'
414
+ )
415
+
416
+
265
417
  def output_system_info():
266
418
  """Show system information if the loglevel is in DEBUG."""
267
419
  if console._LOG_LEVEL > constants.LogLevel.DEBUG:
reflex/utils/format.py CHANGED
@@ -9,7 +9,7 @@ import re
9
9
  from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
10
10
 
11
11
  from reflex import constants
12
- from reflex.utils import exceptions, types
12
+ from reflex.utils import exceptions
13
13
  from reflex.utils.console import deprecate
14
14
 
15
15
  if TYPE_CHECKING:
@@ -345,6 +345,7 @@ def format_prop(
345
345
  Raises:
346
346
  exceptions.InvalidStylePropError: If the style prop value is not a valid type.
347
347
  TypeError: If the prop is not valid.
348
+ ValueError: If the prop is not a string.
348
349
  """
349
350
  # import here to avoid circular import.
350
351
  from reflex.event import EventChain
@@ -391,7 +392,8 @@ def format_prop(
391
392
  raise TypeError(f"Could not format prop: {prop} of type {type(prop)}") from e
392
393
 
393
394
  # Wrap the variable in braces.
394
- assert isinstance(prop, str), "The prop must be a string."
395
+ if not isinstance(prop, str):
396
+ raise ValueError(f"Invalid prop: {prop}. Expected a string.")
395
397
  return wrap(prop, "{", check_first=False)
396
398
 
397
399
 
@@ -624,48 +626,6 @@ def format_query_params(router_data: dict[str, Any]) -> dict[str, str]:
624
626
  return {k.replace("-", "_"): v for k, v in params.items()}
625
627
 
626
628
 
627
- def format_state(value: Any, key: Optional[str] = None) -> Any:
628
- """Recursively format values in the given state.
629
-
630
- Args:
631
- value: The state to format.
632
- key: The key associated with the value (optional).
633
-
634
- Returns:
635
- The formatted state.
636
-
637
- Raises:
638
- TypeError: If the given value is not a valid state.
639
- """
640
- from reflex.utils import serializers
641
-
642
- # Handle dicts.
643
- if isinstance(value, dict):
644
- return {k: format_state(v, k) for k, v in value.items()}
645
-
646
- # Handle lists, sets, typles.
647
- if isinstance(value, types.StateIterBases):
648
- return [format_state(v) for v in value]
649
-
650
- # Return state vars as is.
651
- if isinstance(value, types.StateBases):
652
- return value
653
-
654
- # Serialize the value.
655
- serialized = serializers.serialize(value)
656
- if serialized is not None:
657
- return serialized
658
-
659
- if key is None:
660
- raise TypeError(
661
- f"No JSON serializer found for var {value} of type {type(value)}."
662
- )
663
- else:
664
- raise TypeError(
665
- f"No JSON serializer found for State Var '{key}' of value {value} of type {type(value)}."
666
- )
667
-
668
-
669
629
  def format_state_name(state_name: str) -> str:
670
630
  """Format a state name, replacing dots with double underscore.
671
631
 
@@ -704,6 +664,8 @@ def format_library_name(library_fullname: str):
704
664
  Returns:
705
665
  The name without the @version if it was part of the name
706
666
  """
667
+ if library_fullname.startswith("https://"):
668
+ return library_fullname
707
669
  lib, at, version = library_fullname.rpartition("@")
708
670
  if not lib:
709
671
  lib = at + version
reflex/utils/path_ops.py CHANGED
@@ -129,6 +129,41 @@ def which(program: str | Path) -> str | Path | None:
129
129
  return shutil.which(str(program))
130
130
 
131
131
 
132
+ def use_system_install(var_name: str) -> bool:
133
+ """Check if the system install should be used.
134
+
135
+ Args:
136
+ var_name: The name of the environment variable.
137
+
138
+ Raises:
139
+ ValueError: If the variable name is invalid.
140
+
141
+ Returns:
142
+ Whether the associated env var should use the system install.
143
+ """
144
+ if not var_name.startswith("REFLEX_USE_SYSTEM_"):
145
+ raise ValueError("Invalid system install variable name.")
146
+ return os.getenv(var_name, "").lower() in ["true", "1", "yes"]
147
+
148
+
149
+ def use_system_node() -> bool:
150
+ """Check if the system node should be used.
151
+
152
+ Returns:
153
+ Whether the system node should be used.
154
+ """
155
+ return use_system_install(constants.Node.USE_SYSTEM_VAR)
156
+
157
+
158
+ def use_system_bun() -> bool:
159
+ """Check if the system bun should be used.
160
+
161
+ Returns:
162
+ Whether the system bun should be used.
163
+ """
164
+ return use_system_install(constants.Bun.USE_SYSTEM_VAR)
165
+
166
+
132
167
  def get_node_bin_path() -> str | None:
133
168
  """Get the node binary dir path.
134
169
 
@@ -149,7 +184,7 @@ def get_node_path() -> str | None:
149
184
  The path to the node binary file.
150
185
  """
151
186
  node_path = Path(constants.Node.PATH)
152
- if not node_path.exists():
187
+ if use_system_node() or not node_path.exists():
153
188
  return str(which("node"))
154
189
  return str(node_path)
155
190
 
@@ -16,7 +16,7 @@ import shutil
16
16
  import stat
17
17
  import sys
18
18
  import tempfile
19
- import textwrap
19
+ import time
20
20
  import zipfile
21
21
  from datetime import datetime
22
22
  from fileinput import FileInput
@@ -36,6 +36,7 @@ from reflex import constants, model
36
36
  from reflex.compiler import templates
37
37
  from reflex.config import Config, get_config
38
38
  from reflex.utils import console, net, path_ops, processes
39
+ from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs
39
40
  from reflex.utils.format import format_library_name
40
41
  from reflex.utils.registry import _get_best_registry
41
42
 
@@ -142,7 +143,7 @@ def check_node_version() -> bool:
142
143
  # Compare the version numbers
143
144
  return (
144
145
  current_version >= version.parse(constants.Node.MIN_VERSION)
145
- if constants.IS_WINDOWS
146
+ if constants.IS_WINDOWS or path_ops.use_system_node()
146
147
  else current_version == version.parse(constants.Node.VERSION)
147
148
  )
148
149
  return False
@@ -1033,6 +1034,8 @@ def validate_bun():
1033
1034
  # if a custom bun path is provided, make sure its valid
1034
1035
  # This is specific to non-FHS OS
1035
1036
  bun_path = get_config().bun_path
1037
+ if path_ops.use_system_bun():
1038
+ bun_path = path_ops.which("bun")
1036
1039
  if bun_path != constants.Bun.DEFAULT_PATH:
1037
1040
  console.info(f"Using custom Bun path: {bun_path}")
1038
1041
  bun_version = get_bun_version()
@@ -1455,19 +1458,37 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
1455
1458
  Args:
1456
1459
  app_name: The name of the app.
1457
1460
  generation_hash: The generation hash from reflex.build.
1461
+
1462
+ Raises:
1463
+ GeneratedCodeHasNoFunctionDefs: If the fetched code has no function definitions
1464
+ (the refactored reflex code is expected to have at least one root function defined).
1458
1465
  """
1459
1466
  # Download the reflex code for the generation.
1460
- resp = net.get(
1461
- constants.Templates.REFLEX_BUILD_CODE_URL.format(
1462
- generation_hash=generation_hash
1467
+ url = constants.Templates.REFLEX_BUILD_CODE_URL.format(
1468
+ generation_hash=generation_hash
1469
+ )
1470
+ resp = net.get(url)
1471
+ while resp.status_code == httpx.codes.SERVICE_UNAVAILABLE:
1472
+ console.debug("Waiting for the code to be generated...")
1473
+ time.sleep(1)
1474
+ resp = net.get(url)
1475
+ resp.raise_for_status()
1476
+
1477
+ # Determine the name of the last function, which renders the generated code.
1478
+ defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
1479
+ if not defined_funcs:
1480
+ raise GeneratedCodeHasNoFunctionDefs(
1481
+ f"No function definitions found in generated code from {url!r}."
1463
1482
  )
1464
- ).raise_for_status()
1483
+ render_func_name = defined_funcs[-1]
1465
1484
 
1466
1485
  def replace_content(_match):
1467
1486
  return "\n".join(
1468
1487
  [
1469
- "def index() -> rx.Component:",
1470
- textwrap.indent("return " + resp.text, " "),
1488
+ resp.text,
1489
+ "",
1490
+ "" "def index() -> rx.Component:",
1491
+ f" return {render_func_name}()",
1471
1492
  "",
1472
1493
  "",
1473
1494
  ],
@@ -1475,13 +1496,20 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
1475
1496
 
1476
1497
  main_module_path = Path(app_name, app_name + constants.Ext.PY)
1477
1498
  main_module_code = main_module_path.read_text()
1478
- main_module_path.write_text(
1479
- re.sub(
1480
- r"def index\(\).*:\n([^\n]\s+.*\n+)+",
1481
- replace_content,
1482
- main_module_code,
1483
- )
1499
+
1500
+ main_module_code = re.sub(
1501
+ r"def index\(\).*:\n([^\n]\s+.*\n+)+",
1502
+ replace_content,
1503
+ main_module_code,
1504
+ )
1505
+ # Make the app use light mode until flexgen enforces the conversion of
1506
+ # tailwind colors to radix colors.
1507
+ main_module_code = re.sub(
1508
+ r"app\s*=\s*rx\.App\(\s*\)",
1509
+ 'app = rx.App(theme=rx.theme(color_mode="light"))',
1510
+ main_module_code,
1484
1511
  )
1512
+ main_module_path.write_text(main_module_code)
1485
1513
 
1486
1514
 
1487
1515
  def format_address_width(address_width) -> int | None:
@@ -12,7 +12,6 @@ from pathlib import Path
12
12
  from typing import (
13
13
  Any,
14
14
  Callable,
15
- Dict,
16
15
  List,
17
16
  Literal,
18
17
  Optional,
@@ -126,7 +125,8 @@ def serialize(
126
125
  # If there is no serializer, return None.
127
126
  if serializer is None:
128
127
  if dataclasses.is_dataclass(value) and not isinstance(value, type):
129
- return serialize(dataclasses.asdict(value))
128
+ return {k.name: getattr(value, k.name) for k in dataclasses.fields(value)}
129
+
130
130
  if get_type:
131
131
  return None, None
132
132
  return None
@@ -214,32 +214,6 @@ def serialize_type(value: type) -> str:
214
214
  return value.__name__
215
215
 
216
216
 
217
- @serializer
218
- def serialize_str(value: str) -> str:
219
- """Serialize a string.
220
-
221
- Args:
222
- value: The string to serialize.
223
-
224
- Returns:
225
- The serialized string.
226
- """
227
- return value
228
-
229
-
230
- @serializer
231
- def serialize_primitive(value: Union[bool, int, float, None]):
232
- """Serialize a primitive type.
233
-
234
- Args:
235
- value: The number/bool/None to serialize.
236
-
237
- Returns:
238
- The serialized number/bool/None.
239
- """
240
- return value
241
-
242
-
243
217
  @serializer
244
218
  def serialize_base(value: Base) -> dict:
245
219
  """Serialize a Base instance.
@@ -250,33 +224,20 @@ def serialize_base(value: Base) -> dict:
250
224
  Returns:
251
225
  The serialized Base.
252
226
  """
253
- return {k: serialize(v) for k, v in value.dict().items() if not callable(v)}
227
+ return {k: v for k, v in value.dict().items() if not callable(v)}
254
228
 
255
229
 
256
230
  @serializer
257
- def serialize_list(value: Union[List, Tuple, Set]) -> list:
258
- """Serialize a list to a JSON string.
231
+ def serialize_set(value: Set) -> list:
232
+ """Serialize a set to a JSON serializable list.
259
233
 
260
234
  Args:
261
- value: The list to serialize.
235
+ value: The set to serialize.
262
236
 
263
237
  Returns:
264
238
  The serialized list.
265
239
  """
266
- return [serialize(item) for item in value]
267
-
268
-
269
- @serializer
270
- def serialize_dict(prop: Dict[str, Any]) -> dict:
271
- """Serialize a dictionary to a JSON string.
272
-
273
- Args:
274
- prop: The dictionary to serialize.
275
-
276
- Returns:
277
- The serialized dictionary.
278
- """
279
- return {k: serialize(v) for k, v in prop.items()}
240
+ return list(value)
280
241
 
281
242
 
282
243
  @serializer(to=str)
reflex/utils/types.py CHANGED
@@ -9,6 +9,7 @@ import sys
9
9
  import types
10
10
  from functools import cached_property, lru_cache, wraps
11
11
  from typing import (
12
+ TYPE_CHECKING,
12
13
  Any,
13
14
  Callable,
14
15
  ClassVar,
@@ -96,8 +97,22 @@ PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
96
97
  StateVar = Union[PrimitiveType, Base, None]
97
98
  StateIterVar = Union[list, set, tuple]
98
99
 
99
- # ArgsSpec = Callable[[Var], list[Var]]
100
- ArgsSpec = Callable
100
+ if TYPE_CHECKING:
101
+ from reflex.vars.base import Var
102
+
103
+ # ArgsSpec = Callable[[Var], list[Var]]
104
+ ArgsSpec = (
105
+ Callable[[], List[Var]]
106
+ | Callable[[Var], List[Var]]
107
+ | Callable[[Var, Var], List[Var]]
108
+ | Callable[[Var, Var, Var], List[Var]]
109
+ | Callable[[Var, Var, Var, Var], List[Var]]
110
+ | Callable[[Var, Var, Var, Var, Var], List[Var]]
111
+ | Callable[[Var, Var, Var, Var, Var, Var], List[Var]]
112
+ | Callable[[Var, Var, Var, Var, Var, Var, Var], List[Var]]
113
+ )
114
+ else:
115
+ ArgsSpec = Callable[..., List[Any]]
101
116
 
102
117
 
103
118
  PrimitiveToAnnotation = {