reflex 0.2.2__py3-none-any.whl → 0.2.3__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/counter/counter.py +1 -1
- reflex/.templates/apps/default/default.py +1 -1
- reflex/.templates/web/utils/state.js +5 -1
- reflex/__init__.py +1 -0
- reflex/app.py +40 -24
- reflex/components/datadisplay/datatable.py +16 -19
- reflex/components/navigation/breadcrumb.py +31 -8
- reflex/components/tags/iter_tag.py +1 -1
- reflex/components/tags/tag.py +40 -38
- reflex/components/typography/heading.py +3 -0
- reflex/components/typography/markdown.py +86 -30
- reflex/config.py +0 -6
- reflex/constants.py +59 -14
- reflex/model.py +2 -2
- reflex/page.py +66 -0
- reflex/reflex.py +93 -79
- reflex/route.py +11 -21
- reflex/state.py +29 -12
- reflex/testing.py +3 -1
- reflex/utils/build.py +17 -61
- reflex/utils/console.py +112 -22
- reflex/utils/exec.py +25 -42
- reflex/utils/format.py +19 -0
- reflex/utils/imports.py +1 -1
- reflex/utils/prerequisites.py +242 -145
- reflex/utils/processes.py +96 -16
- {reflex-0.2.2.dist-info → reflex-0.2.3.dist-info}/METADATA +2 -2
- {reflex-0.2.2.dist-info → reflex-0.2.3.dist-info}/RECORD +31 -30
- {reflex-0.2.2.dist-info → reflex-0.2.3.dist-info}/WHEEL +1 -1
- reflex-0.2.3.dist-info/entry_points.txt +3 -0
- reflex-0.2.2.dist-info/entry_points.txt +0 -3
- {reflex-0.2.2.dist-info → reflex-0.2.3.dist-info}/LICENSE +0 -0
reflex/constants.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Constants used throughout the package."""
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
|
|
3
4
|
import os
|
|
5
|
+
import platform
|
|
4
6
|
import re
|
|
5
7
|
from enum import Enum
|
|
6
8
|
from types import SimpleNamespace
|
|
@@ -43,14 +45,10 @@ def get_value(key: str, default: Any = None, type_: Type = str) -> Type:
|
|
|
43
45
|
MODULE_NAME = "reflex"
|
|
44
46
|
# The current version of Reflex.
|
|
45
47
|
VERSION = metadata.version(MODULE_NAME)
|
|
46
|
-
# Minimum version of Node.js required to run Reflex.
|
|
47
|
-
MIN_NODE_VERSION = "16.8.0"
|
|
48
|
-
|
|
49
|
-
# Valid bun versions.
|
|
50
|
-
MIN_BUN_VERSION = "0.5.9"
|
|
51
|
-
MAX_BUN_VERSION = "0.6.9"
|
|
52
48
|
|
|
53
49
|
# Files and directories used to init a new project.
|
|
50
|
+
# The directory to store reflex dependencies.
|
|
51
|
+
REFLEX_DIR = os.path.expandvars(os.path.join("$HOME", f".{MODULE_NAME}"))
|
|
54
52
|
# The root directory of the reflex library.
|
|
55
53
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
56
54
|
# The name of the assets directory.
|
|
@@ -64,6 +62,42 @@ ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
|
|
|
64
62
|
# The jinja template directory.
|
|
65
63
|
JINJA_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "jinja")
|
|
66
64
|
|
|
65
|
+
# Bun config.
|
|
66
|
+
# The Bun version.
|
|
67
|
+
BUN_VERSION = "0.7.0"
|
|
68
|
+
# The directory to store the bun.
|
|
69
|
+
BUN_ROOT_PATH = os.path.join(REFLEX_DIR, ".bun")
|
|
70
|
+
# The bun path.
|
|
71
|
+
BUN_PATH = os.path.join(BUN_ROOT_PATH, "bin", "bun")
|
|
72
|
+
# URL to bun install script.
|
|
73
|
+
BUN_INSTALL_URL = "https://bun.sh/install"
|
|
74
|
+
|
|
75
|
+
# NVM / Node config.
|
|
76
|
+
# The NVM version.
|
|
77
|
+
NVM_VERSION = "0.39.1"
|
|
78
|
+
# The Node version.
|
|
79
|
+
NODE_VERSION = "18.17.0"
|
|
80
|
+
# The minimum required node version.
|
|
81
|
+
NODE_VERSION_MIN = "16.8.0"
|
|
82
|
+
# The directory to store nvm.
|
|
83
|
+
NVM_DIR = os.path.join(REFLEX_DIR, ".nvm")
|
|
84
|
+
# The nvm path.
|
|
85
|
+
NVM_PATH = os.path.join(NVM_DIR, "nvm.sh")
|
|
86
|
+
# The node bin path.
|
|
87
|
+
NODE_BIN_PATH = os.path.join(NVM_DIR, "versions", "node", f"v{NODE_VERSION}", "bin")
|
|
88
|
+
# The default path where node is installed.
|
|
89
|
+
NODE_PATH = (
|
|
90
|
+
"node" if platform.system() == "Windows" else os.path.join(NODE_BIN_PATH, "node")
|
|
91
|
+
)
|
|
92
|
+
# The default path where npm is installed.
|
|
93
|
+
NPM_PATH = (
|
|
94
|
+
"npm" if platform.system() == "Windows" else os.path.join(NODE_BIN_PATH, "npm")
|
|
95
|
+
)
|
|
96
|
+
# The URL to the nvm install script.
|
|
97
|
+
NVM_INSTALL_URL = (
|
|
98
|
+
f"https://raw.githubusercontent.com/nvm-sh/nvm/v{NVM_VERSION}/install.sh"
|
|
99
|
+
)
|
|
100
|
+
|
|
67
101
|
# The frontend directories in a project.
|
|
68
102
|
# The web folder where the NextJS app is compiled to.
|
|
69
103
|
WEB_DIR = ".web"
|
|
@@ -110,12 +144,6 @@ BACKEND_PORT = get_value("BACKEND_PORT", "8000")
|
|
|
110
144
|
API_URL = get_value("API_URL", "http://localhost:8000")
|
|
111
145
|
# The deploy url
|
|
112
146
|
DEPLOY_URL = get_value("DEPLOY_URL")
|
|
113
|
-
# bun root location
|
|
114
|
-
BUN_ROOT_PATH = "$HOME/.bun"
|
|
115
|
-
# The default path where bun is installed.
|
|
116
|
-
BUN_PATH = get_value("BUN_PATH", f"{BUN_ROOT_PATH}/bin/bun")
|
|
117
|
-
# Command to install bun.
|
|
118
|
-
INSTALL_BUN = f"curl -fsSL https://bun.sh/install | bash -s -- bun-v{MAX_BUN_VERSION}"
|
|
119
147
|
# Default host in dev mode.
|
|
120
148
|
BACKEND_HOST = get_value("BACKEND_HOST", "0.0.0.0")
|
|
121
149
|
# The default timeout when launching the gunicorn server.
|
|
@@ -200,6 +228,11 @@ TOKEN_EXPIRATION = 60 * 60
|
|
|
200
228
|
# The event namespace for websocket
|
|
201
229
|
EVENT_NAMESPACE = get_value("EVENT_NAMESPACE")
|
|
202
230
|
|
|
231
|
+
# Testing variables.
|
|
232
|
+
# Testing os env set by pytest when running a test case.
|
|
233
|
+
PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
|
|
234
|
+
|
|
235
|
+
|
|
203
236
|
# Env modes
|
|
204
237
|
class Env(str, Enum):
|
|
205
238
|
"""The environment modes."""
|
|
@@ -218,6 +251,18 @@ class LogLevel(str, Enum):
|
|
|
218
251
|
ERROR = "error"
|
|
219
252
|
CRITICAL = "critical"
|
|
220
253
|
|
|
254
|
+
def __le__(self, other: LogLevel) -> bool:
|
|
255
|
+
"""Compare log levels.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
other: The other log level.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
True if the log level is less than or equal to the other log level.
|
|
262
|
+
"""
|
|
263
|
+
levels = list(LogLevel)
|
|
264
|
+
return levels.index(self) <= levels.index(other)
|
|
265
|
+
|
|
221
266
|
|
|
222
267
|
# Templates
|
|
223
268
|
class Template(str, Enum):
|
|
@@ -328,6 +373,7 @@ class RouteVar(SimpleNamespace):
|
|
|
328
373
|
CLIENT_TOKEN = "token"
|
|
329
374
|
HEADERS = "headers"
|
|
330
375
|
PATH = "pathname"
|
|
376
|
+
ORIGIN = "asPath"
|
|
331
377
|
SESSION_ID = "sid"
|
|
332
378
|
QUERY = "query"
|
|
333
379
|
COOKIE = "cookie"
|
|
@@ -346,8 +392,7 @@ class RouteRegex(SimpleNamespace):
|
|
|
346
392
|
|
|
347
393
|
|
|
348
394
|
# 404 variables
|
|
349
|
-
|
|
350
|
-
SLUG_404 = "[..._]"
|
|
395
|
+
SLUG_404 = "404"
|
|
351
396
|
TITLE_404 = "404 - Not Found"
|
|
352
397
|
FAVICON_404 = "favicon.ico"
|
|
353
398
|
DESCRIPTION_404 = "The page was not found"
|
reflex/model.py
CHANGED
|
@@ -37,8 +37,8 @@ def get_engine(url: Optional[str] = None):
|
|
|
37
37
|
if url is None:
|
|
38
38
|
raise ValueError("No database url configured")
|
|
39
39
|
if not Path(constants.ALEMBIC_CONFIG).exists():
|
|
40
|
-
console.
|
|
41
|
-
"
|
|
40
|
+
console.warn(
|
|
41
|
+
"Database is not initialized, run [bold]reflex db init[/bold] first."
|
|
42
42
|
)
|
|
43
43
|
echo_db_query = False
|
|
44
44
|
if conf.env == constants.Env.DEV and constants.SQLALCHEMY_ECHO:
|
reflex/page.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""The page decorator and associated variables and functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional, Union
|
|
6
|
+
|
|
7
|
+
from reflex.components.component import Component
|
|
8
|
+
from reflex.event import EventHandler
|
|
9
|
+
|
|
10
|
+
DECORATED_PAGES = []
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def page(
|
|
14
|
+
route: Optional[str] = None,
|
|
15
|
+
title: Optional[str] = None,
|
|
16
|
+
image: Optional[str] = None,
|
|
17
|
+
description: Optional[str] = None,
|
|
18
|
+
meta: Optional[str] = None,
|
|
19
|
+
script_tags: Optional[List[Component]] = None,
|
|
20
|
+
on_load: Optional[Union[EventHandler, List[EventHandler]]] = None,
|
|
21
|
+
):
|
|
22
|
+
"""Decorate a function as a page.
|
|
23
|
+
|
|
24
|
+
rx.App() will automatically call add_page() for any method decorated with page
|
|
25
|
+
when App.compile is called.
|
|
26
|
+
|
|
27
|
+
All defaults are None because they will use the one from add_page().
|
|
28
|
+
|
|
29
|
+
Note: the decorated functions still need to be imported.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
route: The route to reach the page.
|
|
33
|
+
title: The title of the page.
|
|
34
|
+
image: The favicon of the page.
|
|
35
|
+
description: The description of the page.
|
|
36
|
+
meta: Additionnal meta to add to the page.
|
|
37
|
+
on_load: The event handler(s) called when the page load.
|
|
38
|
+
script_tags: scripts to attach to the page
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The decorated function.
|
|
42
|
+
"""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def decorator(render_fn):
|
|
46
|
+
kwargs = {}
|
|
47
|
+
if route:
|
|
48
|
+
kwargs["route"] = route
|
|
49
|
+
if title:
|
|
50
|
+
kwargs["title"] = title
|
|
51
|
+
if image:
|
|
52
|
+
kwargs["image"] = image
|
|
53
|
+
if description:
|
|
54
|
+
kwargs["description"] = description
|
|
55
|
+
if meta:
|
|
56
|
+
kwargs["meta"] = meta
|
|
57
|
+
if script_tags:
|
|
58
|
+
kwargs["script_tags"] = script_tags
|
|
59
|
+
if on_load:
|
|
60
|
+
kwargs["on_load"] = on_load
|
|
61
|
+
|
|
62
|
+
DECORATED_PAGES.append((render_fn, kwargs))
|
|
63
|
+
|
|
64
|
+
return render_fn
|
|
65
|
+
|
|
66
|
+
return decorator
|
reflex/reflex.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Reflex CLI to create, run, and deploy apps."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import platform
|
|
5
4
|
import signal
|
|
6
5
|
import threading
|
|
7
6
|
from pathlib import Path
|
|
@@ -15,54 +14,79 @@ from reflex.config import get_config
|
|
|
15
14
|
from reflex.utils import build, console, exec, prerequisites, processes, telemetry
|
|
16
15
|
|
|
17
16
|
# Create the app.
|
|
18
|
-
cli = typer.Typer()
|
|
17
|
+
cli = typer.Typer(add_completion=False)
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
def version(value: bool):
|
|
21
|
+
"""Get the Reflex version.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
value: Whether the version flag was passed.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
typer.Exit: If the version flag was passed.
|
|
28
|
+
"""
|
|
29
|
+
if value:
|
|
30
|
+
console.print(constants.VERSION)
|
|
31
|
+
raise typer.Exit()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@cli.callback()
|
|
35
|
+
def main(
|
|
36
|
+
version: bool = typer.Option(
|
|
37
|
+
None,
|
|
38
|
+
"-v",
|
|
39
|
+
"--version",
|
|
40
|
+
callback=version,
|
|
41
|
+
help="Get the Reflex version.",
|
|
42
|
+
is_eager=True,
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
"""Reflex CLI to create, run, and deploy apps."""
|
|
46
|
+
pass
|
|
25
47
|
|
|
26
48
|
|
|
27
49
|
@cli.command()
|
|
28
50
|
def init(
|
|
29
|
-
name: str = typer.Option(
|
|
51
|
+
name: str = typer.Option(
|
|
52
|
+
None, metavar="APP_NAME", help="The name of the app to be initialized."
|
|
53
|
+
),
|
|
30
54
|
template: constants.Template = typer.Option(
|
|
31
|
-
constants.Template.DEFAULT, help="
|
|
55
|
+
constants.Template.DEFAULT, help="The template to initialize the app with."
|
|
56
|
+
),
|
|
57
|
+
loglevel: constants.LogLevel = typer.Option(
|
|
58
|
+
constants.LogLevel.INFO, help="The log level to use."
|
|
32
59
|
),
|
|
33
60
|
):
|
|
34
61
|
"""Initialize a new Reflex app in the current directory."""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Make sure they don't name the app "reflex".
|
|
38
|
-
if app_name == constants.MODULE_NAME:
|
|
39
|
-
console.print(
|
|
40
|
-
f"[red]The app directory cannot be named [bold]{constants.MODULE_NAME}."
|
|
41
|
-
)
|
|
42
|
-
raise typer.Exit()
|
|
62
|
+
# Set the log level.
|
|
63
|
+
console.set_log_level(loglevel)
|
|
43
64
|
|
|
65
|
+
# Get the app name.
|
|
66
|
+
app_name = prerequisites.get_default_app_name() if name is None else name
|
|
44
67
|
console.rule(f"[bold]Initializing {app_name}")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
prerequisites.
|
|
68
|
+
|
|
69
|
+
# Set up the web project.
|
|
70
|
+
prerequisites.initialize_frontend_dependencies()
|
|
48
71
|
|
|
49
72
|
# Migrate Pynecone projects to Reflex.
|
|
50
73
|
prerequisites.migrate_to_reflex()
|
|
51
74
|
|
|
52
75
|
# Set up the app directory, only if the config doesn't exist.
|
|
76
|
+
config = get_config()
|
|
53
77
|
if not os.path.exists(constants.CONFIG_FILE):
|
|
54
78
|
prerequisites.create_config(app_name)
|
|
55
79
|
prerequisites.initialize_app_directory(app_name, template)
|
|
56
80
|
build.set_reflex_project_hash()
|
|
57
|
-
telemetry.send("init",
|
|
81
|
+
telemetry.send("init", config.telemetry_enabled)
|
|
58
82
|
else:
|
|
59
|
-
|
|
60
|
-
telemetry.send("reinit", get_config().telemetry_enabled)
|
|
83
|
+
telemetry.send("reinit", config.telemetry_enabled)
|
|
61
84
|
|
|
62
85
|
# Initialize the .gitignore.
|
|
63
86
|
prerequisites.initialize_gitignore()
|
|
87
|
+
|
|
64
88
|
# Finish initializing the app.
|
|
65
|
-
console.
|
|
89
|
+
console.success(f"Initialized {app_name}")
|
|
66
90
|
|
|
67
91
|
|
|
68
92
|
@cli.command()
|
|
@@ -74,18 +98,17 @@ def run(
|
|
|
74
98
|
False, "--frontend-only", help="Execute only frontend."
|
|
75
99
|
),
|
|
76
100
|
backend: bool = typer.Option(False, "--backend-only", help="Execute only backend."),
|
|
77
|
-
loglevel: constants.LogLevel = typer.Option(
|
|
78
|
-
constants.LogLevel.ERROR, help="The log level to use."
|
|
79
|
-
),
|
|
80
101
|
frontend_port: str = typer.Option(None, help="Specify a different frontend port."),
|
|
81
102
|
backend_port: str = typer.Option(None, help="Specify a different backend port."),
|
|
82
103
|
backend_host: str = typer.Option(None, help="Specify the backend host."),
|
|
104
|
+
loglevel: constants.LogLevel = typer.Option(
|
|
105
|
+
constants.LogLevel.INFO, help="The log level to use."
|
|
106
|
+
),
|
|
83
107
|
):
|
|
84
108
|
"""Run the app in the current directory."""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
109
|
+
# Set the log level.
|
|
110
|
+
console.set_log_level(loglevel)
|
|
111
|
+
|
|
89
112
|
# Set ports as os env variables to take precedence over config and
|
|
90
113
|
# .env variables(if override_os_envs flag in config is set to False).
|
|
91
114
|
build.set_os_env(
|
|
@@ -94,17 +117,20 @@ def run(
|
|
|
94
117
|
backend_host=backend_host,
|
|
95
118
|
)
|
|
96
119
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
backend_port =
|
|
101
|
-
backend_host =
|
|
120
|
+
# Get the ports from the config.
|
|
121
|
+
config = get_config()
|
|
122
|
+
frontend_port = config.frontend_port if frontend_port is None else frontend_port
|
|
123
|
+
backend_port = config.backend_port if backend_port is None else backend_port
|
|
124
|
+
backend_host = config.backend_host if backend_host is None else backend_host
|
|
102
125
|
|
|
103
126
|
# If no --frontend-only and no --backend-only, then turn on frontend and backend both
|
|
104
127
|
if not frontend and not backend:
|
|
105
128
|
frontend = True
|
|
106
129
|
backend = True
|
|
107
130
|
|
|
131
|
+
# Check that the app is initialized.
|
|
132
|
+
prerequisites.check_initialized(frontend=frontend)
|
|
133
|
+
|
|
108
134
|
# If something is running on the ports, ask the user if they want to kill or change it.
|
|
109
135
|
if frontend and processes.is_process_on_port(frontend_port):
|
|
110
136
|
frontend_port = processes.change_or_terminate_port(frontend_port, "frontend")
|
|
@@ -112,20 +138,6 @@ def run(
|
|
|
112
138
|
if backend and processes.is_process_on_port(backend_port):
|
|
113
139
|
backend_port = processes.change_or_terminate_port(backend_port, "backend")
|
|
114
140
|
|
|
115
|
-
# Check that the app is initialized.
|
|
116
|
-
if frontend and not prerequisites.is_initialized():
|
|
117
|
-
console.print(
|
|
118
|
-
"[red]The app is not initialized. Run [bold]reflex init[/bold] first."
|
|
119
|
-
)
|
|
120
|
-
raise typer.Exit()
|
|
121
|
-
|
|
122
|
-
# Check that the template is up to date.
|
|
123
|
-
if frontend and not prerequisites.is_latest_template():
|
|
124
|
-
console.print(
|
|
125
|
-
"[red]The base app template has updated. Run [bold]reflex init[/bold] again."
|
|
126
|
-
)
|
|
127
|
-
raise typer.Exit()
|
|
128
|
-
|
|
129
141
|
# Get the app module.
|
|
130
142
|
console.rule("[bold]Starting Reflex App")
|
|
131
143
|
app = prerequisites.get_app()
|
|
@@ -153,18 +165,16 @@ def run(
|
|
|
153
165
|
assert setup_frontend and frontend_cmd and backend_cmd, "Invalid env"
|
|
154
166
|
|
|
155
167
|
# Post a telemetry event.
|
|
156
|
-
telemetry.send(f"run-{env.value}",
|
|
168
|
+
telemetry.send(f"run-{env.value}", config.telemetry_enabled)
|
|
157
169
|
|
|
158
170
|
# Run the frontend and backend.
|
|
159
171
|
if frontend:
|
|
160
|
-
setup_frontend(Path.cwd()
|
|
161
|
-
threading.Thread(
|
|
162
|
-
target=frontend_cmd, args=(Path.cwd(), frontend_port, loglevel)
|
|
163
|
-
).start()
|
|
172
|
+
setup_frontend(Path.cwd())
|
|
173
|
+
threading.Thread(target=frontend_cmd, args=(Path.cwd(), frontend_port)).start()
|
|
164
174
|
if backend:
|
|
165
175
|
threading.Thread(
|
|
166
176
|
target=backend_cmd,
|
|
167
|
-
args=(app.__name__, backend_host, backend_port
|
|
177
|
+
args=(app.__name__, backend_host, backend_port),
|
|
168
178
|
).start()
|
|
169
179
|
|
|
170
180
|
# Display custom message when there is a keyboard interrupt.
|
|
@@ -176,7 +186,6 @@ def deploy(dry_run: bool = typer.Option(False, help="Whether to run a dry run.")
|
|
|
176
186
|
"""Deploy the app to the Reflex hosting service."""
|
|
177
187
|
# Get the app config.
|
|
178
188
|
config = get_config()
|
|
179
|
-
config.api_url = prerequisites.get_production_backend_url()
|
|
180
189
|
|
|
181
190
|
# Check if the deploy url is set.
|
|
182
191
|
if config.rxdeploy_url is None:
|
|
@@ -185,7 +194,7 @@ def deploy(dry_run: bool = typer.Option(False, help="Whether to run a dry run.")
|
|
|
185
194
|
|
|
186
195
|
# Compile the app in production mode.
|
|
187
196
|
typer.echo("Compiling production app")
|
|
188
|
-
export(
|
|
197
|
+
export()
|
|
189
198
|
|
|
190
199
|
# Exit early if this is a dry run.
|
|
191
200
|
if dry_run:
|
|
@@ -217,29 +226,29 @@ def export(
|
|
|
217
226
|
backend: bool = typer.Option(
|
|
218
227
|
True, "--frontend-only", help="Export only frontend.", show_default=False
|
|
219
228
|
),
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"--for-reflex-deploy",
|
|
223
|
-
help="Whether export the app for Reflex Deploy Service.",
|
|
229
|
+
loglevel: constants.LogLevel = typer.Option(
|
|
230
|
+
constants.LogLevel.INFO, help="The log level to use."
|
|
224
231
|
),
|
|
225
232
|
):
|
|
226
233
|
"""Export the app to a zip file."""
|
|
227
|
-
|
|
234
|
+
# Set the log level.
|
|
235
|
+
console.set_log_level(loglevel)
|
|
228
236
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
config.api_url = prerequisites.get_production_backend_url()
|
|
237
|
+
# Check that the app is initialized.
|
|
238
|
+
prerequisites.check_initialized(frontend=frontend)
|
|
232
239
|
|
|
233
240
|
# Compile the app in production mode and export it.
|
|
234
241
|
console.rule("[bold]Compiling production app and preparing for export.")
|
|
235
242
|
|
|
236
243
|
if frontend:
|
|
237
|
-
#
|
|
244
|
+
# Ensure module can be imported and app.compile() is called.
|
|
238
245
|
prerequisites.get_app()
|
|
239
|
-
#
|
|
246
|
+
# Set up .web directory and install frontend dependencies.
|
|
240
247
|
build.setup_frontend(Path.cwd())
|
|
241
248
|
|
|
242
|
-
|
|
249
|
+
# Export the app.
|
|
250
|
+
config = get_config()
|
|
251
|
+
build.export(
|
|
243
252
|
backend=backend,
|
|
244
253
|
frontend=frontend,
|
|
245
254
|
zip=zipping,
|
|
@@ -247,15 +256,15 @@ def export(
|
|
|
247
256
|
)
|
|
248
257
|
|
|
249
258
|
# Post a telemetry event.
|
|
250
|
-
telemetry.send("export",
|
|
259
|
+
telemetry.send("export", config.telemetry_enabled)
|
|
251
260
|
|
|
252
261
|
if zipping:
|
|
253
|
-
console.
|
|
262
|
+
console.log(
|
|
254
263
|
"""Backend & Frontend compiled. See [green bold]backend.zip[/green bold]
|
|
255
264
|
and [green bold]frontend.zip[/green bold]."""
|
|
256
265
|
)
|
|
257
266
|
else:
|
|
258
|
-
console.
|
|
267
|
+
console.log(
|
|
259
268
|
"""Backend & Frontend compiled. See [green bold]app[/green bold]
|
|
260
269
|
and [green bold].web/_static[/green bold] directories."""
|
|
261
270
|
)
|
|
@@ -267,16 +276,22 @@ db_cli = typer.Typer()
|
|
|
267
276
|
@db_cli.command(name="init")
|
|
268
277
|
def db_init():
|
|
269
278
|
"""Create database schema and migration configuration."""
|
|
279
|
+
# Check the database url.
|
|
270
280
|
if get_config().db_url is None:
|
|
271
|
-
console.
|
|
281
|
+
console.error("db_url is not configured, cannot initialize.")
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
# Check the alembic config.
|
|
272
285
|
if Path(constants.ALEMBIC_CONFIG).exists():
|
|
273
|
-
console.
|
|
274
|
-
"
|
|
286
|
+
console.error(
|
|
287
|
+
"Database is already initialized. Use "
|
|
275
288
|
"[bold]reflex db makemigrations[/bold] to create schema change "
|
|
276
289
|
"scripts and [bold]reflex db migrate[/bold] to apply migrations "
|
|
277
290
|
"to a new or existing database.",
|
|
278
291
|
)
|
|
279
292
|
return
|
|
293
|
+
|
|
294
|
+
# Initialize the database.
|
|
280
295
|
prerequisites.get_app()
|
|
281
296
|
model.Model.alembic_init()
|
|
282
297
|
model.Model.migrate(autogenerate=True)
|
|
@@ -308,13 +323,12 @@ def makemigrations(
|
|
|
308
323
|
except CommandError as command_error:
|
|
309
324
|
if "Target database is not up to date." not in str(command_error):
|
|
310
325
|
raise
|
|
311
|
-
console.
|
|
312
|
-
f"
|
|
326
|
+
console.error(
|
|
327
|
+
f"{command_error} Run [bold]reflex db migrate[/bold] to update database."
|
|
313
328
|
)
|
|
314
329
|
|
|
315
330
|
|
|
316
|
-
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema")
|
|
317
|
-
main = cli
|
|
331
|
+
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
|
|
318
332
|
|
|
319
333
|
if __name__ == "__main__":
|
|
320
|
-
|
|
334
|
+
cli()
|
reflex/route.py
CHANGED
|
@@ -7,8 +7,8 @@ from typing import Dict, List, Optional, Union
|
|
|
7
7
|
|
|
8
8
|
from reflex import constants
|
|
9
9
|
from reflex.event import EventHandler
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
from reflex.page import page
|
|
11
|
+
from reflex.utils.console import deprecate
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def route(
|
|
@@ -37,25 +37,15 @@ def route(
|
|
|
37
37
|
Returns:
|
|
38
38
|
The decorated function.
|
|
39
39
|
"""
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if description:
|
|
50
|
-
kwargs["description"] = description
|
|
51
|
-
if on_load:
|
|
52
|
-
kwargs["on_load"] = on_load
|
|
53
|
-
|
|
54
|
-
DECORATED_ROUTES.append((render_fn, kwargs))
|
|
55
|
-
|
|
56
|
-
return render_fn
|
|
57
|
-
|
|
58
|
-
return decorator
|
|
40
|
+
deprecate("@rx.route is deprecated and is being replaced by @rx.page instead")
|
|
41
|
+
|
|
42
|
+
return page(
|
|
43
|
+
route=route,
|
|
44
|
+
title=title,
|
|
45
|
+
image=image,
|
|
46
|
+
description=description,
|
|
47
|
+
on_load=on_load,
|
|
48
|
+
)
|
|
59
49
|
|
|
60
50
|
|
|
61
51
|
def verify_route_validity(route: str) -> None:
|
reflex/state.py
CHANGED
|
@@ -476,13 +476,19 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
476
476
|
"""
|
|
477
477
|
return self.router_data.get(constants.RouteVar.CLIENT_IP, "")
|
|
478
478
|
|
|
479
|
-
def get_current_page(self) -> str:
|
|
479
|
+
def get_current_page(self, origin=False) -> str:
|
|
480
480
|
"""Obtain the path of current page from the router data.
|
|
481
481
|
|
|
482
|
+
Args:
|
|
483
|
+
origin: whether to return the base route as shown in browser
|
|
484
|
+
|
|
482
485
|
Returns:
|
|
483
486
|
The current page.
|
|
484
487
|
"""
|
|
485
|
-
|
|
488
|
+
if origin:
|
|
489
|
+
return self.router_data.get(constants.RouteVar.ORIGIN, "")
|
|
490
|
+
else:
|
|
491
|
+
return self.router_data.get(constants.RouteVar.PATH, "")
|
|
486
492
|
|
|
487
493
|
def get_query_params(self) -> Dict[str, str]:
|
|
488
494
|
"""Obtain the query parameters for the queried page.
|
|
@@ -595,6 +601,10 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
595
601
|
self.mark_dirty()
|
|
596
602
|
return
|
|
597
603
|
|
|
604
|
+
# Make sure lists and dicts are converted to ReflexList and ReflexDict.
|
|
605
|
+
if name in self.vars and types._isinstance(value, Union[List, Dict]):
|
|
606
|
+
value = _convert_mutable_datatypes(value, self._reassign_field, name)
|
|
607
|
+
|
|
598
608
|
# Set the attribute.
|
|
599
609
|
super().__setattr__(name, value)
|
|
600
610
|
|
|
@@ -730,8 +740,14 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
730
740
|
|
|
731
741
|
# Handle regular generators.
|
|
732
742
|
elif inspect.isgenerator(events):
|
|
733
|
-
|
|
734
|
-
|
|
743
|
+
try:
|
|
744
|
+
while True:
|
|
745
|
+
yield next(events), False
|
|
746
|
+
except StopIteration as si:
|
|
747
|
+
# the "return" value of the generator is not available
|
|
748
|
+
# in the loop, we must catch StopIteration to access it
|
|
749
|
+
if si.value is not None:
|
|
750
|
+
yield si.value, False
|
|
735
751
|
yield None, True
|
|
736
752
|
|
|
737
753
|
# Handle regular event chains.
|
|
@@ -984,21 +1000,22 @@ def _convert_mutable_datatypes(
|
|
|
984
1000
|
The converted field_value
|
|
985
1001
|
"""
|
|
986
1002
|
if isinstance(field_value, list):
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1003
|
+
field_value = [
|
|
1004
|
+
_convert_mutable_datatypes(value, reassign_field, field_name)
|
|
1005
|
+
for value in field_value
|
|
1006
|
+
]
|
|
991
1007
|
|
|
992
1008
|
field_value = ReflexList(
|
|
993
1009
|
field_value, reassign_field=reassign_field, field_name=field_name
|
|
994
1010
|
)
|
|
995
1011
|
|
|
996
1012
|
if isinstance(field_value, dict):
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1013
|
+
field_value = {
|
|
1014
|
+
key: _convert_mutable_datatypes(value, reassign_field, field_name)
|
|
1015
|
+
for key, value in field_value.items()
|
|
1016
|
+
}
|
|
1001
1017
|
field_value = ReflexDict(
|
|
1002
1018
|
field_value, reassign_field=reassign_field, field_name=field_name
|
|
1003
1019
|
)
|
|
1020
|
+
|
|
1004
1021
|
return field_value
|
reflex/testing.py
CHANGED
|
@@ -65,6 +65,7 @@ if platform.system == "Windows":
|
|
|
65
65
|
else:
|
|
66
66
|
FRONTEND_POPEN_ARGS["start_new_session"] = True
|
|
67
67
|
|
|
68
|
+
|
|
68
69
|
# borrowed from py3.11
|
|
69
70
|
class chdir(contextlib.AbstractContextManager):
|
|
70
71
|
"""Non thread-safe context manager to change the current working directory."""
|
|
@@ -150,6 +151,7 @@ class AppHarness:
|
|
|
150
151
|
reflex.reflex.init(
|
|
151
152
|
name=self.app_name,
|
|
152
153
|
template=reflex.constants.Template.DEFAULT,
|
|
154
|
+
loglevel=reflex.constants.LogLevel.INFO,
|
|
153
155
|
)
|
|
154
156
|
self.app_module_path.write_text(source_code)
|
|
155
157
|
with chdir(self.app_path):
|
|
@@ -179,7 +181,7 @@ class AppHarness:
|
|
|
179
181
|
frontend_env = os.environ.copy()
|
|
180
182
|
frontend_env["PORT"] = "0"
|
|
181
183
|
self.frontend_process = subprocess.Popen(
|
|
182
|
-
[reflex.utils.prerequisites.
|
|
184
|
+
[reflex.utils.prerequisites.get_install_package_manager(), "run", "dev"],
|
|
183
185
|
stdout=subprocess.PIPE,
|
|
184
186
|
encoding="utf-8",
|
|
185
187
|
cwd=self.app_path / reflex.constants.WEB_DIR,
|