reflex 0.6.4a3__py3-none-any.whl → 0.6.5a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +0 -14
  2. reflex/.templates/jinja/web/pages/utils.js.jinja2 +4 -8
  3. reflex/.templates/web/components/shiki/code.js +16 -11
  4. reflex/.templates/web/utils/state.js +29 -21
  5. reflex/__init__.py +4 -0
  6. reflex/__init__.pyi +4 -0
  7. reflex/app.py +148 -154
  8. reflex/app_mixins/lifespan.py +5 -1
  9. reflex/app_mixins/middleware.py +3 -1
  10. reflex/app_mixins/mixin.py +3 -2
  11. reflex/base.py +2 -4
  12. reflex/compiler/compiler.py +111 -37
  13. reflex/components/base/app_wrap.pyi +17 -17
  14. reflex/components/base/bare.py +72 -3
  15. reflex/components/base/body.pyi +17 -17
  16. reflex/components/base/document.pyi +81 -81
  17. reflex/components/base/error_boundary.pyi +25 -18
  18. reflex/components/base/fragment.pyi +17 -17
  19. reflex/components/base/head.pyi +33 -33
  20. reflex/components/base/link.pyi +33 -33
  21. reflex/components/base/meta.pyi +65 -65
  22. reflex/components/base/script.py +4 -4
  23. reflex/components/base/script.pyi +23 -20
  24. reflex/components/component.py +250 -31
  25. reflex/components/core/banner.py +1 -1
  26. reflex/components/core/banner.pyi +81 -81
  27. reflex/components/core/client_side_routing.pyi +33 -33
  28. reflex/components/core/clipboard.py +2 -2
  29. reflex/components/core/clipboard.pyi +24 -18
  30. reflex/components/core/debounce.py +2 -2
  31. reflex/components/core/debounce.pyi +18 -18
  32. reflex/components/core/html.pyi +17 -17
  33. reflex/components/core/upload.py +82 -28
  34. reflex/components/core/upload.pyi +77 -72
  35. reflex/components/datadisplay/code.py +55 -40
  36. reflex/components/datadisplay/code.pyi +46 -44
  37. reflex/components/datadisplay/dataeditor.py +21 -20
  38. reflex/components/datadisplay/dataeditor.pyi +103 -35
  39. reflex/components/datadisplay/shiki_code_block.py +60 -27
  40. reflex/components/datadisplay/shiki_code_block.pyi +86 -65
  41. reflex/components/dynamic.py +9 -5
  42. reflex/components/el/element.pyi +17 -17
  43. reflex/components/el/elements/base.pyi +17 -17
  44. reflex/components/el/elements/forms.py +12 -3
  45. reflex/components/el/elements/forms.pyi +293 -233
  46. reflex/components/el/elements/inline.pyi +449 -449
  47. reflex/components/el/elements/media.pyi +401 -401
  48. reflex/components/el/elements/metadata.pyi +97 -97
  49. reflex/components/el/elements/other.pyi +113 -113
  50. reflex/components/el/elements/scripts.pyi +49 -49
  51. reflex/components/el/elements/sectioning.pyi +241 -241
  52. reflex/components/el/elements/tables.pyi +161 -161
  53. reflex/components/el/elements/typography.pyi +241 -241
  54. reflex/components/gridjs/datatable.pyi +33 -33
  55. reflex/components/lucide/icon.py +1 -1
  56. reflex/components/lucide/icon.pyi +33 -33
  57. reflex/components/markdown/markdown.py +180 -49
  58. reflex/components/markdown/markdown.pyi +36 -19
  59. reflex/components/moment/moment.py +17 -21
  60. reflex/components/moment/moment.pyi +26 -21
  61. reflex/components/next/base.pyi +17 -17
  62. reflex/components/next/image.py +3 -3
  63. reflex/components/next/image.pyi +21 -19
  64. reflex/components/next/link.pyi +17 -17
  65. reflex/components/next/video.pyi +17 -17
  66. reflex/components/plotly/plotly.py +79 -78
  67. reflex/components/plotly/plotly.pyi +91 -41
  68. reflex/components/props.py +34 -0
  69. reflex/components/radix/primitives/accordion.py +15 -8
  70. reflex/components/radix/primitives/accordion.pyi +121 -118
  71. reflex/components/radix/primitives/base.pyi +33 -33
  72. reflex/components/radix/primitives/drawer.py +41 -20
  73. reflex/components/radix/primitives/drawer.pyi +279 -190
  74. reflex/components/radix/primitives/form.py +2 -2
  75. reflex/components/radix/primitives/form.pyi +200 -167
  76. reflex/components/radix/primitives/progress.pyi +81 -81
  77. reflex/components/radix/primitives/slider.pyi +89 -83
  78. reflex/components/radix/themes/base.py +27 -1
  79. reflex/components/radix/themes/base.pyi +286 -113
  80. reflex/components/radix/themes/color_mode.py +17 -9
  81. reflex/components/radix/themes/color_mode.pyi +68 -56
  82. reflex/components/radix/themes/components/alert_dialog.py +8 -5
  83. reflex/components/radix/themes/components/alert_dialog.pyi +125 -117
  84. reflex/components/radix/themes/components/aspect_ratio.pyi +17 -17
  85. reflex/components/radix/themes/components/avatar.py +1 -5
  86. reflex/components/radix/themes/components/avatar.pyi +17 -17
  87. reflex/components/radix/themes/components/badge.py +1 -5
  88. reflex/components/radix/themes/components/badge.pyi +17 -17
  89. reflex/components/radix/themes/components/button.pyi +18 -21
  90. reflex/components/radix/themes/components/callout.py +1 -4
  91. reflex/components/radix/themes/components/callout.pyi +81 -81
  92. reflex/components/radix/themes/components/card.py +1 -3
  93. reflex/components/radix/themes/components/card.pyi +17 -17
  94. reflex/components/radix/themes/components/checkbox.py +4 -8
  95. reflex/components/radix/themes/components/checkbox.pyi +61 -52
  96. reflex/components/radix/themes/components/checkbox_cards.pyi +33 -33
  97. reflex/components/radix/themes/components/checkbox_group.pyi +33 -33
  98. reflex/components/radix/themes/components/context_menu.py +121 -28
  99. reflex/components/radix/themes/components/context_menu.pyi +250 -147
  100. reflex/components/radix/themes/components/data_list.pyi +65 -65
  101. reflex/components/radix/themes/components/dialog.py +11 -11
  102. reflex/components/radix/themes/components/dialog.pyi +135 -120
  103. reflex/components/radix/themes/components/dropdown_menu.py +14 -25
  104. reflex/components/radix/themes/components/dropdown_menu.pyi +157 -145
  105. reflex/components/radix/themes/components/hover_card.py +19 -7
  106. reflex/components/radix/themes/components/hover_card.pyi +102 -67
  107. reflex/components/radix/themes/components/icon_button.pyi +18 -21
  108. reflex/components/radix/themes/components/inset.py +1 -3
  109. reflex/components/radix/themes/components/inset.pyi +17 -17
  110. reflex/components/radix/themes/components/popover.py +22 -13
  111. reflex/components/radix/themes/components/popover.pyi +98 -72
  112. reflex/components/radix/themes/components/progress.pyi +17 -17
  113. reflex/components/radix/themes/components/radio.pyi +17 -17
  114. reflex/components/radix/themes/components/radio_cards.py +2 -2
  115. reflex/components/radix/themes/components/radio_cards.pyi +37 -34
  116. reflex/components/radix/themes/components/radio_group.py +3 -7
  117. reflex/components/radix/themes/components/radio_group.pyi +69 -66
  118. reflex/components/radix/themes/components/scroll_area.py +1 -3
  119. reflex/components/radix/themes/components/scroll_area.pyi +17 -17
  120. reflex/components/radix/themes/components/segmented_control.pyi +37 -34
  121. reflex/components/radix/themes/components/select.py +7 -11
  122. reflex/components/radix/themes/components/select.pyi +175 -154
  123. reflex/components/radix/themes/components/separator.py +1 -4
  124. reflex/components/radix/themes/components/separator.pyi +17 -17
  125. reflex/components/radix/themes/components/skeleton.pyi +17 -17
  126. reflex/components/radix/themes/components/slider.py +12 -21
  127. reflex/components/radix/themes/components/slider.pyi +47 -25
  128. reflex/components/radix/themes/components/spinner.py +1 -4
  129. reflex/components/radix/themes/components/spinner.pyi +17 -17
  130. reflex/components/radix/themes/components/switch.py +3 -6
  131. reflex/components/radix/themes/components/switch.pyi +21 -18
  132. reflex/components/radix/themes/components/table.py +21 -5
  133. reflex/components/radix/themes/components/table.pyi +392 -116
  134. reflex/components/radix/themes/components/tabs.py +3 -6
  135. reflex/components/radix/themes/components/tabs.pyi +89 -83
  136. reflex/components/radix/themes/components/text_area.py +1 -5
  137. reflex/components/radix/themes/components/text_area.pyi +43 -20
  138. reflex/components/radix/themes/components/text_field.py +1 -5
  139. reflex/components/radix/themes/components/text_field.pyi +101 -55
  140. reflex/components/radix/themes/components/tooltip.py +5 -7
  141. reflex/components/radix/themes/components/tooltip.pyi +25 -22
  142. reflex/components/radix/themes/layout/base.py +2 -27
  143. reflex/components/radix/themes/layout/base.pyi +82 -82
  144. reflex/components/radix/themes/layout/box.pyi +17 -17
  145. reflex/components/radix/themes/layout/center.pyi +17 -17
  146. reflex/components/radix/themes/layout/container.pyi +17 -17
  147. reflex/components/radix/themes/layout/flex.py +1 -6
  148. reflex/components/radix/themes/layout/flex.pyi +17 -17
  149. reflex/components/radix/themes/layout/grid.py +1 -6
  150. reflex/components/radix/themes/layout/grid.pyi +17 -17
  151. reflex/components/radix/themes/layout/list.py +20 -15
  152. reflex/components/radix/themes/layout/list.pyi +175 -92
  153. reflex/components/radix/themes/layout/section.pyi +17 -17
  154. reflex/components/radix/themes/layout/spacer.pyi +17 -17
  155. reflex/components/radix/themes/layout/stack.py +6 -6
  156. reflex/components/radix/themes/layout/stack.pyi +91 -62
  157. reflex/components/radix/themes/typography/blockquote.py +2 -8
  158. reflex/components/radix/themes/typography/blockquote.pyi +17 -17
  159. reflex/components/radix/themes/typography/code.py +4 -10
  160. reflex/components/radix/themes/typography/code.pyi +19 -18
  161. reflex/components/radix/themes/typography/heading.py +4 -11
  162. reflex/components/radix/themes/typography/heading.pyi +19 -18
  163. reflex/components/radix/themes/typography/link.py +4 -10
  164. reflex/components/radix/themes/typography/link.pyi +19 -18
  165. reflex/components/radix/themes/typography/text.py +4 -11
  166. reflex/components/radix/themes/typography/text.pyi +115 -114
  167. reflex/components/react_player/audio.pyi +58 -33
  168. reflex/components/react_player/react_player.py +17 -17
  169. reflex/components/react_player/react_player.pyi +55 -33
  170. reflex/components/react_player/video.pyi +58 -33
  171. reflex/components/recharts/cartesian.py +45 -45
  172. reflex/components/recharts/cartesian.pyi +389 -304
  173. reflex/components/recharts/charts.py +22 -22
  174. reflex/components/recharts/charts.pyi +226 -179
  175. reflex/components/recharts/general.py +26 -27
  176. reflex/components/recharts/general.pyi +106 -99
  177. reflex/components/recharts/polar.py +33 -33
  178. reflex/components/recharts/polar.pyi +70 -64
  179. reflex/components/recharts/recharts.pyi +33 -33
  180. reflex/components/sonner/toast.py +9 -36
  181. reflex/components/sonner/toast.pyi +20 -24
  182. reflex/components/suneditor/editor.py +8 -8
  183. reflex/components/suneditor/editor.pyi +50 -25
  184. reflex/components/tags/iter_tag.py +1 -10
  185. reflex/components/tags/tag.py +1 -4
  186. reflex/config.py +198 -35
  187. reflex/constants/__init__.py +4 -16
  188. reflex/constants/base.py +7 -14
  189. reflex/constants/colors.py +0 -1
  190. reflex/constants/installer.py +12 -7
  191. reflex/constants/state.py +4 -0
  192. reflex/custom_components/custom_components.py +6 -6
  193. reflex/event.py +486 -241
  194. reflex/experimental/client_state.py +9 -9
  195. reflex/experimental/layout.py +2 -2
  196. reflex/experimental/layout.pyi +95 -87
  197. reflex/experimental/misc.py +1 -1
  198. reflex/istate/__init__.py +1 -0
  199. reflex/istate/proxy.py +33 -0
  200. reflex/istate/wrappers.py +27 -0
  201. reflex/model.py +7 -7
  202. reflex/page.py +2 -1
  203. reflex/reflex.py +142 -8
  204. reflex/state.py +127 -46
  205. reflex/testing.py +9 -7
  206. reflex/utils/console.py +0 -1
  207. reflex/utils/exceptions.py +31 -3
  208. reflex/utils/exec.py +33 -14
  209. reflex/utils/format.py +15 -12
  210. reflex/utils/net.py +1 -1
  211. reflex/utils/path_ops.py +2 -2
  212. reflex/utils/prerequisites.py +82 -46
  213. reflex/utils/pyi_generator.py +63 -20
  214. reflex/utils/registry.py +1 -1
  215. reflex/utils/serializers.py +75 -36
  216. reflex/utils/telemetry.py +3 -2
  217. reflex/utils/types.py +125 -10
  218. reflex/vars/base.py +131 -119
  219. reflex/vars/function.py +59 -12
  220. reflex/vars/number.py +3 -1
  221. reflex/vars/object.py +30 -24
  222. reflex/vars/sequence.py +7 -7
  223. {reflex-0.6.4a3.dist-info → reflex-0.6.5a1.dist-info}/METADATA +3 -3
  224. reflex-0.6.5a1.dist-info/RECORD +394 -0
  225. reflex-0.6.4a3.dist-info/RECORD +0 -391
  226. {reflex-0.6.4a3.dist-info → reflex-0.6.5a1.dist-info}/LICENSE +0 -0
  227. {reflex-0.6.4a3.dist-info → reflex-0.6.5a1.dist-info}/WHEEL +0 -0
  228. {reflex-0.6.4a3.dist-info → reflex-0.6.5a1.dist-info}/entry_points.txt +0 -0
reflex/app.py CHANGED
@@ -6,23 +6,25 @@ import asyncio
6
6
  import concurrent.futures
7
7
  import contextlib
8
8
  import copy
9
+ import dataclasses
9
10
  import functools
10
11
  import inspect
11
12
  import io
12
13
  import json
13
14
  import multiprocessing
14
- import os
15
15
  import platform
16
16
  import sys
17
17
  import traceback
18
18
  from datetime import datetime
19
19
  from pathlib import Path
20
20
  from typing import (
21
+ TYPE_CHECKING,
21
22
  Any,
22
23
  AsyncIterator,
23
24
  Callable,
24
25
  Coroutine,
25
26
  Dict,
27
+ Generic,
26
28
  List,
27
29
  Optional,
28
30
  Set,
@@ -44,10 +46,9 @@ from starlette_admin.contrib.sqla.view import ModelView
44
46
  from reflex import constants
45
47
  from reflex.admin import AdminDash
46
48
  from reflex.app_mixins import AppMixin, LifespanMixin, MiddlewareMixin
47
- from reflex.base import Base
48
49
  from reflex.compiler import compiler
49
50
  from reflex.compiler import utils as compiler_utils
50
- from reflex.compiler.compiler import ExecutorSafeFunctions
51
+ from reflex.compiler.compiler import ExecutorSafeFunctions, compile_theme
51
52
  from reflex.components.base.app_wrap import AppWrap
52
53
  from reflex.components.base.error_boundary import ErrorBoundary
53
54
  from reflex.components.base.fragment import Fragment
@@ -65,11 +66,17 @@ from reflex.components.core.client_side_routing import (
65
66
  from reflex.components.core.upload import Upload, get_upload_dir
66
67
  from reflex.components.radix import themes
67
68
  from reflex.config import environment, get_config
68
- from reflex.event import Event, EventHandler, EventSpec, window_alert
69
- from reflex.model import Model, get_db_status
70
- from reflex.page import (
71
- DECORATED_PAGES,
69
+ from reflex.event import (
70
+ BASE_STATE,
71
+ Event,
72
+ EventHandler,
73
+ EventSpec,
74
+ EventType,
75
+ IndividualEventType,
76
+ window_alert,
72
77
  )
78
+ from reflex.model import Model, get_db_status
79
+ from reflex.page import DECORATED_PAGES
73
80
  from reflex.route import (
74
81
  get_route_args,
75
82
  replace_brackets_with_keywords,
@@ -85,9 +92,12 @@ from reflex.state import (
85
92
  code_uses_state_contexts,
86
93
  )
87
94
  from reflex.utils import codespaces, console, exceptions, format, prerequisites, types
88
- from reflex.utils.exec import is_prod_mode, is_testing_env, should_skip_compile
95
+ from reflex.utils.exec import is_prod_mode, is_testing_env
89
96
  from reflex.utils.imports import ImportVar
90
97
 
98
+ if TYPE_CHECKING:
99
+ from reflex.vars import Var
100
+
91
101
  # Define custom types.
92
102
  ComponentCallable = Callable[[], Component]
93
103
  Reducer = Callable[[Event], Coroutine[Any, Any, StateUpdate]]
@@ -170,7 +180,23 @@ class OverlayFragment(Fragment):
170
180
  pass
171
181
 
172
182
 
173
- class App(MiddlewareMixin, LifespanMixin, Base):
183
+ @dataclasses.dataclass(
184
+ frozen=True,
185
+ )
186
+ class UnevaluatedPage(Generic[BASE_STATE]):
187
+ """An uncompiled page."""
188
+
189
+ component: Union[Component, ComponentCallable]
190
+ route: str
191
+ title: Union[Var, str, None]
192
+ description: Union[Var, str, None]
193
+ image: str
194
+ on_load: Union[EventType[[], BASE_STATE], None]
195
+ meta: List[Dict[str, str]]
196
+
197
+
198
+ @dataclasses.dataclass()
199
+ class App(MiddlewareMixin, LifespanMixin):
174
200
  """The main Reflex app that encapsulates the backend and frontend.
175
201
 
176
202
  Every Reflex app needs an app defined in its main module.
@@ -192,24 +218,26 @@ class App(MiddlewareMixin, LifespanMixin, Base):
192
218
  """
193
219
 
194
220
  # The global [theme](https://reflex.dev/docs/styling/theming/#theme) for the entire app.
195
- theme: Optional[Component] = themes.theme(accent_color="blue")
221
+ theme: Optional[Component] = dataclasses.field(
222
+ default_factory=lambda: themes.theme(accent_color="blue")
223
+ )
196
224
 
197
225
  # The [global style](https://reflex.dev/docs/styling/overview/#global-styles}) for the app.
198
- style: ComponentStyle = {}
226
+ style: ComponentStyle = dataclasses.field(default_factory=dict)
199
227
 
200
228
  # A list of URLs to [stylesheets](https://reflex.dev/docs/styling/custom-stylesheets/) to include in the app.
201
- stylesheets: List[str] = []
229
+ stylesheets: List[str] = dataclasses.field(default_factory=list)
202
230
 
203
231
  # A component that is present on every page (defaults to the Connection Error banner).
204
232
  overlay_component: Optional[Union[Component, ComponentCallable]] = (
205
- default_overlay_component()
233
+ dataclasses.field(default_factory=default_overlay_component)
206
234
  )
207
235
 
208
236
  # Error boundary component to wrap the app with.
209
237
  error_boundary: Optional[ComponentCallable] = default_error_boundary
210
238
 
211
239
  # Components to add to the head of every page.
212
- head_components: List[Component] = []
240
+ head_components: List[Component] = dataclasses.field(default_factory=list)
213
241
 
214
242
  # The Socket.IO AsyncServer instance.
215
243
  sio: Optional[AsyncServer] = None
@@ -220,8 +248,13 @@ class App(MiddlewareMixin, LifespanMixin, Base):
220
248
  # Attributes to add to the html root tag of every page.
221
249
  html_custom_attrs: Optional[Dict[str, str]] = None
222
250
 
251
+ # A map from a route to an unevaluated page. PRIVATE.
252
+ unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
253
+ default_factory=dict
254
+ )
255
+
223
256
  # A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
224
- pages: Dict[str, Component] = {}
257
+ pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
225
258
 
226
259
  # The backend API object. PRIVATE.
227
260
  api: FastAPI = None # type: ignore
@@ -233,7 +266,9 @@ class App(MiddlewareMixin, LifespanMixin, Base):
233
266
  _state_manager: Optional[StateManager] = None
234
267
 
235
268
  # Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
236
- load_events: Dict[str, List[Union[EventHandler, EventSpec]]] = {}
269
+ load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
270
+ default_factory=dict
271
+ )
237
272
 
238
273
  # Admin dashboard to view and manage the database. PRIVATE.
239
274
  admin_dash: Optional[AdminDash] = None
@@ -242,7 +277,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
242
277
  event_namespace: Optional[EventNamespace] = None
243
278
 
244
279
  # Background tasks that are currently running. PRIVATE.
245
- background_tasks: Set[asyncio.Task] = set()
280
+ background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
246
281
 
247
282
  # Frontend Error Handler Function
248
283
  frontend_exception_handler: Callable[[Exception], None] = (
@@ -254,23 +289,14 @@ class App(MiddlewareMixin, LifespanMixin, Base):
254
289
  [Exception], Union[EventSpec, List[EventSpec], None]
255
290
  ] = default_backend_exception_handler
256
291
 
257
- def __init__(self, **kwargs):
292
+ def __post_init__(self):
258
293
  """Initialize the app.
259
294
 
260
- Args:
261
- **kwargs: Kwargs to initialize the app with.
262
-
263
295
  Raises:
264
296
  ValueError: If the event namespace is not provided in the config.
265
297
  Also, if there are multiple client subclasses of rx.BaseState(Subclasses of rx.BaseState should consist
266
298
  of the DefaultState and the client app state).
267
299
  """
268
- if "connect_error_component" in kwargs:
269
- raise ValueError(
270
- "`connect_error_component` is deprecated, use `overlay_component` instead"
271
- )
272
- super().__init__(**kwargs)
273
-
274
300
  # Special case to allow test cases have multiple subclasses of rx.BaseState.
275
301
  if not is_testing_env() and BaseState.__subclasses__() != [State]:
276
302
  # Only rx.State is allowed as Base State subclass.
@@ -381,8 +407,8 @@ class App(MiddlewareMixin, LifespanMixin, Base):
381
407
 
382
408
  def _add_optional_endpoints(self):
383
409
  """Add optional api endpoints (_upload)."""
384
- # To upload files.
385
410
  if Upload.is_used:
411
+ # To upload files.
386
412
  self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
387
413
 
388
414
  # To access uploaded files.
@@ -442,12 +468,10 @@ class App(MiddlewareMixin, LifespanMixin, Base):
442
468
  self,
443
469
  component: Component | ComponentCallable,
444
470
  route: str | None = None,
445
- title: str | None = None,
446
- description: str | None = None,
471
+ title: str | Var | None = None,
472
+ description: str | Var | None = None,
447
473
  image: str = constants.DefaultPage.IMAGE,
448
- on_load: (
449
- EventHandler | EventSpec | list[EventHandler | EventSpec] | None
450
- ) = None,
474
+ on_load: EventType[[], BASE_STATE] | None = None,
451
475
  meta: list[dict[str, str]] = constants.DefaultPage.META_LIST,
452
476
  ):
453
477
  """Add a page to the app.
@@ -479,13 +503,13 @@ class App(MiddlewareMixin, LifespanMixin, Base):
479
503
  # Check if the route given is valid
480
504
  verify_route_validity(route)
481
505
 
482
- if route in self.pages and os.getenv(constants.RELOAD_CONFIG):
506
+ if route in self.unevaluated_pages and environment.RELOAD_CONFIG.is_set():
483
507
  # when the app is reloaded(typically for app harness tests), we should maintain
484
508
  # the latest render function of a route.This applies typically to decorated pages
485
509
  # since they are only added when app._compile is called.
486
- self.pages.pop(route)
510
+ self.unevaluated_pages.pop(route)
487
511
 
488
- if route in self.pages:
512
+ if route in self.unevaluated_pages:
489
513
  route_name = (
490
514
  f"`{route}` or `/`"
491
515
  if route == constants.PageNames.INDEX_ROUTE
@@ -501,59 +525,39 @@ class App(MiddlewareMixin, LifespanMixin, Base):
501
525
  state = self.state if self.state else State
502
526
  state.setup_dynamic_args(get_route_args(route))
503
527
 
504
- # Generate the component if it is a callable.
505
- component = self._generate_component(component)
506
-
507
- # unpack components that return tuples in an rx.fragment.
508
- if isinstance(component, tuple):
509
- component = Fragment.create(*component)
510
-
511
- # Ensure state is enabled if this page uses state.
512
- if self.state is None:
513
- if on_load or component._has_stateful_event_triggers():
514
- self._enable_state()
515
- else:
516
- for var in component._get_vars(include_children=True):
517
- var_data = var._get_all_var_data()
518
- if not var_data:
519
- continue
520
- if not var_data.state:
521
- continue
522
- self._enable_state()
523
- break
524
-
525
- component = OverlayFragment.create(component)
528
+ if on_load:
529
+ self.load_events[route] = (
530
+ on_load if isinstance(on_load, list) else [on_load]
531
+ )
526
532
 
527
- meta_args = {
528
- "title": (
529
- title
530
- if title is not None
531
- else format.make_default_page_title(get_config().app_name, route)
532
- ),
533
- "image": image,
534
- "meta": meta,
535
- }
533
+ self.unevaluated_pages[route] = UnevaluatedPage(
534
+ component=component,
535
+ route=route,
536
+ title=title,
537
+ description=description,
538
+ image=image,
539
+ on_load=on_load,
540
+ meta=meta,
541
+ )
536
542
 
537
- if description is not None:
538
- meta_args["description"] = description
543
+ def _compile_page(self, route: str):
544
+ """Compile a page.
539
545
 
540
- # Add meta information to the component.
541
- compiler_utils.add_meta(
542
- component,
543
- **meta_args,
546
+ Args:
547
+ route: The route of the page to compile.
548
+ """
549
+ component, enable_state = compiler.compile_unevaluated_page(
550
+ route, self.unevaluated_pages[route], self.state, self.style, self.theme
544
551
  )
545
552
 
553
+ if enable_state:
554
+ self._enable_state()
555
+
546
556
  # Add the page.
547
557
  self._check_routes_conflict(route)
548
558
  self.pages[route] = component
549
559
 
550
- # Add the load events.
551
- if on_load:
552
- if not isinstance(on_load, list):
553
- on_load = [on_load]
554
- self.load_events[route] = on_load
555
-
556
- def get_load_events(self, route: str) -> list[EventHandler | EventSpec]:
560
+ def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
557
561
  """Get the load events for a route.
558
562
 
559
563
  Args:
@@ -612,9 +616,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
612
616
  title: str = constants.Page404.TITLE,
613
617
  image: str = constants.Page404.IMAGE,
614
618
  description: str = constants.Page404.DESCRIPTION,
615
- on_load: (
616
- EventHandler | EventSpec | list[EventHandler | EventSpec] | None
617
- ) = None,
619
+ on_load: EventType[[], BASE_STATE] | None = None,
618
620
  meta: list[dict[str, str]] = constants.DefaultPage.META_LIST,
619
621
  ):
620
622
  """Define a custom 404 page for any url having no match.
@@ -718,7 +720,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
718
720
  Whether the app should be compiled.
719
721
  """
720
722
  # Check the environment variable.
721
- if should_skip_compile():
723
+ if environment.REFLEX_SKIP_COMPILE.get():
722
724
  return False
723
725
 
724
726
  nocompile = prerequisites.get_web_dir() / constants.NOCOMPILE_FILE
@@ -827,13 +829,33 @@ class App(MiddlewareMixin, LifespanMixin, Base):
827
829
  """
828
830
  from reflex.utils.exceptions import ReflexRuntimeError
829
831
 
832
+ self.pages = {}
833
+
830
834
  def get_compilation_time() -> str:
831
835
  return str(datetime.now().time()).split(".")[0]
832
836
 
833
837
  # Render a default 404 page if the user didn't supply one
834
- if constants.Page404.SLUG not in self.pages:
838
+ if constants.Page404.SLUG not in self.unevaluated_pages:
835
839
  self.add_custom_404_page()
836
840
 
841
+ # Fix up the style.
842
+ self.style = evaluate_style_namespaces(self.style)
843
+
844
+ # Add the app wrappers.
845
+ app_wrappers: Dict[tuple[int, str], Component] = {
846
+ # Default app wrap component renders {children}
847
+ (0, "AppWrap"): AppWrap.create()
848
+ }
849
+
850
+ if self.theme is not None:
851
+ # If a theme component was provided, wrap the app with it
852
+ app_wrappers[(20, "Theme")] = self.theme
853
+ # Fix #2992 by removing the top-level appearance prop
854
+ self.theme.appearance = None
855
+
856
+ for route in self.unevaluated_pages:
857
+ self._compile_page(route)
858
+
837
859
  # Add the optional endpoints (_upload)
838
860
  self._add_optional_endpoints()
839
861
 
@@ -868,28 +890,15 @@ class App(MiddlewareMixin, LifespanMixin, Base):
868
890
  # Store the compile results.
869
891
  compile_results = []
870
892
 
871
- # Add the app wrappers.
872
- app_wrappers: Dict[tuple[int, str], Component] = {
873
- # Default app wrap component renders {children}
874
- (0, "AppWrap"): AppWrap.create()
875
- }
876
- if self.theme is not None:
877
- # If a theme component was provided, wrap the app with it
878
- app_wrappers[(20, "Theme")] = self.theme
879
-
880
893
  progress.advance(task)
881
894
 
882
- # Fix up the style.
883
- self.style = evaluate_style_namespaces(self.style)
884
-
885
895
  # Track imports and custom components found.
886
896
  all_imports = {}
887
897
  custom_components = set()
888
898
 
889
- for _route, component in self.pages.items():
890
- # Merge the component style with the app style.
891
- component._add_style_recursive(self.style, self.theme)
892
-
899
+ # This has to happen before compiling stateful components as that
900
+ # prevents recursive functions from reaching all components.
901
+ for component in self.pages.values():
893
902
  # Add component._get_all_imports() to all_imports.
894
903
  all_imports.update(component._get_all_imports())
895
904
 
@@ -899,8 +908,6 @@ class App(MiddlewareMixin, LifespanMixin, Base):
899
908
  # Add the custom components from the page to the set.
900
909
  custom_components |= component._get_all_custom_components()
901
910
 
902
- progress.advance(task)
903
-
904
911
  # Perform auto-memoization of stateful components.
905
912
  (
906
913
  stateful_components_path,
@@ -918,6 +925,8 @@ class App(MiddlewareMixin, LifespanMixin, Base):
918
925
  )
919
926
  compile_results.append((stateful_components_path, stateful_components_code))
920
927
 
928
+ progress.advance(task)
929
+
921
930
  # Compile the root document before fork.
922
931
  compile_results.append(
923
932
  compiler.compile_document_root(
@@ -927,37 +936,14 @@ class App(MiddlewareMixin, LifespanMixin, Base):
927
936
  )
928
937
  )
929
938
 
930
- # Compile the contexts before fork.
931
- compile_results.append(
932
- compiler.compile_contexts(self.state, self.theme),
933
- )
934
- # Fix #2992 by removing the top-level appearance prop
935
- if self.theme is not None:
936
- self.theme.appearance = None
937
-
938
- app_root = self._app_root(app_wrappers=app_wrappers)
939
-
940
939
  progress.advance(task)
941
940
 
942
- # Prepopulate the global ExecutorSafeFunctions class with input data required by the compile functions.
943
- # This is required for multiprocessing to work, in presence of non-picklable inputs.
944
- for route, component in zip(self.pages, page_components):
945
- ExecutorSafeFunctions.COMPILE_PAGE_ARGS_BY_ROUTE[route] = (
946
- route,
947
- component,
948
- self.state,
949
- )
950
-
951
- ExecutorSafeFunctions.COMPILE_APP_APP_ROOT = app_root
952
- ExecutorSafeFunctions.CUSTOM_COMPONENTS = custom_components
953
- ExecutorSafeFunctions.STYLE = self.style
954
-
955
941
  # Use a forking process pool, if possible. Much faster, especially for large sites.
956
942
  # Fallback to ThreadPoolExecutor as something that will always work.
957
943
  executor = None
958
944
  if (
959
945
  platform.system() in ("Linux", "Darwin")
960
- and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES)
946
+ and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES.get())
961
947
  is not None
962
948
  ):
963
949
  executor = concurrent.futures.ProcessPoolExecutor(
@@ -966,39 +952,34 @@ class App(MiddlewareMixin, LifespanMixin, Base):
966
952
  )
967
953
  else:
968
954
  executor = concurrent.futures.ThreadPoolExecutor(
969
- max_workers=environment.REFLEX_COMPILE_THREADS
955
+ max_workers=environment.REFLEX_COMPILE_THREADS.get()
970
956
  )
971
957
 
958
+ for route, component in zip(self.pages, page_components):
959
+ ExecutorSafeFunctions.COMPONENTS[route] = component
960
+
961
+ ExecutorSafeFunctions.STATE = self.state
962
+
972
963
  with executor:
973
964
  result_futures = []
974
- custom_components_future = None
975
-
976
- def _mark_complete(_=None):
977
- progress.advance(task)
978
965
 
979
966
  def _submit_work(fn, *args, **kwargs):
980
967
  f = executor.submit(fn, *args, **kwargs)
981
- f.add_done_callback(_mark_complete)
968
+ # f = executor.apipe(fn, *args, **kwargs)
982
969
  result_futures.append(f)
983
970
 
984
- # Compile all page components.
971
+ # Compile the pre-compiled pages.
985
972
  for route in self.pages:
986
- _submit_work(ExecutorSafeFunctions.compile_page, route)
987
-
988
- # Compile the app wrapper.
989
- _submit_work(ExecutorSafeFunctions.compile_app)
990
-
991
- # Compile the custom components.
992
- custom_components_future = executor.submit(
993
- ExecutorSafeFunctions.compile_custom_components,
994
- )
995
- custom_components_future.add_done_callback(_mark_complete)
973
+ _submit_work(
974
+ ExecutorSafeFunctions.compile_page,
975
+ route,
976
+ )
996
977
 
997
978
  # Compile the root stylesheet with base styles.
998
979
  _submit_work(compiler.compile_root_stylesheet, self.stylesheets)
999
980
 
1000
981
  # Compile the theme.
1001
- _submit_work(ExecutorSafeFunctions.compile_theme)
982
+ _submit_work(compile_theme, self.style)
1002
983
 
1003
984
  # Compile the Tailwind config.
1004
985
  if config.tailwind is not None:
@@ -1012,21 +993,34 @@ class App(MiddlewareMixin, LifespanMixin, Base):
1012
993
  # Wait for all compilation tasks to complete.
1013
994
  for future in concurrent.futures.as_completed(result_futures):
1014
995
  compile_results.append(future.result())
996
+ progress.advance(task)
1015
997
 
1016
- # Special case for custom_components, since we need the compiled imports
1017
- # to install proper frontend packages.
1018
- (
1019
- *custom_components_result,
1020
- custom_components_imports,
1021
- ) = custom_components_future.result()
1022
- compile_results.append(custom_components_result)
1023
- all_imports.update(custom_components_imports)
998
+ app_root = self._app_root(app_wrappers=app_wrappers)
1024
999
 
1025
1000
  # Get imports from AppWrap components.
1026
1001
  all_imports.update(app_root._get_all_imports())
1027
1002
 
1028
1003
  progress.advance(task)
1029
1004
 
1005
+ # Compile the contexts.
1006
+ compile_results.append(
1007
+ compiler.compile_contexts(self.state, self.theme),
1008
+ )
1009
+ progress.advance(task)
1010
+
1011
+ # Compile the app root.
1012
+ compile_results.append(
1013
+ compiler.compile_app(app_root),
1014
+ )
1015
+ progress.advance(task)
1016
+
1017
+ # Compile custom components.
1018
+ *custom_components_result, custom_components_imports = (
1019
+ compiler.compile_components(custom_components)
1020
+ )
1021
+ compile_results.append(custom_components_result)
1022
+ all_imports.update(custom_components_imports)
1023
+
1030
1024
  progress.advance(task)
1031
1025
  progress.stop()
1032
1026
 
@@ -1393,7 +1387,7 @@ def upload(app: App):
1393
1387
  if isinstance(func, EventHandler):
1394
1388
  if func.is_background:
1395
1389
  raise UploadTypeError(
1396
- f"@rx.background is not supported for upload handler `{handler}`.",
1390
+ f"@rx.event(background=True) is not supported for upload handler `{handler}`.",
1397
1391
  )
1398
1392
  func = func.fn
1399
1393
  if isinstance(func, functools.partial):
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import contextlib
7
+ import dataclasses
7
8
  import functools
8
9
  import inspect
9
10
  from typing import Callable, Coroutine, Set, Union
@@ -16,11 +17,14 @@ from reflex.utils.exceptions import InvalidLifespanTaskType
16
17
  from .mixin import AppMixin
17
18
 
18
19
 
20
+ @dataclasses.dataclass
19
21
  class LifespanMixin(AppMixin):
20
22
  """A Mixin that allow tasks to run during the whole app lifespan."""
21
23
 
22
24
  # Lifespan tasks that are planned to run.
23
- lifespan_tasks: Set[Union[asyncio.Task, Callable]] = set()
25
+ lifespan_tasks: Set[Union[asyncio.Task, Callable]] = dataclasses.field(
26
+ default_factory=set
27
+ )
24
28
 
25
29
  @contextlib.asynccontextmanager
26
30
  async def _run_lifespan_tasks(self, app: FastAPI):
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import dataclasses
6
7
  from typing import List
7
8
 
8
9
  from reflex.event import Event
@@ -12,11 +13,12 @@ from reflex.state import BaseState, StateUpdate
12
13
  from .mixin import AppMixin
13
14
 
14
15
 
16
+ @dataclasses.dataclass
15
17
  class MiddlewareMixin(AppMixin):
16
18
  """Middleware Mixin that allow to add middleware to the app."""
17
19
 
18
20
  # Middleware to add to the app. Users should use `add_middleware`. PRIVATE.
19
- middleware: List[Middleware] = []
21
+ middleware: List[Middleware] = dataclasses.field(default_factory=list)
20
22
 
21
23
  def _init_mixin(self):
22
24
  self.middleware.append(HydrateMiddleware())
@@ -1,9 +1,10 @@
1
1
  """Default mixin for all app mixins."""
2
2
 
3
- from reflex.base import Base
3
+ import dataclasses
4
4
 
5
5
 
6
- class AppMixin(Base):
6
+ @dataclasses.dataclass
7
+ class AppMixin:
7
8
  """Define the base class for all app mixins."""
8
9
 
9
10
  def _init_mixin(self):
reflex/base.py CHANGED
@@ -16,9 +16,6 @@ except ModuleNotFoundError:
16
16
  from pydantic.fields import ModelField # type: ignore
17
17
 
18
18
 
19
- from reflex import constants
20
-
21
-
22
19
  def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
23
20
  """Ensure that the field's name does not shadow an existing attribute of the model.
24
21
 
@@ -31,7 +28,8 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
31
28
  """
32
29
  from reflex.utils.exceptions import VarNameError
33
30
 
34
- reload = os.getenv(constants.RELOAD_CONFIG) == "True"
31
+ # can't use reflex.config.environment here cause of circular import
32
+ reload = os.getenv("__RELOAD_CONFIG", "").lower() == "true"
35
33
  for base in bases:
36
34
  try:
37
35
  if not reload and getattr(base, field_name, None):