reflex 0.7.0a4__py3-none-any.whl → 0.7.1__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 (128) hide show
  1. reflex/.templates/jinja/web/package.json.jinja2 +7 -1
  2. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
  3. reflex/__init__.py +1 -0
  4. reflex/__init__.pyi +1 -0
  5. reflex/app.py +269 -86
  6. reflex/base.py +4 -10
  7. reflex/compiler/compiler.py +46 -12
  8. reflex/compiler/templates.py +1 -2
  9. reflex/compiler/utils.py +23 -14
  10. reflex/components/base/bare.py +109 -16
  11. reflex/components/component.py +179 -124
  12. reflex/components/core/__init__.py +1 -0
  13. reflex/components/core/__init__.pyi +1 -0
  14. reflex/components/core/auto_scroll.py +114 -0
  15. reflex/components/core/auto_scroll.pyi +284 -0
  16. reflex/components/core/banner.py +40 -9
  17. reflex/components/core/banner.pyi +400 -87
  18. reflex/components/core/breakpoints.py +1 -1
  19. reflex/components/core/cond.py +0 -8
  20. reflex/components/core/foreach.py +12 -2
  21. reflex/components/core/html.pyi +200 -19
  22. reflex/components/core/match.py +4 -4
  23. reflex/components/core/sticky.py +4 -30
  24. reflex/components/core/sticky.pyi +874 -90
  25. reflex/components/core/upload.py +3 -5
  26. reflex/components/core/upload.pyi +2 -4
  27. reflex/components/datadisplay/code.py +36 -10
  28. reflex/components/datadisplay/code.pyi +1 -1
  29. reflex/components/datadisplay/dataeditor.py +1 -3
  30. reflex/components/datadisplay/dataeditor.pyi +1 -3
  31. reflex/components/el/elements/base.py +95 -17
  32. reflex/components/el/elements/base.pyi +278 -19
  33. reflex/components/el/elements/forms.py +124 -102
  34. reflex/components/el/elements/forms.pyi +2787 -365
  35. reflex/components/el/elements/inline.py +24 -15
  36. reflex/components/el/elements/inline.pyi +5655 -546
  37. reflex/components/el/elements/media.py +79 -95
  38. reflex/components/el/elements/media.pyi +5167 -565
  39. reflex/components/el/elements/metadata.py +19 -17
  40. reflex/components/el/elements/metadata.pyi +841 -89
  41. reflex/components/el/elements/other.py +3 -5
  42. reflex/components/el/elements/other.pyi +1404 -137
  43. reflex/components/el/elements/scripts.py +10 -13
  44. reflex/components/el/elements/scripts.pyi +634 -65
  45. reflex/components/el/elements/sectioning.pyi +3001 -286
  46. reflex/components/el/elements/tables.py +14 -35
  47. reflex/components/el/elements/tables.pyi +2029 -218
  48. reflex/components/el/elements/typography.py +10 -13
  49. reflex/components/el/elements/typography.pyi +3014 -297
  50. reflex/components/lucide/icon.py +22 -6
  51. reflex/components/markdown/markdown.py +30 -10
  52. reflex/components/markdown/markdown.pyi +3 -2
  53. reflex/components/plotly/plotly.py +1 -3
  54. reflex/components/plotly/plotly.pyi +1 -3
  55. reflex/components/radix/primitives/form.pyi +624 -93
  56. reflex/components/radix/themes/color_mode.py +1 -1
  57. reflex/components/radix/themes/color_mode.pyi +213 -31
  58. reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
  59. reflex/components/radix/themes/components/badge.pyi +199 -18
  60. reflex/components/radix/themes/components/button.pyi +213 -31
  61. reflex/components/radix/themes/components/callout.pyi +1000 -95
  62. reflex/components/radix/themes/components/card.pyi +199 -18
  63. reflex/components/radix/themes/components/context_menu.py +79 -1
  64. reflex/components/radix/themes/components/context_menu.pyi +320 -1
  65. reflex/components/radix/themes/components/dialog.pyi +199 -18
  66. reflex/components/radix/themes/components/hover_card.pyi +199 -18
  67. reflex/components/radix/themes/components/icon_button.pyi +213 -31
  68. reflex/components/radix/themes/components/inset.pyi +199 -18
  69. reflex/components/radix/themes/components/popover.pyi +199 -18
  70. reflex/components/radix/themes/components/table.pyi +1437 -154
  71. reflex/components/radix/themes/components/text_area.py +2 -2
  72. reflex/components/radix/themes/components/text_area.pyi +201 -20
  73. reflex/components/radix/themes/components/text_field.py +1 -1
  74. reflex/components/radix/themes/components/text_field.pyi +444 -88
  75. reflex/components/radix/themes/layout/box.pyi +200 -19
  76. reflex/components/radix/themes/layout/center.pyi +199 -18
  77. reflex/components/radix/themes/layout/container.pyi +199 -18
  78. reflex/components/radix/themes/layout/flex.pyi +199 -18
  79. reflex/components/radix/themes/layout/grid.pyi +199 -18
  80. reflex/components/radix/themes/layout/list.pyi +604 -57
  81. reflex/components/radix/themes/layout/section.pyi +199 -18
  82. reflex/components/radix/themes/layout/spacer.pyi +199 -18
  83. reflex/components/radix/themes/layout/stack.pyi +597 -54
  84. reflex/components/radix/themes/typography/blockquote.pyi +200 -19
  85. reflex/components/radix/themes/typography/code.pyi +199 -18
  86. reflex/components/radix/themes/typography/heading.pyi +199 -18
  87. reflex/components/radix/themes/typography/link.pyi +238 -28
  88. reflex/components/radix/themes/typography/text.pyi +1394 -127
  89. reflex/components/react_player/react_player.py +1 -1
  90. reflex/components/react_player/react_player.pyi +1 -3
  91. reflex/components/sonner/toast.py +41 -12
  92. reflex/components/sonner/toast.pyi +20 -6
  93. reflex/components/tags/iter_tag.py +4 -0
  94. reflex/components/tags/tag.py +3 -3
  95. reflex/config.py +187 -28
  96. reflex/constants/__init__.py +2 -0
  97. reflex/constants/base.py +6 -0
  98. reflex/constants/compiler.py +9 -0
  99. reflex/constants/event.py +1 -0
  100. reflex/constants/installer.py +8 -5
  101. reflex/constants/utils.py +1 -3
  102. reflex/event.py +7 -16
  103. reflex/experimental/layout.pyi +597 -54
  104. reflex/py.typed +0 -0
  105. reflex/reflex.py +44 -48
  106. reflex/state.py +49 -44
  107. reflex/style.py +15 -22
  108. reflex/testing.py +2 -0
  109. reflex/utils/build.py +12 -0
  110. reflex/utils/console.py +4 -0
  111. reflex/utils/decorator.py +25 -0
  112. reflex/utils/exec.py +92 -34
  113. reflex/utils/format.py +35 -6
  114. reflex/utils/path_ops.py +32 -1
  115. reflex/utils/prerequisites.py +45 -35
  116. reflex/utils/processes.py +12 -13
  117. reflex/utils/serializers.py +20 -43
  118. reflex/utils/telemetry.py +4 -15
  119. reflex/utils/types.py +36 -66
  120. reflex/vars/base.py +53 -76
  121. reflex/vars/function.py +17 -5
  122. reflex/vars/number.py +1 -1
  123. reflex/vars/sequence.py +80 -4
  124. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/METADATA +4 -5
  125. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/RECORD +128 -124
  126. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/LICENSE +0 -0
  127. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/WHEEL +0 -0
  128. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/entry_points.txt +0 -0
@@ -15,7 +15,13 @@
15
15
  "devDependencies": {
16
16
  {% for package, version in dev_dependencies.items() %}
17
17
  "{{ package }}": "{{ version }}"{% if not loop.last %},{% endif %}
18
-
18
+
19
+ {% endfor %}
20
+ },
21
+ "overrides": {
22
+ {% for package, version in overrides.items() %}
23
+ "{{ package }}": "{{ version }}"{% if not loop.last %},{% endif %}
24
+
19
25
  {% endfor %}
20
26
  }
21
27
  }
@@ -10,7 +10,9 @@ import {
10
10
  export default function RadixThemesColorModeProvider({ children }) {
11
11
  const { theme, resolvedTheme, setTheme } = useTheme();
12
12
  const [rawColorMode, setRawColorMode] = useState(defaultColorMode);
13
- const [resolvedColorMode, setResolvedColorMode] = useState("dark");
13
+ const [resolvedColorMode, setResolvedColorMode] = useState(
14
+ defaultColorMode === "dark" ? "dark" : "light"
15
+ );
14
16
 
15
17
  useEffect(() => {
16
18
  if (isDevMode) {
reflex/__init__.py CHANGED
@@ -248,6 +248,7 @@ COMPONENTS_CORE_MAPPING: dict = {
248
248
  "selected_files",
249
249
  "upload",
250
250
  ],
251
+ "components.core.auto_scroll": ["auto_scroll"],
251
252
  }
252
253
 
253
254
  COMPONENTS_BASE_MAPPING: dict = {
reflex/__init__.pyi CHANGED
@@ -34,6 +34,7 @@ from .components.component import Component as Component
34
34
  from .components.component import ComponentNamespace as ComponentNamespace
35
35
  from .components.component import NoSSRComponent as NoSSRComponent
36
36
  from .components.component import memo as memo
37
+ from .components.core.auto_scroll import auto_scroll as auto_scroll
37
38
  from .components.core.banner import connection_banner as connection_banner
38
39
  from .components.core.banner import connection_modal as connection_modal
39
40
  from .components.core.breakpoints import breakpoints as breakpoints
reflex/app.py CHANGED
@@ -11,17 +11,17 @@ import functools
11
11
  import inspect
12
12
  import io
13
13
  import json
14
- import multiprocessing
15
- import platform
16
14
  import sys
17
15
  import traceback
18
16
  from datetime import datetime
19
17
  from pathlib import Path
18
+ from timeit import default_timer as timer
20
19
  from types import SimpleNamespace
21
20
  from typing import (
22
21
  TYPE_CHECKING,
23
22
  Any,
24
23
  AsyncIterator,
24
+ BinaryIO,
25
25
  Callable,
26
26
  Coroutine,
27
27
  Dict,
@@ -35,12 +35,15 @@ from typing import (
35
35
  get_type_hints,
36
36
  )
37
37
 
38
- from fastapi import FastAPI, HTTPException, Request, UploadFile
38
+ from fastapi import FastAPI, HTTPException, Request
39
+ from fastapi import UploadFile as FastAPIUploadFile
39
40
  from fastapi.middleware import cors
40
41
  from fastapi.responses import JSONResponse, StreamingResponse
41
42
  from fastapi.staticfiles import StaticFiles
42
43
  from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
43
44
  from socketio import ASGIApp, AsyncNamespace, AsyncServer
45
+ from starlette.datastructures import Headers
46
+ from starlette.datastructures import UploadFile as StarletteUploadFile
44
47
  from starlette_admin.contrib.sqla.admin import Admin
45
48
  from starlette_admin.contrib.sqla.view import ModelView
46
49
 
@@ -72,7 +75,8 @@ from reflex.components.core.client_side_routing import (
72
75
  from reflex.components.core.sticky import sticky
73
76
  from reflex.components.core.upload import Upload, get_upload_dir
74
77
  from reflex.components.radix import themes
75
- from reflex.config import environment, get_config
78
+ from reflex.components.sonner.toast import toast
79
+ from reflex.config import ExecutorType, environment, get_config
76
80
  from reflex.event import (
77
81
  _EVENT_FIELDS,
78
82
  Event,
@@ -81,7 +85,6 @@ from reflex.event import (
81
85
  EventType,
82
86
  IndividualEventType,
83
87
  get_hydrate_event,
84
- window_alert,
85
88
  )
86
89
  from reflex.model import Model, get_db_status
87
90
  from reflex.page import DECORATED_PAGES
@@ -97,6 +100,7 @@ from reflex.state import (
97
100
  StateManager,
98
101
  StateUpdate,
99
102
  _substate_key,
103
+ all_base_state_classes,
100
104
  code_uses_state_contexts,
101
105
  )
102
106
  from reflex.utils import (
@@ -108,12 +112,13 @@ from reflex.utils import (
108
112
  prerequisites,
109
113
  types,
110
114
  )
111
- from reflex.utils.exec import is_prod_mode, is_testing_env
115
+ from reflex.utils.exec import get_compile_context, is_prod_mode, is_testing_env
112
116
  from reflex.utils.imports import ImportVar
113
117
 
114
118
  if TYPE_CHECKING:
115
119
  from reflex.vars import Var
116
120
 
121
+
117
122
  # Define custom types.
118
123
  ComponentCallable = Callable[[], Component]
119
124
  Reducer = Callable[[Event], Coroutine[Any, Any, StateUpdate]]
@@ -139,7 +144,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
139
144
  EventSpec: The window alert event.
140
145
 
141
146
  """
142
- from reflex.components.sonner.toast import Toaster, toast
147
+ from reflex.components.sonner.toast import toast
143
148
 
144
149
  error = traceback.format_exc()
145
150
 
@@ -150,18 +155,44 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
150
155
  if is_prod_mode()
151
156
  else [f"{type(exception).__name__}: {exception}.", "See logs for details."]
152
157
  )
153
- if Toaster.is_used:
154
- return toast(
155
- "An error occurred.",
156
- level="error",
157
- description="<br/>".join(error_message),
158
- position="top-center",
159
- id="backend_error",
160
- style={"width": "500px"},
161
- )
162
- else:
163
- error_message.insert(0, "An error occurred.")
164
- return window_alert("\n".join(error_message))
158
+
159
+ return toast(
160
+ "An error occurred.",
161
+ level="error",
162
+ fallback_to_alert=True,
163
+ description="<br/>".join(error_message),
164
+ position="top-center",
165
+ id="backend_error",
166
+ style={"width": "500px"},
167
+ )
168
+
169
+
170
+ def extra_overlay_function() -> Optional[Component]:
171
+ """Extra overlay function to add to the overlay component.
172
+
173
+ Returns:
174
+ The extra overlay function.
175
+ """
176
+ config = get_config()
177
+
178
+ extra_config = config.extra_overlay_function
179
+ config_overlay = None
180
+ if extra_config:
181
+ module, _, function_name = extra_config.rpartition(".")
182
+ try:
183
+ module = __import__(module)
184
+ config_overlay = Fragment.create(getattr(module, function_name)())
185
+ config_overlay._get_all_imports()
186
+ except Exception as e:
187
+ from reflex.compiler.utils import save_error
188
+
189
+ log_path = save_error(e)
190
+
191
+ console.error(
192
+ f"Error loading extra_overlay_function {extra_config}. Error saved to {log_path}"
193
+ )
194
+
195
+ return config_overlay
165
196
 
166
197
 
167
198
  def default_overlay_component() -> Component:
@@ -170,14 +201,21 @@ def default_overlay_component() -> Component:
170
201
  Returns:
171
202
  The default overlay_component, which is a connection_modal.
172
203
  """
173
- config = get_config()
204
+ from reflex.components.component import memo
205
+
206
+ def default_overlay_components():
207
+ return Fragment.create(
208
+ connection_pulser(),
209
+ connection_toaster(),
210
+ *(
211
+ [backend_disabled()]
212
+ if get_compile_context() == constants.CompileContext.DEPLOY
213
+ else []
214
+ ),
215
+ *codespaces.codespaces_auto_redirect(),
216
+ )
174
217
 
175
- return Fragment.create(
176
- connection_pulser(),
177
- connection_toaster(),
178
- *([backend_disabled()] if config.is_reflex_cloud else []),
179
- *codespaces.codespaces_auto_redirect(),
180
- )
218
+ return Fragment.create(memo(default_overlay_components)())
181
219
 
182
220
 
183
221
  def default_error_boundary(*children: Component) -> Component:
@@ -199,6 +237,53 @@ class OverlayFragment(Fragment):
199
237
  pass
200
238
 
201
239
 
240
+ @dataclasses.dataclass(frozen=True)
241
+ class UploadFile(StarletteUploadFile):
242
+ """A file uploaded to the server.
243
+
244
+ Args:
245
+ file: The standard Python file object (non-async).
246
+ filename: The original file name.
247
+ size: The size of the file in bytes.
248
+ headers: The headers of the request.
249
+ """
250
+
251
+ file: BinaryIO
252
+
253
+ path: Optional[Path] = dataclasses.field(default=None)
254
+
255
+ _deprecated_filename: Optional[str] = dataclasses.field(default=None)
256
+
257
+ size: Optional[int] = dataclasses.field(default=None)
258
+
259
+ headers: Headers = dataclasses.field(default_factory=Headers)
260
+
261
+ @property
262
+ def name(self) -> Optional[str]:
263
+ """Get the name of the uploaded file.
264
+
265
+ Returns:
266
+ The name of the uploaded file.
267
+ """
268
+ if self.path:
269
+ return self.path.name
270
+
271
+ @property
272
+ def filename(self) -> Optional[str]:
273
+ """Get the filename of the uploaded file.
274
+
275
+ Returns:
276
+ The filename of the uploaded file.
277
+ """
278
+ console.deprecate(
279
+ feature_name="UploadFile.filename",
280
+ reason="Use UploadFile.name instead.",
281
+ deprecation_version="0.7.1",
282
+ removal_version="0.8.0",
283
+ )
284
+ return self._deprecated_filename
285
+
286
+
202
287
  @dataclasses.dataclass(
203
288
  frozen=True,
204
289
  )
@@ -249,11 +334,26 @@ class App(MiddlewareMixin, LifespanMixin):
249
334
 
250
335
  # A component that is present on every page (defaults to the Connection Error banner).
251
336
  overlay_component: Optional[Union[Component, ComponentCallable]] = (
252
- dataclasses.field(default_factory=default_overlay_component)
337
+ dataclasses.field(default=None)
253
338
  )
254
339
 
255
340
  # Error boundary component to wrap the app with.
256
- error_boundary: Optional[ComponentCallable] = default_error_boundary
341
+ error_boundary: Optional[ComponentCallable] = dataclasses.field(default=None)
342
+
343
+ # App wraps to be applied to the whole app. Expected to be a dictionary of (order, name) to a function that takes whether the state is enabled and optionally returns a component.
344
+ app_wraps: Dict[tuple[int, str], Callable[[bool], Optional[Component]]] = (
345
+ dataclasses.field(
346
+ default_factory=lambda: {
347
+ (55, "ErrorBoundary"): (
348
+ lambda stateful: default_error_boundary() if stateful else None
349
+ ),
350
+ (5, "Overlay"): (
351
+ lambda stateful: default_overlay_component() if stateful else None
352
+ ),
353
+ (4, "ExtraOverlay"): lambda stateful: extra_overlay_function(),
354
+ }
355
+ )
356
+ )
257
357
 
258
358
  # Components to add to the head of every page.
259
359
  head_components: List[Component] = dataclasses.field(default_factory=list)
@@ -275,6 +375,9 @@ class App(MiddlewareMixin, LifespanMixin):
275
375
  # A map from a page route to the component to render. Users should use `add_page`.
276
376
  _pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
277
377
 
378
+ # A mapping of pages which created states as they were being evaluated.
379
+ _stateful_pages: Dict[str, None] = dataclasses.field(default_factory=dict)
380
+
278
381
  # The backend API object.
279
382
  _api: FastAPI | None = None
280
383
 
@@ -308,6 +411,9 @@ class App(MiddlewareMixin, LifespanMixin):
308
411
  [Exception], Union[EventSpec, List[EventSpec], None]
309
412
  ] = default_backend_exception_handler
310
413
 
414
+ # Put the toast provider in the app wrap.
415
+ toaster: Component | None = dataclasses.field(default_factory=toast.provider)
416
+
311
417
  @property
312
418
  def api(self) -> FastAPI | None:
313
419
  """Get the backend api.
@@ -489,8 +595,10 @@ class App(MiddlewareMixin, LifespanMixin):
489
595
  """Add optional api endpoints (_upload)."""
490
596
  if not self.api:
491
597
  return
492
-
493
- if Upload.is_used:
598
+ upload_is_used_marker = (
599
+ prerequisites.get_backend_dir() / constants.Dirs.UPLOAD_IS_USED
600
+ )
601
+ if Upload.is_used or upload_is_used_marker.exists():
494
602
  # To upload files.
495
603
  self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
496
604
 
@@ -500,10 +608,15 @@ class App(MiddlewareMixin, LifespanMixin):
500
608
  StaticFiles(directory=get_upload_dir()),
501
609
  name="uploaded_files",
502
610
  )
611
+
612
+ upload_is_used_marker.parent.mkdir(parents=True, exist_ok=True)
613
+ upload_is_used_marker.touch()
503
614
  if codespaces.is_running_in_codespaces():
504
615
  self.api.get(str(constants.Endpoint.AUTH_CODESPACE))(
505
616
  codespaces.auth_codespace
506
617
  )
618
+ if environment.REFLEX_ADD_ALL_ROUTES_ENDPOINT.get():
619
+ self.add_all_routes_endpoint()
507
620
 
508
621
  def _add_cors(self):
509
622
  """Add CORS middleware to the app."""
@@ -541,7 +654,9 @@ class App(MiddlewareMixin, LifespanMixin):
541
654
  Returns:
542
655
  The generated component.
543
656
  """
544
- return component if isinstance(component, Component) else component()
657
+ from reflex.compiler.compiler import into_component
658
+
659
+ return into_component(component)
545
660
 
546
661
  def add_page(
547
662
  self,
@@ -642,13 +757,19 @@ class App(MiddlewareMixin, LifespanMixin):
642
757
  route: The route of the page to compile.
643
758
  save_page: If True, the compiled page is saved to self._pages.
644
759
  """
760
+ n_states_before = len(all_base_state_classes)
645
761
  component, enable_state = compiler.compile_unevaluated_page(
646
762
  route, self._unevaluated_pages[route], self._state, self.style, self.theme
647
763
  )
648
764
 
765
+ # Indicate that the app should use state.
649
766
  if enable_state:
650
767
  self._enable_state()
651
768
 
769
+ # Indicate that evaluating this page creates one or more state classes.
770
+ if len(all_base_state_classes) > n_states_before:
771
+ self._stateful_pages[route] = None
772
+
652
773
  # Add the page.
653
774
  self._check_routes_conflict(route)
654
775
  if save_page:
@@ -863,33 +984,17 @@ class App(MiddlewareMixin, LifespanMixin):
863
984
  for k, component in self._pages.items():
864
985
  self._pages[k] = self._add_overlay_to_component(component)
865
986
 
866
- def _add_error_boundary_to_component(self, component: Component) -> Component:
867
- if self.error_boundary is None:
868
- return component
869
-
870
- component = self.error_boundary(*component.children)
871
-
872
- return component
873
-
874
- def _setup_error_boundary(self):
875
- """If a State is not used and no error_boundary is specified, do not render the error boundary."""
876
- if self._state is None and self.error_boundary is default_error_boundary:
877
- self.error_boundary = None
878
-
879
- for k, component in self._pages.items():
880
- # Skip the 404 page
881
- if k == constants.Page404.SLUG:
882
- continue
883
- self._pages[k] = self._add_error_boundary_to_component(component)
884
-
885
987
  def _setup_sticky_badge(self):
886
988
  """Add the sticky badge to the app."""
887
- for k, component in self._pages.items():
888
- # Would be nice to share single sticky_badge across all pages, but
889
- # it bungles the StatefulComponent compile step.
989
+ from reflex.components.component import memo
990
+
991
+ @memo
992
+ def memoized_badge():
890
993
  sticky_badge = sticky()
891
994
  sticky_badge._add_style_recursive({})
892
- self._pages[k] = Fragment.create(sticky_badge, component)
995
+ return sticky_badge
996
+
997
+ self.app_wraps[(0, "StickyBadge")] = lambda _: memoized_badge()
893
998
 
894
999
  def _apply_decorated_pages(self):
895
1000
  """Add @rx.page decorated pages to the app.
@@ -956,6 +1061,20 @@ class App(MiddlewareMixin, LifespanMixin):
956
1061
  def get_compilation_time() -> str:
957
1062
  return str(datetime.now().time()).split(".")[0]
958
1063
 
1064
+ should_compile = self._should_compile()
1065
+ backend_dir = prerequisites.get_backend_dir()
1066
+ if not should_compile and backend_dir.exists():
1067
+ stateful_pages_marker = backend_dir / constants.Dirs.STATEFUL_PAGES
1068
+ if stateful_pages_marker.exists():
1069
+ with stateful_pages_marker.open("r") as f:
1070
+ stateful_pages = json.load(f)
1071
+ for route in stateful_pages:
1072
+ console.info(f"BE Evaluating stateful page: {route}")
1073
+ self._compile_page(route, save_page=False)
1074
+ self._enable_state()
1075
+ self._add_optional_endpoints()
1076
+ return
1077
+
959
1078
  # Render a default 404 page if the user didn't supply one
960
1079
  if constants.Page404.SLUG not in self._unevaluated_pages:
961
1080
  self.add_page(route=constants.Page404.SLUG)
@@ -987,6 +1106,9 @@ class App(MiddlewareMixin, LifespanMixin):
987
1106
  console.debug(f"Evaluating page: {route}")
988
1107
  self._compile_page(route, save_page=should_compile)
989
1108
 
1109
+ # Save the pages which created new states at eval time.
1110
+ self._write_stateful_pages_marker()
1111
+
990
1112
  # Add the optional endpoints (_upload)
991
1113
  self._add_optional_endpoints()
992
1114
 
@@ -1012,24 +1134,48 @@ class App(MiddlewareMixin, LifespanMixin):
1012
1134
  )
1013
1135
 
1014
1136
  with console.timing("Evaluate Pages (Frontend)"):
1137
+ performance_metrics: list[tuple[str, float]] = []
1015
1138
  for route in self._unevaluated_pages:
1016
1139
  console.debug(f"Evaluating page: {route}")
1140
+ start = timer()
1017
1141
  self._compile_page(route, save_page=should_compile)
1142
+ end = timer()
1143
+ performance_metrics.append((route, end - start))
1018
1144
  progress.advance(task)
1145
+ console.debug(
1146
+ "Slowest pages:\n"
1147
+ + "\n".join(
1148
+ f"{route}: {time * 1000:.1f}ms"
1149
+ for route, time in sorted(
1150
+ performance_metrics, key=lambda x: x[1], reverse=True
1151
+ )[:10]
1152
+ )
1153
+ )
1154
+ # Save the pages which created new states at eval time.
1155
+ self._write_stateful_pages_marker()
1019
1156
 
1020
1157
  # Add the optional endpoints (_upload)
1021
1158
  self._add_optional_endpoints()
1022
1159
 
1023
1160
  self._validate_var_dependencies()
1024
1161
  self._setup_overlay_component()
1025
- self._setup_error_boundary()
1026
- if config.show_built_with_reflex:
1162
+
1163
+ if config.show_built_with_reflex is None:
1164
+ if (
1165
+ get_compile_context() == constants.CompileContext.DEPLOY
1166
+ and prerequisites.get_user_tier() in ["pro", "team", "enterprise"]
1167
+ ):
1168
+ config.show_built_with_reflex = False
1169
+ else:
1170
+ config.show_built_with_reflex = True
1171
+
1172
+ if is_prod_mode() and config.show_built_with_reflex:
1027
1173
  self._setup_sticky_badge()
1028
1174
 
1029
1175
  progress.advance(task)
1030
1176
 
1031
1177
  # Store the compile results.
1032
- compile_results = []
1178
+ compile_results: list[tuple[str, str]] = []
1033
1179
 
1034
1180
  progress.advance(task)
1035
1181
 
@@ -1049,6 +1195,35 @@ class App(MiddlewareMixin, LifespanMixin):
1049
1195
  # Add the custom components from the page to the set.
1050
1196
  custom_components |= component._get_all_custom_components()
1051
1197
 
1198
+ if (toaster := self.toaster) is not None:
1199
+ from reflex.components.component import memo
1200
+
1201
+ @memo
1202
+ def memoized_toast_provider():
1203
+ return toaster
1204
+
1205
+ toast_provider = Fragment.create(memoized_toast_provider())
1206
+
1207
+ app_wrappers[(1, "ToasterProvider")] = toast_provider
1208
+
1209
+ # Add the app wraps to the app.
1210
+ for key, app_wrap in self.app_wraps.items():
1211
+ component = app_wrap(self._state is not None)
1212
+ if component is not None:
1213
+ app_wrappers[key] = component
1214
+
1215
+ for component in app_wrappers.values():
1216
+ custom_components |= component._get_all_custom_components()
1217
+
1218
+ if self.error_boundary:
1219
+ console.deprecate(
1220
+ feature_name="App.error_boundary",
1221
+ reason="Use app_wraps instead.",
1222
+ deprecation_version="0.7.1",
1223
+ removal_version="0.8.0",
1224
+ )
1225
+ app_wrappers[(55, "ErrorBoundary")] = self.error_boundary()
1226
+
1052
1227
  # Perform auto-memoization of stateful components.
1053
1228
  with console.timing("Auto-memoize StatefulComponents"):
1054
1229
  (
@@ -1090,33 +1265,19 @@ class App(MiddlewareMixin, LifespanMixin):
1090
1265
  ),
1091
1266
  )
1092
1267
 
1093
- # Use a forking process pool, if possible. Much faster, especially for large sites.
1094
- # Fallback to ThreadPoolExecutor as something that will always work.
1095
- executor = None
1096
- if (
1097
- platform.system() in ("Linux", "Darwin")
1098
- and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES.get())
1099
- is not None
1100
- ):
1101
- executor = concurrent.futures.ProcessPoolExecutor(
1102
- max_workers=number_of_processes or None,
1103
- mp_context=multiprocessing.get_context("fork"),
1104
- )
1105
- else:
1106
- executor = concurrent.futures.ThreadPoolExecutor(
1107
- max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
1108
- )
1268
+ executor = ExecutorType.get_executor_from_environment()
1109
1269
 
1110
1270
  for route, component in zip(self._pages, page_components, strict=True):
1111
1271
  ExecutorSafeFunctions.COMPONENTS[route] = component
1112
1272
 
1113
1273
  ExecutorSafeFunctions.STATE = self._state
1114
1274
 
1115
- with executor:
1116
- result_futures = []
1275
+ with console.timing("Compile to Javascript"), executor as executor:
1276
+ result_futures: list[concurrent.futures.Future[tuple[str, str]]] = []
1117
1277
 
1118
- def _submit_work(fn: Callable, *args, **kwargs):
1278
+ def _submit_work(fn: Callable[..., tuple[str, str]], *args, **kwargs):
1119
1279
  f = executor.submit(fn, *args, **kwargs)
1280
+ f.add_done_callback(lambda _: progress.advance(task))
1120
1281
  result_futures.append(f)
1121
1282
 
1122
1283
  # Compile the pre-compiled pages.
@@ -1142,10 +1303,10 @@ class App(MiddlewareMixin, LifespanMixin):
1142
1303
  _submit_work(compiler.remove_tailwind_from_postcss)
1143
1304
 
1144
1305
  # Wait for all compilation tasks to complete.
1145
- with console.timing("Compile to Javascript"):
1146
- for future in concurrent.futures.as_completed(result_futures):
1147
- compile_results.append(future.result())
1148
- progress.advance(task)
1306
+ compile_results.extend(
1307
+ future.result()
1308
+ for future in concurrent.futures.as_completed(result_futures)
1309
+ )
1149
1310
 
1150
1311
  app_root = self._app_root(app_wrappers=app_wrappers)
1151
1312
 
@@ -1170,10 +1331,12 @@ class App(MiddlewareMixin, LifespanMixin):
1170
1331
  progress.advance(task)
1171
1332
 
1172
1333
  # Compile custom components.
1173
- *custom_components_result, custom_components_imports = (
1174
- compiler.compile_components(custom_components)
1175
- )
1176
- compile_results.append(custom_components_result)
1334
+ (
1335
+ custom_components_output,
1336
+ custom_components_result,
1337
+ custom_components_imports,
1338
+ ) = compiler.compile_components(custom_components)
1339
+ compile_results.append((custom_components_output, custom_components_result))
1177
1340
  all_imports.update(custom_components_imports)
1178
1341
 
1179
1342
  progress.advance(task)
@@ -1211,6 +1374,25 @@ class App(MiddlewareMixin, LifespanMixin):
1211
1374
  for output_path, code in compile_results:
1212
1375
  compiler_utils.write_page(output_path, code)
1213
1376
 
1377
+ def _write_stateful_pages_marker(self):
1378
+ """Write list of routes that create dynamic states for the backend to use later."""
1379
+ if self._state is not None:
1380
+ stateful_pages_marker = (
1381
+ prerequisites.get_backend_dir() / constants.Dirs.STATEFUL_PAGES
1382
+ )
1383
+ stateful_pages_marker.parent.mkdir(parents=True, exist_ok=True)
1384
+ with stateful_pages_marker.open("w") as f:
1385
+ json.dump(list(self._stateful_pages), f)
1386
+
1387
+ def add_all_routes_endpoint(self):
1388
+ """Add an endpoint to the app that returns all the routes."""
1389
+ if not self.api:
1390
+ return
1391
+
1392
+ @self.api.get(str(constants.Endpoint.ALL_ROUTES))
1393
+ async def all_routes():
1394
+ return list(self._unevaluated_pages.keys())
1395
+
1214
1396
  @contextlib.asynccontextmanager
1215
1397
  async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
1216
1398
  """Modify the state out of band.
@@ -1517,7 +1699,7 @@ def upload(app: App):
1517
1699
  The upload function.
1518
1700
  """
1519
1701
 
1520
- async def upload_file(request: Request, files: List[UploadFile]):
1702
+ async def upload_file(request: Request, files: List[FastAPIUploadFile]):
1521
1703
  """Upload a file.
1522
1704
 
1523
1705
  Args:
@@ -1593,7 +1775,8 @@ def upload(app: App):
1593
1775
  file_copies.append(
1594
1776
  UploadFile(
1595
1777
  file=content_copy,
1596
- filename=file.filename,
1778
+ path=Path(file.filename.lstrip("/")) if file.filename else None,
1779
+ _deprecated_filename=file.filename,
1597
1780
  size=file.size,
1598
1781
  headers=file.headers,
1599
1782
  )
reflex/base.py CHANGED
@@ -5,15 +5,9 @@ from __future__ import annotations
5
5
  import os
6
6
  from typing import TYPE_CHECKING, Any, List, Type
7
7
 
8
- try:
9
- import pydantic.v1.main as pydantic_main
10
- from pydantic.v1 import BaseModel
11
- from pydantic.v1.fields import ModelField
12
- except ModuleNotFoundError:
13
- if not TYPE_CHECKING:
14
- import pydantic.main as pydantic_main
15
- from pydantic import BaseModel
16
- from pydantic.fields import ModelField
8
+ import pydantic.v1.main as pydantic_main
9
+ from pydantic.v1 import BaseModel
10
+ from pydantic.v1.fields import ModelField
17
11
 
18
12
 
19
13
  def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
@@ -50,7 +44,7 @@ if TYPE_CHECKING:
50
44
  from reflex.vars import Var
51
45
 
52
46
 
53
- class Base(BaseModel): # pyright: ignore [reportPossiblyUnboundVariable]
47
+ class Base(BaseModel):
54
48
  """The base class subclassed by all Reflex classes.
55
49
 
56
50
  This class wraps Pydantic and provides common methods such as