Add unified logging (#1462)
This commit is contained in:
parent
e1cb09e9d4
commit
068bcd906e
@ -1,4 +1,5 @@
|
|||||||
"""Constants used throughout the package."""
|
"""Constants used throughout the package."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
@ -250,6 +251,18 @@ class LogLevel(str, Enum):
|
|||||||
ERROR = "error"
|
ERROR = "error"
|
||||||
CRITICAL = "critical"
|
CRITICAL = "critical"
|
||||||
|
|
||||||
|
def __le__(self, other: LogLevel) -> bool:
|
||||||
|
"""Compare log levels.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
other: The other log level.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the log level is less than or equal to the other log level.
|
||||||
|
"""
|
||||||
|
levels = list(LogLevel)
|
||||||
|
return levels.index(self) <= levels.index(other)
|
||||||
|
|
||||||
|
|
||||||
# Templates
|
# Templates
|
||||||
class Template(str, Enum):
|
class Template(str, Enum):
|
||||||
|
@ -37,8 +37,8 @@ def get_engine(url: Optional[str] = None):
|
|||||||
if url is None:
|
if url is None:
|
||||||
raise ValueError("No database url configured")
|
raise ValueError("No database url configured")
|
||||||
if not Path(constants.ALEMBIC_CONFIG).exists():
|
if not Path(constants.ALEMBIC_CONFIG).exists():
|
||||||
console.print(
|
console.warn(
|
||||||
"[red]Database is not initialized, run [bold]reflex db init[/bold] first."
|
"Database is not initialized, run [bold]reflex db init[/bold] first."
|
||||||
)
|
)
|
||||||
echo_db_query = False
|
echo_db_query = False
|
||||||
if conf.env == constants.Env.DEV and constants.SQLALCHEMY_ECHO:
|
if conf.env == constants.Env.DEV and constants.SQLALCHEMY_ECHO:
|
||||||
|
@ -14,7 +14,7 @@ from reflex.config import get_config
|
|||||||
from reflex.utils import build, console, exec, prerequisites, processes, telemetry
|
from reflex.utils import build, console, exec, prerequisites, processes, telemetry
|
||||||
|
|
||||||
# Create the app.
|
# Create the app.
|
||||||
cli = typer.Typer()
|
cli = typer.Typer(add_completion=False)
|
||||||
|
|
||||||
|
|
||||||
def version(value: bool):
|
def version(value: bool):
|
||||||
@ -35,25 +35,33 @@ def version(value: bool):
|
|||||||
def main(
|
def main(
|
||||||
version: bool = typer.Option(
|
version: bool = typer.Option(
|
||||||
None,
|
None,
|
||||||
"--version",
|
|
||||||
"-v",
|
"-v",
|
||||||
|
"--version",
|
||||||
callback=version,
|
callback=version,
|
||||||
help="Get the Reflex version.",
|
help="Get the Reflex version.",
|
||||||
is_eager=True,
|
is_eager=True,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
"""Reflex CLI global configuration."""
|
"""Reflex CLI to create, run, and deploy apps."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def init(
|
def init(
|
||||||
name: str = typer.Option(None, help="Name of the app to be initialized."),
|
name: str = typer.Option(
|
||||||
|
None, metavar="APP_NAME", help="The name of the app to be initialized."
|
||||||
|
),
|
||||||
template: constants.Template = typer.Option(
|
template: constants.Template = typer.Option(
|
||||||
constants.Template.DEFAULT, help="Template to use for the app."
|
constants.Template.DEFAULT, help="The template to initialize the app with."
|
||||||
|
),
|
||||||
|
loglevel: constants.LogLevel = typer.Option(
|
||||||
|
constants.LogLevel.INFO, help="The log level to use."
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
"""Initialize a new Reflex app in the current directory."""
|
"""Initialize a new Reflex app in the current directory."""
|
||||||
|
# Set the log level.
|
||||||
|
console.set_log_level(loglevel)
|
||||||
|
|
||||||
# Get the app name.
|
# Get the app name.
|
||||||
app_name = prerequisites.get_default_app_name() if name is None else name
|
app_name = prerequisites.get_default_app_name() if name is None else name
|
||||||
console.rule(f"[bold]Initializing {app_name}")
|
console.rule(f"[bold]Initializing {app_name}")
|
||||||
@ -78,7 +86,7 @@ def init(
|
|||||||
prerequisites.initialize_gitignore()
|
prerequisites.initialize_gitignore()
|
||||||
|
|
||||||
# Finish initializing the app.
|
# Finish initializing the app.
|
||||||
console.log(f"[bold green]Finished Initializing: {app_name}")
|
console.success(f"Finished Initializing: {app_name}")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -90,16 +98,16 @@ def run(
|
|||||||
False, "--frontend-only", help="Execute only frontend."
|
False, "--frontend-only", help="Execute only frontend."
|
||||||
),
|
),
|
||||||
backend: bool = typer.Option(False, "--backend-only", help="Execute only backend."),
|
backend: bool = typer.Option(False, "--backend-only", help="Execute only backend."),
|
||||||
loglevel: constants.LogLevel = typer.Option(
|
|
||||||
constants.LogLevel.ERROR, help="The log level to use."
|
|
||||||
),
|
|
||||||
frontend_port: str = typer.Option(None, help="Specify a different frontend port."),
|
frontend_port: str = typer.Option(None, help="Specify a different frontend port."),
|
||||||
backend_port: str = typer.Option(None, help="Specify a different backend port."),
|
backend_port: str = typer.Option(None, help="Specify a different backend port."),
|
||||||
backend_host: str = typer.Option(None, help="Specify the backend host."),
|
backend_host: str = typer.Option(None, help="Specify the backend host."),
|
||||||
|
loglevel: constants.LogLevel = typer.Option(
|
||||||
|
constants.LogLevel.INFO, help="The log level to use."
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""Run the app in the current directory."""
|
"""Run the app in the current directory."""
|
||||||
# Check that the app is initialized.
|
# Set the log level.
|
||||||
prerequisites.check_initialized(frontend=frontend)
|
console.set_log_level(loglevel)
|
||||||
|
|
||||||
# Set ports as os env variables to take precedence over config and
|
# Set ports as os env variables to take precedence over config and
|
||||||
# .env variables(if override_os_envs flag in config is set to False).
|
# .env variables(if override_os_envs flag in config is set to False).
|
||||||
@ -120,6 +128,9 @@ def run(
|
|||||||
frontend = True
|
frontend = True
|
||||||
backend = True
|
backend = True
|
||||||
|
|
||||||
|
# Check that the app is initialized.
|
||||||
|
prerequisites.check_initialized(frontend=frontend)
|
||||||
|
|
||||||
# If something is running on the ports, ask the user if they want to kill or change it.
|
# If something is running on the ports, ask the user if they want to kill or change it.
|
||||||
if frontend and processes.is_process_on_port(frontend_port):
|
if frontend and processes.is_process_on_port(frontend_port):
|
||||||
frontend_port = processes.change_or_terminate_port(frontend_port, "frontend")
|
frontend_port = processes.change_or_terminate_port(frontend_port, "frontend")
|
||||||
@ -158,14 +169,12 @@ def run(
|
|||||||
|
|
||||||
# Run the frontend and backend.
|
# Run the frontend and backend.
|
||||||
if frontend:
|
if frontend:
|
||||||
setup_frontend(Path.cwd(), loglevel)
|
setup_frontend(Path.cwd())
|
||||||
threading.Thread(
|
threading.Thread(target=frontend_cmd, args=(Path.cwd(), frontend_port)).start()
|
||||||
target=frontend_cmd, args=(Path.cwd(), frontend_port, loglevel)
|
|
||||||
).start()
|
|
||||||
if backend:
|
if backend:
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=backend_cmd,
|
target=backend_cmd,
|
||||||
args=(app.__name__, backend_host, backend_port, loglevel),
|
args=(app.__name__, backend_host, backend_port),
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
# Display custom message when there is a keyboard interrupt.
|
# Display custom message when there is a keyboard interrupt.
|
||||||
@ -217,8 +226,14 @@ def export(
|
|||||||
backend: bool = typer.Option(
|
backend: bool = typer.Option(
|
||||||
True, "--frontend-only", help="Export only frontend.", show_default=False
|
True, "--frontend-only", help="Export only frontend.", show_default=False
|
||||||
),
|
),
|
||||||
|
loglevel: constants.LogLevel = typer.Option(
|
||||||
|
constants.LogLevel.INFO, help="The log level to use."
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""Export the app to a zip file."""
|
"""Export the app to a zip file."""
|
||||||
|
# Set the log level.
|
||||||
|
console.set_log_level(loglevel)
|
||||||
|
|
||||||
# Check that the app is initialized.
|
# Check that the app is initialized.
|
||||||
prerequisites.check_initialized(frontend=frontend)
|
prerequisites.check_initialized(frontend=frontend)
|
||||||
|
|
||||||
@ -233,7 +248,7 @@ def export(
|
|||||||
|
|
||||||
# Export the app.
|
# Export the app.
|
||||||
config = get_config()
|
config = get_config()
|
||||||
build.export_app(
|
build.export(
|
||||||
backend=backend,
|
backend=backend,
|
||||||
frontend=frontend,
|
frontend=frontend,
|
||||||
zip=zipping,
|
zip=zipping,
|
||||||
@ -244,12 +259,12 @@ def export(
|
|||||||
telemetry.send("export", config.telemetry_enabled)
|
telemetry.send("export", config.telemetry_enabled)
|
||||||
|
|
||||||
if zipping:
|
if zipping:
|
||||||
console.rule(
|
console.log(
|
||||||
"""Backend & Frontend compiled. See [green bold]backend.zip[/green bold]
|
"""Backend & Frontend compiled. See [green bold]backend.zip[/green bold]
|
||||||
and [green bold]frontend.zip[/green bold]."""
|
and [green bold]frontend.zip[/green bold]."""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
console.rule(
|
console.log(
|
||||||
"""Backend & Frontend compiled. See [green bold]app[/green bold]
|
"""Backend & Frontend compiled. See [green bold]app[/green bold]
|
||||||
and [green bold].web/_static[/green bold] directories."""
|
and [green bold].web/_static[/green bold] directories."""
|
||||||
)
|
)
|
||||||
@ -261,16 +276,22 @@ db_cli = typer.Typer()
|
|||||||
@db_cli.command(name="init")
|
@db_cli.command(name="init")
|
||||||
def db_init():
|
def db_init():
|
||||||
"""Create database schema and migration configuration."""
|
"""Create database schema and migration configuration."""
|
||||||
|
# Check the database url.
|
||||||
if get_config().db_url is None:
|
if get_config().db_url is None:
|
||||||
console.print("[red]db_url is not configured, cannot initialize.")
|
console.error("db_url is not configured, cannot initialize.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check the alembic config.
|
||||||
if Path(constants.ALEMBIC_CONFIG).exists():
|
if Path(constants.ALEMBIC_CONFIG).exists():
|
||||||
console.print(
|
console.error(
|
||||||
"[red]Database is already initialized. Use "
|
"Database is already initialized. Use "
|
||||||
"[bold]reflex db makemigrations[/bold] to create schema change "
|
"[bold]reflex db makemigrations[/bold] to create schema change "
|
||||||
"scripts and [bold]reflex db migrate[/bold] to apply migrations "
|
"scripts and [bold]reflex db migrate[/bold] to apply migrations "
|
||||||
"to a new or existing database.",
|
"to a new or existing database.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Initialize the database.
|
||||||
prerequisites.get_app()
|
prerequisites.get_app()
|
||||||
model.Model.alembic_init()
|
model.Model.alembic_init()
|
||||||
model.Model.migrate(autogenerate=True)
|
model.Model.migrate(autogenerate=True)
|
||||||
@ -302,8 +323,8 @@ def makemigrations(
|
|||||||
except CommandError as command_error:
|
except CommandError as command_error:
|
||||||
if "Target database is not up to date." not in str(command_error):
|
if "Target database is not up to date." not in str(command_error):
|
||||||
raise
|
raise
|
||||||
console.print(
|
console.error(
|
||||||
f"[red]{command_error} Run [bold]reflex db migrate[/bold] to update database."
|
f"{command_error} Run [bold]reflex db migrate[/bold] to update database."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -151,6 +151,7 @@ class AppHarness:
|
|||||||
reflex.reflex.init(
|
reflex.reflex.init(
|
||||||
name=self.app_name,
|
name=self.app_name,
|
||||||
template=reflex.constants.Template.DEFAULT,
|
template=reflex.constants.Template.DEFAULT,
|
||||||
|
loglevel=reflex.constants.LogLevel.INFO,
|
||||||
)
|
)
|
||||||
self.app_module_path.write_text(source_code)
|
self.app_module_path.write_text(source_code)
|
||||||
with chdir(self.app_path):
|
with chdir(self.app_path):
|
||||||
|
@ -9,12 +9,10 @@ import subprocess
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.config import get_config
|
from reflex.config import get_config
|
||||||
from reflex.utils import console, path_ops, prerequisites
|
from reflex.utils import console, path_ops, prerequisites
|
||||||
from reflex.utils.processes import new_process
|
from reflex.utils.processes import new_process, show_progress
|
||||||
|
|
||||||
|
|
||||||
def update_json_file(file_path: str, update_dict: dict[str, Union[int, str]]):
|
def update_json_file(file_path: str, update_dict: dict[str, Union[int, str]]):
|
||||||
@ -39,7 +37,9 @@ def update_json_file(file_path: str, update_dict: dict[str, Union[int, str]]):
|
|||||||
|
|
||||||
def set_reflex_project_hash():
|
def set_reflex_project_hash():
|
||||||
"""Write the hash of the Reflex project to a REFLEX_JSON."""
|
"""Write the hash of the Reflex project to a REFLEX_JSON."""
|
||||||
update_json_file(constants.REFLEX_JSON, {"project_hash": random.getrandbits(128)})
|
project_hash = random.getrandbits(128)
|
||||||
|
console.debug(f"Setting project hash to {project_hash}.")
|
||||||
|
update_json_file(constants.REFLEX_JSON, {"project_hash": project_hash})
|
||||||
|
|
||||||
|
|
||||||
def set_environment_variables():
|
def set_environment_variables():
|
||||||
@ -86,21 +86,19 @@ def generate_sitemap_config(deploy_url: str):
|
|||||||
f.write(templates.SITEMAP_CONFIG(config=config))
|
f.write(templates.SITEMAP_CONFIG(config=config))
|
||||||
|
|
||||||
|
|
||||||
def export_app(
|
def export(
|
||||||
backend: bool = True,
|
backend: bool = True,
|
||||||
frontend: bool = True,
|
frontend: bool = True,
|
||||||
zip: bool = False,
|
zip: bool = False,
|
||||||
deploy_url: Optional[str] = None,
|
deploy_url: Optional[str] = None,
|
||||||
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
|
||||||
):
|
):
|
||||||
"""Zip up the app for deployment.
|
"""Export the app for deployment.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
backend: Whether to zip up the backend app.
|
backend: Whether to zip up the backend app.
|
||||||
frontend: Whether to zip up the frontend app.
|
frontend: Whether to zip up the frontend app.
|
||||||
zip: Whether to zip the app.
|
zip: Whether to zip the app.
|
||||||
deploy_url: The URL of the deployed app.
|
deploy_url: The URL of the deployed app.
|
||||||
loglevel: The log level to use.
|
|
||||||
"""
|
"""
|
||||||
# Remove the static folder.
|
# Remove the static folder.
|
||||||
path_ops.rm(constants.WEB_STATIC_DIR)
|
path_ops.rm(constants.WEB_STATIC_DIR)
|
||||||
@ -111,13 +109,6 @@ def export_app(
|
|||||||
generate_sitemap_config(deploy_url)
|
generate_sitemap_config(deploy_url)
|
||||||
command = "export-sitemap"
|
command = "export-sitemap"
|
||||||
|
|
||||||
# Create a progress object
|
|
||||||
progress = Progress(
|
|
||||||
*Progress.get_default_columns()[:-1],
|
|
||||||
MofNCompleteColumn(),
|
|
||||||
TimeElapsedColumn(),
|
|
||||||
)
|
|
||||||
|
|
||||||
checkpoints = [
|
checkpoints = [
|
||||||
"Linting and checking ",
|
"Linting and checking ",
|
||||||
"Compiled successfully",
|
"Compiled successfully",
|
||||||
@ -130,36 +121,12 @@ def export_app(
|
|||||||
"Export successful",
|
"Export successful",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add a single task to the progress object
|
|
||||||
task = progress.add_task("Creating Production Build: ", total=len(checkpoints))
|
|
||||||
|
|
||||||
# Start the subprocess with the progress bar.
|
# Start the subprocess with the progress bar.
|
||||||
try:
|
process = new_process(
|
||||||
with progress, new_process(
|
[prerequisites.get_package_manager(), "run", command],
|
||||||
[prerequisites.get_package_manager(), "run", command],
|
cwd=constants.WEB_DIR,
|
||||||
cwd=constants.WEB_DIR,
|
)
|
||||||
) as export_process:
|
show_progress("Creating Production Build", process, checkpoints)
|
||||||
assert export_process.stdout is not None, "No stdout for export process."
|
|
||||||
for line in export_process.stdout:
|
|
||||||
if loglevel == constants.LogLevel.DEBUG:
|
|
||||||
print(line, end="")
|
|
||||||
|
|
||||||
# Check for special strings and update the progress bar.
|
|
||||||
for special_string in checkpoints:
|
|
||||||
if special_string in line:
|
|
||||||
if special_string == checkpoints[-1]:
|
|
||||||
progress.update(task, completed=len(checkpoints))
|
|
||||||
break # Exit the loop if the completion message is found
|
|
||||||
else:
|
|
||||||
progress.update(task, advance=1)
|
|
||||||
break
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
console.print(f"[red]Export process errored: {e}")
|
|
||||||
console.print(
|
|
||||||
"[red]Run in with [bold]--loglevel debug[/bold] to see the full error."
|
|
||||||
)
|
|
||||||
os._exit(1)
|
|
||||||
|
|
||||||
# Zip up the app.
|
# Zip up the app.
|
||||||
if zip:
|
if zip:
|
||||||
@ -203,14 +170,12 @@ def posix_export(backend: bool = True, frontend: bool = True):
|
|||||||
|
|
||||||
def setup_frontend(
|
def setup_frontend(
|
||||||
root: Path,
|
root: Path,
|
||||||
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
|
||||||
disable_telemetry: bool = True,
|
disable_telemetry: bool = True,
|
||||||
):
|
):
|
||||||
"""Set up the frontend to run the app.
|
"""Set up the frontend to run the app.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
root: The root path of the project.
|
root: The root path of the project.
|
||||||
loglevel: The log level to use.
|
|
||||||
disable_telemetry: Whether to disable the Next telemetry.
|
disable_telemetry: Whether to disable the Next telemetry.
|
||||||
"""
|
"""
|
||||||
# Install frontend packages.
|
# Install frontend packages.
|
||||||
@ -242,15 +207,13 @@ def setup_frontend(
|
|||||||
|
|
||||||
def setup_frontend_prod(
|
def setup_frontend_prod(
|
||||||
root: Path,
|
root: Path,
|
||||||
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
|
||||||
disable_telemetry: bool = True,
|
disable_telemetry: bool = True,
|
||||||
):
|
):
|
||||||
"""Set up the frontend for prod mode.
|
"""Set up the frontend for prod mode.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
root: The root path of the project.
|
root: The root path of the project.
|
||||||
loglevel: The log level to use.
|
|
||||||
disable_telemetry: Whether to disable the Next telemetry.
|
disable_telemetry: Whether to disable the Next telemetry.
|
||||||
"""
|
"""
|
||||||
setup_frontend(root, loglevel, disable_telemetry)
|
setup_frontend(root, disable_telemetry)
|
||||||
export_app(loglevel=loglevel, deploy_url=get_config().deploy_url)
|
export(deploy_url=get_config().deploy_url)
|
||||||
|
@ -5,56 +5,123 @@ from __future__ import annotations
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
||||||
from rich.prompt import Prompt
|
from rich.prompt import Prompt
|
||||||
from rich.status import Status
|
|
||||||
|
from reflex.constants import LogLevel
|
||||||
|
|
||||||
# Console for pretty printing.
|
# Console for pretty printing.
|
||||||
_console = Console()
|
_console = Console()
|
||||||
|
|
||||||
|
# The current log level.
|
||||||
|
LOG_LEVEL = LogLevel.INFO
|
||||||
|
|
||||||
def deprecate(msg: str) -> None:
|
|
||||||
"""Print a deprecation warning.
|
def set_log_level(log_level: LogLevel):
|
||||||
|
"""Set the log level.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg: The deprecation message.
|
log_level: The log level to set.
|
||||||
"""
|
"""
|
||||||
_console.print(f"[yellow]DeprecationWarning: {msg}[/yellow]")
|
global LOG_LEVEL
|
||||||
|
LOG_LEVEL = log_level
|
||||||
|
|
||||||
|
|
||||||
def warn(msg: str) -> None:
|
def print(msg: str, **kwargs):
|
||||||
"""Print a warning about bad usage in Reflex.
|
"""Print a message.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg: The warning message.
|
msg: The message to print.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
"""
|
"""
|
||||||
_console.print(f"[orange1]UsageWarning: {msg}[/orange1]")
|
_console.print(msg, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def log(msg: str) -> None:
|
def debug(msg: str, **kwargs):
|
||||||
|
"""Print a debug message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg: The debug message.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
|
"""
|
||||||
|
if LOG_LEVEL <= LogLevel.DEBUG:
|
||||||
|
print(f"[blue]Debug: {msg}[/blue]", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def info(msg: str, **kwargs):
|
||||||
|
"""Print an info message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg: The info message.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
|
"""
|
||||||
|
if LOG_LEVEL <= LogLevel.INFO:
|
||||||
|
print(f"[cyan]Info: {msg}[/cyan]", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def success(msg: str, **kwargs):
|
||||||
|
"""Print a success message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg: The success message.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
|
"""
|
||||||
|
if LOG_LEVEL <= LogLevel.INFO:
|
||||||
|
print(f"[green]Success: {msg}[/green]", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg: str, **kwargs):
|
||||||
"""Takes a string and logs it to the console.
|
"""Takes a string and logs it to the console.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg: The message to log.
|
msg: The message to log.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
"""
|
"""
|
||||||
_console.log(msg)
|
if LOG_LEVEL <= LogLevel.INFO:
|
||||||
|
_console.log(msg, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def print(msg: str) -> None:
|
def rule(title: str, **kwargs):
|
||||||
"""Prints the given message to the console.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg: The message to print to the console.
|
|
||||||
"""
|
|
||||||
_console.print(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def rule(title: str) -> None:
|
|
||||||
"""Prints a horizontal rule with a title.
|
"""Prints a horizontal rule with a title.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
title: The title of the rule.
|
title: The title of the rule.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
"""
|
"""
|
||||||
_console.rule(title)
|
_console.rule(title, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def warn(msg: str, **kwargs):
|
||||||
|
"""Print a warning message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg: The warning message.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
|
"""
|
||||||
|
if LOG_LEVEL <= LogLevel.WARNING:
|
||||||
|
print(f"[orange1]Warning: {msg}[/orange1]", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def deprecate(msg: str, **kwargs):
|
||||||
|
"""Print a deprecation warning.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg: The deprecation message.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
|
"""
|
||||||
|
if LOG_LEVEL <= LogLevel.WARNING:
|
||||||
|
print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def error(msg: str, **kwargs):
|
||||||
|
"""Print an error message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg: The error message.
|
||||||
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
|
"""
|
||||||
|
if LOG_LEVEL <= LogLevel.ERROR:
|
||||||
|
print(f"[red]Error: {msg}[/red]", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def ask(
|
def ask(
|
||||||
@ -69,19 +136,33 @@ def ask(
|
|||||||
default: The default option selected.
|
default: The default option selected.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A string
|
A string with the user input.
|
||||||
"""
|
"""
|
||||||
return Prompt.ask(question, choices=choices, default=default) # type: ignore
|
return Prompt.ask(question, choices=choices, default=default) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def status(msg: str) -> Status:
|
def progress():
|
||||||
"""Returns a status,
|
"""Create a new progress bar.
|
||||||
which can be used as a context manager.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg: The message to be used as status title.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The status of the console.
|
A new progress bar.
|
||||||
"""
|
"""
|
||||||
return _console.status(msg)
|
return Progress(
|
||||||
|
*Progress.get_default_columns()[:-1],
|
||||||
|
MofNCompleteColumn(),
|
||||||
|
TimeElapsedColumn(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def status(*args, **kwargs):
|
||||||
|
"""Create a status with a spinner.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*args: Args to pass to the status.
|
||||||
|
**kwargs: Kwargs to pass to the status.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new status.
|
||||||
|
"""
|
||||||
|
return _console.status(*args, **kwargs)
|
||||||
|
@ -3,12 +3,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from rich import print
|
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.config import get_config
|
from reflex.config import get_config
|
||||||
from reflex.utils import console, prerequisites, processes
|
from reflex.utils import console, prerequisites, processes
|
||||||
@ -28,13 +24,11 @@ def start_watching_assets_folder(root):
|
|||||||
|
|
||||||
def run_process_and_launch_url(
|
def run_process_and_launch_url(
|
||||||
run_command: list[str],
|
run_command: list[str],
|
||||||
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
|
||||||
):
|
):
|
||||||
"""Run the process and launch the URL.
|
"""Run the process and launch the URL.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
run_command: The command to run.
|
run_command: The command to run.
|
||||||
loglevel: The log level to use.
|
|
||||||
"""
|
"""
|
||||||
process = new_process(
|
process = new_process(
|
||||||
run_command,
|
run_command,
|
||||||
@ -45,22 +39,20 @@ def run_process_and_launch_url(
|
|||||||
for line in process.stdout:
|
for line in process.stdout:
|
||||||
if "ready started server on" in line:
|
if "ready started server on" in line:
|
||||||
url = line.split("url: ")[-1].strip()
|
url = line.split("url: ")[-1].strip()
|
||||||
print(f"App running at: [bold green]{url}")
|
console.print(f"App running at: [bold green]{url}")
|
||||||
if loglevel == constants.LogLevel.DEBUG:
|
else:
|
||||||
print(line, end="")
|
console.debug(line)
|
||||||
|
|
||||||
|
|
||||||
def run_frontend(
|
def run_frontend(
|
||||||
root: Path,
|
root: Path,
|
||||||
port: str,
|
port: str,
|
||||||
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
|
||||||
):
|
):
|
||||||
"""Run the frontend.
|
"""Run the frontend.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
root: The root path of the project.
|
root: The root path of the project.
|
||||||
port: The port to run the frontend on.
|
port: The port to run the frontend on.
|
||||||
loglevel: The log level to use.
|
|
||||||
"""
|
"""
|
||||||
# Start watching asset folder.
|
# Start watching asset folder.
|
||||||
start_watching_assets_folder(root)
|
start_watching_assets_folder(root)
|
||||||
@ -68,29 +60,25 @@ def run_frontend(
|
|||||||
# Run the frontend in development mode.
|
# Run the frontend in development mode.
|
||||||
console.rule("[bold green]App Running")
|
console.rule("[bold green]App Running")
|
||||||
os.environ["PORT"] = get_config().frontend_port if port is None else port
|
os.environ["PORT"] = get_config().frontend_port if port is None else port
|
||||||
run_process_and_launch_url(
|
run_process_and_launch_url([prerequisites.get_package_manager(), "run", "dev"])
|
||||||
[prerequisites.get_package_manager(), "run", "dev"], loglevel
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_frontend_prod(
|
def run_frontend_prod(
|
||||||
root: Path,
|
root: Path,
|
||||||
port: str,
|
port: str,
|
||||||
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
|
||||||
):
|
):
|
||||||
"""Run the frontend.
|
"""Run the frontend.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
root: The root path of the project (to keep same API as run_frontend).
|
root: The root path of the project (to keep same API as run_frontend).
|
||||||
port: The port to run the frontend on.
|
port: The port to run the frontend on.
|
||||||
loglevel: The log level to use.
|
|
||||||
"""
|
"""
|
||||||
# Set the port.
|
# Set the port.
|
||||||
os.environ["PORT"] = get_config().frontend_port if port is None else port
|
os.environ["PORT"] = get_config().frontend_port if port is None else port
|
||||||
|
|
||||||
# Run the frontend in production mode.
|
# Run the frontend in production mode.
|
||||||
console.rule("[bold green]App Running")
|
console.rule("[bold green]App Running")
|
||||||
run_process_and_launch_url([constants.NPM_PATH, "run", "prod"], loglevel)
|
run_process_and_launch_url([constants.NPM_PATH, "run", "prod"])
|
||||||
|
|
||||||
|
|
||||||
def run_backend(
|
def run_backend(
|
||||||
@ -107,20 +95,23 @@ def run_backend(
|
|||||||
port: The app port
|
port: The app port
|
||||||
loglevel: The log level.
|
loglevel: The log level.
|
||||||
"""
|
"""
|
||||||
cmd = [
|
new_process(
|
||||||
"uvicorn",
|
[
|
||||||
f"{app_name}:{constants.APP_VAR}.{constants.API_VAR}",
|
"uvicorn",
|
||||||
"--host",
|
f"{app_name}:{constants.APP_VAR}.{constants.API_VAR}",
|
||||||
host,
|
"--host",
|
||||||
"--port",
|
host,
|
||||||
str(port),
|
"--port",
|
||||||
"--log-level",
|
str(port),
|
||||||
loglevel,
|
"--log-level",
|
||||||
"--reload",
|
loglevel,
|
||||||
"--reload-dir",
|
"--reload",
|
||||||
app_name.split(".")[0],
|
"--reload-dir",
|
||||||
]
|
app_name.split(".")[0],
|
||||||
subprocess.run(cmd)
|
],
|
||||||
|
run=True,
|
||||||
|
show_logs=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_backend_prod(
|
def run_backend_prod(
|
||||||
@ -147,7 +138,7 @@ def run_backend_prod(
|
|||||||
str(port),
|
str(port),
|
||||||
f"{app_name}:{constants.APP_VAR}",
|
f"{app_name}:{constants.APP_VAR}",
|
||||||
]
|
]
|
||||||
if platform.system() == "Windows"
|
if prerequisites.IS_WINDOWS
|
||||||
else [
|
else [
|
||||||
*constants.RUN_BACKEND_PROD,
|
*constants.RUN_BACKEND_PROD,
|
||||||
"--bind",
|
"--bind",
|
||||||
@ -164,4 +155,4 @@ def run_backend_prod(
|
|||||||
"--workers",
|
"--workers",
|
||||||
str(num_workers),
|
str(num_workers),
|
||||||
]
|
]
|
||||||
subprocess.run(command)
|
new_process(command, run=True, show_logs=True)
|
||||||
|
@ -7,11 +7,9 @@ import json
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
|
||||||
from fileinput import FileInput
|
from fileinput import FileInput
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
@ -26,34 +24,32 @@ from redis import Redis
|
|||||||
from reflex import constants, model
|
from reflex import constants, model
|
||||||
from reflex.config import get_config
|
from reflex.config import get_config
|
||||||
from reflex.utils import console, path_ops
|
from reflex.utils import console, path_ops
|
||||||
|
from reflex.utils.processes import new_process, show_logs, show_status
|
||||||
|
|
||||||
IS_WINDOWS = platform.system() == "Windows"
|
IS_WINDOWS = platform.system() == "Windows"
|
||||||
|
|
||||||
|
|
||||||
def check_node_version():
|
def check_node_version() -> bool:
|
||||||
"""Check the version of Node.js.
|
"""Check the version of Node.js.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Whether the version of Node.js is valid.
|
Whether the version of Node.js is valid.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Run the node -v command and capture the output
|
# Run the node -v command and capture the output.
|
||||||
result = subprocess.run(
|
result = new_process([constants.NODE_PATH, "-v"], run=True)
|
||||||
[constants.NODE_PATH, "-v"],
|
except FileNotFoundError:
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
|
|
||||||
current_version = version.parse(result.stdout.decode())
|
|
||||||
# Compare the version numbers
|
|
||||||
return (
|
|
||||||
current_version >= version.parse(constants.NODE_VERSION_MIN)
|
|
||||||
if IS_WINDOWS
|
|
||||||
else current_version == version.parse(constants.NODE_VERSION)
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
|
||||||
|
current_version = version.parse(result.stdout) # type: ignore
|
||||||
|
# Compare the version numbers
|
||||||
|
return (
|
||||||
|
current_version >= version.parse(constants.NODE_VERSION_MIN)
|
||||||
|
if IS_WINDOWS
|
||||||
|
else current_version == version.parse(constants.NODE_VERSION)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_bun_version() -> Optional[version.Version]:
|
def get_bun_version() -> Optional[version.Version]:
|
||||||
"""Get the version of bun.
|
"""Get the version of bun.
|
||||||
@ -63,13 +59,9 @@ def get_bun_version() -> Optional[version.Version]:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Run the bun -v command and capture the output
|
# Run the bun -v command and capture the output
|
||||||
result = subprocess.run(
|
result = new_process([constants.BUN_PATH, "-v"], run=True)
|
||||||
[constants.BUN_PATH, "-v"],
|
return version.parse(result.stdout) # type: ignore
|
||||||
stdout=subprocess.PIPE,
|
except FileNotFoundError:
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
return version.parse(result.stdout.decode().strip())
|
|
||||||
except Exception:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -98,7 +90,7 @@ def get_install_package_manager() -> str:
|
|||||||
get_config()
|
get_config()
|
||||||
|
|
||||||
# On Windows, we use npm instead of bun.
|
# On Windows, we use npm instead of bun.
|
||||||
if platform.system() == "Windows":
|
if IS_WINDOWS:
|
||||||
return get_windows_package_manager()
|
return get_windows_package_manager()
|
||||||
|
|
||||||
# On other platforms, we use bun.
|
# On other platforms, we use bun.
|
||||||
@ -114,7 +106,7 @@ def get_package_manager() -> str:
|
|||||||
"""
|
"""
|
||||||
get_config()
|
get_config()
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if IS_WINDOWS:
|
||||||
return get_windows_package_manager()
|
return get_windows_package_manager()
|
||||||
return constants.NPM_PATH
|
return constants.NPM_PATH
|
||||||
|
|
||||||
@ -141,7 +133,7 @@ def get_redis() -> Optional[Redis]:
|
|||||||
if config.redis_url is None:
|
if config.redis_url is None:
|
||||||
return None
|
return None
|
||||||
redis_url, redis_port = config.redis_url.split(":")
|
redis_url, redis_port = config.redis_url.split(":")
|
||||||
print("Using redis at", config.redis_url)
|
console.info(f"Using redis at {config.redis_url}")
|
||||||
return Redis(host=redis_url, port=int(redis_port), db=0)
|
return Redis(host=redis_url, port=int(redis_port), db=0)
|
||||||
|
|
||||||
|
|
||||||
@ -173,8 +165,8 @@ def get_default_app_name() -> str:
|
|||||||
|
|
||||||
# Make sure the app is not named "reflex".
|
# Make sure the app is not named "reflex".
|
||||||
if app_name == constants.MODULE_NAME:
|
if app_name == constants.MODULE_NAME:
|
||||||
console.print(
|
console.error(
|
||||||
f"[red]The app directory cannot be named [bold]{constants.MODULE_NAME}."
|
f"The app directory cannot be named [bold]{constants.MODULE_NAME}[/bold]."
|
||||||
)
|
)
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
@ -192,6 +184,7 @@ def create_config(app_name: str):
|
|||||||
|
|
||||||
config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
|
config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
|
||||||
with open(constants.CONFIG_FILE, "w") as f:
|
with open(constants.CONFIG_FILE, "w") as f:
|
||||||
|
console.debug(f"Creating {constants.CONFIG_FILE}")
|
||||||
f.write(templates.RXCONFIG.render(app_name=app_name, config_name=config_name))
|
f.write(templates.RXCONFIG.render(app_name=app_name, config_name=config_name))
|
||||||
|
|
||||||
|
|
||||||
@ -204,8 +197,10 @@ def initialize_gitignore():
|
|||||||
if os.path.exists(constants.GITIGNORE_FILE):
|
if os.path.exists(constants.GITIGNORE_FILE):
|
||||||
with open(constants.GITIGNORE_FILE, "r") as f:
|
with open(constants.GITIGNORE_FILE, "r") as f:
|
||||||
files |= set([line.strip() for line in f.readlines()])
|
files |= set([line.strip() for line in f.readlines()])
|
||||||
|
|
||||||
# Write files to the .gitignore file.
|
# Write files to the .gitignore file.
|
||||||
with open(constants.GITIGNORE_FILE, "w") as f:
|
with open(constants.GITIGNORE_FILE, "w") as f:
|
||||||
|
console.debug(f"Creating {constants.GITIGNORE_FILE}")
|
||||||
f.write(f"{(path_ops.join(sorted(files))).lstrip()}")
|
f.write(f"{(path_ops.join(sorted(files))).lstrip()}")
|
||||||
|
|
||||||
|
|
||||||
@ -256,16 +251,22 @@ def initialize_bun():
|
|||||||
"""Check that bun requirements are met, and install if not."""
|
"""Check that bun requirements are met, and install if not."""
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
# Bun is not supported on Windows.
|
# Bun is not supported on Windows.
|
||||||
|
console.debug("Skipping bun installation on Windows.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check the bun version.
|
# Check the bun version.
|
||||||
if get_bun_version() != version.parse(constants.BUN_VERSION):
|
bun_version = get_bun_version()
|
||||||
|
if bun_version != version.parse(constants.BUN_VERSION):
|
||||||
|
console.debug(
|
||||||
|
f"Current bun version ({bun_version}) does not match ({constants.BUN_VERSION})."
|
||||||
|
)
|
||||||
remove_existing_bun_installation()
|
remove_existing_bun_installation()
|
||||||
install_bun()
|
install_bun()
|
||||||
|
|
||||||
|
|
||||||
def remove_existing_bun_installation():
|
def remove_existing_bun_installation():
|
||||||
"""Remove existing bun installation."""
|
"""Remove existing bun installation."""
|
||||||
|
console.debug("Removing existing bun installation.")
|
||||||
if os.path.exists(constants.BUN_PATH):
|
if os.path.exists(constants.BUN_PATH):
|
||||||
path_ops.rm(constants.BUN_ROOT_PATH)
|
path_ops.rm(constants.BUN_ROOT_PATH)
|
||||||
|
|
||||||
@ -279,17 +280,13 @@ def initialize_node():
|
|||||||
def download_and_run(url: str, *args, **env):
|
def download_and_run(url: str, *args, **env):
|
||||||
"""Download and run a script.
|
"""Download and run a script.
|
||||||
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The url of the script.
|
url: The url of the script.
|
||||||
args: The arguments to pass to the script.
|
args: The arguments to pass to the script.
|
||||||
env: The environment variables to use.
|
env: The environment variables to use.
|
||||||
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
Exit: if installation failed
|
|
||||||
"""
|
"""
|
||||||
# Download the script
|
# Download the script
|
||||||
|
console.debug(f"Downloading {url}")
|
||||||
response = httpx.get(url)
|
response = httpx.get(url)
|
||||||
if response.status_code != httpx.codes.OK:
|
if response.status_code != httpx.codes.OK:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@ -300,13 +297,9 @@ def download_and_run(url: str, *args, **env):
|
|||||||
f.write(response.text)
|
f.write(response.text)
|
||||||
|
|
||||||
# Run the script.
|
# Run the script.
|
||||||
env = {
|
env = {**os.environ, **env}
|
||||||
**os.environ,
|
process = new_process(["bash", f.name, *args], env=env)
|
||||||
**env,
|
show_logs(f"Installing {url}", process)
|
||||||
}
|
|
||||||
result = subprocess.run(["bash", f.name, *args], env=env)
|
|
||||||
if result.returncode != 0:
|
|
||||||
raise typer.Exit(code=result.returncode)
|
|
||||||
|
|
||||||
|
|
||||||
def install_node():
|
def install_node():
|
||||||
@ -318,8 +311,8 @@ def install_node():
|
|||||||
"""
|
"""
|
||||||
# NVM is not supported on Windows.
|
# NVM is not supported on Windows.
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
console.print(
|
console.error(
|
||||||
f"[red]Node.js version {constants.NODE_VERSION} or higher is required to run Reflex."
|
f"Node.js version {constants.NODE_VERSION} or higher is required to run Reflex."
|
||||||
)
|
)
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
@ -330,7 +323,7 @@ def install_node():
|
|||||||
|
|
||||||
# Install node.
|
# Install node.
|
||||||
# We use bash -c as we need to source nvm.sh to use nvm.
|
# We use bash -c as we need to source nvm.sh to use nvm.
|
||||||
result = subprocess.run(
|
process = new_process(
|
||||||
[
|
[
|
||||||
"bash",
|
"bash",
|
||||||
"-c",
|
"-c",
|
||||||
@ -338,8 +331,7 @@ def install_node():
|
|||||||
],
|
],
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
if result.returncode != 0:
|
show_logs("Installing node", process)
|
||||||
raise typer.Exit(code=result.returncode)
|
|
||||||
|
|
||||||
|
|
||||||
def install_bun():
|
def install_bun():
|
||||||
@ -350,13 +342,15 @@ def install_bun():
|
|||||||
"""
|
"""
|
||||||
# Bun is not supported on Windows.
|
# Bun is not supported on Windows.
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
|
console.debug("Skipping bun installation on Windows.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip if bun is already installed.
|
# Skip if bun is already installed.
|
||||||
if os.path.exists(constants.BUN_PATH):
|
if os.path.exists(constants.BUN_PATH):
|
||||||
|
console.debug("Skipping bun installation as it is already installed.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if unzip is installed
|
# if unzip is installed
|
||||||
unzip_path = path_ops.which("unzip")
|
unzip_path = path_ops.which("unzip")
|
||||||
if unzip_path is None:
|
if unzip_path is None:
|
||||||
raise FileNotFoundError("Reflex requires unzip to be installed.")
|
raise FileNotFoundError("Reflex requires unzip to be installed.")
|
||||||
@ -371,24 +365,21 @@ def install_bun():
|
|||||||
|
|
||||||
def install_frontend_packages():
|
def install_frontend_packages():
|
||||||
"""Installs the base and custom frontend packages."""
|
"""Installs the base and custom frontend packages."""
|
||||||
# Install the frontend packages.
|
|
||||||
console.rule("[bold]Installing frontend packages")
|
|
||||||
|
|
||||||
# Install the base packages.
|
# Install the base packages.
|
||||||
subprocess.run(
|
process = new_process(
|
||||||
[get_install_package_manager(), "install"],
|
[get_install_package_manager(), "install", "--loglevel", "silly"],
|
||||||
cwd=constants.WEB_DIR,
|
cwd=constants.WEB_DIR,
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
)
|
)
|
||||||
|
show_status("Installing base frontend packages", process)
|
||||||
|
|
||||||
# Install the app packages.
|
# Install the app packages.
|
||||||
packages = get_config().frontend_packages
|
packages = get_config().frontend_packages
|
||||||
if len(packages) > 0:
|
if len(packages) > 0:
|
||||||
subprocess.run(
|
process = new_process(
|
||||||
[get_install_package_manager(), "add", *packages],
|
[get_install_package_manager(), "add", *packages],
|
||||||
cwd=constants.WEB_DIR,
|
cwd=constants.WEB_DIR,
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
)
|
)
|
||||||
|
show_status("Installing custom frontend packages", process)
|
||||||
|
|
||||||
|
|
||||||
def check_initialized(frontend: bool = True):
|
def check_initialized(frontend: bool = True):
|
||||||
@ -406,22 +397,22 @@ def check_initialized(frontend: bool = True):
|
|||||||
|
|
||||||
# Check if the app is initialized.
|
# Check if the app is initialized.
|
||||||
if not (has_config and has_reflex_dir and has_web_dir):
|
if not (has_config and has_reflex_dir and has_web_dir):
|
||||||
console.print(
|
console.error(
|
||||||
f"[red]The app is not initialized. Run [bold]{constants.MODULE_NAME} init[/bold] first."
|
f"The app is not initialized. Run [bold]{constants.MODULE_NAME} init[/bold] first."
|
||||||
)
|
)
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
# Check that the template is up to date.
|
# Check that the template is up to date.
|
||||||
if frontend and not is_latest_template():
|
if frontend and not is_latest_template():
|
||||||
console.print(
|
console.error(
|
||||||
"[red]The base app template has updated. Run [bold]reflex init[/bold] again."
|
"The base app template has updated. Run [bold]reflex init[/bold] again."
|
||||||
)
|
)
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
# Print a warning for Windows users.
|
# Print a warning for Windows users.
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
console.print(
|
console.warn(
|
||||||
"[yellow][WARNING] We strongly advise using Windows Subsystem for Linux (WSL) for optimal performance with reflex."
|
"We strongly advise using Windows Subsystem for Linux (WSL) for optimal performance with reflex."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -462,17 +453,16 @@ def initialize_frontend_dependencies():
|
|||||||
def check_admin_settings():
|
def check_admin_settings():
|
||||||
"""Check if admin settings are set and valid for logging in cli app."""
|
"""Check if admin settings are set and valid for logging in cli app."""
|
||||||
admin_dash = get_config().admin_dash
|
admin_dash = get_config().admin_dash
|
||||||
current_time = datetime.now()
|
|
||||||
if admin_dash:
|
if admin_dash:
|
||||||
if not admin_dash.models:
|
if not admin_dash.models:
|
||||||
console.print(
|
console.log(
|
||||||
f"[yellow][Admin Dashboard][/yellow] :megaphone: Admin dashboard enabled, but no models defined in [bold magenta]rxconfig.py[/bold magenta]. Time: {current_time}"
|
f"[yellow][Admin Dashboard][/yellow] :megaphone: Admin dashboard enabled, but no models defined in [bold magenta]rxconfig.py[/bold magenta]."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
console.print(
|
console.log(
|
||||||
f"[yellow][Admin Dashboard][/yellow] Admin enabled, building admin dashboard. Time: {current_time}"
|
f"[yellow][Admin Dashboard][/yellow] Admin enabled, building admin dashboard."
|
||||||
)
|
)
|
||||||
console.print(
|
console.log(
|
||||||
"Admin dashboard running at: [bold green]http://localhost:8000/admin[/bold green]"
|
"Admin dashboard running at: [bold green]http://localhost:8000/admin[/bold green]"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -484,8 +474,8 @@ def check_db_initialized() -> bool:
|
|||||||
True if alembic is initialized (or if database is not used).
|
True if alembic is initialized (or if database is not used).
|
||||||
"""
|
"""
|
||||||
if get_config().db_url is not None and not Path(constants.ALEMBIC_CONFIG).exists():
|
if get_config().db_url is not None and not Path(constants.ALEMBIC_CONFIG).exists():
|
||||||
console.print(
|
console.error(
|
||||||
"[red]Database is not initialized. Run [bold]reflex db init[/bold] first."
|
"Database is not initialized. Run [bold]reflex db init[/bold] first."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@ -501,14 +491,14 @@ def check_schema_up_to_date():
|
|||||||
connection=connection,
|
connection=connection,
|
||||||
write_migration_scripts=False,
|
write_migration_scripts=False,
|
||||||
):
|
):
|
||||||
console.print(
|
console.error(
|
||||||
"[red]Detected database schema changes. Run [bold]reflex db makemigrations[/bold] "
|
"Detected database schema changes. Run [bold]reflex db makemigrations[/bold] "
|
||||||
"to generate migration scripts.",
|
"to generate migration scripts.",
|
||||||
)
|
)
|
||||||
except CommandError as command_error:
|
except CommandError as command_error:
|
||||||
if "Target database is not up to date." in str(command_error):
|
if "Target database is not up to date." in str(command_error):
|
||||||
console.print(
|
console.error(
|
||||||
f"[red]{command_error} Run [bold]reflex db migrate[/bold] to update database."
|
f"{command_error} Run [bold]reflex db migrate[/bold] to update database."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -527,7 +517,7 @@ def migrate_to_reflex():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Rename pcconfig to rxconfig.
|
# Rename pcconfig to rxconfig.
|
||||||
console.print(
|
console.log(
|
||||||
f"[bold]Renaming {constants.OLD_CONFIG_FILE} to {constants.CONFIG_FILE}"
|
f"[bold]Renaming {constants.OLD_CONFIG_FILE} to {constants.CONFIG_FILE}"
|
||||||
)
|
)
|
||||||
os.rename(constants.OLD_CONFIG_FILE, constants.CONFIG_FILE)
|
os.rename(constants.OLD_CONFIG_FILE, constants.CONFIG_FILE)
|
||||||
|
@ -7,8 +7,7 @@ import os
|
|||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from typing import List, Optional
|
||||||
from typing import Optional
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
@ -101,7 +100,7 @@ def change_or_terminate_port(port, _type) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
The new port or the current one.
|
The new port or the current one.
|
||||||
"""
|
"""
|
||||||
console.print(
|
console.info(
|
||||||
f"Something is already running on port [bold underline]{port}[/bold underline]. This is the port the {_type} runs on."
|
f"Something is already running on port [bold underline]{port}[/bold underline]. This is the port the {_type} runs on."
|
||||||
)
|
)
|
||||||
frontend_action = console.ask("Kill or change it?", choices=["k", "c", "n"])
|
frontend_action = console.ask("Kill or change it?", choices=["k", "c", "n"])
|
||||||
@ -115,41 +114,119 @@ def change_or_terminate_port(port, _type) -> str:
|
|||||||
if is_process_on_port(new_port):
|
if is_process_on_port(new_port):
|
||||||
return change_or_terminate_port(new_port, _type)
|
return change_or_terminate_port(new_port, _type)
|
||||||
else:
|
else:
|
||||||
console.print(
|
console.info(
|
||||||
f"The {_type} will run on port [bold underline]{new_port}[/bold underline]."
|
f"The {_type} will run on port [bold underline]{new_port}[/bold underline]."
|
||||||
)
|
)
|
||||||
return new_port
|
return new_port
|
||||||
else:
|
else:
|
||||||
console.print("Exiting...")
|
console.log("Exiting...")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
def new_process(args, **kwargs):
|
def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
|
||||||
"""Wrapper over subprocess.Popen to unify the launch of child processes.
|
"""Wrapper over subprocess.Popen to unify the launch of child processes.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
args: A string, or a sequence of program arguments.
|
args: A string, or a sequence of program arguments.
|
||||||
|
run: Whether to run the process to completion.
|
||||||
|
show_logs: Whether to show the logs of the process.
|
||||||
**kwargs: Kwargs to override default wrap values to pass to subprocess.Popen as arguments.
|
**kwargs: Kwargs to override default wrap values to pass to subprocess.Popen as arguments.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Execute a child program in a new process.
|
Execute a child program in a new process.
|
||||||
"""
|
"""
|
||||||
|
# Add the node bin path to the PATH environment variable.
|
||||||
env = {
|
env = {
|
||||||
**os.environ,
|
**os.environ,
|
||||||
"PATH": os.pathsep.join([constants.NODE_BIN_PATH, os.environ["PATH"]]),
|
"PATH": os.pathsep.join([constants.NODE_BIN_PATH, os.environ["PATH"]]),
|
||||||
}
|
}
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"env": env,
|
"env": env,
|
||||||
"stderr": subprocess.STDOUT,
|
"stderr": None if show_logs else subprocess.STDOUT,
|
||||||
"stdout": subprocess.PIPE,
|
"stdout": None if show_logs else subprocess.PIPE,
|
||||||
"universal_newlines": True,
|
"universal_newlines": True,
|
||||||
"encoding": "UTF-8",
|
"encoding": "UTF-8",
|
||||||
**kwargs,
|
**kwargs,
|
||||||
}
|
}
|
||||||
return subprocess.Popen(
|
console.debug(f"Running command: {args}")
|
||||||
args,
|
fn = subprocess.run if run else subprocess.Popen
|
||||||
**kwargs,
|
return fn(args, **kwargs)
|
||||||
)
|
|
||||||
|
|
||||||
|
def stream_logs(
|
||||||
|
message: str,
|
||||||
|
process: subprocess.Popen,
|
||||||
|
):
|
||||||
|
"""Stream the logs for a process.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: The message to display.
|
||||||
|
process: The process.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
The lines of the process output.
|
||||||
|
"""
|
||||||
|
with process:
|
||||||
|
console.debug(message)
|
||||||
|
if process.stdout is None:
|
||||||
|
return
|
||||||
|
for line in process.stdout:
|
||||||
|
console.debug(line, end="")
|
||||||
|
yield line
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
console.error(f"Error during {message}")
|
||||||
|
console.error(
|
||||||
|
"Run in with [bold]--loglevel debug[/bold] to see the full error."
|
||||||
|
)
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def show_logs(
|
||||||
|
message: str,
|
||||||
|
process: subprocess.Popen,
|
||||||
|
):
|
||||||
|
"""Show the logs for a process.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: The message to display.
|
||||||
|
process: The process.
|
||||||
|
"""
|
||||||
|
for _ in stream_logs(message, process):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def show_status(message: str, process: subprocess.Popen):
|
||||||
|
"""Show the status of a process.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: The initial message to display.
|
||||||
|
process: The process.
|
||||||
|
"""
|
||||||
|
with console.status(message) as status:
|
||||||
|
for line in stream_logs(message, process):
|
||||||
|
status.update(f"{message}: {line}")
|
||||||
|
|
||||||
|
|
||||||
|
def show_progress(message: str, process: subprocess.Popen, checkpoints: List[str]):
|
||||||
|
"""Show a progress bar for a process.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: The message to display.
|
||||||
|
process: The process.
|
||||||
|
checkpoints: The checkpoints to advance the progress bar.
|
||||||
|
"""
|
||||||
|
# Iterate over the process output.
|
||||||
|
with console.progress() as progress:
|
||||||
|
task = progress.add_task(f"{message}: ", total=len(checkpoints))
|
||||||
|
for line in stream_logs(message, process):
|
||||||
|
# Check for special strings and update the progress bar.
|
||||||
|
for special_string in checkpoints:
|
||||||
|
if special_string in line:
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
if special_string == checkpoints[-1]:
|
||||||
|
progress.update(task, completed=len(checkpoints))
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def catch_keyboard_interrupt(signal, frame):
|
def catch_keyboard_interrupt(signal, frame):
|
||||||
@ -159,5 +236,4 @@ def catch_keyboard_interrupt(signal, frame):
|
|||||||
signal: The keyboard interrupt signal.
|
signal: The keyboard interrupt signal.
|
||||||
frame: The current stack frame.
|
frame: The current stack frame.
|
||||||
"""
|
"""
|
||||||
current_time = datetime.now().isoformat()
|
console.log("Reflex app stopped.")
|
||||||
console.print(f"\nReflex app stopped at time: {current_time}")
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import typing
|
import typing
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List, Union
|
from typing import Any, List, Union
|
||||||
@ -529,10 +528,6 @@ def test_node_install_unix(tmp_path, mocker):
|
|||||||
nvm_root_path = tmp_path / ".reflex" / ".nvm"
|
nvm_root_path = tmp_path / ".reflex" / ".nvm"
|
||||||
|
|
||||||
mocker.patch("reflex.utils.prerequisites.constants.NVM_DIR", nvm_root_path)
|
mocker.patch("reflex.utils.prerequisites.constants.NVM_DIR", nvm_root_path)
|
||||||
subprocess_run = mocker.patch(
|
|
||||||
"reflex.utils.prerequisites.subprocess.run",
|
|
||||||
return_value=subprocess.CompletedProcess(args="", returncode=0),
|
|
||||||
)
|
|
||||||
mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", False)
|
mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", False)
|
||||||
|
|
||||||
class Resp(Base):
|
class Resp(Base):
|
||||||
@ -540,13 +535,15 @@ def test_node_install_unix(tmp_path, mocker):
|
|||||||
text = "test"
|
text = "test"
|
||||||
|
|
||||||
mocker.patch("httpx.get", return_value=Resp())
|
mocker.patch("httpx.get", return_value=Resp())
|
||||||
mocker.patch("reflex.utils.prerequisites.download_and_run")
|
download = mocker.patch("reflex.utils.prerequisites.download_and_run")
|
||||||
|
mocker.patch("reflex.utils.prerequisites.new_process")
|
||||||
|
mocker.patch("reflex.utils.prerequisites.show_logs")
|
||||||
|
|
||||||
prerequisites.install_node()
|
prerequisites.install_node()
|
||||||
|
|
||||||
assert nvm_root_path.exists()
|
assert nvm_root_path.exists()
|
||||||
subprocess_run.assert_called()
|
download.assert_called()
|
||||||
subprocess_run.call_count = 2
|
download.call_count = 2
|
||||||
|
|
||||||
|
|
||||||
def test_bun_install_without_unzip(mocker):
|
def test_bun_install_without_unzip(mocker):
|
||||||
|
Loading…
Reference in New Issue
Block a user