From d1703f41b676c4c89a1c3f7dd83cf15f3bee701f Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Thu, 18 May 2023 12:34:37 -0700 Subject: [PATCH] CLI Update + Threading (#1046) --- pynecone/app.py | 14 ++------ pynecone/pc.py | 20 +++++------ pynecone/utils/build.py | 23 +++++++----- pynecone/utils/exec.py | 80 +++++++++++++++++++++++++++-------------- scripts/integration.sh | 2 +- 5 files changed, 80 insertions(+), 59 deletions(-) diff --git a/pynecone/app.py b/pynecone/app.py index 35abc524f..32389612c 100644 --- a/pynecone/app.py +++ b/pynecone/app.py @@ -378,23 +378,13 @@ class App(Base): ): self.pages[froute(constants.SLUG_404)] = component - def compile(self, force_compile: bool = False): - """Compile the app and output it to the pages folder. - - If the config environment is set to production, the app will - not be compiled. - - Args: - force_compile: Whether to force the app to compile. - """ + def compile(self): + """Compile the app and output it to the pages folder.""" for render, kwargs in DECORATED_ROUTES: self.add_page(render, **kwargs) # Get the env mode. config = get_config() - if config.env != constants.Env.DEV and not force_compile: - print("Skipping compilation in non-dev mode.") - return # Update models during hot reload. if config.db_url is not None: diff --git a/pynecone/pc.py b/pynecone/pc.py index 824aa56c4..851a154cf 100644 --- a/pynecone/pc.py +++ b/pynecone/pc.py @@ -2,6 +2,7 @@ import os import platform +import threading from pathlib import Path import httpx @@ -125,16 +126,15 @@ def run( telemetry.send(f"run-{env.value}", get_config().telemetry_enabled) # Run the frontend and backend. - try: - if frontend: - frontend_cmd(app.app, Path.cwd(), frontend_port) - if backend: - backend_cmd(app.__name__, port=int(backend_port), loglevel=loglevel) - finally: - if frontend: - processes.kill_process_on_port(frontend_port) - if backend: - processes.kill_process_on_port(backend_port) + # try: + if backend: + threading.Thread( + target=backend_cmd, args=(app.__name__, backend_port, loglevel) + ).start() + if frontend: + threading.Thread( + target=frontend_cmd, args=(app.app, Path.cwd(), frontend_port) + ).start() @cli.command() diff --git a/pynecone/utils/build.py b/pynecone/utils/build.py index 84c07ee8e..477e5624d 100644 --- a/pynecone/utils/build.py +++ b/pynecone/utils/build.py @@ -77,6 +77,7 @@ def export_app( frontend: bool = True, zip: bool = False, deploy_url: Optional[str] = None, + loglevel: constants.LogLevel = constants.LogLevel.ERROR, ): """Zip up the app for deployment. @@ -86,10 +87,8 @@ def export_app( frontend: Whether to zip up the frontend app. zip: Whether to zip the app. deploy_url: The URL of the deployed app. + loglevel: The log level to use. """ - # Force compile the app. - app.compile(force_compile=True) - # Remove the static folder. path_ops.rm(constants.WEB_STATIC_DIR) @@ -106,18 +105,17 @@ def export_app( # Start the progress bar with progress: # Run the subprocess command - process = subprocess.Popen( + export_process = subprocess.Popen( [prerequisites.get_package_manager(), "run", "export"], cwd=constants.WEB_DIR, - stderr=subprocess.DEVNULL, + env=os.environ, + stderr=subprocess.STDOUT, stdout=subprocess.PIPE, # Redirect stdout to a pipe universal_newlines=True, # Set universal_newlines to True for text mode ) - # Read the output of the subprocess line by line - if process.stdout: - for line in iter(process.stdout.readline, ""): - # Update the progress bar based on the output + if export_process.stdout: + for line in iter(export_process.stdout.readline, ""): if "Linting and checking " in line: progress.update(task, advance=100) elif "Compiled successfully" in line: @@ -127,8 +125,15 @@ def export_app( elif "automatically rendered as static HTML" in line: progress.update(task, advance=100) elif "Export successful" in line: + print("DOOE") progress.update(task, completed=500) break # Exit the loop if the completion message is found + elif loglevel == constants.LogLevel.DEBUG: + print(line, end="") + + # Wait for the subprocess to complete + export_process.wait() + print("Export process completed.") # Zip up the app. if zip: diff --git a/pynecone/utils/exec.py b/pynecone/utils/exec.py index c3d640462..8a5fafac8 100644 --- a/pynecone/utils/exec.py +++ b/pynecone/utils/exec.py @@ -5,11 +5,10 @@ from __future__ import annotations import os import platform import subprocess +from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING -import typer -import uvicorn from rich import print from pynecone import constants @@ -32,44 +31,58 @@ def start_watching_assets_folder(root): asset_watch.start() -def run_process_and_launch_url(run_command: list[str], root: Path): +def run_process_and_launch_url( + run_command: list[str], + root: Path, + loglevel: constants.LogLevel = constants.LogLevel.ERROR, +): """Run the process and launch the URL. Args: run_command: The command to run. root: root path of the project. + loglevel: The log level to use. """ process = subprocess.Popen( run_command, cwd=constants.WEB_DIR, env=os.environ, - stderr=subprocess.DEVNULL, + stderr=subprocess.STDOUT, stdout=subprocess.PIPE, universal_newlines=True, ) - message_found = False + current_time = datetime.now() if process.stdout: for line in process.stdout: if "ready started server on" in line: url = line.split("url: ")[-1].strip() print(f"App running at: [bold green]{url}") - typer.launch(url) - message_found = True - break - - if not message_found and process.stdout: - for line in process.stdout: - print(line, end="") + if ( + "Fast Refresh" in line + and (datetime.now() - current_time).total_seconds() > 1 + ): + print( + f"[yellow][Updating App][/yellow] Applying changes and refreshing. Time: {current_time}" + ) + current_time = datetime.now() + elif loglevel == constants.LogLevel.DEBUG: + print(line, end="") -def run_frontend(app: App, root: Path, port: str): +def run_frontend( + app: App, + root: Path, + port: str, + loglevel: constants.LogLevel = constants.LogLevel.ERROR, +): """Run the frontend. Args: app: The app. root: root path of the project. port: port of the app. + loglevel: The log level to use. """ # validate bun version prerequisites.validate_and_install_bun(initialize=False) @@ -77,33 +90,36 @@ def run_frontend(app: App, root: Path, port: str): # Set up the frontend. setup_frontend(root) - # start watching asset folder + # Start watching asset folder. start_watching_assets_folder(root) - # Compile the frontend. - app.compile(force_compile=True) - # Run the frontend in development mode. console.rule("[bold green]App Running") os.environ["PORT"] = get_config().port if port is None else port run_process_and_launch_url( - [prerequisites.get_package_manager(), "run", "dev"], root + [prerequisites.get_package_manager(), "run", "dev"], root, loglevel ) -def run_frontend_prod(app: App, root: Path, port: str): +def run_frontend_prod( + app: App, + root: Path, + port: str, + loglevel: constants.LogLevel = constants.LogLevel.ERROR, +): """Run the frontend. Args: app: The app. root: root path of the project. port: port of the app. + loglevel: The log level to use. """ # Set up the frontend. setup_frontend(root) # Export the app. - export_app(app) + export_app(app, loglevel=loglevel) # Set the port. os.environ["PORT"] = get_config().port if port is None else port @@ -111,7 +127,7 @@ def run_frontend_prod(app: App, root: Path, port: str): # Run the frontend in production mode. console.rule("[bold green]App Running") run_process_and_launch_url( - [prerequisites.get_package_manager(), "run", "prod"], root + [prerequisites.get_package_manager(), "run", "prod"], root, loglevel ) @@ -127,13 +143,23 @@ def run_backend( """ setup_backend() - uvicorn.run( + cmd = [ + "uvicorn", f"{app_name}:{constants.APP_VAR}.{constants.API_VAR}", - host=constants.BACKEND_HOST, - port=port, - log_level=loglevel, - reload=True, - ) + "--host", + constants.BACKEND_HOST, + "--port", + str(port), + "--log-level", + loglevel, + "--reload", + ] + process = subprocess.Popen(cmd) + + try: + process.wait() + except KeyboardInterrupt: + process.terminate() def run_backend_prod( diff --git a/scripts/integration.sh b/scripts/integration.sh index 1f32f0073..8bc04d17c 100644 --- a/scripts/integration.sh +++ b/scripts/integration.sh @@ -15,7 +15,7 @@ while ! nc -z localhost 3000 || ! lsof -i :8000 >/dev/null; do echo "Error: Server process with PID $pid exited early" break fi - if ((wait_time >= 300)); then + if ((wait_time >= 500)); then echo "Error: Timeout waiting for ports 3000 and 8000 to become available" exit 1 fi