Skip to content

Commit 5702a18

Browse files
[GTM-836]Rework Init workflow (#4377)
* Rework Init workflow * minor format * refactor * add comments * fix pyright alongside some improvements * add demolink for blank template * fix darglint * Add more templates and keep template name in kebab case * revert getting other templates since we'll use the submodules approach * remove debug statement * Improvements based on standup comments * Add redirect logic * changes based on new flow --------- Co-authored-by: Masen Furer <[email protected]>
1 parent 9faa5d6 commit 5702a18

File tree

4 files changed

+198
-71
lines changed

4 files changed

+198
-71
lines changed

reflex/constants/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ class Templates(SimpleNamespace):
9797
# The default template
9898
DEFAULT = "blank"
9999

100+
# The AI template
101+
AI = "ai"
102+
103+
# The option for the user to choose a remote template.
104+
CHOOSE_TEMPLATES = "choose-templates"
105+
106+
# The URL to find reflex templates.
107+
REFLEX_TEMPLATES_URL = "https://reflex.dev/templates"
108+
109+
# Demo url for the default template.
110+
DEFAULT_TEMPLATE_URL = "https://blank-template.reflex.run"
111+
100112
# The reflex.build frontend host
101113
REFLEX_BUILD_FRONTEND = "https://flexgen.reflex.run"
102114

reflex/reflex.py

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from reflex.config import environment, get_config
1818
from reflex.custom_components.custom_components import custom_components_cli
1919
from reflex.state import reset_disk_state_manager
20-
from reflex.utils import console, redir, telemetry
20+
from reflex.utils import console, telemetry
2121

2222
# Disable typer+rich integration for help panels
2323
typer.core.rich = None # type: ignore
@@ -89,38 +89,16 @@ def _init(
8989
# Set up the web project.
9090
prerequisites.initialize_frontend_dependencies()
9191

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-
10892
# Initialize the app.
109-
template = 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)
11694

11795
# Initialize the .gitignore.
11896
prerequisites.initialize_gitignore()
11997

12098
# Initialize the requirements.txt.
12199
prerequisites.initialize_requirements_txt()
122100

123-
template_msg = "" if not template else f" using the {template} template"
101+
template_msg = f" using the {template} template" if template else ""
124102
# Finish initializing the app.
125103
console.success(f"Initialized {app_name}{template_msg}")
126104

reflex/utils/prerequisites.py

Lines changed: 170 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from reflex import constants, model
3535
from reflex.compiler import templates
3636
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
3838
from reflex.utils.exceptions import (
3939
GeneratedCodeHasNoFunctionDefs,
4040
raise_system_package_missing_error,
@@ -1211,7 +1211,7 @@ def check_schema_up_to_date():
12111211
)
12121212

12131213

1214-
def prompt_for_template(templates: list[Template]) -> str:
1214+
def prompt_for_template_options(templates: list[Template]) -> str:
12151215
"""Prompt the user to specify a template.
12161216
12171217
Args:
@@ -1223,9 +1223,14 @@ def prompt_for_template(templates: list[Template]) -> str:
12231223
# Show the user the URLs of each template to preview.
12241224
console.print("\nGet started with a template:")
12251225

1226+
def format_demo_url_str(url: str) -> str:
1227+
return f" ({url})" if url else ""
1228+
12261229
# Prompt the user to select a template.
12271230
id_to_name = {
1228-
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}"
12291234
for idx, template in enumerate(templates)
12301235
}
12311236
for id in range(len(id_to_name)):
@@ -1380,15 +1385,119 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
13801385
shutil.rmtree(unzip_dir)
13811386

13821387

1383-
def initialize_app(app_name: str, template: str | None = None) -> str | None:
1384-
"""Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
1388+
def initialize_default_app(app_name: str):
1389+
"""Initialize the default app.
13851390
13861391
Args:
13871392
app_name: The name of the app.
1388-
template: The name of the template to use.
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.
13891436
13901437
Raises:
1391-
Exit: If template is directly provided in the command flag and is invalid.
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:
1495+
"""Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
1496+
1497+
Args:
1498+
app_name: The name of the app.
1499+
template: The name of the template to use.
1500+
ai: Whether to use AI to generate the template.
13921501
13931502
Returns:
13941503
The name of the template.
@@ -1401,54 +1510,73 @@ def initialize_app(app_name: str, template: str | None = None) -> str | None:
14011510
telemetry.send("reinit")
14021511
return
14031512

1513+
generation_hash = None
1514+
if ai:
1515+
generation_hash = generate_template_using_ai(template)
1516+
template = constants.Templates.DEFAULT
1517+
14041518
templates: dict[str, Template] = {}
14051519

14061520
# Don't fetch app templates if the user directly asked for DEFAULT.
1407-
if template is None or (template != constants.Templates.DEFAULT):
1408-
try:
1409-
# Get the available templates
1410-
templates = fetch_app_templates(constants.Reflex.VERSION)
1411-
if template is None and len(templates) > 0:
1412-
template = prompt_for_template(list(templates.values()))
1413-
except Exception as e:
1414-
console.warn("Failed to fetch templates. Falling back to default template.")
1415-
console.debug(f"Error while fetching templates: {e}")
1416-
finally:
1417-
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()
14181532

14191533
# If the blank template is selected, create a blank app.
1420-
if template == constants.Templates.DEFAULT:
1534+
if template in (constants.Templates.DEFAULT,):
14211535
# Default app creation behavior: a blank app.
1422-
create_config(app_name)
1423-
initialize_app_directory(app_name)
1536+
initialize_default_app(app_name)
14241537
else:
1425-
# Fetch App templates from the backend server.
1426-
console.debug(f"Available templates: {templates}")
1427-
1428-
# If user selects a template, it needs to exist.
1429-
if template in templates:
1430-
template_url = templates[template].code_url
1431-
else:
1432-
# Check if the template is a github repo.
1433-
if template.startswith("https://github.com"):
1434-
template_url = (
1435-
f"{template.strip('/').replace('.git', '')}/archive/main.zip"
1436-
)
1437-
else:
1438-
console.error(f"Template `{template}` not found.")
1439-
raise typer.Exit(1)
1440-
1441-
if template_url is None:
1442-
return
1443-
1444-
create_config_init_app_from_remote_template(
1445-
app_name=app_name, template_url=template_url
1538+
validate_and_create_app_using_remote_template(
1539+
app_name=app_name, template=template, templates=templates
14461540
)
14471541

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+
)
14481547
telemetry.send("init", template=template)
1548+
14491549
return template
14501550

14511551

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+
]
1578+
1579+
14521580
def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
14531581
"""Overwrite the `index` function in the main module with reflex.build generated code.
14541582

reflex/utils/redir.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@
1010
from . import console
1111

1212

13+
def open_browser(target_url: str) -> None:
14+
"""Open a browser window to target_url.
15+
16+
Args:
17+
target_url: The URL to open in the browser.
18+
"""
19+
if not webbrowser.open(target_url):
20+
console.warn(
21+
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
22+
)
23+
24+
1325
def open_browser_and_wait(
1426
target_url: str, poll_url: str, interval: int = 2
1527
) -> httpx.Response:
@@ -23,10 +35,7 @@ def open_browser_and_wait(
2335
Returns:
2436
The response from the poll_url.
2537
"""
26-
if not webbrowser.open(target_url):
27-
console.warn(
28-
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
29-
)
38+
open_browser(target_url)
3039
console.info("[b]Complete the workflow in the browser to continue.[/b]")
3140
while True:
3241
try:

0 commit comments

Comments
 (0)