Use concurrent.futures for threading (#1483)
This commit is contained in:
parent
b9536bcf40
commit
91c0de4b5f
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import threading
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@ -167,19 +166,18 @@ def run(
|
|||||||
# Post a telemetry event.
|
# Post a telemetry event.
|
||||||
telemetry.send(f"run-{env.value}", config.telemetry_enabled)
|
telemetry.send(f"run-{env.value}", config.telemetry_enabled)
|
||||||
|
|
||||||
# Run the frontend and backend.
|
|
||||||
if frontend:
|
|
||||||
setup_frontend(Path.cwd())
|
|
||||||
threading.Thread(target=frontend_cmd, args=(Path.cwd(), frontend_port)).start()
|
|
||||||
if backend:
|
|
||||||
threading.Thread(
|
|
||||||
target=backend_cmd,
|
|
||||||
args=(app.__name__, backend_host, backend_port),
|
|
||||||
).start()
|
|
||||||
|
|
||||||
# Display custom message when there is a keyboard interrupt.
|
# Display custom message when there is a keyboard interrupt.
|
||||||
signal.signal(signal.SIGINT, processes.catch_keyboard_interrupt)
|
signal.signal(signal.SIGINT, processes.catch_keyboard_interrupt)
|
||||||
|
|
||||||
|
# Run the frontend and backend together.
|
||||||
|
commands = []
|
||||||
|
if frontend:
|
||||||
|
setup_frontend(Path.cwd())
|
||||||
|
commands.append((frontend_cmd, Path.cwd(), frontend_port))
|
||||||
|
if backend:
|
||||||
|
commands.append((backend_cmd, app.__name__, backend_host, backend_port))
|
||||||
|
processes.run_concurrently(*commands)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def deploy(dry_run: bool = typer.Option(False, help="Whether to run a dry run.")):
|
def deploy(dry_run: bool = typer.Option(False, help="Whether to run a dry run.")):
|
||||||
|
@ -121,7 +121,7 @@ def error(msg: str, **kwargs):
|
|||||||
kwargs: Keyword arguments to pass to the print function.
|
kwargs: Keyword arguments to pass to the print function.
|
||||||
"""
|
"""
|
||||||
if LOG_LEVEL <= LogLevel.ERROR:
|
if LOG_LEVEL <= LogLevel.ERROR:
|
||||||
print(f"[red]Error: {msg}[/red]", **kwargs)
|
print(f"[red]{msg}[/red]", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def ask(
|
def ask(
|
||||||
|
@ -9,7 +9,6 @@ import platform
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
|
||||||
from fileinput import FileInput
|
from fileinput import FileInput
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
@ -167,7 +166,7 @@ def get_default_app_name() -> str:
|
|||||||
console.error(
|
console.error(
|
||||||
f"The app directory cannot be named [bold]{constants.MODULE_NAME}[/bold]."
|
f"The app directory cannot be named [bold]{constants.MODULE_NAME}[/bold]."
|
||||||
)
|
)
|
||||||
raise typer.Exit()
|
raise typer.Exit(1)
|
||||||
|
|
||||||
return app_name
|
return app_name
|
||||||
|
|
||||||
@ -315,7 +314,7 @@ def install_node():
|
|||||||
console.error(
|
console.error(
|
||||||
f"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(1)
|
||||||
|
|
||||||
# Create the nvm directory and install.
|
# Create the nvm directory and install.
|
||||||
path_ops.mkdir(constants.NVM_DIR)
|
path_ops.mkdir(constants.NVM_DIR)
|
||||||
@ -332,7 +331,7 @@ def install_node():
|
|||||||
],
|
],
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
processes.show_status("", process)
|
processes.show_status("Installing node", process)
|
||||||
|
|
||||||
|
|
||||||
def install_bun():
|
def install_bun():
|
||||||
@ -401,14 +400,14 @@ def check_initialized(frontend: bool = True):
|
|||||||
console.error(
|
console.error(
|
||||||
f"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(1)
|
||||||
|
|
||||||
# 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.error(
|
console.error(
|
||||||
"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(1)
|
||||||
|
|
||||||
# Print a warning for Windows users.
|
# Print a warning for Windows users.
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
@ -436,20 +435,11 @@ def initialize_frontend_dependencies():
|
|||||||
path_ops.mkdir(constants.REFLEX_DIR)
|
path_ops.mkdir(constants.REFLEX_DIR)
|
||||||
|
|
||||||
# Install the frontend dependencies.
|
# Install the frontend dependencies.
|
||||||
threads = [
|
processes.run_concurrently(install_node, install_bun)
|
||||||
threading.Thread(target=initialize_bun),
|
|
||||||
threading.Thread(target=initialize_node),
|
|
||||||
]
|
|
||||||
for thread in threads:
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
# Set up the web directory.
|
# Set up the web directory.
|
||||||
initialize_web_directory()
|
initialize_web_directory()
|
||||||
|
|
||||||
# Wait for the threads to finish.
|
|
||||||
for thread in threads:
|
|
||||||
thread.join()
|
|
||||||
|
|
||||||
|
|
||||||
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."""
|
||||||
|
@ -2,15 +2,17 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
from concurrent import futures
|
||||||
from typing import List, Optional
|
from typing import Callable, List, Optional, Tuple, Union
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
import typer
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.config import get_config
|
from reflex.config import get_config
|
||||||
@ -99,6 +101,10 @@ def change_or_terminate_port(port, _type) -> str:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The new port or the current one.
|
The new port or the current one.
|
||||||
|
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exit: If the user wants to exit.
|
||||||
"""
|
"""
|
||||||
console.info(
|
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."
|
||||||
@ -120,7 +126,7 @@ def change_or_terminate_port(port, _type) -> str:
|
|||||||
return new_port
|
return new_port
|
||||||
else:
|
else:
|
||||||
console.log("Exiting...")
|
console.log("Exiting...")
|
||||||
sys.exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
|
|
||||||
def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
|
def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
|
||||||
@ -153,6 +159,26 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
|
|||||||
return fn(args, **kwargs)
|
return fn(args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def run_concurrently(*fns: Union[Callable, Tuple]):
|
||||||
|
"""Run functions concurrently in a thread pool.
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*fns: The functions to run.
|
||||||
|
"""
|
||||||
|
# Convert the functions to tuples.
|
||||||
|
fns = [fn if isinstance(fn, tuple) else (fn,) for fn in fns] # type: ignore
|
||||||
|
|
||||||
|
# Run the functions concurrently.
|
||||||
|
with futures.ThreadPoolExecutor(max_workers=len(fns)) as executor:
|
||||||
|
# Submit the tasks.
|
||||||
|
tasks = [executor.submit(*fn) for fn in fns] # type: ignore
|
||||||
|
|
||||||
|
# Get the results in the order completed to check any exceptions.
|
||||||
|
for task in futures.as_completed(tasks):
|
||||||
|
task.result()
|
||||||
|
|
||||||
|
|
||||||
def stream_logs(
|
def stream_logs(
|
||||||
message: str,
|
message: str,
|
||||||
process: subprocess.Popen,
|
process: subprocess.Popen,
|
||||||
@ -165,21 +191,27 @@ def stream_logs(
|
|||||||
|
|
||||||
Yields:
|
Yields:
|
||||||
The lines of the process output.
|
The lines of the process output.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exit: If the process failed.
|
||||||
"""
|
"""
|
||||||
|
# Store the tail of the logs.
|
||||||
|
logs = collections.deque(maxlen=512)
|
||||||
with process:
|
with process:
|
||||||
console.debug(message)
|
console.debug(message)
|
||||||
if process.stdout is None:
|
if process.stdout is None:
|
||||||
return
|
return
|
||||||
for line in process.stdout:
|
for line in process.stdout:
|
||||||
console.debug(line, end="")
|
console.debug(line, end="")
|
||||||
|
logs.append(line)
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
console.error(f"Error during {message}")
|
console.error(f"{message} failed with exit code {process.returncode}")
|
||||||
console.error(
|
for line in logs:
|
||||||
"Run in with [bold]--loglevel debug[/bold] to see the full error."
|
console.error(line, end="")
|
||||||
)
|
console.error("Run with [bold]--loglevel debug [/bold] for the full log.")
|
||||||
os._exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
|
||||||
def show_logs(
|
def show_logs(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Unit tests for the included testing tools."""
|
"""Unit tests for the included testing tools."""
|
||||||
from reflex.testing import AppHarness
|
from reflex.testing import AppHarness
|
||||||
|
from reflex.utils.prerequisites import IS_WINDOWS
|
||||||
|
|
||||||
|
|
||||||
def test_app_harness(tmp_path):
|
def test_app_harness(tmp_path):
|
||||||
@ -8,6 +9,9 @@ def test_app_harness(tmp_path):
|
|||||||
Args:
|
Args:
|
||||||
tmp_path: pytest tmp_path fixture
|
tmp_path: pytest tmp_path fixture
|
||||||
"""
|
"""
|
||||||
|
# Skip in Windows CI.
|
||||||
|
if IS_WINDOWS:
|
||||||
|
return
|
||||||
|
|
||||||
def BasicApp():
|
def BasicApp():
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
Loading…
Reference in New Issue
Block a user