reflex 0.6.5a3__py3-none-any.whl → 0.6.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 (48) hide show
  1. reflex/.templates/web/utils/state.js +15 -1
  2. reflex/__init__.py +3 -1
  3. reflex/__init__.pyi +3 -0
  4. reflex/app.py +4 -2
  5. reflex/assets.py +95 -0
  6. reflex/base.py +2 -2
  7. reflex/components/base/error_boundary.py +99 -36
  8. reflex/components/base/error_boundary.pyi +3 -4
  9. reflex/components/component.py +29 -7
  10. reflex/components/core/cond.py +8 -0
  11. reflex/components/datadisplay/code.py +1 -1
  12. reflex/components/datadisplay/logo.py +26 -13
  13. reflex/components/el/__init__.pyi +2 -0
  14. reflex/components/el/elements/__init__.py +1 -0
  15. reflex/components/el/elements/__init__.pyi +3 -0
  16. reflex/components/el/elements/forms.py +1 -0
  17. reflex/components/el/elements/forms.pyi +1 -0
  18. reflex/components/moment/moment.py +4 -3
  19. reflex/components/moment/moment.pyi +12 -2
  20. reflex/components/radix/primitives/drawer.py +5 -23
  21. reflex/components/radix/themes/base.py +3 -0
  22. reflex/components/radix/themes/components/segmented_control.py +3 -1
  23. reflex/components/radix/themes/components/segmented_control.pyi +7 -2
  24. reflex/components/radix/themes/components/text_field.py +3 -0
  25. reflex/components/radix/themes/components/text_field.pyi +4 -0
  26. reflex/components/sonner/toast.py +23 -12
  27. reflex/components/sonner/toast.pyi +6 -6
  28. reflex/config.py +60 -9
  29. reflex/constants/base.py +12 -0
  30. reflex/constants/installer.py +3 -3
  31. reflex/constants/style.py +1 -1
  32. reflex/event.py +22 -5
  33. reflex/experimental/assets.py +14 -36
  34. reflex/reflex.py +15 -34
  35. reflex/state.py +81 -23
  36. reflex/utils/exceptions.py +4 -0
  37. reflex/utils/prerequisites.py +174 -40
  38. reflex/utils/redir.py +13 -4
  39. reflex/utils/serializers.py +52 -1
  40. reflex/utils/telemetry.py +2 -1
  41. reflex/utils/types.py +52 -1
  42. reflex/vars/base.py +18 -4
  43. reflex/vars/function.py +283 -37
  44. {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/METADATA +3 -2
  45. {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/RECORD +48 -47
  46. {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/LICENSE +0 -0
  47. {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/WHEEL +0 -0
  48. {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/entry_points.txt +0 -0
reflex/reflex.py CHANGED
@@ -11,16 +11,16 @@ import typer
11
11
  import typer.core
12
12
  from reflex_cli.deployments import deployments_cli
13
13
  from reflex_cli.utils import dependency
14
- from reflex_cli.v2.deployments import hosting_cli
14
+ from reflex_cli.v2.deployments import check_version, hosting_cli
15
15
 
16
16
  from reflex import constants
17
17
  from reflex.config import environment, get_config
18
18
  from reflex.custom_components.custom_components import custom_components_cli
19
19
  from reflex.state import reset_disk_state_manager
20
- from reflex.utils import console, redir, telemetry
20
+ from reflex.utils import console, telemetry
21
21
 
22
22
  # Disable typer+rich integration for help panels
23
- typer.core.rich = False # type: ignore
23
+ typer.core.rich = None # type: ignore
24
24
 
25
25
  # Create the app.
26
26
  try:
@@ -89,30 +89,8 @@ def _init(
89
89
  # Set up the web project.
90
90
  prerequisites.initialize_frontend_dependencies()
91
91
 
92
- # Integrate with reflex.build.
93
- generation_hash = None
94
- if ai:
95
- if template is None:
96
- # If AI is requested and no template specified, redirect the user to reflex.build.
97
- generation_hash = redir.reflex_build_redirect()
98
- elif prerequisites.is_generation_hash(template):
99
- # Otherwise treat the template as a generation hash.
100
- generation_hash = template
101
- else:
102
- console.error(
103
- "Cannot use `--template` option with `--ai` option. Please remove `--template` option."
104
- )
105
- raise typer.Exit(2)
106
- template = constants.Templates.DEFAULT
107
-
108
92
  # Initialize the app.
109
- prerequisites.initialize_app(app_name, template)
110
-
111
- # If a reflex.build generation hash is available, download the code and apply it to the main module.
112
- if generation_hash:
113
- prerequisites.initialize_main_module_index_from_generation(
114
- app_name, generation_hash=generation_hash
115
- )
93
+ template = prerequisites.initialize_app(app_name, template, ai)
116
94
 
117
95
  # Initialize the .gitignore.
118
96
  prerequisites.initialize_gitignore()
@@ -120,8 +98,9 @@ def _init(
120
98
  # Initialize the requirements.txt.
121
99
  prerequisites.initialize_requirements_txt()
122
100
 
101
+ template_msg = f" using the {template} template" if template else ""
123
102
  # Finish initializing the app.
124
- console.success(f"Initialized {app_name}")
103
+ console.success(f"Initialized {app_name}{template_msg}")
125
104
 
126
105
 
127
106
  @cli.command()
@@ -389,6 +368,8 @@ def loginv2(loglevel: constants.LogLevel = typer.Option(config.loglevel)):
389
368
  """Authenicate with experimental Reflex hosting service."""
390
369
  from reflex_cli.v2 import cli as hosting_cli
391
370
 
371
+ check_version()
372
+
392
373
  hosting_cli.login()
393
374
 
394
375
 
@@ -417,6 +398,8 @@ def logoutv2(
417
398
  """Log out of access to Reflex hosting service."""
418
399
  from reflex_cli.v2.utils import hosting
419
400
 
401
+ check_version()
402
+
420
403
  console.set_log_level(loglevel)
421
404
 
422
405
  hosting.log_out_on_browser()
@@ -636,7 +619,7 @@ def deployv2(
636
619
  list(),
637
620
  "-r",
638
621
  "--region",
639
- help="The regions to deploy to. For multiple envs, repeat this option, e.g. --region sjc --region iad",
622
+ help="The regions to deploy to. `reflex apps regions` For multiple envs, repeat this option, e.g. --region sjc --region iad",
640
623
  ),
641
624
  envs: List[str] = typer.Option(
642
625
  list(),
@@ -646,13 +629,12 @@ def deployv2(
646
629
  vmtype: Optional[str] = typer.Option(
647
630
  None,
648
631
  "--vmtype",
649
- help="Vm type id. Run reflex apps vmtypes list to get options.",
632
+ help="Vm type id. Run `reflex apps vmtypes` to get options.",
650
633
  ),
651
634
  hostname: Optional[str] = typer.Option(
652
635
  None,
653
636
  "--hostname",
654
637
  help="The hostname of the frontend.",
655
- hidden=True,
656
638
  ),
657
639
  interactive: bool = typer.Option(
658
640
  True,
@@ -662,7 +644,6 @@ def deployv2(
662
644
  None,
663
645
  "--envfile",
664
646
  help="The path to an env file to use. Will override any envs set manually.",
665
- hidden=True,
666
647
  ),
667
648
  loglevel: constants.LogLevel = typer.Option(
668
649
  config.loglevel, help="The log level to use."
@@ -670,14 +651,12 @@ def deployv2(
670
651
  project: Optional[str] = typer.Option(
671
652
  None,
672
653
  "--project",
673
- help="project to deploy to",
674
- hidden=True,
654
+ help="project id to deploy to",
675
655
  ),
676
656
  token: Optional[str] = typer.Option(
677
657
  None,
678
658
  "--token",
679
659
  help="token to use for auth",
680
- hidden=True,
681
660
  ),
682
661
  ):
683
662
  """Deploy the app to the Reflex hosting service."""
@@ -687,6 +666,8 @@ def deployv2(
687
666
  from reflex.utils import export as export_utils
688
667
  from reflex.utils import prerequisites
689
668
 
669
+ check_version()
670
+
690
671
  # Set the log level.
691
672
  console.set_log_level(loglevel)
692
673
 
reflex/state.py CHANGED
@@ -43,7 +43,7 @@ from sqlalchemy.orm import DeclarativeBase
43
43
  from typing_extensions import Self
44
44
 
45
45
  from reflex import event
46
- from reflex.config import get_config
46
+ from reflex.config import PerformanceMode, get_config
47
47
  from reflex.istate.data import RouterData
48
48
  from reflex.istate.storage import ClientStorageBase
49
49
  from reflex.model import Model
@@ -62,6 +62,13 @@ try:
62
62
  except ModuleNotFoundError:
63
63
  import pydantic
64
64
 
65
+ from pydantic import BaseModel as BaseModelV2
66
+
67
+ try:
68
+ from pydantic.v1 import BaseModel as BaseModelV1
69
+ except ModuleNotFoundError:
70
+ BaseModelV1 = BaseModelV2
71
+
65
72
  import wrapt
66
73
  from redis.asyncio import Redis
67
74
  from redis.exceptions import ResponseError
@@ -87,8 +94,10 @@ from reflex.utils.exceptions import (
87
94
  ImmutableStateError,
88
95
  InvalidStateManagerMode,
89
96
  LockExpiredError,
97
+ ReflexRuntimeError,
90
98
  SetUndefinedStateVarError,
91
99
  StateSchemaMismatchError,
100
+ StateTooLargeError,
92
101
  )
93
102
  from reflex.utils.exec import is_testing_env
94
103
  from reflex.utils.serializers import serializer
@@ -109,10 +118,11 @@ Delta = Dict[str, Any]
109
118
  var = computed_var
110
119
 
111
120
 
112
- # If the state is this large, it's considered a performance issue.
113
- TOO_LARGE_SERIALIZED_STATE = 100 * 1024 # 100kb
114
- # Only warn about each state class size once.
115
- _WARNED_ABOUT_STATE_SIZE: Set[str] = set()
121
+ if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
122
+ # If the state is this large, it's considered a performance issue.
123
+ TOO_LARGE_SERIALIZED_STATE = environment.REFLEX_STATE_SIZE_LIMIT.get() * 1024
124
+ # Only warn about each state class size once.
125
+ _WARNED_ABOUT_STATE_SIZE: Set[str] = set()
116
126
 
117
127
  # Errors caught during pickling of state
118
128
  HANDLED_PICKLE_ERRORS = (
@@ -387,6 +397,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
387
397
  "State classes should not be instantiated directly in a Reflex app. "
388
398
  "See https://reflex.dev/docs/state/ for further information."
389
399
  )
400
+ if type(self)._mixin:
401
+ raise ReflexRuntimeError(
402
+ f"{type(self).__name__} is a state mixin and cannot be instantiated directly."
403
+ )
390
404
  kwargs["parent_state"] = parent_state
391
405
  super().__init__()
392
406
  for name, value in kwargs.items():
@@ -1243,7 +1257,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1243
1257
  if parent_state is not None:
1244
1258
  return getattr(parent_state, name)
1245
1259
 
1246
- if isinstance(value, MutableProxy.__mutable_types__) and (
1260
+ if MutableProxy._is_mutable_type(value) and (
1247
1261
  name in super().__getattribute__("base_vars") or name in backend_vars
1248
1262
  ):
1249
1263
  # track changes in mutable containers (list, dict, set, etc)
@@ -1890,7 +1904,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1890
1904
  )
1891
1905
 
1892
1906
  subdelta: Dict[str, Any] = {
1893
- prop: self.get_value(getattr(self, prop))
1907
+ prop: self.get_value(prop)
1894
1908
  for prop in delta_vars
1895
1909
  if not types.is_backend_base_variable(prop, type(self))
1896
1910
  }
@@ -1982,9 +1996,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1982
1996
  Returns:
1983
1997
  The value of the field.
1984
1998
  """
1985
- if isinstance(key, MutableProxy):
1986
- return super().get_value(key.__wrapped__)
1987
- return super().get_value(key)
1999
+ value = super().get_value(key)
2000
+ if isinstance(value, MutableProxy):
2001
+ return value.__wrapped__
2002
+ return value
1988
2003
 
1989
2004
  def dict(
1990
2005
  self, include_computed: bool = True, initial: bool = False, **kwargs
@@ -2006,8 +2021,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
2006
2021
  self._mark_dirty()
2007
2022
 
2008
2023
  base_vars = {
2009
- prop_name: self.get_value(getattr(self, prop_name))
2010
- for prop_name in self.base_vars
2024
+ prop_name: self.get_value(prop_name) for prop_name in self.base_vars
2011
2025
  }
2012
2026
  if initial and include_computed:
2013
2027
  computed_vars = {
@@ -2016,7 +2030,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
2016
2030
  cv._initial_value
2017
2031
  if is_computed_var(cv)
2018
2032
  and not isinstance(cv._initial_value, types.Unset)
2019
- else self.get_value(getattr(self, prop_name))
2033
+ else self.get_value(prop_name)
2020
2034
  )
2021
2035
  for prop_name, cv in self.computed_vars.items()
2022
2036
  if not cv._backend
@@ -2024,7 +2038,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
2024
2038
  elif include_computed:
2025
2039
  computed_vars = {
2026
2040
  # Include the computed vars.
2027
- prop_name: self.get_value(getattr(self, prop_name))
2041
+ prop_name: self.get_value(prop_name)
2028
2042
  for prop_name, cv in self.computed_vars.items()
2029
2043
  if not cv._backend
2030
2044
  }
@@ -2092,7 +2106,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
2092
2106
  state["__dict__"].pop(inherited_var_name, None)
2093
2107
  return state
2094
2108
 
2095
- def _warn_if_too_large(
2109
+ def _check_state_size(
2096
2110
  self,
2097
2111
  pickle_state_size: int,
2098
2112
  ):
@@ -2100,6 +2114,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
2100
2114
 
2101
2115
  Args:
2102
2116
  pickle_state_size: The size of the pickled state.
2117
+
2118
+ Raises:
2119
+ StateTooLargeError: If the state is too large.
2103
2120
  """
2104
2121
  state_full_name = self.get_full_name()
2105
2122
  if (
@@ -2107,10 +2124,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
2107
2124
  and pickle_state_size > TOO_LARGE_SERIALIZED_STATE
2108
2125
  and self.substates
2109
2126
  ):
2110
- console.warn(
2127
+ msg = (
2111
2128
  f"State {state_full_name} serializes to {pickle_state_size} bytes "
2112
- "which may present performance issues. Consider reducing the size of this state."
2129
+ + "which may present performance issues. Consider reducing the size of this state."
2113
2130
  )
2131
+ if environment.REFLEX_PERF_MODE.get() == PerformanceMode.WARN:
2132
+ console.warn(msg)
2133
+ elif environment.REFLEX_PERF_MODE.get() == PerformanceMode.RAISE:
2134
+ raise StateTooLargeError(msg)
2114
2135
  _WARNED_ABOUT_STATE_SIZE.add(state_full_name)
2115
2136
 
2116
2137
  @classmethod
@@ -2152,7 +2173,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
2152
2173
  """
2153
2174
  try:
2154
2175
  pickle_state = pickle.dumps((self._to_schema(), self))
2155
- self._warn_if_too_large(len(pickle_state))
2176
+ if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
2177
+ self._check_state_size(len(pickle_state))
2156
2178
  return pickle_state
2157
2179
  except HANDLED_PICKLE_ERRORS as og_pickle_error:
2158
2180
  error = (
@@ -2367,6 +2389,23 @@ class ComponentState(State, mixin=True):
2367
2389
  # The number of components created from this class.
2368
2390
  _per_component_state_instance_count: ClassVar[int] = 0
2369
2391
 
2392
+ def __init__(self, *args, **kwargs):
2393
+ """Do not allow direct initialization of the ComponentState.
2394
+
2395
+ Args:
2396
+ *args: The args to pass to the State init method.
2397
+ **kwargs: The kwargs to pass to the State init method.
2398
+
2399
+ Raises:
2400
+ ReflexRuntimeError: If the ComponentState is initialized directly.
2401
+ """
2402
+ if type(self)._mixin:
2403
+ raise ReflexRuntimeError(
2404
+ f"{ComponentState.__name__} {type(self).__name__} is not meant to be initialized directly. "
2405
+ + "Use the `create` method to create a new instance and access the state via the `State` attribute."
2406
+ )
2407
+ super().__init__(*args, **kwargs)
2408
+
2370
2409
  @classmethod
2371
2410
  def __init_subclass__(cls, mixin: bool = True, **kwargs):
2372
2411
  """Overwrite mixin default to True.
@@ -3526,7 +3565,16 @@ class MutableProxy(wrapt.ObjectProxy):
3526
3565
  pydantic.BaseModel.__dict__
3527
3566
  )
3528
3567
 
3529
- __mutable_types__ = (list, dict, set, Base, DeclarativeBase)
3568
+ # These types will be wrapped in MutableProxy
3569
+ __mutable_types__ = (
3570
+ list,
3571
+ dict,
3572
+ set,
3573
+ Base,
3574
+ DeclarativeBase,
3575
+ BaseModelV2,
3576
+ BaseModelV1,
3577
+ )
3530
3578
 
3531
3579
  def __init__(self, wrapped: Any, state: BaseState, field_name: str):
3532
3580
  """Create a proxy for a mutable object that tracks changes.
@@ -3566,6 +3614,18 @@ class MutableProxy(wrapt.ObjectProxy):
3566
3614
  if wrapped is not None:
3567
3615
  return wrapped(*args, **(kwargs or {}))
3568
3616
 
3617
+ @classmethod
3618
+ def _is_mutable_type(cls, value: Any) -> bool:
3619
+ """Check if a value is of a mutable type and should be wrapped.
3620
+
3621
+ Args:
3622
+ value: The value to check.
3623
+
3624
+ Returns:
3625
+ Whether the value is of a mutable type.
3626
+ """
3627
+ return isinstance(value, cls.__mutable_types__)
3628
+
3569
3629
  def _wrap_recursive(self, value: Any) -> Any:
3570
3630
  """Wrap a value recursively if it is mutable.
3571
3631
 
@@ -3576,9 +3636,7 @@ class MutableProxy(wrapt.ObjectProxy):
3576
3636
  The wrapped value.
3577
3637
  """
3578
3638
  # Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
3579
- if isinstance(value, self.__mutable_types__) and not isinstance(
3580
- value, MutableProxy
3581
- ):
3639
+ if self._is_mutable_type(value) and not isinstance(value, MutableProxy):
3582
3640
  return type(self)(
3583
3641
  wrapped=value,
3584
3642
  state=self._self_state,
@@ -3636,7 +3694,7 @@ class MutableProxy(wrapt.ObjectProxy):
3636
3694
  self._wrap_recursive_decorator,
3637
3695
  )
3638
3696
 
3639
- if isinstance(value, self.__mutable_types__) and __name not in (
3697
+ if self._is_mutable_type(value) and __name not in (
3640
3698
  "__wrapped__",
3641
3699
  "_self_state",
3642
3700
  ):
@@ -151,6 +151,10 @@ class InvalidPropValueError(ReflexError):
151
151
  """Raised when a prop value is invalid."""
152
152
 
153
153
 
154
+ class StateTooLargeError(ReflexError):
155
+ """Raised when the state is too large to be serialized."""
156
+
157
+
154
158
  class SystemPackageMissingError(ReflexError):
155
159
  """Raised when a system package is missing."""
156
160
 
@@ -34,7 +34,7 @@ from redis.asyncio import Redis
34
34
  from reflex import constants, model
35
35
  from reflex.compiler import templates
36
36
  from reflex.config import Config, environment, get_config
37
- from reflex.utils import console, net, path_ops, processes
37
+ from reflex.utils import console, net, path_ops, processes, redir
38
38
  from reflex.utils.exceptions import (
39
39
  GeneratedCodeHasNoFunctionDefs,
40
40
  raise_system_package_missing_error,
@@ -93,6 +93,8 @@ def check_latest_package_version(package_name: str):
93
93
  Args:
94
94
  package_name: The name of the package.
95
95
  """
96
+ if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
97
+ return
96
98
  try:
97
99
  # Get the latest version from PyPI
98
100
  current_version = importlib.metadata.version(package_name)
@@ -1209,7 +1211,7 @@ def check_schema_up_to_date():
1209
1211
  )
1210
1212
 
1211
1213
 
1212
- def prompt_for_template(templates: list[Template]) -> str:
1214
+ def prompt_for_template_options(templates: list[Template]) -> str:
1213
1215
  """Prompt the user to specify a template.
1214
1216
 
1215
1217
  Args:
@@ -1221,9 +1223,14 @@ def prompt_for_template(templates: list[Template]) -> str:
1221
1223
  # Show the user the URLs of each template to preview.
1222
1224
  console.print("\nGet started with a template:")
1223
1225
 
1226
+ def format_demo_url_str(url: str) -> str:
1227
+ return f" ({url})" if url else ""
1228
+
1224
1229
  # Prompt the user to select a template.
1225
1230
  id_to_name = {
1226
- str(idx): f"{template.name} ({template.demo_url}) - {template.description}"
1231
+ str(
1232
+ idx
1233
+ ): f"{template.name.replace('_', ' ').replace('-', ' ')}{format_demo_url_str(template.demo_url)} - {template.description}"
1227
1234
  for idx, template in enumerate(templates)
1228
1235
  }
1229
1236
  for id in range(len(id_to_name)):
@@ -1378,15 +1385,122 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
1378
1385
  shutil.rmtree(unzip_dir)
1379
1386
 
1380
1387
 
1381
- def initialize_app(app_name: str, template: str | None = None):
1388
+ def initialize_default_app(app_name: str):
1389
+ """Initialize the default app.
1390
+
1391
+ Args:
1392
+ app_name: The name of the app.
1393
+ """
1394
+ create_config(app_name)
1395
+ initialize_app_directory(app_name)
1396
+
1397
+
1398
+ def validate_and_create_app_using_remote_template(app_name, template, templates):
1399
+ """Validate and create an app using a remote template.
1400
+
1401
+ Args:
1402
+ app_name: The name of the app.
1403
+ template: The name of the template.
1404
+ templates: The available templates.
1405
+
1406
+ Raises:
1407
+ Exit: If the template is not found.
1408
+ """
1409
+ # If user selects a template, it needs to exist.
1410
+ if template in templates:
1411
+ template_url = templates[template].code_url
1412
+ else:
1413
+ # Check if the template is a github repo.
1414
+ if template.startswith("https://github.com"):
1415
+ template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
1416
+ else:
1417
+ console.error(f"Template `{template}` not found.")
1418
+ raise typer.Exit(1)
1419
+
1420
+ if template_url is None:
1421
+ return
1422
+
1423
+ create_config_init_app_from_remote_template(
1424
+ app_name=app_name, template_url=template_url
1425
+ )
1426
+
1427
+
1428
+ def generate_template_using_ai(template: str | None = None) -> str:
1429
+ """Generate a template using AI(Flexgen).
1430
+
1431
+ Args:
1432
+ template: The name of the template.
1433
+
1434
+ Returns:
1435
+ The generation hash.
1436
+
1437
+ Raises:
1438
+ Exit: If the template and ai flags are used.
1439
+ """
1440
+ if template is None:
1441
+ # If AI is requested and no template specified, redirect the user to reflex.build.
1442
+ return redir.reflex_build_redirect()
1443
+ elif is_generation_hash(template):
1444
+ # Otherwise treat the template as a generation hash.
1445
+ return template
1446
+ else:
1447
+ console.error(
1448
+ "Cannot use `--template` option with `--ai` option. Please remove `--template` option."
1449
+ )
1450
+ raise typer.Exit(2)
1451
+
1452
+
1453
+ def fetch_remote_templates(
1454
+ template: Optional[str] = None,
1455
+ ) -> tuple[str, dict[str, Template]]:
1456
+ """Fetch the available remote templates.
1457
+
1458
+ Args:
1459
+ template: The name of the template.
1460
+
1461
+ Returns:
1462
+ The selected template and the available templates.
1463
+
1464
+ Raises:
1465
+ Exit: If the template is not valid or if the template is not specified.
1466
+ """
1467
+ available_templates = {}
1468
+
1469
+ try:
1470
+ # Get the available templates
1471
+ available_templates = fetch_app_templates(constants.Reflex.VERSION)
1472
+ except Exception as e:
1473
+ console.warn("Failed to fetch templates. Falling back to default template.")
1474
+ console.debug(f"Error while fetching templates: {e}")
1475
+ template = constants.Templates.DEFAULT
1476
+
1477
+ if template == constants.Templates.DEFAULT:
1478
+ return template, available_templates
1479
+
1480
+ if template in available_templates:
1481
+ return template, available_templates
1482
+
1483
+ else:
1484
+ if template is not None:
1485
+ console.error(f"{template!r} is not a valid template name.")
1486
+ console.print(
1487
+ f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
1488
+ )
1489
+ raise typer.Exit(0)
1490
+
1491
+
1492
+ def initialize_app(
1493
+ app_name: str, template: str | None = None, ai: bool = False
1494
+ ) -> str | None:
1382
1495
  """Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
1383
1496
 
1384
1497
  Args:
1385
1498
  app_name: The name of the app.
1386
1499
  template: The name of the template to use.
1500
+ ai: Whether to use AI to generate the template.
1387
1501
 
1388
- Raises:
1389
- Exit: If template is directly provided in the command flag and is invalid.
1502
+ Returns:
1503
+ The name of the template.
1390
1504
  """
1391
1505
  # Local imports to avoid circular imports.
1392
1506
  from reflex.utils import telemetry
@@ -1396,51 +1510,71 @@ def initialize_app(app_name: str, template: str | None = None):
1396
1510
  telemetry.send("reinit")
1397
1511
  return
1398
1512
 
1513
+ generation_hash = None
1514
+ if ai:
1515
+ generation_hash = generate_template_using_ai(template)
1516
+ template = constants.Templates.DEFAULT
1517
+
1399
1518
  templates: dict[str, Template] = {}
1400
1519
 
1401
1520
  # Don't fetch app templates if the user directly asked for DEFAULT.
1402
- if template is None or (template != constants.Templates.DEFAULT):
1403
- try:
1404
- # Get the available templates
1405
- templates = fetch_app_templates(constants.Reflex.VERSION)
1406
- if template is None and len(templates) > 0:
1407
- template = prompt_for_template(list(templates.values()))
1408
- except Exception as e:
1409
- console.warn("Failed to fetch templates. Falling back to default template.")
1410
- console.debug(f"Error while fetching templates: {e}")
1411
- finally:
1412
- template = template or constants.Templates.DEFAULT
1521
+ if template is not None and (template not in (constants.Templates.DEFAULT,)):
1522
+ template, templates = fetch_remote_templates(template)
1523
+
1524
+ if template is None:
1525
+ template = prompt_for_template_options(get_init_cli_prompt_options())
1526
+ if template == constants.Templates.AI:
1527
+ generation_hash = generate_template_using_ai()
1528
+ # change to the default to allow creation of default app
1529
+ template = constants.Templates.DEFAULT
1530
+ elif template == constants.Templates.CHOOSE_TEMPLATES:
1531
+ template, templates = fetch_remote_templates()
1413
1532
 
1414
1533
  # If the blank template is selected, create a blank app.
1415
- if template == constants.Templates.DEFAULT:
1534
+ if template in (constants.Templates.DEFAULT,):
1416
1535
  # Default app creation behavior: a blank app.
1417
- create_config(app_name)
1418
- initialize_app_directory(app_name)
1536
+ initialize_default_app(app_name)
1419
1537
  else:
1420
- # Fetch App templates from the backend server.
1421
- console.debug(f"Available templates: {templates}")
1538
+ validate_and_create_app_using_remote_template(
1539
+ app_name=app_name, template=template, templates=templates
1540
+ )
1422
1541
 
1423
- # If user selects a template, it needs to exist.
1424
- if template in templates:
1425
- template_url = templates[template].code_url
1426
- else:
1427
- # Check if the template is a github repo.
1428
- if template.startswith("https://github.com"):
1429
- template_url = (
1430
- f"{template.strip('/').replace('.git', '')}/archive/main.zip"
1431
- )
1432
- else:
1433
- console.error(f"Template `{template}` not found.")
1434
- raise typer.Exit(1)
1542
+ # If a reflex.build generation hash is available, download the code and apply it to the main module.
1543
+ if generation_hash:
1544
+ initialize_main_module_index_from_generation(
1545
+ app_name, generation_hash=generation_hash
1546
+ )
1547
+ telemetry.send("init", template=template)
1435
1548
 
1436
- if template_url is None:
1437
- return
1549
+ return template
1438
1550
 
1439
- create_config_init_app_from_remote_template(
1440
- app_name=app_name, template_url=template_url
1441
- )
1442
1551
 
1443
- telemetry.send("init", template=template)
1552
+ def get_init_cli_prompt_options() -> list[Template]:
1553
+ """Get the CLI options for initializing a Reflex app.
1554
+
1555
+ Returns:
1556
+ The CLI options.
1557
+ """
1558
+ return [
1559
+ Template(
1560
+ name=constants.Templates.DEFAULT,
1561
+ description="A blank Reflex app.",
1562
+ demo_url=constants.Templates.DEFAULT_TEMPLATE_URL,
1563
+ code_url="",
1564
+ ),
1565
+ Template(
1566
+ name=constants.Templates.AI,
1567
+ description="Generate a template using AI [Experimental]",
1568
+ demo_url="",
1569
+ code_url="",
1570
+ ),
1571
+ Template(
1572
+ name=constants.Templates.CHOOSE_TEMPLATES,
1573
+ description="Choose an existing template.",
1574
+ demo_url="",
1575
+ code_url="",
1576
+ ),
1577
+ ]
1444
1578
 
1445
1579
 
1446
1580
  def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):