reflex 0.5.10a3__py3-none-any.whl → 0.6.0a1__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 (237) hide show
  1. reflex/.templates/jinja/web/pages/utils.js.jinja2 +4 -4
  2. reflex/.templates/jinja/web/utils/context.js.jinja2 +1 -1
  3. reflex/.templates/jinja/web/utils/theme.js.jinja2 +1 -1
  4. reflex/__init__.py +3 -2
  5. reflex/__init__.pyi +2 -2
  6. reflex/app.py +43 -9
  7. reflex/base.py +3 -2
  8. reflex/compiler/compiler.py +6 -6
  9. reflex/compiler/utils.py +5 -3
  10. reflex/components/base/app_wrap.py +2 -4
  11. reflex/components/base/app_wrap.pyi +17 -17
  12. reflex/components/base/bare.py +7 -4
  13. reflex/components/base/body.pyi +17 -17
  14. reflex/components/base/document.pyi +81 -81
  15. reflex/components/base/error_boundary.py +10 -8
  16. reflex/components/base/error_boundary.pyi +20 -19
  17. reflex/components/base/fragment.pyi +17 -17
  18. reflex/components/base/head.pyi +33 -33
  19. reflex/components/base/link.pyi +34 -33
  20. reflex/components/base/meta.pyi +65 -65
  21. reflex/components/base/script.py +2 -1
  22. reflex/components/base/script.pyi +21 -20
  23. reflex/components/component.py +116 -145
  24. reflex/components/core/banner.py +59 -60
  25. reflex/components/core/banner.pyi +86 -150
  26. reflex/components/core/client_side_routing.py +2 -1
  27. reflex/components/core/client_side_routing.pyi +34 -33
  28. reflex/components/core/clipboard.py +2 -2
  29. reflex/components/core/clipboard.pyi +19 -18
  30. reflex/components/core/cond.py +21 -44
  31. reflex/components/core/debounce.py +6 -8
  32. reflex/components/core/debounce.pyi +19 -18
  33. reflex/components/core/foreach.py +5 -14
  34. reflex/components/core/html.pyi +18 -17
  35. reflex/components/core/match.py +36 -43
  36. reflex/components/core/upload.py +32 -25
  37. reflex/components/core/upload.pyi +84 -73
  38. reflex/components/datadisplay/code.py +55 -28
  39. reflex/components/datadisplay/code.pyi +20 -17
  40. reflex/components/datadisplay/dataeditor.py +17 -11
  41. reflex/components/datadisplay/dataeditor.pyi +34 -33
  42. reflex/components/el/__init__.py +0 -1
  43. reflex/components/el/__init__.pyi +0 -11
  44. reflex/components/el/element.pyi +17 -17
  45. reflex/components/el/elements/__init__.py +1 -7
  46. reflex/components/el/elements/__init__.pyi +1 -15
  47. reflex/components/el/elements/base.pyi +18 -17
  48. reflex/components/el/elements/forms.py +24 -31
  49. reflex/components/el/elements/forms.pyi +237 -236
  50. reflex/components/el/elements/inline.pyi +450 -449
  51. reflex/components/el/elements/media.py +0 -21
  52. reflex/components/el/elements/media.pyi +338 -337
  53. reflex/components/el/elements/metadata.py +3 -2
  54. reflex/components/el/elements/metadata.pyi +98 -97
  55. reflex/components/el/elements/other.pyi +114 -113
  56. reflex/components/el/elements/scripts.pyi +50 -49
  57. reflex/components/el/elements/sectioning.pyi +242 -241
  58. reflex/components/el/elements/tables.pyi +162 -161
  59. reflex/components/el/elements/typography.pyi +242 -241
  60. reflex/components/gridjs/datatable.py +13 -14
  61. reflex/components/gridjs/datatable.pyi +34 -33
  62. reflex/components/lucide/icon.py +2 -126
  63. reflex/components/lucide/icon.pyi +34 -142
  64. reflex/components/markdown/markdown.py +30 -35
  65. reflex/components/markdown/markdown.pyi +29 -32
  66. reflex/components/moment/moment.pyi +19 -18
  67. reflex/components/next/base.pyi +17 -17
  68. reflex/components/next/image.py +0 -4
  69. reflex/components/next/image.pyi +20 -19
  70. reflex/components/next/link.pyi +18 -17
  71. reflex/components/next/video.pyi +18 -17
  72. reflex/components/plotly/plotly.py +16 -28
  73. reflex/components/plotly/plotly.pyi +36 -35
  74. reflex/components/props.py +21 -10
  75. reflex/components/radix/__init__.pyi +1 -1
  76. reflex/components/radix/primitives/__init__.pyi +0 -1
  77. reflex/components/radix/primitives/accordion.py +7 -8
  78. reflex/components/radix/primitives/accordion.pyi +117 -116
  79. reflex/components/radix/primitives/base.pyi +34 -33
  80. reflex/components/radix/primitives/drawer.pyi +169 -168
  81. reflex/components/radix/primitives/form.pyi +168 -167
  82. reflex/components/radix/primitives/progress.pyi +82 -81
  83. reflex/components/radix/primitives/slider.pyi +84 -83
  84. reflex/components/radix/themes/base.py +8 -4
  85. reflex/components/radix/themes/base.pyi +114 -113
  86. reflex/components/radix/themes/color_mode.py +12 -21
  87. reflex/components/radix/themes/color_mode.pyi +67 -67
  88. reflex/components/radix/themes/components/__init__.pyi +1 -0
  89. reflex/components/radix/themes/components/alert_dialog.pyi +118 -117
  90. reflex/components/radix/themes/components/aspect_ratio.pyi +18 -17
  91. reflex/components/radix/themes/components/avatar.pyi +18 -17
  92. reflex/components/radix/themes/components/badge.pyi +18 -17
  93. reflex/components/radix/themes/components/button.pyi +18 -17
  94. reflex/components/radix/themes/components/callout.pyi +82 -81
  95. reflex/components/radix/themes/components/card.pyi +18 -17
  96. reflex/components/radix/themes/components/checkbox.py +2 -3
  97. reflex/components/radix/themes/components/checkbox.pyi +53 -52
  98. reflex/components/radix/themes/components/checkbox_cards.pyi +34 -33
  99. reflex/components/radix/themes/components/checkbox_group.pyi +34 -33
  100. reflex/components/radix/themes/components/context_menu.pyi +140 -139
  101. reflex/components/radix/themes/components/data_list.py +5 -0
  102. reflex/components/radix/themes/components/data_list.pyi +71 -65
  103. reflex/components/radix/themes/components/dialog.pyi +121 -120
  104. reflex/components/radix/themes/components/dropdown_menu.pyi +142 -141
  105. reflex/components/radix/themes/components/hover_card.pyi +68 -67
  106. reflex/components/radix/themes/components/icon_button.py +2 -1
  107. reflex/components/radix/themes/components/icon_button.pyi +18 -17
  108. reflex/components/radix/themes/components/inset.pyi +18 -17
  109. reflex/components/radix/themes/components/popover.pyi +73 -72
  110. reflex/components/radix/themes/components/progress.pyi +18 -17
  111. reflex/components/radix/themes/components/radio.pyi +18 -17
  112. reflex/components/radix/themes/components/radio_cards.pyi +35 -34
  113. reflex/components/radix/themes/components/radio_group.py +35 -31
  114. reflex/components/radix/themes/components/radio_group.pyi +73 -66
  115. reflex/components/radix/themes/components/scroll_area.pyi +18 -17
  116. reflex/components/radix/themes/components/segmented_control.pyi +35 -34
  117. reflex/components/radix/themes/components/select.py +2 -1
  118. reflex/components/radix/themes/components/select.pyi +155 -154
  119. reflex/components/radix/themes/components/separator.py +2 -3
  120. reflex/components/radix/themes/components/separator.pyi +18 -17
  121. reflex/components/radix/themes/components/skeleton.pyi +18 -17
  122. reflex/components/radix/themes/components/slider.py +2 -1
  123. reflex/components/radix/themes/components/slider.pyi +20 -19
  124. reflex/components/radix/themes/components/spinner.pyi +18 -17
  125. reflex/components/radix/themes/components/switch.pyi +19 -18
  126. reflex/components/radix/themes/components/table.pyi +114 -113
  127. reflex/components/radix/themes/components/tabs.pyi +84 -83
  128. reflex/components/radix/themes/components/text_area.pyi +21 -20
  129. reflex/components/radix/themes/components/text_field.py +0 -79
  130. reflex/components/radix/themes/components/text_field.pyi +57 -63
  131. reflex/components/radix/themes/components/tooltip.pyi +21 -20
  132. reflex/components/radix/themes/layout/base.pyi +18 -17
  133. reflex/components/radix/themes/layout/box.pyi +18 -17
  134. reflex/components/radix/themes/layout/center.pyi +18 -17
  135. reflex/components/radix/themes/layout/container.py +2 -3
  136. reflex/components/radix/themes/layout/container.pyi +18 -17
  137. reflex/components/radix/themes/layout/flex.pyi +18 -17
  138. reflex/components/radix/themes/layout/grid.pyi +18 -17
  139. reflex/components/radix/themes/layout/list.py +5 -4
  140. reflex/components/radix/themes/layout/list.pyi +86 -85
  141. reflex/components/radix/themes/layout/section.py +2 -3
  142. reflex/components/radix/themes/layout/section.pyi +18 -17
  143. reflex/components/radix/themes/layout/spacer.pyi +18 -17
  144. reflex/components/radix/themes/layout/stack.pyi +50 -49
  145. reflex/components/radix/themes/typography/blockquote.pyi +18 -17
  146. reflex/components/radix/themes/typography/code.pyi +18 -17
  147. reflex/components/radix/themes/typography/heading.pyi +18 -17
  148. reflex/components/radix/themes/typography/link.pyi +18 -17
  149. reflex/components/radix/themes/typography/text.pyi +114 -113
  150. reflex/components/react_player/audio.pyi +34 -33
  151. reflex/components/react_player/react_player.pyi +34 -33
  152. reflex/components/react_player/video.pyi +34 -33
  153. reflex/components/recharts/cartesian.py +23 -19
  154. reflex/components/recharts/cartesian.pyi +297 -296
  155. reflex/components/recharts/charts.py +6 -5
  156. reflex/components/recharts/charts.pyi +179 -178
  157. reflex/components/recharts/general.py +8 -7
  158. reflex/components/recharts/general.pyi +82 -81
  159. reflex/components/recharts/polar.py +14 -13
  160. reflex/components/recharts/polar.pyi +76 -75
  161. reflex/components/recharts/recharts.pyi +33 -33
  162. reflex/components/sonner/toast.py +30 -33
  163. reflex/components/sonner/toast.pyi +27 -25
  164. reflex/components/suneditor/editor.py +2 -1
  165. reflex/components/suneditor/editor.pyi +27 -26
  166. reflex/components/tags/iter_tag.py +16 -16
  167. reflex/components/tags/tag.py +8 -10
  168. reflex/constants/base.py +3 -1
  169. reflex/constants/event.py +1 -0
  170. reflex/event.py +89 -79
  171. reflex/experimental/__init__.py +25 -6
  172. reflex/experimental/client_state.py +34 -58
  173. reflex/experimental/hooks.py +13 -18
  174. reflex/experimental/layout.py +5 -5
  175. reflex/experimental/layout.pyi +84 -83
  176. reflex/{experimental/vars → ivars}/__init__.py +0 -1
  177. reflex/ivars/base.py +2180 -0
  178. reflex/ivars/function.py +200 -0
  179. reflex/ivars/number.py +1137 -0
  180. reflex/ivars/object.py +564 -0
  181. reflex/ivars/sequence.py +1601 -0
  182. reflex/model.py +22 -0
  183. reflex/reflex.py +4 -0
  184. reflex/state.py +388 -73
  185. reflex/style.py +52 -34
  186. reflex/testing.py +8 -3
  187. reflex/utils/exceptions.py +12 -0
  188. reflex/utils/exec.py +0 -14
  189. reflex/utils/format.py +74 -223
  190. reflex/utils/net.py +43 -0
  191. reflex/utils/path_ops.py +13 -1
  192. reflex/utils/prerequisites.py +46 -26
  193. reflex/utils/pyi_generator.py +5 -4
  194. reflex/utils/serializers.py +13 -31
  195. reflex/utils/types.py +44 -9
  196. reflex/vars.py +127 -2230
  197. {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/METADATA +4 -6
  198. reflex-0.6.0a1.dist-info/RECORD +384 -0
  199. reflex/.templates/apps/demo/.gitignore +0 -4
  200. reflex/.templates/apps/demo/assets/favicon.ico +0 -0
  201. reflex/.templates/apps/demo/assets/github.svg +0 -10
  202. reflex/.templates/apps/demo/assets/icon.svg +0 -37
  203. reflex/.templates/apps/demo/assets/logo.svg +0 -68
  204. reflex/.templates/apps/demo/assets/paneleft.svg +0 -13
  205. reflex/.templates/apps/demo/code/__init__.py +0 -1
  206. reflex/.templates/apps/demo/code/demo.py +0 -127
  207. reflex/.templates/apps/demo/code/pages/__init__.py +0 -7
  208. reflex/.templates/apps/demo/code/pages/chatapp.py +0 -31
  209. reflex/.templates/apps/demo/code/pages/datatable.py +0 -360
  210. reflex/.templates/apps/demo/code/pages/forms.py +0 -257
  211. reflex/.templates/apps/demo/code/pages/graphing.py +0 -253
  212. reflex/.templates/apps/demo/code/pages/home.py +0 -56
  213. reflex/.templates/apps/demo/code/sidebar.py +0 -178
  214. reflex/.templates/apps/demo/code/state.py +0 -22
  215. reflex/.templates/apps/demo/code/states/form_state.py +0 -40
  216. reflex/.templates/apps/demo/code/states/pie_state.py +0 -47
  217. reflex/.templates/apps/demo/code/styles.py +0 -68
  218. reflex/.templates/apps/demo/code/webui/__init__.py +0 -0
  219. reflex/.templates/apps/demo/code/webui/components/__init__.py +0 -4
  220. reflex/.templates/apps/demo/code/webui/components/chat.py +0 -118
  221. reflex/.templates/apps/demo/code/webui/components/loading_icon.py +0 -19
  222. reflex/.templates/apps/demo/code/webui/components/modal.py +0 -56
  223. reflex/.templates/apps/demo/code/webui/components/navbar.py +0 -70
  224. reflex/.templates/apps/demo/code/webui/components/sidebar.py +0 -66
  225. reflex/.templates/apps/demo/code/webui/state.py +0 -146
  226. reflex/.templates/apps/demo/code/webui/styles.py +0 -88
  227. reflex/experimental/vars/base.py +0 -583
  228. reflex/experimental/vars/function.py +0 -290
  229. reflex/experimental/vars/number.py +0 -1458
  230. reflex/experimental/vars/object.py +0 -804
  231. reflex/experimental/vars/sequence.py +0 -1764
  232. reflex/utils/watch.py +0 -96
  233. reflex/vars.pyi +0 -218
  234. reflex-0.5.10a3.dist-info/RECORD +0 -413
  235. {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/LICENSE +0 -0
  236. {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/WHEEL +0 -0
  237. {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/entry_points.txt +0 -0
reflex/utils/format.py CHANGED
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
10
10
 
11
11
  from reflex import constants
12
12
  from reflex.utils import exceptions, types
13
- from reflex.vars import BaseVar, Var
13
+ from reflex.vars import Var
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from reflex.components.component import ComponentStyle
@@ -263,32 +263,6 @@ def format_string(string: str) -> str:
263
263
  return _wrap_js_string(_escape_js_string(string))
264
264
 
265
265
 
266
- def format_f_string_prop(prop: BaseVar) -> str:
267
- """Format the string in a given prop as an f-string.
268
-
269
- Args:
270
- prop: The prop to format.
271
-
272
- Returns:
273
- The formatted string.
274
- """
275
- s = prop._var_full_name
276
- var_data = prop._var_data
277
- interps = var_data.interpolations if var_data else []
278
- parts: List[str] = []
279
-
280
- if interps:
281
- for i, (start, end) in enumerate(interps):
282
- prev_end = interps[i - 1][1] if i > 0 else 0
283
- parts.append(_escape_js_string(s[prev_end:start]))
284
- parts.append(s[start:end])
285
- parts.append(_escape_js_string(s[interps[-1][1] :]))
286
- else:
287
- parts.append(_escape_js_string(s))
288
-
289
- return _wrap_js_string("".join(parts))
290
-
291
-
292
266
  def format_var(var: Var) -> str:
293
267
  """Format the given Var as a javascript value.
294
268
 
@@ -298,13 +272,7 @@ def format_var(var: Var) -> str:
298
272
  Returns:
299
273
  The formatted Var.
300
274
  """
301
- if not var._var_is_local or var._var_is_string:
302
- return str(var)
303
- if types._issubclass(var._var_type, str):
304
- return format_string(var._var_full_name)
305
- if is_wrapped(var._var_full_name, "{"):
306
- return var._var_full_name
307
- return json_dumps(var._var_full_name)
275
+ return str(var)
308
276
 
309
277
 
310
278
  def format_route(route: str, format_case=True) -> str:
@@ -329,46 +297,11 @@ def format_route(route: str, format_case=True) -> str:
329
297
  return route
330
298
 
331
299
 
332
- def format_cond(
333
- cond: str | Var,
334
- true_value: str | Var,
335
- false_value: str | Var = '""',
336
- is_prop=False,
300
+ def format_match(
301
+ cond: str | ImmutableVar,
302
+ match_cases: List[List[ImmutableVar]],
303
+ default: ImmutableVar,
337
304
  ) -> str:
338
- """Format a conditional expression.
339
-
340
- Args:
341
- cond: The cond.
342
- true_value: The value to return if the cond is true.
343
- false_value: The value to return if the cond is false.
344
- is_prop: Whether the cond is a prop
345
-
346
- Returns:
347
- The formatted conditional expression.
348
- """
349
- # Use Python truthiness.
350
- cond = f"isTrue({cond})"
351
-
352
- def create_var(cond_part):
353
- return Var.create_safe(cond_part, _var_is_string=isinstance(cond_part, str))
354
-
355
- # Format prop conds.
356
- if is_prop:
357
- true_value = create_var(true_value)
358
- prop1 = true_value._replace(
359
- _var_is_local=True,
360
- )
361
-
362
- false_value = create_var(false_value)
363
- prop2 = false_value._replace(_var_is_local=True)
364
- # unwrap '{}' to avoid f-string semantics for Var
365
- return f"{cond} ? {prop1._var_name_unwrapped} : {prop2._var_name_unwrapped}"
366
-
367
- # Format component conds.
368
- return wrap(f"{cond} ? {true_value} : {false_value}", "{")
369
-
370
-
371
- def format_match(cond: str | Var, match_cases: List[BaseVar], default: Var) -> str:
372
305
  """Format a match expression whose return type is a Var.
373
306
 
374
307
  Args:
@@ -387,24 +320,19 @@ def format_match(cond: str | Var, match_cases: List[BaseVar], default: Var) -> s
387
320
  return_value = case[-1]
388
321
 
389
322
  case_conditions = " ".join(
390
- [
391
- f"case JSON.stringify({condition._var_name_unwrapped}):"
392
- for condition in conditions
393
- ]
394
- )
395
- case_code = (
396
- f"{case_conditions} return ({return_value._var_name_unwrapped}); break;"
323
+ [f"case JSON.stringify({str(condition)}):" for condition in conditions]
397
324
  )
325
+ case_code = f"{case_conditions} return ({str(return_value)}); break;"
398
326
  switch_code += case_code
399
327
 
400
- switch_code += f"default: return ({default._var_name_unwrapped}); break;"
328
+ switch_code += f"default: return ({str(default)}); break;"
401
329
  switch_code += "};})()"
402
330
 
403
331
  return switch_code
404
332
 
405
333
 
406
334
  def format_prop(
407
- prop: Union[Var, EventChain, ComponentStyle, str],
335
+ prop: Union[ImmutableVar, EventChain, ComponentStyle, str],
408
336
  ) -> Union[int, float, str]:
409
337
  """Format a prop.
410
338
 
@@ -420,21 +348,16 @@ def format_prop(
420
348
  """
421
349
  # import here to avoid circular import.
422
350
  from reflex.event import EventChain
351
+ from reflex.ivars import ImmutableVar
423
352
  from reflex.utils import serializers
424
353
 
425
354
  try:
426
355
  # Handle var props.
427
- if isinstance(prop, Var):
428
- if not prop._var_is_local or prop._var_is_string:
429
- return str(prop)
430
- if isinstance(prop, BaseVar) and types._issubclass(prop._var_type, str):
431
- if prop._var_data and prop._var_data.interpolations:
432
- return format_f_string_prop(prop)
433
- return format_string(prop._var_full_name)
434
- prop = prop._var_full_name
356
+ if isinstance(prop, ImmutableVar):
357
+ return str(prop)
435
358
 
436
359
  # Handle event props.
437
- elif isinstance(prop, EventChain):
360
+ if isinstance(prop, EventChain):
438
361
  sig = inspect.signature(prop.args_spec) # type: ignore
439
362
  if sig.parameters:
440
363
  arg_def = ",".join(f"_{p}" for p in sig.parameters)
@@ -483,11 +406,26 @@ def format_props(*single_props, **key_value_props) -> list[str]:
483
406
  The formatted props list.
484
407
  """
485
408
  # Format all the props.
409
+ from reflex.ivars.base import ImmutableVar, LiteralVar
410
+
486
411
  return [
487
- f"{name}={format_prop(prop)}"
412
+ (
413
+ f"{name}={format_prop(prop)}"
414
+ if isinstance(prop, ImmutableVar) and not isinstance(prop, ImmutableVar)
415
+ else (
416
+ f"{name}={{{format_prop(prop if isinstance(prop, ImmutableVar) else LiteralVar.create(prop))}}}"
417
+ )
418
+ )
488
419
  for name, prop in sorted(key_value_props.items())
489
420
  if prop is not None
490
- ] + [str(prop) for prop in single_props]
421
+ ] + [
422
+ (
423
+ str(prop)
424
+ if isinstance(prop, ImmutableVar) and not isinstance(prop, ImmutableVar)
425
+ else f"{str(LiteralVar.create(prop))}"
426
+ )
427
+ for prop in single_props
428
+ ]
491
429
 
492
430
 
493
431
  def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
@@ -502,13 +440,13 @@ def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
502
440
  # Get the class that defines the event handler.
503
441
  parts = handler.fn.__qualname__.split(".")
504
442
 
505
- # If there's no enclosing class, just return the function name.
506
- if len(parts) == 1:
507
- return ("", parts[-1])
508
-
509
443
  # Get the state full name
510
444
  state_full_name = handler.state_full_name
511
445
 
446
+ # If there's no enclosing class, just return the function name.
447
+ if not state_full_name:
448
+ return ("", parts[-1])
449
+
512
450
  # Get the function name
513
451
  name = parts[-1]
514
452
 
@@ -555,7 +493,7 @@ def format_event(event_spec: EventSpec) -> str:
555
493
  "`",
556
494
  )
557
495
  if val._var_is_string
558
- else val._var_full_name
496
+ else str(val)
559
497
  ),
560
498
  )
561
499
  )
@@ -572,54 +510,20 @@ def format_event(event_spec: EventSpec) -> str:
572
510
  return f"Event({', '.join(event_args)})"
573
511
 
574
512
 
575
- def format_event_chain(
576
- event_chain: EventChain | Var[EventChain],
577
- event_arg: Var | None = None,
578
- ) -> str:
579
- """Format an event chain as a javascript invocation.
580
-
581
- Args:
582
- event_chain: The event chain to queue on the frontend.
583
- event_arg: The browser-native event (only used to preventDefault).
584
-
585
- Returns:
586
- Compiled javascript code to queue the given event chain on the frontend.
587
-
588
- Raises:
589
- ValueError: When the given event chain is not a valid event chain.
590
- """
591
- if isinstance(event_chain, Var):
592
- from reflex.event import EventChain
593
-
594
- if event_chain._var_type is not EventChain:
595
- raise ValueError(f"Invalid event chain: {event_chain}")
596
- return "".join(
597
- [
598
- "(() => {",
599
- format_var(event_chain),
600
- f"; preventDefault({format_var(event_arg)})" if event_arg else "",
601
- "})()",
602
- ]
603
- )
604
-
605
- chain = ",".join([format_event(event) for event in event_chain.events])
606
- return "".join(
607
- [
608
- f"addEvents([{chain}]",
609
- f", {format_var(event_arg)}" if event_arg else "",
610
- ")",
611
- ]
612
- )
513
+ if TYPE_CHECKING:
514
+ from reflex.ivars import ImmutableVar
613
515
 
614
516
 
615
517
  def format_queue_events(
616
- events: EventSpec
617
- | EventHandler
618
- | Callable
619
- | List[EventSpec | EventHandler | Callable]
620
- | None = None,
518
+ events: (
519
+ EventSpec
520
+ | EventHandler
521
+ | Callable
522
+ | List[EventSpec | EventHandler | Callable]
523
+ | None
524
+ ) = None,
621
525
  args_spec: Optional[ArgsSpec] = None,
622
- ) -> Var[EventChain]:
526
+ ) -> ImmutableVar[EventChain]:
623
527
  """Format a list of event handler / event spec as a javascript callback.
624
528
 
625
529
  The resulting code can be passed to interfaces that expect a callback
@@ -645,11 +549,10 @@ def format_queue_events(
645
549
  call_event_fn,
646
550
  call_event_handler,
647
551
  )
552
+ from reflex.ivars import FunctionVar, ImmutableVar
648
553
 
649
554
  if not events:
650
- return Var.create_safe(
651
- "() => null", _var_is_string=False, _var_is_local=False
652
- ).to(EventChain)
555
+ return ImmutableVar("(() => null)").to(FunctionVar, EventChain) # type: ignore
653
556
 
654
557
  # If no spec is provided, the function will take no arguments.
655
558
  def _default_args_spec():
@@ -674,7 +577,7 @@ def format_queue_events(
674
577
  specs = [call_event_handler(spec, args_spec or _default_args_spec)]
675
578
  elif isinstance(spec, type(lambda: None)):
676
579
  specs = call_event_fn(spec, args_spec or _default_args_spec) # type: ignore
677
- if isinstance(specs, Var):
580
+ if isinstance(specs, ImmutableVar):
678
581
  raise ValueError(
679
582
  f"Invalid event spec: {specs}. Expected a list of EventSpecs."
680
583
  )
@@ -682,12 +585,10 @@ def format_queue_events(
682
585
 
683
586
  # Return the final code snippet, expecting queueEvents, processEvent, and socket to be in scope.
684
587
  # Typically this snippet will _only_ run from within an rx.call_script eval context.
685
- return Var.create_safe(
588
+ return ImmutableVar(
686
589
  f"{arg_def} => {{queueEvents([{','.join(payloads)}], {constants.CompileVars.SOCKET}); "
687
590
  f"processEvent({constants.CompileVars.SOCKET})}}",
688
- _var_is_string=False,
689
- _var_is_local=False,
690
- ).to(EventChain)
591
+ ).to(FunctionVar, EventChain) # type: ignore
691
592
 
692
593
 
693
594
  def format_query_params(router_data: dict[str, Any]) -> dict[str, str]:
@@ -774,41 +675,6 @@ def format_ref(ref: str) -> str:
774
675
  return f"ref_{clean_ref}"
775
676
 
776
677
 
777
- def format_array_ref(refs: str, idx: Var | None) -> str:
778
- """Format a ref accessed by array.
779
-
780
- Args:
781
- refs : The ref array to access.
782
- idx : The index of the ref in the array.
783
-
784
- Returns:
785
- The formatted ref.
786
- """
787
- clean_ref = re.sub(r"[^\w]+", "_", refs)
788
- if idx is not None:
789
- idx._var_is_local = True
790
- return f"refs_{clean_ref}[{idx}]"
791
- return f"refs_{clean_ref}"
792
-
793
-
794
- def format_breadcrumbs(route: str) -> list[tuple[str, str]]:
795
- """Take a route and return a list of tuple for use in breadcrumb.
796
-
797
- Args:
798
- route: The route to transform.
799
-
800
- Returns:
801
- list[tuple[str, str]]: the list of tuples for the breadcrumb.
802
- """
803
- route_parts = route.lstrip("/").split("/")
804
-
805
- # create and return breadcrumbs
806
- return [
807
- (part, "/".join(["", *route_parts[: i + 1]]))
808
- for i, part in enumerate(route_parts)
809
- ]
810
-
811
-
812
678
  def format_library_name(library_fullname: str):
813
679
  """Format the name of a library.
814
680
 
@@ -839,42 +705,6 @@ def json_dumps(obj: Any) -> str:
839
705
  return json.dumps(obj, ensure_ascii=False, default=serializers.serialize)
840
706
 
841
707
 
842
- def unwrap_vars(value: str) -> str:
843
- """Unwrap var values from a JSON string.
844
-
845
- For example, "{var}" will be unwrapped to "var".
846
-
847
- Args:
848
- value: The JSON string to unwrap.
849
-
850
- Returns:
851
- The unwrapped JSON string.
852
- """
853
-
854
- def unescape_double_quotes_in_var(m: re.Match) -> str:
855
- prefix = m.group(1) or ""
856
- # Since the outer quotes are removed, the inner escaped quotes must be unescaped.
857
- return prefix + re.sub('\\\\"', '"', m.group(2))
858
-
859
- # This substitution is necessary to unwrap var values.
860
- return (
861
- re.sub(
862
- pattern=r"""
863
- (?<!\\) # must NOT start with a backslash
864
- " # match opening double quote of JSON value
865
- (<reflex.Var>.*?</reflex.Var>)? # Optional encoded VarData (non-greedy)
866
- {(.*?)} # extract the value between curly braces (non-greedy)
867
- " # match must end with an unescaped double quote
868
- """,
869
- repl=unescape_double_quotes_in_var,
870
- string=value,
871
- flags=re.VERBOSE,
872
- )
873
- .replace('"`', "`")
874
- .replace('`"', "`")
875
- )
876
-
877
-
878
708
  def collect_form_dict_names(form_dict: dict[str, Any]) -> dict[str, Any]:
879
709
  """Collapse keys with consecutive suffixes into a single list value.
880
710
 
@@ -897,6 +727,23 @@ def collect_form_dict_names(form_dict: dict[str, Any]) -> dict[str, Any]:
897
727
  return collapsed
898
728
 
899
729
 
730
+ def format_array_ref(refs: str, idx: ImmutableVar | None) -> str:
731
+ """Format a ref accessed by array.
732
+
733
+ Args:
734
+ refs : The ref array to access.
735
+ idx : The index of the ref in the array.
736
+
737
+ Returns:
738
+ The formatted ref.
739
+ """
740
+ clean_ref = re.sub(r"[^\w]+", "_", refs)
741
+ if idx is not None:
742
+ # idx._var_is_local = True
743
+ return f"refs_{clean_ref}[{str(idx)}]"
744
+ return f"refs_{clean_ref}"
745
+
746
+
900
747
  def format_data_editor_column(col: str | dict):
901
748
  """Format a given column into the proper format.
902
749
 
@@ -909,6 +756,8 @@ def format_data_editor_column(col: str | dict):
909
756
  Returns:
910
757
  The formatted column.
911
758
  """
759
+ from reflex.ivars import ImmutableVar
760
+
912
761
  if isinstance(col, str):
913
762
  return {"title": col, "id": col.lower(), "type": "str"}
914
763
 
@@ -921,7 +770,7 @@ def format_data_editor_column(col: str | dict):
921
770
  col["overlayIcon"] = None
922
771
  return col
923
772
 
924
- if isinstance(col, BaseVar):
773
+ if isinstance(col, ImmutableVar):
925
774
  return col
926
775
 
927
776
  raise ValueError(
@@ -938,7 +787,9 @@ def format_data_editor_cell(cell: Any):
938
787
  Returns:
939
788
  The formatted cell.
940
789
  """
790
+ from reflex.ivars.base import ImmutableVar
791
+
941
792
  return {
942
- "kind": Var.create(value="GridCellKind.Text", _var_is_string=False),
793
+ "kind": ImmutableVar.create("GridCellKind.Text"),
943
794
  "data": cell,
944
795
  }
reflex/utils/net.py ADDED
@@ -0,0 +1,43 @@
1
+ """Helpers for downloading files from the network."""
2
+
3
+ import os
4
+
5
+ import httpx
6
+
7
+ from . import console
8
+
9
+
10
+ def _httpx_verify_kwarg() -> bool:
11
+ """Get the value of the HTTPX verify keyword argument.
12
+
13
+ Returns:
14
+ True if SSL verification is enabled, False otherwise
15
+ """
16
+ ssl_no_verify = os.environ.get("SSL_NO_VERIFY", "").lower() in ["true", "1", "yes"]
17
+ return not ssl_no_verify
18
+
19
+
20
+ def get(url: str, **kwargs) -> httpx.Response:
21
+ """Make an HTTP GET request.
22
+
23
+ Args:
24
+ url: The URL to request.
25
+ **kwargs: Additional keyword arguments to pass to httpx.get.
26
+
27
+ Returns:
28
+ The response object.
29
+
30
+ Raises:
31
+ httpx.ConnectError: If the connection cannot be established.
32
+ """
33
+ kwargs.setdefault("verify", _httpx_verify_kwarg())
34
+ try:
35
+ return httpx.get(url, **kwargs)
36
+ except httpx.ConnectError as err:
37
+ if "CERTIFICATE_VERIFY_FAILED" in str(err):
38
+ # If the error is a certificate verification error, recommend mitigating steps.
39
+ console.error(
40
+ f"Certificate verification failed for {url}. Set environment variable SSL_CERT_FILE to the "
41
+ "path of the certificate file or SSL_NO_VERIFY=1 to disable verification."
42
+ )
43
+ raise
reflex/utils/path_ops.py CHANGED
@@ -81,6 +81,18 @@ def mkdir(path: str | Path):
81
81
  Path(path).mkdir(parents=True, exist_ok=True)
82
82
 
83
83
 
84
+ def ls(path: str | Path) -> list[Path]:
85
+ """List the contents of a directory.
86
+
87
+ Args:
88
+ path: The path to the directory.
89
+
90
+ Returns:
91
+ A list of paths to the contents of the directory.
92
+ """
93
+ return list(Path(path).iterdir())
94
+
95
+
84
96
  def ln(src: str | Path, dest: str | Path, overwrite: bool = False) -> bool:
85
97
  """Create a symbolic link.
86
98
 
@@ -197,4 +209,4 @@ def find_replace(directory: str | Path, find: str, replace: str):
197
209
  filepath = Path(root, file)
198
210
  text = filepath.read_text(encoding="utf-8")
199
211
  text = re.sub(find, replace, text)
200
- filepath.write_text(text)
212
+ filepath.write_text(text, encoding="utf-8")
@@ -28,13 +28,14 @@ import typer
28
28
  from alembic.util.exc import CommandError
29
29
  from packaging import version
30
30
  from redis import Redis as RedisSync
31
+ from redis import exceptions
31
32
  from redis.asyncio import Redis
32
33
 
33
34
  from reflex import constants, model
34
35
  from reflex.base import Base
35
36
  from reflex.compiler import templates
36
37
  from reflex.config import Config, get_config
37
- from reflex.utils import console, path_ops, processes
38
+ from reflex.utils import console, net, path_ops, processes
38
39
  from reflex.utils.format import format_library_name
39
40
  from reflex.utils.registry import _get_best_registry
40
41
 
@@ -80,7 +81,7 @@ def check_latest_package_version(package_name: str):
80
81
  # Get the latest version from PyPI
81
82
  current_version = importlib.metadata.version(package_name)
82
83
  url = f"https://pypi.org/pypi/{package_name}/json"
83
- response = httpx.get(url)
84
+ response = net.get(url)
84
85
  latest_version = response.json()["info"]["version"]
85
86
  if (
86
87
  version.parse(current_version) < version.parse(latest_version)
@@ -324,24 +325,43 @@ def parse_redis_url() -> str | dict | None:
324
325
  """Parse the REDIS_URL in config if applicable.
325
326
 
326
327
  Returns:
327
- If redis-py syntax, return the URL as it is. Otherwise, return the host/port/db as a dict.
328
+ If url is non-empty, return the URL as it is.
329
+
330
+ Raises:
331
+ ValueError: If the REDIS_URL is not a supported scheme.
328
332
  """
329
333
  config = get_config()
330
334
  if not config.redis_url:
331
335
  return None
332
- if config.redis_url.startswith(("redis://", "rediss://", "unix://")):
333
- return config.redis_url
334
- console.deprecate(
335
- feature_name="host[:port] style redis urls",
336
- reason="redis-py url syntax is now being used",
337
- deprecation_version="0.3.6",
338
- removal_version="0.6.0",
339
- )
340
- redis_url, has_port, redis_port = config.redis_url.partition(":")
341
- if not has_port:
342
- redis_port = 6379
343
- console.info(f"Using redis at {config.redis_url}")
344
- return dict(host=redis_url, port=int(redis_port), db=0)
336
+ if not config.redis_url.startswith(("redis://", "rediss://", "unix://")):
337
+ raise ValueError(
338
+ "REDIS_URL must start with 'redis://', 'rediss://', or 'unix://'."
339
+ )
340
+ return config.redis_url
341
+
342
+
343
+ async def get_redis_status() -> bool | None:
344
+ """Checks the status of the Redis connection.
345
+
346
+ Attempts to connect to Redis and send a ping command to verify connectivity.
347
+
348
+ Returns:
349
+ bool or None: The status of the Redis connection:
350
+ - True: Redis is accessible and responding.
351
+ - False: Redis is not accessible due to a connection error.
352
+ - None: Redis not used i.e redis_url is not set in rxconfig.
353
+ """
354
+ try:
355
+ status = True
356
+ redis_client = get_redis_sync()
357
+ if redis_client is not None:
358
+ redis_client.ping()
359
+ else:
360
+ status = None
361
+ except exceptions.RedisError:
362
+ status = False
363
+
364
+ return status
345
365
 
346
366
 
347
367
  def validate_app_name(app_name: str | None = None) -> str:
@@ -670,7 +690,7 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
670
690
  """
671
691
  # Download the script
672
692
  console.debug(f"Downloading {url}")
673
- response = httpx.get(url)
693
+ response = net.get(url)
674
694
  if response.status_code != httpx.codes.OK:
675
695
  response.raise_for_status()
676
696
 
@@ -700,11 +720,11 @@ def download_and_extract_fnm_zip():
700
720
  try:
701
721
  # Download the FNM zip release.
702
722
  # TODO: show progress to improve UX
703
- with httpx.stream("GET", url, follow_redirects=True) as response:
704
- response.raise_for_status()
705
- with open(fnm_zip_file, "wb") as output_file:
706
- for chunk in response.iter_bytes():
707
- output_file.write(chunk)
723
+ response = net.get(url, follow_redirects=True)
724
+ response.raise_for_status()
725
+ with open(fnm_zip_file, "wb") as output_file:
726
+ for chunk in response.iter_bytes():
727
+ output_file.write(chunk)
708
728
 
709
729
  # Extract the downloaded zip file.
710
730
  with zipfile.ZipFile(fnm_zip_file, "r") as zip_ref:
@@ -1222,7 +1242,7 @@ def fetch_app_templates(version: str) -> dict[str, Template]:
1222
1242
  """
1223
1243
 
1224
1244
  def get_release_by_tag(tag: str) -> dict | None:
1225
- response = httpx.get(constants.Reflex.RELEASES_URL)
1245
+ response = net.get(constants.Reflex.RELEASES_URL)
1226
1246
  response.raise_for_status()
1227
1247
  releases = response.json()
1228
1248
  for release in releases:
@@ -1243,7 +1263,7 @@ def fetch_app_templates(version: str) -> dict[str, Template]:
1243
1263
  else:
1244
1264
  templates_url = asset["browser_download_url"]
1245
1265
 
1246
- templates_data = httpx.get(templates_url, follow_redirects=True).json()["templates"]
1266
+ templates_data = net.get(templates_url, follow_redirects=True).json()["templates"]
1247
1267
 
1248
1268
  for template in templates_data:
1249
1269
  if template["name"] == "blank":
@@ -1286,7 +1306,7 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
1286
1306
  zip_file_path = Path(temp_dir) / "template.zip"
1287
1307
  try:
1288
1308
  # Note: following redirects can be risky. We only allow this for reflex built templates at the moment.
1289
- response = httpx.get(template_url, follow_redirects=True)
1309
+ response = net.get(template_url, follow_redirects=True)
1290
1310
  console.debug(f"Server responded download request: {response}")
1291
1311
  response.raise_for_status()
1292
1312
  except httpx.HTTPError as he:
@@ -1417,7 +1437,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
1417
1437
  generation_hash: The generation hash from reflex.build.
1418
1438
  """
1419
1439
  # Download the reflex code for the generation.
1420
- resp = httpx.get(
1440
+ resp = net.get(
1421
1441
  constants.Templates.REFLEX_BUILD_CODE_URL.format(
1422
1442
  generation_hash=generation_hash
1423
1443
  )