reflex 0.4.6a3__py3-none-any.whl → 0.4.7__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.
- reflex/.templates/apps/blank/code/blank.py +1 -0
- reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +1 -1
- reflex/.templates/jinja/custom_components/src.py.jinja2 +8 -8
- reflex/.templates/jinja/web/pages/index.js.jinja2 +0 -4
- reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +2 -6
- reflex/.templates/web/utils/state.js +6 -1
- reflex/__init__.py +2 -0
- reflex/__init__.pyi +2 -0
- reflex/app.py +12 -16
- reflex/app.pyi +2 -0
- reflex/compiler/compiler.py +10 -11
- reflex/compiler/utils.py +3 -3
- reflex/components/chakra/forms/pininput.py +2 -1
- reflex/components/component.py +71 -131
- reflex/components/core/banner.py +1 -1
- reflex/components/core/upload.py +2 -1
- reflex/components/datadisplay/__init__.py +1 -0
- reflex/components/datadisplay/logo.py +49 -0
- reflex/components/el/elements/forms.py +7 -4
- reflex/components/el/elements/forms.pyi +0 -1
- reflex/components/lucide/icon.py +3 -2
- reflex/components/lucide/icon.pyi +2 -2
- reflex/components/markdown/markdown.py +10 -6
- reflex/components/markdown/markdown.pyi +0 -3
- reflex/components/radix/themes/components/select.py +10 -3
- reflex/config.py +1 -1
- reflex/config.pyi +1 -1
- reflex/constants/base.py +4 -5
- reflex/constants/base.pyi +94 -0
- reflex/constants/compiler.py +8 -0
- reflex/custom_components/custom_components.py +33 -38
- reflex/experimental/__init__.py +14 -0
- reflex/experimental/hooks.py +75 -0
- reflex/page.py +1 -1
- reflex/reflex.py +18 -32
- reflex/style.py +4 -4
- reflex/testing.py +1 -1
- reflex/utils/console.py +6 -4
- reflex/utils/exec.py +17 -1
- reflex/utils/export.py +0 -3
- reflex/utils/prerequisites.py +243 -43
- reflex/utils/processes.py +6 -1
- reflex/utils/telemetry.py +14 -2
- reflex/utils/types.py +3 -2
- reflex/vars.py +6 -6
- reflex/vars.pyi +2 -1
- {reflex-0.4.6a3.dist-info → reflex-0.4.7.dist-info}/METADATA +15 -10
- {reflex-0.4.6a3.dist-info → reflex-0.4.7.dist-info}/RECORD +51 -65
- {reflex-0.4.6a3.dist-info → reflex-0.4.7.dist-info}/WHEEL +1 -1
- reflex/.templates/apps/sidebar/README.md +0 -69
- reflex/.templates/apps/sidebar/assets/favicon.ico +0 -0
- reflex/.templates/apps/sidebar/assets/github.svg +0 -10
- reflex/.templates/apps/sidebar/assets/logo.svg +0 -68
- reflex/.templates/apps/sidebar/assets/paneleft.svg +0 -13
- reflex/.templates/apps/sidebar/assets/reflex_black.svg +0 -37
- reflex/.templates/apps/sidebar/assets/reflex_white.svg +0 -8
- reflex/.templates/apps/sidebar/code/__init__.py +0 -1
- reflex/.templates/apps/sidebar/code/components/__init__.py +0 -0
- reflex/.templates/apps/sidebar/code/components/sidebar.py +0 -152
- reflex/.templates/apps/sidebar/code/pages/__init__.py +0 -3
- reflex/.templates/apps/sidebar/code/pages/dashboard.py +0 -22
- reflex/.templates/apps/sidebar/code/pages/index.py +0 -18
- reflex/.templates/apps/sidebar/code/pages/settings.py +0 -61
- reflex/.templates/apps/sidebar/code/sidebar.py +0 -16
- reflex/.templates/apps/sidebar/code/styles.py +0 -60
- reflex/.templates/apps/sidebar/code/templates/__init__.py +0 -1
- reflex/.templates/apps/sidebar/code/templates/template.py +0 -145
- {reflex-0.4.6a3.dist-info → reflex-0.4.7.dist-info}/LICENSE +0 -0
- {reflex-0.4.6a3.dist-info → reflex-0.4.7.dist-info}/entry_points.txt +0 -0
reflex/utils/console.py
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import List, Optional
|
|
6
|
-
|
|
7
5
|
from rich.console import Console
|
|
8
6
|
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
|
9
7
|
from rich.prompt import Prompt
|
|
@@ -150,7 +148,10 @@ def error(msg: str, **kwargs):
|
|
|
150
148
|
|
|
151
149
|
|
|
152
150
|
def ask(
|
|
153
|
-
question: str,
|
|
151
|
+
question: str,
|
|
152
|
+
choices: list[str] | None = None,
|
|
153
|
+
default: str | None = None,
|
|
154
|
+
show_choices: bool = True,
|
|
154
155
|
) -> str:
|
|
155
156
|
"""Takes a prompt question and optionally a list of choices
|
|
156
157
|
and returns the user input.
|
|
@@ -159,11 +160,12 @@ def ask(
|
|
|
159
160
|
question: The question to ask the user.
|
|
160
161
|
choices: A list of choices to select from.
|
|
161
162
|
default: The default option selected.
|
|
163
|
+
show_choices: Whether to show the choices.
|
|
162
164
|
|
|
163
165
|
Returns:
|
|
164
166
|
A string with the user input.
|
|
165
167
|
"""
|
|
166
|
-
return Prompt.ask(question, choices=choices, default=default) # type: ignore
|
|
168
|
+
return Prompt.ask(question, choices=choices, default=default, show_choices=show_choices) # type: ignore
|
|
167
169
|
|
|
168
170
|
|
|
169
171
|
def progress():
|
reflex/utils/exec.py
CHANGED
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import platform
|
|
9
9
|
import re
|
|
10
|
+
import subprocess
|
|
10
11
|
import sys
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from urllib.parse import urljoin
|
|
@@ -18,6 +19,9 @@ from reflex.config import get_config
|
|
|
18
19
|
from reflex.utils import console, path_ops
|
|
19
20
|
from reflex.utils.watch import AssetFolderWatch
|
|
20
21
|
|
|
22
|
+
# For uvicorn windows bug fix (#2335)
|
|
23
|
+
frontend_process = None
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
def start_watching_assets_folder(root):
|
|
23
27
|
"""Start watching assets folder.
|
|
@@ -66,6 +70,9 @@ def kill(proc_pid: int):
|
|
|
66
70
|
process.kill()
|
|
67
71
|
|
|
68
72
|
|
|
73
|
+
# run_process_and_launch_url is assumed to be used
|
|
74
|
+
# only to launch the frontend
|
|
75
|
+
# If this is not the case, might have to change the logic
|
|
69
76
|
def run_process_and_launch_url(run_command: list[str]):
|
|
70
77
|
"""Run the process and launch the URL.
|
|
71
78
|
|
|
@@ -81,9 +88,17 @@ def run_process_and_launch_url(run_command: list[str]):
|
|
|
81
88
|
|
|
82
89
|
while True:
|
|
83
90
|
if process is None:
|
|
91
|
+
kwargs = {}
|
|
92
|
+
if constants.IS_WINDOWS:
|
|
93
|
+
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
|
|
84
94
|
process = processes.new_process(
|
|
85
|
-
run_command,
|
|
95
|
+
run_command,
|
|
96
|
+
cwd=constants.Dirs.WEB,
|
|
97
|
+
shell=constants.IS_WINDOWS,
|
|
98
|
+
**kwargs,
|
|
86
99
|
)
|
|
100
|
+
global frontend_process
|
|
101
|
+
frontend_process = process
|
|
87
102
|
if process.stdout:
|
|
88
103
|
for line in processes.stream_logs("Starting frontend", process):
|
|
89
104
|
match = re.search(constants.Next.FRONTEND_LISTENING_REGEX, line)
|
|
@@ -175,6 +190,7 @@ def run_backend(
|
|
|
175
190
|
log_level=loglevel.value,
|
|
176
191
|
reload=True,
|
|
177
192
|
reload_dirs=[config.app_name],
|
|
193
|
+
reload_excludes=[constants.Dirs.WEB],
|
|
178
194
|
)
|
|
179
195
|
|
|
180
196
|
|
reflex/utils/export.py
CHANGED
|
@@ -46,9 +46,6 @@ def export(
|
|
|
46
46
|
# Show system info
|
|
47
47
|
exec.output_system_info()
|
|
48
48
|
|
|
49
|
-
# Check that the app is initialized.
|
|
50
|
-
prerequisites.check_initialized(frontend=frontend)
|
|
51
|
-
|
|
52
49
|
# Compile the app in production mode and export it.
|
|
53
50
|
console.rule("[bold]Compiling production app and preparing for export.")
|
|
54
51
|
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -10,6 +10,7 @@ import os
|
|
|
10
10
|
import platform
|
|
11
11
|
import random
|
|
12
12
|
import re
|
|
13
|
+
import shutil
|
|
13
14
|
import stat
|
|
14
15
|
import sys
|
|
15
16
|
import tempfile
|
|
@@ -30,6 +31,7 @@ from redis.asyncio import Redis
|
|
|
30
31
|
|
|
31
32
|
import reflex
|
|
32
33
|
from reflex import constants, model
|
|
34
|
+
from reflex.base import Base
|
|
33
35
|
from reflex.compiler import templates
|
|
34
36
|
from reflex.config import Config, get_config
|
|
35
37
|
from reflex.utils import console, path_ops, processes
|
|
@@ -37,6 +39,15 @@ from reflex.utils import console, path_ops, processes
|
|
|
37
39
|
CURRENTLY_INSTALLING_NODE = False
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
class Template(Base):
|
|
43
|
+
"""A template for a Reflex app."""
|
|
44
|
+
|
|
45
|
+
name: str
|
|
46
|
+
description: str
|
|
47
|
+
code_url: str
|
|
48
|
+
demo_url: str
|
|
49
|
+
|
|
50
|
+
|
|
40
51
|
def check_latest_package_version(package_name: str):
|
|
41
52
|
"""Check if the latest version of the package is installed.
|
|
42
53
|
|
|
@@ -407,17 +418,42 @@ def initialize_requirements_txt():
|
|
|
407
418
|
console.info(f"Unable to check {fp} for reflex dependency.")
|
|
408
419
|
|
|
409
420
|
|
|
410
|
-
def initialize_app_directory(
|
|
421
|
+
def initialize_app_directory(
|
|
422
|
+
app_name: str,
|
|
423
|
+
template_name: str = constants.Templates.DEFAULT,
|
|
424
|
+
template_code_dir_name: str | None = None,
|
|
425
|
+
template_dir: Path | None = None,
|
|
426
|
+
):
|
|
411
427
|
"""Initialize the app directory on reflex init.
|
|
412
428
|
|
|
413
429
|
Args:
|
|
414
430
|
app_name: The name of the app.
|
|
415
|
-
|
|
431
|
+
template_name: The name of the template to use.
|
|
432
|
+
template_code_dir_name: The name of the code directory in the template.
|
|
433
|
+
template_dir: The directory of the template source files.
|
|
434
|
+
|
|
435
|
+
Raises:
|
|
436
|
+
Exit: If template_name, template_code_dir_name, template_dir combination is not supported.
|
|
416
437
|
"""
|
|
417
438
|
console.log("Initializing the app directory.")
|
|
418
439
|
|
|
419
|
-
#
|
|
420
|
-
|
|
440
|
+
# By default, use the blank template from local assets.
|
|
441
|
+
if template_name == constants.Templates.DEFAULT:
|
|
442
|
+
if template_code_dir_name is not None or template_dir is not None:
|
|
443
|
+
console.error(
|
|
444
|
+
f"Only {template_name=} should be provided, got {template_code_dir_name=}, {template_dir=}."
|
|
445
|
+
)
|
|
446
|
+
raise typer.Exit(1)
|
|
447
|
+
template_code_dir_name = constants.Templates.Dirs.CODE
|
|
448
|
+
template_dir = Path(constants.Templates.Dirs.BASE, "apps", template_name)
|
|
449
|
+
else:
|
|
450
|
+
if template_code_dir_name is None or template_dir is None:
|
|
451
|
+
console.error(
|
|
452
|
+
f"For `{template_name}` template, `template_code_dir_name` and `template_dir` should both be provided."
|
|
453
|
+
)
|
|
454
|
+
raise typer.Exit(1)
|
|
455
|
+
|
|
456
|
+
console.debug(f"Using {template_name=} {template_dir=} {template_code_dir_name=}.")
|
|
421
457
|
|
|
422
458
|
# Remove all pyc and __pycache__ dirs in template directory.
|
|
423
459
|
for pyc_file in template_dir.glob("**/*.pyc"):
|
|
@@ -430,16 +466,16 @@ def initialize_app_directory(app_name: str, template: constants.Templates.Kind):
|
|
|
430
466
|
path_ops.cp(str(file), file.name)
|
|
431
467
|
|
|
432
468
|
# Rename the template app to the app name.
|
|
433
|
-
path_ops.mv(
|
|
469
|
+
path_ops.mv(template_code_dir_name, app_name)
|
|
434
470
|
path_ops.mv(
|
|
435
|
-
os.path.join(app_name,
|
|
471
|
+
os.path.join(app_name, template_name + constants.Ext.PY),
|
|
436
472
|
os.path.join(app_name, app_name + constants.Ext.PY),
|
|
437
473
|
)
|
|
438
474
|
|
|
439
475
|
# Fix up the imports.
|
|
440
476
|
path_ops.find_replace(
|
|
441
477
|
app_name,
|
|
442
|
-
f"from {
|
|
478
|
+
f"from {template_name}",
|
|
443
479
|
f"from {app_name}",
|
|
444
480
|
)
|
|
445
481
|
|
|
@@ -802,43 +838,42 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
802
838
|
)
|
|
803
839
|
|
|
804
840
|
|
|
805
|
-
def
|
|
806
|
-
"""Check
|
|
841
|
+
def needs_reinit(frontend: bool = True) -> bool:
|
|
842
|
+
"""Check if an app needs to be reinitialized.
|
|
807
843
|
|
|
808
844
|
Args:
|
|
809
845
|
frontend: Whether to check if the frontend is initialized.
|
|
810
846
|
|
|
847
|
+
Returns:
|
|
848
|
+
Whether the app needs to be reinitialized.
|
|
849
|
+
|
|
811
850
|
Raises:
|
|
812
851
|
Exit: If the app is not initialized.
|
|
813
852
|
"""
|
|
814
|
-
|
|
815
|
-
has_reflex_dir = not frontend or os.path.exists(constants.Reflex.DIR)
|
|
816
|
-
has_web_dir = not frontend or os.path.exists(constants.Dirs.WEB)
|
|
817
|
-
|
|
818
|
-
# Check if the app is initialized.
|
|
819
|
-
if not (has_config and has_reflex_dir and has_web_dir):
|
|
853
|
+
if not os.path.exists(constants.Config.FILE):
|
|
820
854
|
console.error(
|
|
821
|
-
f"
|
|
855
|
+
f"[cyan]{constants.Config.FILE}[/cyan] not found. Move to the root folder of your project, or run [bold]{constants.Reflex.MODULE_NAME} init[/bold] to start a new project."
|
|
822
856
|
)
|
|
823
857
|
raise typer.Exit(1)
|
|
824
858
|
|
|
825
|
-
#
|
|
826
|
-
if
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
859
|
+
# Don't need to reinit if not running in frontend mode.
|
|
860
|
+
if not frontend:
|
|
861
|
+
return False
|
|
862
|
+
|
|
863
|
+
# Make sure the .reflex directory exists.
|
|
864
|
+
if not os.path.exists(constants.Reflex.DIR):
|
|
865
|
+
return True
|
|
866
|
+
|
|
867
|
+
# Make sure the .web directory exists in frontend mode.
|
|
868
|
+
if not os.path.exists(constants.Dirs.WEB):
|
|
869
|
+
return True
|
|
831
870
|
|
|
832
|
-
# Print a warning for Windows users.
|
|
833
871
|
if constants.IS_WINDOWS:
|
|
834
872
|
console.warn(
|
|
835
873
|
"""Windows Subsystem for Linux (WSL) is recommended for improving initial install times."""
|
|
836
874
|
)
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
"Python 3.12 on Windows has known issues with hot reload (reflex-dev/reflex#2335). "
|
|
840
|
-
"Python 3.11 is recommended with this release of Reflex."
|
|
841
|
-
)
|
|
875
|
+
# No need to reinitialize if the app is already initialized.
|
|
876
|
+
return False
|
|
842
877
|
|
|
843
878
|
|
|
844
879
|
def is_latest_template() -> bool:
|
|
@@ -1004,33 +1039,35 @@ def check_schema_up_to_date():
|
|
|
1004
1039
|
)
|
|
1005
1040
|
|
|
1006
1041
|
|
|
1007
|
-
def prompt_for_template() ->
|
|
1042
|
+
def prompt_for_template(templates: list[Template]) -> str:
|
|
1008
1043
|
"""Prompt the user to specify a template.
|
|
1009
1044
|
|
|
1045
|
+
Args:
|
|
1046
|
+
templates: The templates to choose from.
|
|
1047
|
+
|
|
1010
1048
|
Returns:
|
|
1011
|
-
The template the user
|
|
1049
|
+
The template name the user selects.
|
|
1012
1050
|
"""
|
|
1013
|
-
# Show the user the URLs of each
|
|
1051
|
+
# Show the user the URLs of each template to preview.
|
|
1014
1052
|
console.print("\nGet started with a template:")
|
|
1015
|
-
console.print("blank (https://blank-template.reflex.run) - A minimal template.")
|
|
1016
|
-
console.print(
|
|
1017
|
-
"sidebar (https://sidebar-template.reflex.run) - A template with a sidebar to navigate pages."
|
|
1018
|
-
)
|
|
1019
|
-
console.print("")
|
|
1020
1053
|
|
|
1021
1054
|
# Prompt the user to select a template.
|
|
1055
|
+
id_to_name = {
|
|
1056
|
+
str(idx): f"{template.name} ({template.demo_url}) - {template.description}"
|
|
1057
|
+
for idx, template in enumerate(templates)
|
|
1058
|
+
}
|
|
1059
|
+
for id in range(len(id_to_name)):
|
|
1060
|
+
console.print(f"({id}) {id_to_name[str(id)]}")
|
|
1061
|
+
|
|
1022
1062
|
template = console.ask(
|
|
1023
1063
|
"Which template would you like to use?",
|
|
1024
|
-
choices=[
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
if template.value != "demo"
|
|
1028
|
-
],
|
|
1029
|
-
default=constants.Templates.Kind.BLANK.value,
|
|
1064
|
+
choices=[str(i) for i in range(len(id_to_name))],
|
|
1065
|
+
show_choices=False,
|
|
1066
|
+
default="0",
|
|
1030
1067
|
)
|
|
1031
1068
|
|
|
1032
1069
|
# Return the template.
|
|
1033
|
-
return
|
|
1070
|
+
return templates[int(template)].name
|
|
1034
1071
|
|
|
1035
1072
|
|
|
1036
1073
|
def should_show_rx_chakra_migration_instructions() -> bool:
|
|
@@ -1183,3 +1220,166 @@ def migrate_to_reflex():
|
|
|
1183
1220
|
for old, new in updates.items():
|
|
1184
1221
|
line = line.replace(old, new)
|
|
1185
1222
|
print(line, end="")
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
def fetch_app_templates() -> dict[str, Template]:
|
|
1226
|
+
"""Fetch the list of app templates from the Reflex backend server.
|
|
1227
|
+
|
|
1228
|
+
Returns:
|
|
1229
|
+
The name and download URL as a dictionary.
|
|
1230
|
+
"""
|
|
1231
|
+
config = get_config()
|
|
1232
|
+
if not config.cp_backend_url:
|
|
1233
|
+
console.info(
|
|
1234
|
+
"Skip fetching App templates. No backend URL is specified in the config."
|
|
1235
|
+
)
|
|
1236
|
+
return {}
|
|
1237
|
+
try:
|
|
1238
|
+
response = httpx.get(
|
|
1239
|
+
f"{config.cp_backend_url}{constants.Templates.APP_TEMPLATES_ROUTE}"
|
|
1240
|
+
)
|
|
1241
|
+
response.raise_for_status()
|
|
1242
|
+
return {
|
|
1243
|
+
template["name"]: Template.parse_obj(template)
|
|
1244
|
+
for template in response.json()
|
|
1245
|
+
}
|
|
1246
|
+
except httpx.HTTPError as ex:
|
|
1247
|
+
console.info(f"Failed to fetch app templates: {ex}")
|
|
1248
|
+
return {}
|
|
1249
|
+
except (TypeError, KeyError, json.JSONDecodeError) as tkje:
|
|
1250
|
+
console.info(f"Unable to process server response for app templates: {tkje}")
|
|
1251
|
+
return {}
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
def create_config_init_app_from_remote_template(
|
|
1255
|
+
app_name: str,
|
|
1256
|
+
template_url: str,
|
|
1257
|
+
):
|
|
1258
|
+
"""Create new rxconfig and initialize app using a remote template.
|
|
1259
|
+
|
|
1260
|
+
Args:
|
|
1261
|
+
app_name: The name of the app.
|
|
1262
|
+
template_url: The path to the template source code as a zip file.
|
|
1263
|
+
|
|
1264
|
+
Raises:
|
|
1265
|
+
Exit: If any download, file operations fail or unexpected zip file format.
|
|
1266
|
+
|
|
1267
|
+
"""
|
|
1268
|
+
# Create a temp directory for the zip download.
|
|
1269
|
+
try:
|
|
1270
|
+
temp_dir = tempfile.mkdtemp()
|
|
1271
|
+
except OSError as ose:
|
|
1272
|
+
console.error(f"Failed to create temp directory for download: {ose}")
|
|
1273
|
+
raise typer.Exit(1) from ose
|
|
1274
|
+
|
|
1275
|
+
# Use httpx GET with redirects to download the zip file.
|
|
1276
|
+
zip_file_path = Path(temp_dir) / "template.zip"
|
|
1277
|
+
try:
|
|
1278
|
+
# Note: following redirects can be risky. We only allow this for reflex built templates at the moment.
|
|
1279
|
+
response = httpx.get(template_url, follow_redirects=True)
|
|
1280
|
+
console.debug(f"Server responded download request: {response}")
|
|
1281
|
+
response.raise_for_status()
|
|
1282
|
+
except httpx.HTTPError as he:
|
|
1283
|
+
console.error(f"Failed to download the template: {he}")
|
|
1284
|
+
raise typer.Exit(1) from he
|
|
1285
|
+
try:
|
|
1286
|
+
with open(zip_file_path, "wb") as f:
|
|
1287
|
+
f.write(response.content)
|
|
1288
|
+
console.debug(f"Downloaded the zip to {zip_file_path}")
|
|
1289
|
+
except OSError as ose:
|
|
1290
|
+
console.error(f"Unable to write the downloaded zip to disk {ose}")
|
|
1291
|
+
raise typer.Exit(1) from ose
|
|
1292
|
+
|
|
1293
|
+
# Create a temp directory for the zip extraction.
|
|
1294
|
+
try:
|
|
1295
|
+
unzip_dir = Path(tempfile.mkdtemp())
|
|
1296
|
+
except OSError as ose:
|
|
1297
|
+
console.error(f"Failed to create temp directory for extracting zip: {ose}")
|
|
1298
|
+
raise typer.Exit(1) from ose
|
|
1299
|
+
try:
|
|
1300
|
+
zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir)
|
|
1301
|
+
# The zip file downloaded from github looks like:
|
|
1302
|
+
# repo-name-branch/**/*, so we need to remove the top level directory.
|
|
1303
|
+
if len(subdirs := os.listdir(unzip_dir)) != 1:
|
|
1304
|
+
console.error(f"Expected one directory in the zip, found {subdirs}")
|
|
1305
|
+
raise typer.Exit(1)
|
|
1306
|
+
template_dir = unzip_dir / subdirs[0]
|
|
1307
|
+
console.debug(f"Template folder is located at {template_dir}")
|
|
1308
|
+
except Exception as uze:
|
|
1309
|
+
console.error(f"Failed to unzip the template: {uze}")
|
|
1310
|
+
raise typer.Exit(1) from uze
|
|
1311
|
+
|
|
1312
|
+
# Move the rxconfig file here first.
|
|
1313
|
+
path_ops.mv(str(template_dir / constants.Config.FILE), constants.Config.FILE)
|
|
1314
|
+
new_config = get_config(reload=True)
|
|
1315
|
+
|
|
1316
|
+
# Get the template app's name from rxconfig in case it is different than
|
|
1317
|
+
# the source code repo name on github.
|
|
1318
|
+
template_name = new_config.app_name
|
|
1319
|
+
|
|
1320
|
+
create_config(app_name)
|
|
1321
|
+
initialize_app_directory(
|
|
1322
|
+
app_name,
|
|
1323
|
+
template_name=template_name,
|
|
1324
|
+
template_code_dir_name=template_name,
|
|
1325
|
+
template_dir=template_dir,
|
|
1326
|
+
)
|
|
1327
|
+
|
|
1328
|
+
# Clean up the temp directories.
|
|
1329
|
+
shutil.rmtree(temp_dir)
|
|
1330
|
+
shutil.rmtree(unzip_dir)
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
def initialize_app(app_name: str, template: str | None = None):
|
|
1334
|
+
"""Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
|
|
1335
|
+
|
|
1336
|
+
Args:
|
|
1337
|
+
app_name: The name of the app.
|
|
1338
|
+
template: The name of the template to use.
|
|
1339
|
+
|
|
1340
|
+
Raises:
|
|
1341
|
+
Exit: If template is directly provided in the command flag and is invalid.
|
|
1342
|
+
"""
|
|
1343
|
+
# Local imports to avoid circular imports.
|
|
1344
|
+
from reflex.utils import telemetry
|
|
1345
|
+
|
|
1346
|
+
# Check if the app is already initialized.
|
|
1347
|
+
if os.path.exists(constants.Config.FILE):
|
|
1348
|
+
telemetry.send("reinit")
|
|
1349
|
+
return
|
|
1350
|
+
|
|
1351
|
+
# Get the available templates
|
|
1352
|
+
templates: dict[str, Template] = fetch_app_templates()
|
|
1353
|
+
|
|
1354
|
+
# Prompt for a template if not provided.
|
|
1355
|
+
if template is None and len(templates) > 0:
|
|
1356
|
+
template = prompt_for_template(list(templates.values()))
|
|
1357
|
+
elif template is None:
|
|
1358
|
+
template = constants.Templates.DEFAULT
|
|
1359
|
+
assert template is not None
|
|
1360
|
+
|
|
1361
|
+
# If the blank template is selected, create a blank app.
|
|
1362
|
+
if template == constants.Templates.DEFAULT:
|
|
1363
|
+
# Default app creation behavior: a blank app.
|
|
1364
|
+
create_config(app_name)
|
|
1365
|
+
initialize_app_directory(app_name)
|
|
1366
|
+
else:
|
|
1367
|
+
# Fetch App templates from the backend server.
|
|
1368
|
+
console.debug(f"Available templates: {templates}")
|
|
1369
|
+
|
|
1370
|
+
# If user selects a template, it needs to exist.
|
|
1371
|
+
if template in templates:
|
|
1372
|
+
template_url = templates[template].code_url
|
|
1373
|
+
else:
|
|
1374
|
+
# Check if the template is a github repo.
|
|
1375
|
+
if template.startswith("https://github.com"):
|
|
1376
|
+
template_url = f"{template.strip('/')}/archive/main.zip"
|
|
1377
|
+
else:
|
|
1378
|
+
console.error(f"Template `{template}` not found.")
|
|
1379
|
+
raise typer.Exit(1)
|
|
1380
|
+
create_config_init_app_from_remote_template(
|
|
1381
|
+
app_name=app_name,
|
|
1382
|
+
template_url=template_url,
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1385
|
+
telemetry.send("init")
|
reflex/utils/processes.py
CHANGED
|
@@ -14,6 +14,7 @@ import psutil
|
|
|
14
14
|
import typer
|
|
15
15
|
from redis.exceptions import RedisError
|
|
16
16
|
|
|
17
|
+
from reflex import constants
|
|
17
18
|
from reflex.utils import console, path_ops, prerequisites
|
|
18
19
|
|
|
19
20
|
|
|
@@ -227,7 +228,11 @@ def stream_logs(message: str, process: subprocess.Popen, progress=None):
|
|
|
227
228
|
yield line
|
|
228
229
|
|
|
229
230
|
# Check if the process failed (not printing the logs for SIGINT).
|
|
230
|
-
|
|
231
|
+
|
|
232
|
+
# Windows uvicorn bug
|
|
233
|
+
# https://github.com/reflex-dev/reflex/issues/2335
|
|
234
|
+
accepted_return_codes = [0, -2, 15] if constants.IS_WINDOWS else [0, -2]
|
|
235
|
+
if process.returncode not in accepted_return_codes:
|
|
231
236
|
console.error(f"{message} failed with exit code {process.returncode}")
|
|
232
237
|
for line in logs:
|
|
233
238
|
console.error(line, end="")
|
reflex/utils/telemetry.py
CHANGED
|
@@ -4,7 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import multiprocessing
|
|
6
6
|
import platform
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
except ImportError:
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
UTC = None
|
|
8
14
|
|
|
9
15
|
import httpx
|
|
10
16
|
import psutil
|
|
@@ -99,6 +105,12 @@ def _prepare_event(event: str) -> dict:
|
|
|
99
105
|
)
|
|
100
106
|
return {}
|
|
101
107
|
|
|
108
|
+
if UTC is None:
|
|
109
|
+
# for python 3.8, 3.9 & 3.10
|
|
110
|
+
stamp = datetime.utcnow().isoformat()
|
|
111
|
+
else:
|
|
112
|
+
# for python 3.11 & 3.12
|
|
113
|
+
stamp = datetime.now(UTC).isoformat()
|
|
102
114
|
return {
|
|
103
115
|
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
|
|
104
116
|
"event": event,
|
|
@@ -111,7 +123,7 @@ def _prepare_event(event: str) -> dict:
|
|
|
111
123
|
"cpu_count": get_cpu_count(),
|
|
112
124
|
"memory": get_memory(),
|
|
113
125
|
},
|
|
114
|
-
"timestamp":
|
|
126
|
+
"timestamp": stamp,
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
|
reflex/utils/types.py
CHANGED
|
@@ -416,11 +416,12 @@ def validate_literal(key: str, value: Any, expected_type: Type, comp_name: str):
|
|
|
416
416
|
):
|
|
417
417
|
allowed_values = expected_type.__args__
|
|
418
418
|
if value not in allowed_values:
|
|
419
|
-
|
|
419
|
+
allowed_value_str = ",".join(
|
|
420
420
|
[str(v) if not isinstance(v, str) else f"'{v}'" for v in allowed_values]
|
|
421
421
|
)
|
|
422
|
+
value_str = f"'{value}'" if isinstance(value, str) else value
|
|
422
423
|
raise ValueError(
|
|
423
|
-
f"prop value for {str(key)} of the `{comp_name}` component should be one of the following: {
|
|
424
|
+
f"prop value for {str(key)} of the `{comp_name}` component should be one of the following: {allowed_value_str}. Got {value_str} instead"
|
|
424
425
|
)
|
|
425
426
|
|
|
426
427
|
|
reflex/vars.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Define a state var."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import contextlib
|
|
@@ -21,7 +22,6 @@ from typing import (
|
|
|
21
22
|
List,
|
|
22
23
|
Literal,
|
|
23
24
|
Optional,
|
|
24
|
-
Set,
|
|
25
25
|
Tuple,
|
|
26
26
|
Type,
|
|
27
27
|
Union,
|
|
@@ -119,7 +119,7 @@ class VarData(Base):
|
|
|
119
119
|
imports: ImportDict = {}
|
|
120
120
|
|
|
121
121
|
# Hooks that need to be present in the component to render this var
|
|
122
|
-
hooks:
|
|
122
|
+
hooks: Dict[str, None] = {}
|
|
123
123
|
|
|
124
124
|
# Positions of interpolated strings. This is used by the decoder to figure
|
|
125
125
|
# out where the interpolations are and only escape the non-interpolated
|
|
@@ -138,7 +138,7 @@ class VarData(Base):
|
|
|
138
138
|
"""
|
|
139
139
|
state = ""
|
|
140
140
|
_imports = {}
|
|
141
|
-
hooks =
|
|
141
|
+
hooks = {}
|
|
142
142
|
interpolations = []
|
|
143
143
|
for var_data in others:
|
|
144
144
|
if var_data is None:
|
|
@@ -182,7 +182,7 @@ class VarData(Base):
|
|
|
182
182
|
# not part of the vardata itself.
|
|
183
183
|
return (
|
|
184
184
|
self.state == other.state
|
|
185
|
-
and self.hooks == other.hooks
|
|
185
|
+
and self.hooks.keys() == other.hooks.keys()
|
|
186
186
|
and imports.collapse_imports(self.imports)
|
|
187
187
|
== imports.collapse_imports(other.imports)
|
|
188
188
|
)
|
|
@@ -200,7 +200,7 @@ class VarData(Base):
|
|
|
200
200
|
lib: [import_var.dict() for import_var in import_vars]
|
|
201
201
|
for lib, import_vars in self.imports.items()
|
|
202
202
|
},
|
|
203
|
-
"hooks":
|
|
203
|
+
"hooks": self.hooks,
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
|
|
@@ -1659,7 +1659,7 @@ class Var:
|
|
|
1659
1659
|
hooks={
|
|
1660
1660
|
"const {0} = useContext(StateContexts.{0})".format(
|
|
1661
1661
|
format.format_state_name(state_name)
|
|
1662
|
-
)
|
|
1662
|
+
): None
|
|
1663
1663
|
},
|
|
1664
1664
|
imports={
|
|
1665
1665
|
f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
|
reflex/vars.pyi
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
""" Generated with stubgen from mypy, then manually edited, do not regen."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
from dataclasses import dataclass
|
|
@@ -35,7 +36,7 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]: ...
|
|
|
35
36
|
class VarData(Base):
|
|
36
37
|
state: str
|
|
37
38
|
imports: dict[str, set[ImportVar]]
|
|
38
|
-
hooks:
|
|
39
|
+
hooks: Dict[str, None]
|
|
39
40
|
interpolations: List[Tuple[int, int]]
|
|
40
41
|
@classmethod
|
|
41
42
|
def merge(cls, *others: VarData | None) -> VarData | None: ...
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: reflex
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.7
|
|
4
4
|
Summary: Web apps in pure Python.
|
|
5
5
|
Home-page: https://reflex.dev
|
|
6
6
|
License: Apache-2.0
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
19
|
Requires-Dist: alembic (>=1.11.1,<2.0)
|
|
19
20
|
Requires-Dist: build (>=1.0.3,<2.0)
|
|
20
21
|
Requires-Dist: charset-normalizer (>=3.3.2,<4.0)
|
|
@@ -74,6 +75,18 @@ Description-Content-Type: text/markdown
|
|
|
74
75
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md)
|
|
75
76
|
|
|
76
77
|
---
|
|
78
|
+
|
|
79
|
+
# Reflex
|
|
80
|
+
|
|
81
|
+
Reflex is a library to build full-stack web apps in pure Python.
|
|
82
|
+
|
|
83
|
+
Key features:
|
|
84
|
+
* **Pure Python** - Write your app's frontend and backend all in Python, no need to learn Javascript.
|
|
85
|
+
* **Full Flexibility** - Reflex is easy to get started with, but can also scale to complex apps.
|
|
86
|
+
* **Deploy Instantly** - After building, deploy your app with a [single command](https://reflex.dev/docs/hosting/deploy-quick-start/) or host it on your own server.
|
|
87
|
+
|
|
88
|
+
See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) to learn how Reflex works under the hood.
|
|
89
|
+
|
|
77
90
|
## ⚙️ Installation
|
|
78
91
|
|
|
79
92
|
Open a terminal and run (Requires Python 3.8+):
|
|
@@ -263,19 +276,11 @@ You can create a multi-page app by adding more pages.
|
|
|
263
276
|
</div>
|
|
264
277
|
|
|
265
278
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
279
|
## ✅ Status
|
|
270
280
|
|
|
271
281
|
Reflex launched in December 2022 with the name Pynecone.
|
|
272
282
|
|
|
273
|
-
As of
|
|
274
|
-
|
|
275
|
-
- :white_check_mark: **Public Alpha**: Anyone can install and use Reflex. There may be issues, but we are working to resolve them actively.
|
|
276
|
-
- :large_orange_diamond: **Public Beta**: Stable enough for non-enterprise use-cases.
|
|
277
|
-
- **Public Hosting Beta**: _Optionally_, deploy and host your apps on Reflex!
|
|
278
|
-
- **Public**: Reflex is production ready.
|
|
283
|
+
As of February 2024, our hosting service is in alpha! During this time anyone can deploy their apps for free. See our [roadmap](https://github.com/reflex-dev/reflex/issues/2727) to see what's planned.
|
|
279
284
|
|
|
280
285
|
Reflex has new releases and features coming every week! Make sure to :star: star and :eyes: watch this repository to stay up to date.
|
|
281
286
|
|