reflex 0.8.5a2__py3-none-any.whl → 0.8.6a1__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 (34) hide show
  1. reflex/.templates/{web/vite.config.js → jinja/web/vite.config.js.jinja2} +11 -0
  2. reflex/.templates/web/utils/state.js +5 -0
  3. reflex/__init__.py +1 -0
  4. reflex/__init__.pyi +2 -0
  5. reflex/app.py +77 -13
  6. reflex/compiler/templates.py +3 -0
  7. reflex/components/base/error_boundary.py +2 -0
  8. reflex/components/datadisplay/dataeditor.py +3 -0
  9. reflex/components/datadisplay/dataeditor.pyi +2 -0
  10. reflex/components/el/__init__.pyi +4 -0
  11. reflex/components/el/elements/__init__.py +1 -0
  12. reflex/components/el/elements/__init__.pyi +5 -0
  13. reflex/components/el/elements/media.py +32 -0
  14. reflex/components/el/elements/media.pyi +261 -0
  15. reflex/components/lucide/icon.py +4 -1
  16. reflex/components/lucide/icon.pyi +4 -1
  17. reflex/components/sonner/toast.py +1 -1
  18. reflex/config.py +15 -0
  19. reflex/constants/base.py +3 -0
  20. reflex/istate/manager.py +2 -1
  21. reflex/plugins/__init__.py +2 -0
  22. reflex/plugins/_screenshot.py +144 -0
  23. reflex/plugins/base.py +14 -1
  24. reflex/state.py +28 -6
  25. reflex/testing.py +11 -0
  26. reflex/utils/decorator.py +1 -0
  27. reflex/utils/monitoring.py +180 -0
  28. reflex/utils/prerequisites.py +17 -0
  29. reflex/utils/token_manager.py +215 -0
  30. {reflex-0.8.5a2.dist-info → reflex-0.8.6a1.dist-info}/METADATA +5 -2
  31. {reflex-0.8.5a2.dist-info → reflex-0.8.6a1.dist-info}/RECORD +34 -31
  32. {reflex-0.8.5a2.dist-info → reflex-0.8.6a1.dist-info}/WHEEL +0 -0
  33. {reflex-0.8.5a2.dist-info → reflex-0.8.6a1.dist-info}/entry_points.txt +0 -0
  34. {reflex-0.8.5a2.dist-info → reflex-0.8.6a1.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,7 @@ from reflex.utils.imports import ImportVar
6
6
  from reflex.vars.base import LiteralVar, Var
7
7
  from reflex.vars.sequence import LiteralStringVar, StringVar
8
8
 
9
- LUCIDE_LIBRARY = "lucide-react@0.534.0"
9
+ LUCIDE_LIBRARY = "lucide-react@0.536.0"
10
10
 
11
11
 
12
12
  class LucideIconComponent(Component):
@@ -93,6 +93,7 @@ class DynamicIcon(LucideIconComponent):
93
93
  tag = "DynamicIcon"
94
94
 
95
95
  name: Var[str]
96
+ size: Var[int]
96
97
 
97
98
  def _get_imports(self):
98
99
  _imports = super()._get_imports()
@@ -874,6 +875,7 @@ LUCIDE_ICON_LIST = [
874
875
  "hamburger",
875
876
  "hammer",
876
877
  "hand_coins",
878
+ "hand_fist",
877
879
  "hand_heart",
878
880
  "hand_helping",
879
881
  "hand_metal",
@@ -1644,6 +1646,7 @@ LUCIDE_ICON_LIST = [
1644
1646
  "truck_electric",
1645
1647
  "truck",
1646
1648
  "turkish_lira",
1649
+ "turntable",
1647
1650
  "turtle",
1648
1651
  "tv_2",
1649
1652
  "tv_minimal_play",
@@ -11,7 +11,7 @@ from reflex.components.core.breakpoints import Breakpoints
11
11
  from reflex.event import EventType, PointerEventInfo
12
12
  from reflex.vars.base import Var
13
13
 
14
- LUCIDE_LIBRARY = "lucide-react@0.534.0"
14
+ LUCIDE_LIBRARY = "lucide-react@0.536.0"
15
15
 
16
16
  class LucideIconComponent(Component):
17
17
  @classmethod
@@ -125,6 +125,7 @@ class DynamicIcon(LucideIconComponent):
125
125
  cls,
126
126
  *children,
127
127
  name: Var[str] | str | None = None,
128
+ size: Var[int] | int | None = None,
128
129
  style: Sequence[Mapping[str, Any]]
129
130
  | Mapping[str, Any]
130
131
  | Var[Mapping[str, Any]]
@@ -939,6 +940,7 @@ LUCIDE_ICON_LIST = [
939
940
  "hamburger",
940
941
  "hammer",
941
942
  "hand_coins",
943
+ "hand_fist",
942
944
  "hand_heart",
943
945
  "hand_helping",
944
946
  "hand_metal",
@@ -1709,6 +1711,7 @@ LUCIDE_ICON_LIST = [
1709
1711
  "truck_electric",
1710
1712
  "truck",
1711
1713
  "turkish_lira",
1714
+ "turntable",
1712
1715
  "turtle",
1713
1716
  "tv_2",
1714
1717
  "tv_minimal_play",
@@ -171,7 +171,7 @@ class ToastProps(NoExtrasAllowedProps):
171
171
  class Toaster(Component):
172
172
  """A Toaster Component for displaying toast notifications."""
173
173
 
174
- library: str | None = "sonner@2.0.6"
174
+ library: str | None = "sonner@2.0.7"
175
175
 
176
176
  tag = "Toaster"
177
177
 
reflex/config.py CHANGED
@@ -29,6 +29,9 @@ from reflex.plugins.sitemap import SitemapPlugin
29
29
  from reflex.utils import console
30
30
  from reflex.utils.exceptions import ConfigError
31
31
 
32
+ if TYPE_CHECKING:
33
+ from pyleak.base import LeakAction
34
+
32
35
 
33
36
  @dataclasses.dataclass(kw_only=True)
34
37
  class DBConfig:
@@ -186,6 +189,18 @@ class BaseConfig:
186
189
  # Telemetry opt-in.
187
190
  telemetry_enabled: bool = True
188
191
 
192
+ # PyLeak monitoring configuration for detecting event loop blocking and resource leaks.
193
+ enable_pyleak_monitoring: bool = False
194
+
195
+ # Threshold in seconds for detecting event loop blocking operations.
196
+ pyleak_blocking_threshold: float = 0.1
197
+
198
+ # Grace period in seconds for thread leak detection cleanup.
199
+ pyleak_thread_grace_period: float = 0.2
200
+
201
+ # Action to take when PyLeak detects issues
202
+ pyleak_action: "LeakAction | None" = None
203
+
189
204
  # The bun path
190
205
  bun_path: ExistingPath = constants.Bun.DEFAULT_PATH
191
206
 
reflex/constants/base.py CHANGED
@@ -160,6 +160,9 @@ class ReactRouter(Javascript):
160
160
  # The react router config file
161
161
  CONFIG_FILE = "react-router.config.js"
162
162
 
163
+ # The associated Vite config file
164
+ VITE_CONFIG_FILE = "vite.config.js"
165
+
163
166
  # Regex to check for message displayed when frontend comes up
164
167
  DEV_FRONTEND_LISTENING_REGEX = r"Local:[\s]+"
165
168
 
reflex/istate/manager.py CHANGED
@@ -143,6 +143,8 @@ class StateManagerMemory(StateManager):
143
143
  token: The token to set the state for.
144
144
  state: The state to set.
145
145
  """
146
+ token = _split_substate_key(token)[0]
147
+ self.states[token] = state
146
148
 
147
149
  @override
148
150
  @contextlib.asynccontextmanager
@@ -165,7 +167,6 @@ class StateManagerMemory(StateManager):
165
167
  async with self._states_locks[token]:
166
168
  state = await self.get_state(token)
167
169
  yield state
168
- await self.set_state(token, state)
169
170
 
170
171
 
171
172
  def _default_token_expiration() -> int:
@@ -1,5 +1,6 @@
1
1
  """Reflex Plugin System."""
2
2
 
3
+ from ._screenshot import ScreenshotPlugin as _ScreenshotPlugin
3
4
  from .base import CommonContext, Plugin, PreCompileContext
4
5
  from .sitemap import SitemapPlugin
5
6
  from .tailwind_v3 import TailwindV3Plugin
@@ -12,4 +13,5 @@ __all__ = [
12
13
  "SitemapPlugin",
13
14
  "TailwindV3Plugin",
14
15
  "TailwindV4Plugin",
16
+ "_ScreenshotPlugin",
15
17
  ]
@@ -0,0 +1,144 @@
1
+ """Plugin to enable screenshot functionality."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from reflex.plugins.base import Plugin as BasePlugin
6
+
7
+ if TYPE_CHECKING:
8
+ from starlette.requests import Request
9
+ from starlette.responses import Response
10
+ from typing_extensions import Unpack
11
+
12
+ from reflex.app import App
13
+ from reflex.plugins.base import PostCompileContext
14
+ from reflex.state import BaseState
15
+
16
+ ACTIVE_CONNECTIONS = "/_active_connections"
17
+ CLONE_STATE = "/_clone_state"
18
+
19
+
20
+ def _deep_copy(state: "BaseState") -> "BaseState":
21
+ """Create a deep copy of the state.
22
+
23
+ Args:
24
+ state: The state to copy.
25
+
26
+ Returns:
27
+ A deep copy of the state.
28
+ """
29
+ import copy
30
+
31
+ copy_of_state = copy.deepcopy(state)
32
+
33
+ def copy_substate(substate: "BaseState") -> "BaseState":
34
+ substate_copy = _deep_copy(substate)
35
+
36
+ substate_copy.parent_state = copy_of_state
37
+
38
+ return substate_copy
39
+
40
+ copy_of_state.substates = {
41
+ substate_name: copy_substate(substate)
42
+ for substate_name, substate in state.substates.items()
43
+ }
44
+
45
+ return copy_of_state
46
+
47
+
48
+ class ScreenshotPlugin(BasePlugin):
49
+ """Plugin to handle screenshot functionality."""
50
+
51
+ def post_compile(self, **context: "Unpack[PostCompileContext]") -> None:
52
+ """Called after the compilation of the plugin.
53
+
54
+ Args:
55
+ context: The context for the plugin.
56
+ """
57
+ app = context["app"]
58
+ self._add_active_connections_endpoint(app)
59
+ self._add_clone_state_endpoint(app)
60
+
61
+ @staticmethod
62
+ def _add_active_connections_endpoint(app: "App") -> None:
63
+ """Add an endpoint to the app that returns the active connections.
64
+
65
+ Args:
66
+ app: The application instance to which the endpoint will be added.
67
+ """
68
+ if not app._api:
69
+ return
70
+
71
+ async def active_connections(_request: "Request") -> "Response":
72
+ from starlette.responses import JSONResponse
73
+
74
+ if not app.event_namespace:
75
+ return JSONResponse({})
76
+
77
+ return JSONResponse(app.event_namespace.token_to_sid)
78
+
79
+ app._api.add_route(
80
+ ACTIVE_CONNECTIONS,
81
+ active_connections,
82
+ methods=["GET"],
83
+ )
84
+
85
+ @staticmethod
86
+ def _add_clone_state_endpoint(app: "App") -> None:
87
+ """Add an endpoint to the app that clones the current state.
88
+
89
+ Args:
90
+ app: The application instance to which the endpoint will be added.
91
+ """
92
+ if not app._api:
93
+ return
94
+
95
+ async def clone_state(request: "Request") -> "Response":
96
+ import uuid
97
+
98
+ from starlette.responses import JSONResponse
99
+
100
+ from reflex.state import _substate_key
101
+
102
+ if not app.event_namespace:
103
+ return JSONResponse({})
104
+
105
+ token_to_clone = await request.json()
106
+
107
+ if not isinstance(token_to_clone, str):
108
+ return JSONResponse(
109
+ {"error": "Token to clone must be a string."}, status_code=400
110
+ )
111
+
112
+ old_state = await app.state_manager.get_state(token_to_clone)
113
+
114
+ new_state = _deep_copy(old_state)
115
+
116
+ new_token = uuid.uuid4().hex
117
+
118
+ all_states = [new_state]
119
+
120
+ found_new = True
121
+
122
+ while found_new:
123
+ found_new = False
124
+
125
+ for state in all_states:
126
+ for substate in state.substates.values():
127
+ substate._was_touched = True
128
+
129
+ if substate not in all_states:
130
+ all_states.append(substate)
131
+
132
+ found_new = True
133
+
134
+ await app.state_manager.set_state(
135
+ _substate_key(new_token, new_state), new_state
136
+ )
137
+
138
+ return JSONResponse(new_token)
139
+
140
+ app._api.add_route(
141
+ CLONE_STATE,
142
+ clone_state,
143
+ methods=["POST"],
144
+ )
reflex/plugins/base.py CHANGED
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, ParamSpec, Protocol, TypedDict
7
7
  from typing_extensions import Unpack
8
8
 
9
9
  if TYPE_CHECKING:
10
- from reflex.app import UnevaluatedPage
10
+ from reflex.app import App, UnevaluatedPage
11
11
 
12
12
 
13
13
  class CommonContext(TypedDict):
@@ -44,6 +44,12 @@ class PreCompileContext(CommonContext):
44
44
  unevaluated_pages: Sequence["UnevaluatedPage"]
45
45
 
46
46
 
47
+ class PostCompileContext(CommonContext):
48
+ """Context for post-compile hooks."""
49
+
50
+ app: "App"
51
+
52
+
47
53
  class Plugin:
48
54
  """Base class for all plugins."""
49
55
 
@@ -104,6 +110,13 @@ class Plugin:
104
110
  context: The context for the plugin.
105
111
  """
106
112
 
113
+ def post_compile(self, **context: Unpack[PostCompileContext]) -> None:
114
+ """Called after the compilation of the plugin.
115
+
116
+ Args:
117
+ context: The context for the plugin.
118
+ """
119
+
107
120
  def __repr__(self):
108
121
  """Return a string representation of the plugin.
109
122
 
reflex/state.py CHANGED
@@ -60,6 +60,7 @@ from reflex.utils.exceptions import (
60
60
  )
61
61
  from reflex.utils.exceptions import ImmutableStateError as ImmutableStateError
62
62
  from reflex.utils.exec import is_testing_env
63
+ from reflex.utils.monitoring import is_pyleak_enabled, monitor_loopblocks
63
64
  from reflex.utils.types import _isinstance, is_union, value_inside_optional
64
65
  from reflex.vars import Field, VarData, field
65
66
  from reflex.vars.base import (
@@ -508,7 +509,7 @@ class BaseState(EvenMoreBasicBaseState):
508
509
 
509
510
  new_backend_vars = {
510
511
  name: value
511
- for name, value in cls.__dict__.items()
512
+ for name, value in list(cls.__dict__.items())
512
513
  if types.is_backend_base_variable(name, cls)
513
514
  }
514
515
  # Add annotated backend vars that may not have a default value.
@@ -1784,7 +1785,11 @@ class BaseState(EvenMoreBasicBaseState):
1784
1785
  from reflex.utils import telemetry
1785
1786
 
1786
1787
  # Get the function to process the event.
1787
- fn = functools.partial(handler.fn, state)
1788
+ if is_pyleak_enabled():
1789
+ console.debug(f"Monitoring leaks for handler: {handler.fn.__qualname__}")
1790
+ fn = functools.partial(monitor_loopblocks(handler.fn), state)
1791
+ else:
1792
+ fn = functools.partial(handler.fn, state)
1788
1793
 
1789
1794
  try:
1790
1795
  type_hints = typing.get_type_hints(handler.fn)
@@ -2463,16 +2468,30 @@ class OnLoadInternalState(State):
2463
2468
  This is a separate substate to avoid deserializing the entire state tree for every page navigation.
2464
2469
  """
2465
2470
 
2471
+ # Cannot properly annotate this as `App` due to circular import issues.
2472
+ _app_ref: ClassVar[Any] = None
2473
+
2466
2474
  def on_load_internal(self) -> list[Event | EventSpec | event.EventCallback] | None:
2467
2475
  """Queue on_load handlers for the current page.
2468
2476
 
2469
2477
  Returns:
2470
2478
  The list of events to queue for on load handling.
2479
+
2480
+ Raises:
2481
+ TypeError: If the app reference is not of type App.
2471
2482
  """
2472
- # Do not app._compile()! It should be already compiled by now.
2473
- load_events = prerequisites.get_and_validate_app().app.get_load_events(
2474
- self.router._page.path
2475
- )
2483
+ from reflex.app import App
2484
+
2485
+ app = type(self)._app_ref or prerequisites.get_and_validate_app().app
2486
+ if not isinstance(app, App):
2487
+ msg = (
2488
+ f"Expected app to be of type {App.__name__}, got {type(app).__name__}."
2489
+ )
2490
+ raise TypeError(msg)
2491
+ # Cache the app reference for subsequent calls.
2492
+ if type(self)._app_ref is None:
2493
+ type(self)._app_ref = app
2494
+ load_events = app.get_load_events(self.router._page.path)
2476
2495
  if not load_events:
2477
2496
  self.is_hydrated = True
2478
2497
  return None # Fast path for navigation with no on_load events defined.
@@ -2646,6 +2665,9 @@ def reload_state_module(
2646
2665
  state: Recursive argument for the state class to reload.
2647
2666
 
2648
2667
  """
2668
+ # Reset the _app_ref of OnLoadInternalState to avoid stale references.
2669
+ if state is OnLoadInternalState:
2670
+ state._app_ref = None
2649
2671
  # Clean out all potentially dirty states of reloaded modules.
2650
2672
  for pd_state in tuple(state._potentially_dirty_states):
2651
2673
  with contextlib.suppress(ValueError):
reflex/testing.py CHANGED
@@ -376,6 +376,17 @@ class AppHarness:
376
376
  msg = "Failed to reset state manager."
377
377
  raise RuntimeError(msg)
378
378
 
379
+ # Also reset the TokenManager to avoid loop affinity issues
380
+ if (
381
+ hasattr(self.app_instance, "event_namespace")
382
+ and self.app_instance.event_namespace is not None
383
+ and hasattr(self.app_instance.event_namespace, "_token_manager")
384
+ ):
385
+ # Import here to avoid circular imports
386
+ from reflex.utils.token_manager import TokenManager
387
+
388
+ self.app_instance.event_namespace._token_manager = TokenManager.create()
389
+
379
390
  def _start_frontend(self):
380
391
  # Set up the frontend.
381
392
  with chdir(self.app_path):
reflex/utils/decorator.py CHANGED
@@ -76,6 +76,7 @@ def debug(f: Callable[P, T]) -> Callable[P, T]:
76
76
  def _write_cached_procedure_file(payload: str, cache_file: Path, value: object):
77
77
  import pickle
78
78
 
79
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
79
80
  cache_file.write_bytes(pickle.dumps((payload, value)))
80
81
 
81
82
 
@@ -0,0 +1,180 @@
1
+ """PyLeak integration for monitoring event loop blocking and resource leaks in Reflex applications."""
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import functools
6
+ import inspect
7
+ import threading
8
+ from collections.abc import AsyncGenerator, Awaitable, Callable, Generator
9
+ from typing import TypeVar, overload
10
+
11
+ from reflex.config import get_config
12
+
13
+ try:
14
+ from pyleak import no_event_loop_blocking, no_task_leaks, no_thread_leaks
15
+ from pyleak.base import LeakAction
16
+
17
+ PYLEAK_AVAILABLE = True
18
+ except ImportError:
19
+ PYLEAK_AVAILABLE = False
20
+ no_event_loop_blocking = no_task_leaks = no_thread_leaks = None # pyright: ignore[reportAssignmentType]
21
+ LeakAction = None # pyright: ignore[reportAssignmentType]
22
+
23
+
24
+ # Thread-local storage to track if monitoring is already active
25
+ _thread_local = threading.local()
26
+
27
+
28
+ def is_pyleak_enabled() -> bool:
29
+ """Check if PyLeak monitoring is enabled and available.
30
+
31
+ Returns:
32
+ True if PyLeak monitoring is enabled in config and PyLeak is available.
33
+ """
34
+ if not PYLEAK_AVAILABLE:
35
+ return False
36
+ config = get_config()
37
+ return config.enable_pyleak_monitoring
38
+
39
+
40
+ @contextlib.contextmanager
41
+ def monitor_sync():
42
+ """Sync context manager for PyLeak monitoring.
43
+
44
+ Yields:
45
+ None: Context for monitoring sync operations.
46
+ """
47
+ if not is_pyleak_enabled():
48
+ yield
49
+ return
50
+
51
+ # Check if monitoring is already active in this thread
52
+ if getattr(_thread_local, "monitoring_active", False):
53
+ yield
54
+ return
55
+
56
+ config = get_config()
57
+ action = config.pyleak_action or LeakAction.WARN # pyright: ignore[reportOptionalMemberAccess]
58
+
59
+ # Mark monitoring as active
60
+ _thread_local.monitoring_active = True
61
+ try:
62
+ with contextlib.ExitStack() as stack:
63
+ # Thread leak detection has issues with background tasks (no_thread_leaks)
64
+ stack.enter_context(
65
+ no_event_loop_blocking( # pyright: ignore[reportOptionalCall]
66
+ action=action,
67
+ threshold=config.pyleak_blocking_threshold,
68
+ )
69
+ )
70
+ yield
71
+ finally:
72
+ _thread_local.monitoring_active = False
73
+
74
+
75
+ @contextlib.asynccontextmanager
76
+ async def monitor_async():
77
+ """Async context manager for PyLeak monitoring.
78
+
79
+ Yields:
80
+ None: Context for monitoring async operations.
81
+ """
82
+ if not is_pyleak_enabled():
83
+ yield
84
+ return
85
+
86
+ # Check if monitoring is already active in this thread
87
+ if getattr(_thread_local, "monitoring_active", False):
88
+ yield
89
+ return
90
+
91
+ config = get_config()
92
+ action = config.pyleak_action or LeakAction.WARN # pyright: ignore[reportOptionalMemberAccess]
93
+
94
+ # Mark monitoring as active
95
+ _thread_local.monitoring_active = True
96
+ try:
97
+ async with contextlib.AsyncExitStack() as stack:
98
+ # Thread leak detection has issues with background tasks (no_thread_leaks)
99
+ # Re-add thread leak later.
100
+
101
+ # Block detection for event loops
102
+ stack.enter_context(
103
+ no_event_loop_blocking( # pyright: ignore[reportOptionalCall]
104
+ action=action,
105
+ threshold=config.pyleak_blocking_threshold,
106
+ )
107
+ )
108
+ # Task leak detection has issues with background tasks (no_task_leaks)
109
+
110
+ yield
111
+ finally:
112
+ _thread_local.monitoring_active = False
113
+
114
+
115
+ YieldType = TypeVar("YieldType")
116
+ SendType = TypeVar("SendType")
117
+ ReturnType = TypeVar("ReturnType")
118
+
119
+
120
+ @overload
121
+ def monitor_loopblocks(
122
+ func: Callable[..., AsyncGenerator[YieldType, ReturnType]],
123
+ ) -> Callable[..., AsyncGenerator[YieldType, ReturnType]]: ...
124
+
125
+
126
+ @overload
127
+ def monitor_loopblocks(
128
+ func: Callable[..., Generator[YieldType, SendType, ReturnType]],
129
+ ) -> Callable[..., Generator[YieldType, SendType, ReturnType]]: ...
130
+
131
+
132
+ @overload
133
+ def monitor_loopblocks(
134
+ func: Callable[..., Awaitable[ReturnType]],
135
+ ) -> Callable[..., Awaitable[ReturnType]]: ...
136
+
137
+
138
+ def monitor_loopblocks(func: Callable) -> Callable:
139
+ """Framework decorator using the monitoring module's context manager.
140
+
141
+ Args:
142
+ func: The function to be monitored for leaks.
143
+
144
+ Returns:
145
+ Decorator function that applies PyLeak monitoring to sync/async functions.
146
+ """
147
+ if inspect.isasyncgenfunction(func):
148
+
149
+ @functools.wraps(func)
150
+ async def async_gen_wrapper(*args, **kwargs):
151
+ async with monitor_async():
152
+ async for item in func(*args, **kwargs):
153
+ yield item
154
+
155
+ return async_gen_wrapper
156
+
157
+ if asyncio.iscoroutinefunction(func):
158
+
159
+ @functools.wraps(func)
160
+ async def async_wrapper(*args, **kwargs):
161
+ async with monitor_async():
162
+ return await func(*args, **kwargs)
163
+
164
+ return async_wrapper
165
+
166
+ if inspect.isgeneratorfunction(func):
167
+
168
+ @functools.wraps(func)
169
+ def gen_wrapper(*args, **kwargs):
170
+ with monitor_sync():
171
+ yield from func(*args, **kwargs)
172
+
173
+ return gen_wrapper
174
+
175
+ @functools.wraps(func)
176
+ def sync_wrapper(*args, **kwargs):
177
+ with monitor_sync():
178
+ return func(*args, **kwargs)
179
+
180
+ return sync_wrapper # pyright: ignore[reportReturnType]
@@ -972,6 +972,9 @@ def initialize_web_directory():
972
972
  console.debug("Initializing the react-router.config.js file.")
973
973
  update_react_router_config()
974
974
 
975
+ console.debug("Initializing the vite.config.js file.")
976
+ initialize_vite_config()
977
+
975
978
  console.debug("Initializing the reflex.json file.")
976
979
  # Initialize the reflex json file.
977
980
  init_reflex_json(project_hash=project_hash)
@@ -996,6 +999,20 @@ def initialize_package_json():
996
999
  output_path.write_text(_compile_package_json())
997
1000
 
998
1001
 
1002
+ def _compile_vite_config(config: Config):
1003
+ # base must have exactly one trailing slash
1004
+ base = "/"
1005
+ if frontend_path := config.frontend_path.strip("/"):
1006
+ base += frontend_path + "/"
1007
+ return templates.VITE_CONFIG.render(base=base)
1008
+
1009
+
1010
+ def initialize_vite_config():
1011
+ """Render and write in .web the vite.config.js file using Reflex config."""
1012
+ vite_config_file_path = get_web_dir() / constants.ReactRouter.VITE_CONFIG_FILE
1013
+ vite_config_file_path.write_text(_compile_vite_config(get_config()))
1014
+
1015
+
999
1016
  def initialize_bun_config():
1000
1017
  """Initialize the bun config file."""
1001
1018
  bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH