reflex 0.6.0a3__py3-none-any.whl → 0.6.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 (61) hide show
  1. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +1 -1
  2. reflex/.templates/jinja/web/pages/_app.js.jinja2 +14 -0
  3. reflex/.templates/web/utils/state.js +70 -41
  4. reflex/app.py +11 -11
  5. reflex/app_mixins/lifespan.py +24 -6
  6. reflex/app_module_for_backend.py +1 -1
  7. reflex/base.py +7 -13
  8. reflex/compiler/utils.py +17 -8
  9. reflex/components/base/bare.py +3 -1
  10. reflex/components/base/meta.py +5 -3
  11. reflex/components/component.py +28 -20
  12. reflex/components/core/breakpoints.py +1 -3
  13. reflex/components/core/cond.py +4 -4
  14. reflex/components/datadisplay/__init__.py +0 -1
  15. reflex/components/datadisplay/__init__.pyi +0 -1
  16. reflex/components/datadisplay/code.py +93 -106
  17. reflex/components/datadisplay/code.pyi +710 -53
  18. reflex/components/datadisplay/logo.py +22 -20
  19. reflex/components/dynamic.py +157 -0
  20. reflex/components/el/elements/forms.py +4 -1
  21. reflex/components/gridjs/datatable.py +2 -1
  22. reflex/components/markdown/markdown.py +10 -6
  23. reflex/components/markdown/markdown.pyi +3 -0
  24. reflex/components/radix/themes/components/progress.py +22 -0
  25. reflex/components/radix/themes/components/progress.pyi +2 -0
  26. reflex/components/radix/themes/components/segmented_control.py +3 -0
  27. reflex/components/radix/themes/components/segmented_control.pyi +2 -0
  28. reflex/components/radix/themes/layout/stack.py +1 -1
  29. reflex/components/recharts/cartesian.py +1 -1
  30. reflex/components/sonner/toast.py +3 -3
  31. reflex/components/tags/iter_tag.py +5 -1
  32. reflex/config.py +2 -2
  33. reflex/constants/base.py +4 -1
  34. reflex/constants/installer.py +8 -1
  35. reflex/event.py +63 -22
  36. reflex/experimental/assets.py +3 -1
  37. reflex/experimental/client_state.py +12 -7
  38. reflex/experimental/misc.py +5 -3
  39. reflex/middleware/hydrate_middleware.py +1 -2
  40. reflex/page.py +10 -3
  41. reflex/reflex.py +20 -3
  42. reflex/state.py +105 -44
  43. reflex/style.py +12 -2
  44. reflex/testing.py +8 -4
  45. reflex/utils/console.py +1 -1
  46. reflex/utils/exceptions.py +4 -0
  47. reflex/utils/exec.py +170 -18
  48. reflex/utils/format.py +6 -44
  49. reflex/utils/path_ops.py +36 -1
  50. reflex/utils/prerequisites.py +62 -21
  51. reflex/utils/serializers.py +7 -46
  52. reflex/utils/telemetry.py +1 -1
  53. reflex/utils/types.py +18 -3
  54. reflex/vars/base.py +303 -43
  55. reflex/vars/number.py +3 -0
  56. reflex/vars/sequence.py +43 -8
  57. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/METADATA +5 -5
  58. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/RECORD +61 -60
  59. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/LICENSE +0 -0
  60. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/WHEEL +0 -0
  61. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/entry_points.txt +0 -0
reflex/testing.py CHANGED
@@ -340,6 +340,9 @@ class AppHarness:
340
340
 
341
341
  This is necessary when the backend is restarted and the state manager is a
342
342
  StateManagerRedis instance.
343
+
344
+ Raises:
345
+ RuntimeError: when the state manager cannot be reset
343
346
  """
344
347
  if (
345
348
  self.app_instance is not None
@@ -354,7 +357,8 @@ class AppHarness:
354
357
  self.app_instance._state_manager = StateManagerRedis.create(
355
358
  state=self.app_instance.state,
356
359
  )
357
- assert isinstance(self.app_instance.state_manager, StateManagerRedis)
360
+ if not isinstance(self.app_instance.state_manager, StateManagerRedis):
361
+ raise RuntimeError("Failed to reset state manager.")
358
362
 
359
363
  def _start_frontend(self):
360
364
  # Set up the frontend.
@@ -787,13 +791,13 @@ class AppHarness:
787
791
  Raises:
788
792
  RuntimeError: when the app hasn't started running
789
793
  TimeoutError: when the timeout expires before any states are seen
794
+ ValueError: when the state_manager is not a memory state manager
790
795
  """
791
796
  if self.app_instance is None:
792
797
  raise RuntimeError("App is not running.")
793
798
  state_manager = self.app_instance.state_manager
794
- assert isinstance(
795
- state_manager, (StateManagerMemory, StateManagerDisk)
796
- ), "Only works with memory state manager"
799
+ if not isinstance(state_manager, (StateManagerMemory, StateManagerDisk)):
800
+ raise ValueError("Only works with memory or disk state manager")
797
801
  if not self._poll_for(
798
802
  target=lambda: state_manager.states,
799
803
  timeout=timeout,
reflex/utils/console.py CHANGED
@@ -58,7 +58,7 @@ def debug(msg: str, **kwargs):
58
58
  kwargs: Keyword arguments to pass to the print function.
59
59
  """
60
60
  if is_debug():
61
- msg_ = f"[blue]Debug: {msg}[/blue]"
61
+ msg_ = f"[purple]Debug: {msg}[/purple]"
62
62
  if progress := kwargs.pop("progress", None):
63
63
  progress.console.print(msg_, **kwargs)
64
64
  else:
@@ -111,3 +111,7 @@ class GeneratedCodeHasNoFunctionDefs(ReflexError):
111
111
 
112
112
  class PrimitiveUnserializableToJSON(ReflexError, ValueError):
113
113
  """Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity."""
114
+
115
+
116
+ class InvalidLifespanTaskType(ReflexError, TypeError):
117
+ """Raised when an invalid task type is registered as a lifespan task."""
reflex/utils/exec.py CHANGED
@@ -16,6 +16,7 @@ import psutil
16
16
 
17
17
  from reflex import constants
18
18
  from reflex.config import get_config
19
+ from reflex.constants.base import LogLevel
19
20
  from reflex.utils import console, path_ops
20
21
  from reflex.utils.prerequisites import get_web_dir
21
22
 
@@ -60,6 +61,13 @@ def kill(proc_pid: int):
60
61
  process.kill()
61
62
 
62
63
 
64
+ def notify_backend():
65
+ """Output a string notifying where the backend is running."""
66
+ console.print(
67
+ f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
68
+ )
69
+
70
+
63
71
  # run_process_and_launch_url is assumed to be used
64
72
  # only to launch the frontend
65
73
  # If this is not the case, might have to change the logic
@@ -103,9 +111,7 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True):
103
111
  f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
104
112
  )
105
113
  if backend_present:
106
- console.print(
107
- f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
108
- )
114
+ notify_backend()
109
115
  first_run = False
110
116
  else:
111
117
  console.print("New packages detected: Updating app...")
@@ -172,10 +178,42 @@ def run_frontend_prod(root: Path, port: str, backend_present=True):
172
178
  )
173
179
 
174
180
 
181
+ def should_use_granian():
182
+ """Whether to use Granian for backend.
183
+
184
+ Returns:
185
+ True if Granian should be used.
186
+ """
187
+ return os.getenv("REFLEX_USE_GRANIAN", "0") == "1"
188
+
189
+
190
+ def get_app_module():
191
+ """Get the app module for the backend.
192
+
193
+ Returns:
194
+ The app module for the backend.
195
+ """
196
+ return f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
197
+
198
+
199
+ def get_granian_target():
200
+ """Get the Granian target for the backend.
201
+
202
+ Returns:
203
+ The Granian target for the backend.
204
+ """
205
+ import reflex
206
+
207
+ app_module_path = Path(reflex.__file__).parent / "app_module_for_backend.py"
208
+
209
+ return f"{str(app_module_path)}:{constants.CompileVars.APP}.{constants.CompileVars.API}"
210
+
211
+
175
212
  def run_backend(
176
213
  host: str,
177
214
  port: int,
178
215
  loglevel: constants.LogLevel = constants.LogLevel.ERROR,
216
+ frontend_present: bool = False,
179
217
  ):
180
218
  """Run the backend.
181
219
 
@@ -183,25 +221,82 @@ def run_backend(
183
221
  host: The app host
184
222
  port: The app port
185
223
  loglevel: The log level.
224
+ frontend_present: Whether the frontend is present.
186
225
  """
187
- import uvicorn
188
-
189
- config = get_config()
190
- app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
191
-
192
226
  web_dir = get_web_dir()
193
227
  # Create a .nocompile file to skip compile for backend.
194
228
  if web_dir.exists():
195
229
  (web_dir / constants.NOCOMPILE_FILE).touch()
196
230
 
231
+ if not frontend_present:
232
+ notify_backend()
233
+
197
234
  # Run the backend in development mode.
235
+ if should_use_granian():
236
+ run_granian_backend(host, port, loglevel)
237
+ else:
238
+ run_uvicorn_backend(host, port, loglevel)
239
+
240
+
241
+ def run_uvicorn_backend(host, port, loglevel: LogLevel):
242
+ """Run the backend in development mode using Uvicorn.
243
+
244
+ Args:
245
+ host: The app host
246
+ port: The app port
247
+ loglevel: The log level.
248
+ """
249
+ import uvicorn
250
+
198
251
  uvicorn.run(
199
- app=f"{app_module}.{constants.CompileVars.API}",
252
+ app=f"{get_app_module()}.{constants.CompileVars.API}",
200
253
  host=host,
201
254
  port=port,
202
255
  log_level=loglevel.value,
203
256
  reload=True,
204
- reload_dirs=[config.app_name],
257
+ reload_dirs=[get_config().app_name],
258
+ )
259
+
260
+
261
+ def run_granian_backend(host, port, loglevel: LogLevel):
262
+ """Run the backend in development mode using Granian.
263
+
264
+ Args:
265
+ host: The app host
266
+ port: The app port
267
+ loglevel: The log level.
268
+ """
269
+ console.debug("Using Granian for backend")
270
+ try:
271
+ from granian import Granian # type: ignore
272
+ from granian.constants import Interfaces # type: ignore
273
+ from granian.log import LogLevels # type: ignore
274
+
275
+ Granian(
276
+ target=get_granian_target(),
277
+ address=host,
278
+ port=port,
279
+ interface=Interfaces.ASGI,
280
+ log_level=LogLevels(loglevel.value),
281
+ reload=True,
282
+ reload_paths=[Path(get_config().app_name)],
283
+ reload_ignore_dirs=[".web"],
284
+ ).serve()
285
+ except ImportError:
286
+ console.error(
287
+ 'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)'
288
+ )
289
+ os._exit(1)
290
+
291
+
292
+ def _get_backend_workers():
293
+ from reflex.utils import processes
294
+
295
+ config = get_config()
296
+ return (
297
+ processes.get_num_workers()
298
+ if not config.gunicorn_workers
299
+ else config.gunicorn_workers
205
300
  )
206
301
 
207
302
 
@@ -209,9 +304,28 @@ def run_backend_prod(
209
304
  host: str,
210
305
  port: int,
211
306
  loglevel: constants.LogLevel = constants.LogLevel.ERROR,
307
+ frontend_present: bool = False,
212
308
  ):
213
309
  """Run the backend.
214
310
 
311
+ Args:
312
+ host: The app host
313
+ port: The app port
314
+ loglevel: The log level.
315
+ frontend_present: Whether the frontend is present.
316
+ """
317
+ if not frontend_present:
318
+ notify_backend()
319
+
320
+ if should_use_granian():
321
+ run_granian_backend_prod(host, port, loglevel)
322
+ else:
323
+ run_uvicorn_backend_prod(host, port, loglevel)
324
+
325
+
326
+ def run_uvicorn_backend_prod(host, port, loglevel):
327
+ """Run the backend in production mode using Uvicorn.
328
+
215
329
  Args:
216
330
  host: The app host
217
331
  port: The app port
@@ -220,14 +334,11 @@ def run_backend_prod(
220
334
  from reflex.utils import processes
221
335
 
222
336
  config = get_config()
223
- num_workers = (
224
- processes.get_num_workers()
225
- if not config.gunicorn_workers
226
- else config.gunicorn_workers
227
- )
337
+
338
+ app_module = get_app_module()
339
+
228
340
  RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split()
229
341
  RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split()
230
- app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
231
342
  command = (
232
343
  [
233
344
  *RUN_BACKEND_PROD_WINDOWS,
@@ -243,7 +354,7 @@ def run_backend_prod(
243
354
  "--bind",
244
355
  f"{host}:{port}",
245
356
  "--threads",
246
- str(num_workers),
357
+ str(_get_backend_workers()),
247
358
  f"{app_module}()",
248
359
  ]
249
360
  )
@@ -252,7 +363,7 @@ def run_backend_prod(
252
363
  "--log-level",
253
364
  loglevel.value,
254
365
  "--workers",
255
- str(num_workers),
366
+ str(_get_backend_workers()),
256
367
  ]
257
368
  processes.new_process(
258
369
  command,
@@ -262,6 +373,47 @@ def run_backend_prod(
262
373
  )
263
374
 
264
375
 
376
+ def run_granian_backend_prod(host, port, loglevel):
377
+ """Run the backend in production mode using Granian.
378
+
379
+ Args:
380
+ host: The app host
381
+ port: The app port
382
+ loglevel: The log level.
383
+ """
384
+ from reflex.utils import processes
385
+
386
+ try:
387
+ from granian.constants import Interfaces # type: ignore
388
+
389
+ command = [
390
+ "granian",
391
+ "--workers",
392
+ str(_get_backend_workers()),
393
+ "--log-level",
394
+ "critical",
395
+ "--host",
396
+ host,
397
+ "--port",
398
+ str(port),
399
+ "--interface",
400
+ str(Interfaces.ASGI),
401
+ get_granian_target(),
402
+ ]
403
+ processes.new_process(
404
+ command,
405
+ run=True,
406
+ show_logs=True,
407
+ env={
408
+ constants.SKIP_COMPILE_ENV_VAR: "yes"
409
+ }, # skip compile for prod backend
410
+ )
411
+ except ImportError:
412
+ console.error(
413
+ 'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)'
414
+ )
415
+
416
+
265
417
  def output_system_info():
266
418
  """Show system information if the loglevel is in DEBUG."""
267
419
  if console._LOG_LEVEL > constants.LogLevel.DEBUG:
reflex/utils/format.py CHANGED
@@ -9,7 +9,7 @@ import re
9
9
  from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
10
10
 
11
11
  from reflex import constants
12
- from reflex.utils import exceptions, types
12
+ from reflex.utils import exceptions
13
13
  from reflex.utils.console import deprecate
14
14
 
15
15
  if TYPE_CHECKING:
@@ -345,6 +345,7 @@ def format_prop(
345
345
  Raises:
346
346
  exceptions.InvalidStylePropError: If the style prop value is not a valid type.
347
347
  TypeError: If the prop is not valid.
348
+ ValueError: If the prop is not a string.
348
349
  """
349
350
  # import here to avoid circular import.
350
351
  from reflex.event import EventChain
@@ -391,7 +392,8 @@ def format_prop(
391
392
  raise TypeError(f"Could not format prop: {prop} of type {type(prop)}") from e
392
393
 
393
394
  # Wrap the variable in braces.
394
- assert isinstance(prop, str), "The prop must be a string."
395
+ if not isinstance(prop, str):
396
+ raise ValueError(f"Invalid prop: {prop}. Expected a string.")
395
397
  return wrap(prop, "{", check_first=False)
396
398
 
397
399
 
@@ -624,48 +626,6 @@ def format_query_params(router_data: dict[str, Any]) -> dict[str, str]:
624
626
  return {k.replace("-", "_"): v for k, v in params.items()}
625
627
 
626
628
 
627
- def format_state(value: Any, key: Optional[str] = None) -> Any:
628
- """Recursively format values in the given state.
629
-
630
- Args:
631
- value: The state to format.
632
- key: The key associated with the value (optional).
633
-
634
- Returns:
635
- The formatted state.
636
-
637
- Raises:
638
- TypeError: If the given value is not a valid state.
639
- """
640
- from reflex.utils import serializers
641
-
642
- # Handle dicts.
643
- if isinstance(value, dict):
644
- return {k: format_state(v, k) for k, v in value.items()}
645
-
646
- # Handle lists, sets, typles.
647
- if isinstance(value, types.StateIterBases):
648
- return [format_state(v) for v in value]
649
-
650
- # Return state vars as is.
651
- if isinstance(value, types.StateBases):
652
- return value
653
-
654
- # Serialize the value.
655
- serialized = serializers.serialize(value)
656
- if serialized is not None:
657
- return serialized
658
-
659
- if key is None:
660
- raise TypeError(
661
- f"No JSON serializer found for var {value} of type {type(value)}."
662
- )
663
- else:
664
- raise TypeError(
665
- f"No JSON serializer found for State Var '{key}' of value {value} of type {type(value)}."
666
- )
667
-
668
-
669
629
  def format_state_name(state_name: str) -> str:
670
630
  """Format a state name, replacing dots with double underscore.
671
631
 
@@ -704,6 +664,8 @@ def format_library_name(library_fullname: str):
704
664
  Returns:
705
665
  The name without the @version if it was part of the name
706
666
  """
667
+ if library_fullname.startswith("https://"):
668
+ return library_fullname
707
669
  lib, at, version = library_fullname.rpartition("@")
708
670
  if not lib:
709
671
  lib = at + version
reflex/utils/path_ops.py CHANGED
@@ -129,6 +129,41 @@ def which(program: str | Path) -> str | Path | None:
129
129
  return shutil.which(str(program))
130
130
 
131
131
 
132
+ def use_system_install(var_name: str) -> bool:
133
+ """Check if the system install should be used.
134
+
135
+ Args:
136
+ var_name: The name of the environment variable.
137
+
138
+ Raises:
139
+ ValueError: If the variable name is invalid.
140
+
141
+ Returns:
142
+ Whether the associated env var should use the system install.
143
+ """
144
+ if not var_name.startswith("REFLEX_USE_SYSTEM_"):
145
+ raise ValueError("Invalid system install variable name.")
146
+ return os.getenv(var_name, "").lower() in ["true", "1", "yes"]
147
+
148
+
149
+ def use_system_node() -> bool:
150
+ """Check if the system node should be used.
151
+
152
+ Returns:
153
+ Whether the system node should be used.
154
+ """
155
+ return use_system_install(constants.Node.USE_SYSTEM_VAR)
156
+
157
+
158
+ def use_system_bun() -> bool:
159
+ """Check if the system bun should be used.
160
+
161
+ Returns:
162
+ Whether the system bun should be used.
163
+ """
164
+ return use_system_install(constants.Bun.USE_SYSTEM_VAR)
165
+
166
+
132
167
  def get_node_bin_path() -> str | None:
133
168
  """Get the node binary dir path.
134
169
 
@@ -149,7 +184,7 @@ def get_node_path() -> str | None:
149
184
  The path to the node binary file.
150
185
  """
151
186
  node_path = Path(constants.Node.PATH)
152
- if not node_path.exists():
187
+ if use_system_node() or not node_path.exists():
153
188
  return str(which("node"))
154
189
  return str(node_path)
155
190
 
@@ -16,7 +16,7 @@ import shutil
16
16
  import stat
17
17
  import sys
18
18
  import tempfile
19
- import textwrap
19
+ import time
20
20
  import zipfile
21
21
  from datetime import datetime
22
22
  from fileinput import FileInput
@@ -36,6 +36,7 @@ from reflex import constants, model
36
36
  from reflex.compiler import templates
37
37
  from reflex.config import Config, get_config
38
38
  from reflex.utils import console, net, path_ops, processes
39
+ from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs
39
40
  from reflex.utils.format import format_library_name
40
41
  from reflex.utils.registry import _get_best_registry
41
42
 
@@ -73,6 +74,18 @@ def get_web_dir() -> Path:
73
74
  return workdir
74
75
 
75
76
 
77
+ def _python_version_check():
78
+ """Emit deprecation warning for deprecated python versions."""
79
+ # Check for end-of-life python versions.
80
+ if sys.version_info < (3, 10):
81
+ console.deprecate(
82
+ feature_name="Support for Python 3.9 and older",
83
+ reason="please upgrade to Python 3.10 or newer",
84
+ deprecation_version="0.6.0",
85
+ removal_version="0.7.0",
86
+ )
87
+
88
+
76
89
  def check_latest_package_version(package_name: str):
77
90
  """Check if the latest version of the package is installed.
78
91
 
@@ -85,15 +98,16 @@ def check_latest_package_version(package_name: str):
85
98
  url = f"https://pypi.org/pypi/{package_name}/json"
86
99
  response = net.get(url)
87
100
  latest_version = response.json()["info"]["version"]
88
- if (
89
- version.parse(current_version) < version.parse(latest_version)
90
- and not get_or_set_last_reflex_version_check_datetime()
91
- ):
92
- # only show a warning when the host version is outdated and
93
- # the last_version_check_datetime is not set in reflex.json
101
+ if get_or_set_last_reflex_version_check_datetime():
102
+ # Versions were already checked and saved in reflex.json, no need to warn again
103
+ return
104
+ if version.parse(current_version) < version.parse(latest_version):
105
+ # Show a warning when the host version is older than PyPI version
94
106
  console.warn(
95
107
  f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
96
108
  )
109
+ # Check for depreacted python versions
110
+ _python_version_check()
97
111
  except Exception:
98
112
  pass
99
113
 
@@ -129,7 +143,7 @@ def check_node_version() -> bool:
129
143
  # Compare the version numbers
130
144
  return (
131
145
  current_version >= version.parse(constants.Node.MIN_VERSION)
132
- if constants.IS_WINDOWS
146
+ if constants.IS_WINDOWS or path_ops.use_system_node()
133
147
  else current_version == version.parse(constants.Node.VERSION)
134
148
  )
135
149
  return False
@@ -290,7 +304,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
290
304
  """
291
305
  app_module = get_app(reload=reload)
292
306
  app = getattr(app_module, constants.CompileVars.APP)
293
- # For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages
307
+ # For py3.9 compatibility when redis is used, we MUST add any decorator pages
294
308
  # before compiling the app in a thread to avoid event loop error (REF-2172).
295
309
  app._apply_decorated_pages()
296
310
  app._compile(export=export)
@@ -1020,6 +1034,8 @@ def validate_bun():
1020
1034
  # if a custom bun path is provided, make sure its valid
1021
1035
  # This is specific to non-FHS OS
1022
1036
  bun_path = get_config().bun_path
1037
+ if path_ops.use_system_bun():
1038
+ bun_path = path_ops.which("bun")
1023
1039
  if bun_path != constants.Bun.DEFAULT_PATH:
1024
1040
  console.info(f"Using custom Bun path: {bun_path}")
1025
1041
  bun_version = get_bun_version()
@@ -1442,19 +1458,37 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
1442
1458
  Args:
1443
1459
  app_name: The name of the app.
1444
1460
  generation_hash: The generation hash from reflex.build.
1461
+
1462
+ Raises:
1463
+ GeneratedCodeHasNoFunctionDefs: If the fetched code has no function definitions
1464
+ (the refactored reflex code is expected to have at least one root function defined).
1445
1465
  """
1446
1466
  # Download the reflex code for the generation.
1447
- resp = net.get(
1448
- constants.Templates.REFLEX_BUILD_CODE_URL.format(
1449
- generation_hash=generation_hash
1467
+ url = constants.Templates.REFLEX_BUILD_CODE_URL.format(
1468
+ generation_hash=generation_hash
1469
+ )
1470
+ resp = net.get(url)
1471
+ while resp.status_code == httpx.codes.SERVICE_UNAVAILABLE:
1472
+ console.debug("Waiting for the code to be generated...")
1473
+ time.sleep(1)
1474
+ resp = net.get(url)
1475
+ resp.raise_for_status()
1476
+
1477
+ # Determine the name of the last function, which renders the generated code.
1478
+ defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
1479
+ if not defined_funcs:
1480
+ raise GeneratedCodeHasNoFunctionDefs(
1481
+ f"No function definitions found in generated code from {url!r}."
1450
1482
  )
1451
- ).raise_for_status()
1483
+ render_func_name = defined_funcs[-1]
1452
1484
 
1453
1485
  def replace_content(_match):
1454
1486
  return "\n".join(
1455
1487
  [
1456
- "def index() -> rx.Component:",
1457
- textwrap.indent("return " + resp.text, " "),
1488
+ resp.text,
1489
+ "",
1490
+ "" "def index() -> rx.Component:",
1491
+ f" return {render_func_name}()",
1458
1492
  "",
1459
1493
  "",
1460
1494
  ],
@@ -1462,13 +1496,20 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
1462
1496
 
1463
1497
  main_module_path = Path(app_name, app_name + constants.Ext.PY)
1464
1498
  main_module_code = main_module_path.read_text()
1465
- main_module_path.write_text(
1466
- re.sub(
1467
- r"def index\(\).*:\n([^\n]\s+.*\n+)+",
1468
- replace_content,
1469
- main_module_code,
1470
- )
1499
+
1500
+ main_module_code = re.sub(
1501
+ r"def index\(\).*:\n([^\n]\s+.*\n+)+",
1502
+ replace_content,
1503
+ main_module_code,
1504
+ )
1505
+ # Make the app use light mode until flexgen enforces the conversion of
1506
+ # tailwind colors to radix colors.
1507
+ main_module_code = re.sub(
1508
+ r"app\s*=\s*rx\.App\(\s*\)",
1509
+ 'app = rx.App(theme=rx.theme(color_mode="light"))',
1510
+ main_module_code,
1471
1511
  )
1512
+ main_module_path.write_text(main_module_code)
1472
1513
 
1473
1514
 
1474
1515
  def format_address_width(address_width) -> int | None: