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
@@ -12,31 +12,33 @@ def logo(**props):
12
12
  Returns:
13
13
  The logo component.
14
14
  """
15
- light_mode_logo = """<svg width="56" height="12" viewBox="0 0 56 12" fill="none" xmlns="http://www.w3.org/2000/svg">
16
- <path d="M0 11.6V0.400024H8.96V4.88002H6.72V2.64002H2.24V4.88002H6.72V7.12002H2.24V11.6H0ZM6.72 11.6V7.12002H8.96V11.6H6.72Z" fill="#110F1F"/>
17
- <path d="M11.2 11.6V0.400024H17.92V2.64002H13.44V4.88002H17.92V7.12002H13.44V9.36002H17.92V11.6H11.2Z" fill="#110F1F"/>
18
- <path d="M20.16 11.6V0.400024H26.88V2.64002H22.4V4.88002H26.88V7.12002H22.4V11.6H20.16Z" fill="#110F1F"/>
19
- <path d="M29.12 11.6V0.400024H31.36V9.36002H35.84V11.6H29.12Z" fill="#110F1F"/>
20
- <path d="M38.08 11.6V0.400024H44.8V2.64002H40.32V4.88002H44.8V7.12002H40.32V9.36002H44.8V11.6H38.08Z" fill="#110F1F"/>
21
- <path d="M47.04 4.88002V0.400024H49.28V4.88002H47.04ZM53.76 4.88002V0.400024H56V4.88002H53.76ZM49.28 7.12002V4.88002H53.76V7.12002H49.28ZM47.04 11.6V7.12002H49.28V11.6H47.04ZM53.76 11.6V7.12002H56V11.6H53.76Z" fill="#110F1F"/>
22
- </svg>"""
23
-
24
- dark_mode_logo = """<svg width="56" height="12" viewBox="0 0 56 12" fill="none" xmlns="http://www.w3.org/2000/svg">
25
- <path d="M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z" fill="white"/>
26
- <path d="M11.2 11.5999V0.399902H17.92V2.6399H13.44V4.8799H17.92V7.1199H13.44V9.3599H17.92V11.5999H11.2Z" fill="white"/>
27
- <path d="M20.16 11.5999V0.399902H26.88V2.6399H22.4V4.8799H26.88V7.1199H22.4V11.5999H20.16Z" fill="white"/>
28
- <path d="M29.12 11.5999V0.399902H31.36V9.3599H35.84V11.5999H29.12Z" fill="white"/>
29
- <path d="M38.08 11.5999V0.399902H44.8V2.6399H40.32V4.8799H44.8V7.1199H40.32V9.3599H44.8V11.5999H38.08Z" fill="white"/>
30
- <path d="M47.04 4.8799V0.399902H49.28V4.8799H47.04ZM53.76 4.8799V0.399902H56V4.8799H53.76ZM49.28 7.1199V4.8799H53.76V7.1199H49.28ZM47.04 11.5999V7.1199H49.28V11.5999H47.04ZM53.76 11.5999V7.1199H56V11.5999H53.76Z" fill="white"/>
31
- </svg>"""
15
+
16
+ def logo_path(d):
17
+ return rx.el.svg.path(
18
+ d=d,
19
+ fill=rx.color_mode_cond("#110F1F", "white"),
20
+ )
21
+
22
+ paths = [
23
+ "M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z",
24
+ "M11.2 11.5999V0.399902H17.92V2.6399H13.44V4.8799H17.92V7.1199H13.44V9.3599H17.92V11.5999H11.2Z",
25
+ "M20.16 11.5999V0.399902H26.88V2.6399H22.4V4.8799H26.88V7.1199H22.4V11.5999H20.16Z",
26
+ "M29.12 11.5999V0.399902H31.36V9.3599H35.84V11.5999H29.12Z",
27
+ "M38.08 11.5999V0.399902H44.8V2.6399H40.32V4.8799H44.8V7.1199H40.32V9.3599H44.8V11.5999H38.08Z",
28
+ "M47.04 4.8799V0.399902H49.28V4.8799H47.04ZM53.76 4.8799V0.399902H56V4.8799H53.76ZM49.28 7.1199V4.8799H53.76V7.1199H49.28ZM47.04 11.5999V7.1199H49.28V11.5999H47.04ZM53.76 11.5999V7.1199H56V11.5999H53.76Z",
29
+ ]
32
30
 
33
31
  return rx.center(
34
32
  rx.link(
35
33
  rx.hstack(
36
34
  "Built with ",
37
- rx.color_mode_cond(
38
- rx.html(light_mode_logo),
39
- rx.html(dark_mode_logo),
35
+ rx.el.svg(
36
+ *[logo_path(d) for d in paths],
37
+ width="56",
38
+ height="12",
39
+ viewBox="0 0 56 12",
40
+ fill="none",
41
+ xmlns="http://www.w3.org/2000/svg",
40
42
  ),
41
43
  text_align="center",
42
44
  align="center",
@@ -0,0 +1,157 @@
1
+ """Components that are dynamically generated on the backend."""
2
+
3
+ from reflex import constants
4
+ from reflex.utils import imports
5
+ from reflex.utils.serializers import serializer
6
+ from reflex.vars import Var, get_unique_variable_name
7
+ from reflex.vars.base import VarData, transform
8
+
9
+
10
+ def get_cdn_url(lib: str) -> str:
11
+ """Get the CDN URL for a library.
12
+
13
+ Args:
14
+ lib: The library to get the CDN URL for.
15
+
16
+ Returns:
17
+ The CDN URL for the library.
18
+ """
19
+ return f"https://cdn.jsdelivr.net/npm/{lib}" + "/+esm"
20
+
21
+
22
+ def load_dynamic_serializer():
23
+ """Load the serializer for dynamic components."""
24
+ # Causes a circular import, so we import here.
25
+ from reflex.components.component import Component
26
+
27
+ @serializer
28
+ def make_component(component: Component) -> str:
29
+ """Generate the code for a dynamic component.
30
+
31
+ Args:
32
+ component: The component to generate code for.
33
+
34
+ Returns:
35
+ The generated code
36
+ """
37
+ # Causes a circular import, so we import here.
38
+ from reflex.compiler import templates, utils
39
+
40
+ rendered_components = {}
41
+ # Include dynamic imports in the shared component.
42
+ if dynamic_imports := component._get_all_dynamic_imports():
43
+ rendered_components.update(
44
+ {dynamic_import: None for dynamic_import in dynamic_imports}
45
+ )
46
+
47
+ # Include custom code in the shared component.
48
+ rendered_components.update(
49
+ {code: None for code in component._get_all_custom_code()},
50
+ )
51
+
52
+ rendered_components[
53
+ templates.STATEFUL_COMPONENT.render(
54
+ tag_name="MySSRComponent",
55
+ memo_trigger_hooks=[],
56
+ component=component,
57
+ )
58
+ ] = None
59
+
60
+ libs_in_window = [
61
+ "react",
62
+ "@radix-ui/themes",
63
+ ]
64
+
65
+ imports = {}
66
+ for lib, names in component._get_all_imports().items():
67
+ if (
68
+ not lib.startswith((".", "/"))
69
+ and not lib.startswith("http")
70
+ and all(
71
+ not lib.startswith(lib_in_window)
72
+ for lib_in_window in libs_in_window
73
+ )
74
+ ):
75
+ imports[get_cdn_url(lib)] = names
76
+ else:
77
+ imports[lib] = names
78
+
79
+ module_code_lines = templates.STATEFUL_COMPONENTS.render(
80
+ imports=utils.compile_imports(imports),
81
+ memoized_code="\n".join(rendered_components),
82
+ ).splitlines()[1:]
83
+
84
+ # Rewrite imports from `/` to destructure from window
85
+ for ix, line in enumerate(module_code_lines[:]):
86
+ if line.startswith("import "):
87
+ if 'from "/' in line:
88
+ module_code_lines[ix] = (
89
+ line.replace("import ", "const ", 1).replace(
90
+ " from ", " = window['__reflex'][", 1
91
+ )
92
+ + "]"
93
+ )
94
+ else:
95
+ for lib in libs_in_window:
96
+ if f'from "{lib}"' in line:
97
+ module_code_lines[ix] = (
98
+ line.replace("import ", "const ", 1)
99
+ .replace(
100
+ f' from "{lib}"', f" = window.__reflex['{lib}']", 1
101
+ )
102
+ .replace(" as ", ": ")
103
+ )
104
+ if line.startswith("export function"):
105
+ module_code_lines[ix] = line.replace(
106
+ "export function", "export default function", 1
107
+ )
108
+
109
+ module_code_lines.insert(0, "const React = window.__reflex.react;")
110
+
111
+ return "//__reflex_evaluate\n" + "\n".join(module_code_lines)
112
+
113
+ @transform
114
+ def evaluate_component(js_string: Var[str]) -> Var[Component]:
115
+ """Evaluate a component.
116
+
117
+ Args:
118
+ js_string: The JavaScript string to evaluate.
119
+
120
+ Returns:
121
+ The evaluated JavaScript string.
122
+ """
123
+ unique_var_name = get_unique_variable_name()
124
+
125
+ return js_string._replace(
126
+ _js_expr=unique_var_name,
127
+ _var_type=Component,
128
+ merge_var_data=VarData.merge(
129
+ VarData(
130
+ imports={
131
+ f"/{constants.Dirs.STATE_PATH}": [
132
+ imports.ImportVar(tag="evalReactComponent"),
133
+ ],
134
+ "react": [
135
+ imports.ImportVar(tag="useState"),
136
+ imports.ImportVar(tag="useEffect"),
137
+ ],
138
+ },
139
+ hooks={
140
+ f"const [{unique_var_name}, set_{unique_var_name}] = useState(null);": None,
141
+ "useEffect(() => {"
142
+ "let isMounted = true;"
143
+ f"evalReactComponent({str(js_string)})"
144
+ ".then((component) => {"
145
+ "if (isMounted) {"
146
+ f"set_{unique_var_name}(component);"
147
+ "}"
148
+ "});"
149
+ "return () => {"
150
+ "isMounted = false;"
151
+ "};"
152
+ "}"
153
+ f", [{str(js_string)}]);": None,
154
+ },
155
+ ),
156
+ ),
157
+ )
@@ -10,7 +10,7 @@ from jinja2 import Environment
10
10
  from reflex.components.el.element import Element
11
11
  from reflex.components.tags.tag import Tag
12
12
  from reflex.constants import Dirs, EventTriggers
13
- from reflex.event import EventChain, EventHandler
13
+ from reflex.event import EventChain, EventHandler, prevent_default
14
14
  from reflex.utils.imports import ImportDict
15
15
  from reflex.vars import VarData
16
16
  from reflex.vars.base import LiteralVar, Var
@@ -148,6 +148,9 @@ class Form(BaseHTML):
148
148
  Returns:
149
149
  The form component.
150
150
  """
151
+ if "on_submit" not in props:
152
+ props["on_submit"] = prevent_default
153
+
151
154
  if "handle_submit_unique_name" in props:
152
155
  return super().create(*children, **props)
153
156
 
@@ -124,7 +124,8 @@ class DataTable(Gridjs):
124
124
  if types.is_dataframe(type(self.data)):
125
125
  # If given a pandas df break up the data and columns
126
126
  data = serialize(self.data)
127
- assert isinstance(data, dict), "Serialized dataframe should be a dict."
127
+ if not isinstance(data, dict):
128
+ raise ValueError("Serialized dataframe should be a dict.")
128
129
  self.columns = LiteralVar.create(data["columns"])
129
130
  self.data = LiteralVar.create(data["data"])
130
131
 
@@ -95,12 +95,16 @@ class Markdown(Component):
95
95
  *children: The children of the component.
96
96
  **props: The properties of the component.
97
97
 
98
+ Raises:
99
+ ValueError: If the children are not valid.
100
+
98
101
  Returns:
99
102
  The markdown component.
100
103
  """
101
- assert (
102
- len(children) == 1 and types._isinstance(children[0], Union[str, Var])
103
- ), "Markdown component must have exactly one child containing the markdown source."
104
+ if len(children) != 1 or not types._isinstance(children[0], Union[str, Var]):
105
+ raise ValueError(
106
+ "Markdown component must have exactly one child containing the markdown source."
107
+ )
104
108
 
105
109
  # Update the base component map with the custom component map.
106
110
  component_map = {**get_base_component_map(), **props.pop("component_map", {})}
@@ -147,7 +151,7 @@ class Markdown(Component):
147
151
  Returns:
148
152
  The imports for the markdown component.
149
153
  """
150
- from reflex.components.datadisplay.code import CodeBlock
154
+ from reflex.components.datadisplay.code import CodeBlock, Theme
151
155
  from reflex.components.radix.themes.typography.code import Code
152
156
 
153
157
  return [
@@ -173,8 +177,8 @@ class Markdown(Component):
173
177
  component(_MOCK_ARG)._get_all_imports() # type: ignore
174
178
  for component in self.component_map.values()
175
179
  ],
176
- CodeBlock.create(theme="light")._get_imports(), # type: ignore,
177
- Code.create()._get_imports(), # type: ignore,
180
+ CodeBlock.create(theme=Theme.light)._get_imports(),
181
+ Code.create()._get_imports(),
178
182
  ]
179
183
 
180
184
  def get_component(self, tag: str, **props) -> Component:
@@ -93,6 +93,9 @@ class Markdown(Component):
93
93
  custom_attrs: custom attribute
94
94
  **props: The properties of the component.
95
95
 
96
+ Raises:
97
+ ValueError: If the children are not valid.
98
+
96
99
  Returns:
97
100
  The markdown component.
98
101
  """
@@ -4,6 +4,7 @@ from typing import Literal
4
4
 
5
5
  from reflex.components.component import Component
6
6
  from reflex.components.core.breakpoints import Responsive
7
+ from reflex.style import Style
7
8
  from reflex.vars.base import Var
8
9
 
9
10
  from ..base import LiteralAccentColor, RadixThemesComponent
@@ -38,6 +39,21 @@ class Progress(RadixThemesComponent):
38
39
  # The duration of the progress bar animation. Once the duration times out, the progress bar will start an indeterminate animation.
39
40
  duration: Var[str]
40
41
 
42
+ # The color of the progress bar fill animation.
43
+ fill_color: Var[str]
44
+
45
+ @staticmethod
46
+ def _color_selector(color: str) -> Style:
47
+ """Return a style object with the correct color and css selector.
48
+
49
+ Args:
50
+ color: Color of the fill part.
51
+
52
+ Returns:
53
+ Style: Style object with the correct css selector and color.
54
+ """
55
+ return Style({".rt-ProgressIndicator": {"background_color": color}})
56
+
41
57
  @classmethod
42
58
  def create(cls, *children, **props) -> Component:
43
59
  """Create a Progress component.
@@ -50,6 +66,12 @@ class Progress(RadixThemesComponent):
50
66
  The Progress Component.
51
67
  """
52
68
  props.setdefault("width", "100%")
69
+ if "fill_color" in props:
70
+ color = props.get("fill_color", "")
71
+ style = props.get("style", {})
72
+ style = style | cls._color_selector(color)
73
+ props["style"] = style
74
+
53
75
  return super().create(*children, **props)
54
76
 
55
77
 
@@ -107,6 +107,7 @@ class Progress(RadixThemesComponent):
107
107
  ]
108
108
  ] = None,
109
109
  duration: Optional[Union[Var[str], str]] = None,
110
+ fill_color: Optional[Union[Var[str], str]] = None,
110
111
  style: Optional[Style] = None,
111
112
  key: Optional[Any] = None,
112
113
  id: Optional[Any] = None,
@@ -162,6 +163,7 @@ class Progress(RadixThemesComponent):
162
163
  high_contrast: Whether to render the progress bar with higher contrast color against background
163
164
  radius: Override theme radius for progress bar: "none" | "small" | "medium" | "large" | "full"
164
165
  duration: The duration of the progress bar animation. Once the duration times out, the progress bar will start an indeterminate animation.
166
+ fill_color: The color of the progress bar fill animation.
165
167
  style: The style of the component.
166
168
  key: A unique key for the component.
167
169
  id: The id for the component.
@@ -23,6 +23,7 @@ class SegmentedControlRoot(RadixThemesComponent):
23
23
  # Variant of button: "classic" | "surface"
24
24
  variant: Var[Literal["classic", "surface"]]
25
25
 
26
+ # The type of the segmented control, either "single" for selecting one option or "multiple" for selecting multiple options.
26
27
  type: Var[Literal["single", "multiple"]]
27
28
 
28
29
  # Override theme color for button
@@ -34,8 +35,10 @@ class SegmentedControlRoot(RadixThemesComponent):
34
35
  # The default value of the segmented control.
35
36
  default_value: Var[Union[str, List[str]]]
36
37
 
38
+ # The current value of the segmented control.
37
39
  value: Var[Union[str, List[str]]]
38
40
 
41
+ # Handles the `onChange` event for the SegmentedControl component.
39
42
  on_change: EventHandler[lambda e0: [e0]]
40
43
 
41
44
  _rename_props = {"onChange": "onValueChange"}
@@ -161,9 +161,11 @@ class SegmentedControlRoot(RadixThemesComponent):
161
161
  *children: Child components.
162
162
  size: The size of the segmented control: "1" | "2" | "3"
163
163
  variant: Variant of button: "classic" | "surface"
164
+ type: The type of the segmented control, either "single" for selecting one option or "multiple" for selecting multiple options.
164
165
  color_scheme: Override theme color for button
165
166
  radius: The radius of the segmented control: "none" | "small" | "medium" | "large" | "full"
166
167
  default_value: The default value of the segmented control.
168
+ value: The current value of the segmented control.
167
169
  style: The style of the component.
168
170
  key: A unique key for the component.
169
171
  id: The id for the component.
@@ -33,7 +33,7 @@ class Stack(Flex):
33
33
  """
34
34
  # Apply the default classname
35
35
  given_class_name = props.pop("class_name", [])
36
- if isinstance(given_class_name, str):
36
+ if not isinstance(given_class_name, list):
37
37
  given_class_name = [given_class_name]
38
38
  props["class_name"] = ["rx-Stack", *given_class_name]
39
39
 
@@ -167,7 +167,7 @@ class ZAxis(Recharts):
167
167
 
168
168
  tag = "ZAxis"
169
169
 
170
- alias = "RechartszAxis"
170
+ alias = "RechartsZAxis"
171
171
 
172
172
  # The key of data displayed in the axis.
173
173
  data_key: Var[Union[str, int]]
@@ -114,6 +114,9 @@ class IterTag(Tag):
114
114
  def render_component(self) -> Component:
115
115
  """Render the component.
116
116
 
117
+ Raises:
118
+ ValueError: If the render function takes more than 2 arguments.
119
+
117
120
  Returns:
118
121
  The rendered component.
119
122
  """
@@ -132,7 +135,8 @@ class IterTag(Tag):
132
135
  component = self.render_fn(arg)
133
136
  else:
134
137
  # If the render function takes the index as an argument.
135
- assert len(args) == 2
138
+ if len(args) != 2:
139
+ raise ValueError("The render function must take 2 arguments.")
136
140
  component = self.render_fn(arg, index)
137
141
 
138
142
  # Nested foreach components or cond must be wrapped in fragments.
reflex/config.py CHANGED
@@ -158,7 +158,7 @@ class Config(Base):
158
158
  app_name: str
159
159
 
160
160
  # The log level to use.
161
- loglevel: constants.LogLevel = constants.LogLevel.INFO
161
+ loglevel: constants.LogLevel = constants.LogLevel.DEFAULT
162
162
 
163
163
  # The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
164
164
  frontend_port: int = constants.DefaultPorts.FRONTEND_PORT
@@ -194,7 +194,7 @@ class Config(Base):
194
194
  cors_allowed_origins: List[str] = ["*"]
195
195
 
196
196
  # Tailwind config.
197
- tailwind: Optional[Dict[str, Any]] = {}
197
+ tailwind: Optional[Dict[str, Any]] = {"plugins": ["@tailwindcss/typography"]}
198
198
 
199
199
  # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?)
200
200
  timeout: int = 120
reflex/constants/base.py CHANGED
@@ -117,7 +117,9 @@ class Templates(SimpleNamespace):
117
117
  REFLEX_BUILD_POLL_URL = REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}"
118
118
 
119
119
  # The URL to fetch the generation's reflex code
120
- REFLEX_BUILD_CODE_URL = REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}"
120
+ REFLEX_BUILD_CODE_URL = (
121
+ REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}/refactored"
122
+ )
121
123
 
122
124
  class Dirs(SimpleNamespace):
123
125
  """Folders used by the template system of Reflex."""
@@ -171,6 +173,7 @@ class LogLevel(str, Enum):
171
173
  """The log levels."""
172
174
 
173
175
  DEBUG = "debug"
176
+ DEFAULT = "default"
174
177
  INFO = "info"
175
178
  WARNING = "warning"
176
179
  ERROR = "error"
@@ -54,6 +54,9 @@ class Bun(SimpleNamespace):
54
54
  # Path of the bunfig file
55
55
  CONFIG_PATH = "bunfig.toml"
56
56
 
57
+ # The environment variable to use the system installed bun.
58
+ USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_BUN"
59
+
57
60
 
58
61
  # FNM config.
59
62
  class Fnm(SimpleNamespace):
@@ -96,6 +99,9 @@ class Node(SimpleNamespace):
96
99
  # The default path where npm is installed.
97
100
  NPM_PATH = os.path.join(BIN_PATH, "npm")
98
101
 
102
+ # The environment variable to use the system installed node.
103
+ USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_NODE"
104
+
99
105
 
100
106
  class PackageJson(SimpleNamespace):
101
107
  """Constants used to build the package.json file."""
@@ -111,10 +117,11 @@ class PackageJson(SimpleNamespace):
111
117
  PATH = "package.json"
112
118
 
113
119
  DEPENDENCIES = {
120
+ "@babel/standalone": "7.25.3",
114
121
  "@emotion/react": "11.11.1",
115
122
  "axios": "1.6.0",
116
123
  "json5": "2.2.3",
117
- "next": "14.0.1",
124
+ "next": "14.2.13",
118
125
  "next-sitemap": "4.1.8",
119
126
  "next-themes": "0.2.1",
120
127
  "react": "18.2.0",
reflex/event.py CHANGED
@@ -18,10 +18,12 @@ from typing import (
18
18
  get_type_hints,
19
19
  )
20
20
 
21
+ from typing_extensions import get_args, get_origin
22
+
21
23
  from reflex import constants
22
24
  from reflex.utils import format
23
25
  from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch
24
- from reflex.utils.types import ArgsSpec
26
+ from reflex.utils.types import ArgsSpec, GenericType
25
27
  from reflex.vars import VarData
26
28
  from reflex.vars.base import LiteralVar, Var
27
29
  from reflex.vars.function import FunctionStringVar, FunctionVar
@@ -417,7 +419,7 @@ class FileUpload:
417
419
  on_upload_progress: Optional[Union[EventHandler, Callable]] = None
418
420
 
419
421
  @staticmethod
420
- def on_upload_progress_args_spec(_prog: Dict[str, Union[int, float, bool]]):
422
+ def on_upload_progress_args_spec(_prog: Var[Dict[str, Union[int, float, bool]]]):
421
423
  """Args spec for on_upload_progress event handler.
422
424
 
423
425
  Returns:
@@ -910,6 +912,20 @@ def call_event_handler(
910
912
  )
911
913
 
912
914
 
915
+ def unwrap_var_annotation(annotation: GenericType):
916
+ """Unwrap a Var annotation or return it as is if it's not Var[X].
917
+
918
+ Args:
919
+ annotation: The annotation to unwrap.
920
+
921
+ Returns:
922
+ The unwrapped annotation.
923
+ """
924
+ if get_origin(annotation) is Var and (args := get_args(annotation)):
925
+ return args[0]
926
+ return annotation
927
+
928
+
913
929
  def parse_args_spec(arg_spec: ArgsSpec):
914
930
  """Parse the args provided in the ArgsSpec of an event trigger.
915
931
 
@@ -921,20 +937,54 @@ def parse_args_spec(arg_spec: ArgsSpec):
921
937
  """
922
938
  spec = inspect.getfullargspec(arg_spec)
923
939
  annotations = get_type_hints(arg_spec)
940
+
924
941
  return arg_spec(
925
942
  *[
926
- Var(f"_{l_arg}").to(annotations.get(l_arg, FrontendEvent))
943
+ Var(f"_{l_arg}").to(
944
+ unwrap_var_annotation(annotations.get(l_arg, FrontendEvent))
945
+ )
927
946
  for l_arg in spec.args
928
947
  ]
929
948
  )
930
949
 
931
950
 
951
+ def check_fn_match_arg_spec(fn: Callable, arg_spec: ArgsSpec) -> List[Var]:
952
+ """Ensures that the function signature matches the passed argument specification
953
+ or raises an EventFnArgMismatch if they do not.
954
+
955
+ Args:
956
+ fn: The function to be validated.
957
+ arg_spec: The argument specification for the event trigger.
958
+
959
+ Returns:
960
+ The parsed arguments from the argument specification.
961
+
962
+ Raises:
963
+ EventFnArgMismatch: Raised if the number of mandatory arguments do not match
964
+ """
965
+ fn_args = inspect.getfullargspec(fn).args
966
+ fn_defaults_args = inspect.getfullargspec(fn).defaults
967
+ n_fn_args = len(fn_args)
968
+ n_fn_defaults_args = len(fn_defaults_args) if fn_defaults_args else 0
969
+ if isinstance(fn, types.MethodType):
970
+ n_fn_args -= 1 # subtract 1 for bound self arg
971
+ parsed_args = parse_args_spec(arg_spec)
972
+ if not (n_fn_args - n_fn_defaults_args <= len(parsed_args) <= n_fn_args):
973
+ raise EventFnArgMismatch(
974
+ "The number of mandatory arguments accepted by "
975
+ f"{fn} ({n_fn_args - n_fn_defaults_args}) "
976
+ "does not match the arguments passed by the event trigger: "
977
+ f"{[str(v) for v in parsed_args]}\n"
978
+ "See https://reflex.dev/docs/events/event-arguments/"
979
+ )
980
+ return parsed_args
981
+
982
+
932
983
  def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
933
984
  """Call a function to a list of event specs.
934
985
 
935
986
  The function should return a single EventSpec, a list of EventSpecs, or a
936
- single Var. The function signature must match the passed arg_spec or
937
- EventFnArgsMismatch will be raised.
987
+ single Var.
938
988
 
939
989
  Args:
940
990
  fn: The function to call.
@@ -944,7 +994,6 @@ def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
944
994
  The event specs from calling the function or a Var.
945
995
 
946
996
  Raises:
947
- EventFnArgMismatch: If the function signature doesn't match the arg spec.
948
997
  EventHandlerValueError: If the lambda returns an unusable value.
949
998
  """
950
999
  # Import here to avoid circular imports.
@@ -952,19 +1001,7 @@ def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
952
1001
  from reflex.utils.exceptions import EventHandlerValueError
953
1002
 
954
1003
  # Check that fn signature matches arg_spec
955
- fn_args = inspect.getfullargspec(fn).args
956
- n_fn_args = len(fn_args)
957
- if isinstance(fn, types.MethodType):
958
- n_fn_args -= 1 # subtract 1 for bound self arg
959
- parsed_args = parse_args_spec(arg_spec)
960
- if len(parsed_args) != n_fn_args:
961
- raise EventFnArgMismatch(
962
- "The number of arguments accepted by "
963
- f"{fn} ({n_fn_args}) "
964
- "does not match the arguments passed by the event trigger: "
965
- f"{[str(v) for v in parsed_args]}\n"
966
- "See https://reflex.dev/docs/events/event-arguments/"
967
- )
1004
+ parsed_args = check_fn_match_arg_spec(fn, arg_spec)
968
1005
 
969
1006
  # Call the function with the parsed args.
970
1007
  out = fn(*parsed_args)
@@ -1025,6 +1062,9 @@ def fix_events(
1025
1062
  token: The user token.
1026
1063
  router_data: The optional router data to set in the event.
1027
1064
 
1065
+ Raises:
1066
+ ValueError: If the event type is not what was expected.
1067
+
1028
1068
  Returns:
1029
1069
  The fixed events.
1030
1070
  """
@@ -1048,7 +1088,8 @@ def fix_events(
1048
1088
  # Otherwise, create an event from the event spec.
1049
1089
  if isinstance(e, EventHandler):
1050
1090
  e = e()
1051
- assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}."
1091
+ if not isinstance(e, EventSpec):
1092
+ raise ValueError(f"Unexpected event type, {type(e)}.")
1052
1093
  name = format.format_event_handler(e.handler)
1053
1094
  payload = {k._js_expr: v._decode() for k, v in e.args} # type: ignore
1054
1095