Run backend from the main thread in dev mode (#1653)
This commit is contained in:
parent
96a6ab95a2
commit
81fd9d1e9c
28
docs/DEBUGGING.md
Normal file
28
docs/DEBUGGING.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Debugging
|
||||
|
||||
It is possible to run Reflex apps in dev mode under a debugger.
|
||||
|
||||
1. Run Reflex as a module: `python -m reflex run --env dev`
|
||||
2. Set current working directory to the dir containing `rxconfig.py`
|
||||
|
||||
## VSCode
|
||||
|
||||
The following launch configuration can be used to interactively debug a Reflex
|
||||
app with breakpoints.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Reflex App",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "reflex",
|
||||
"args": "run --env dev",
|
||||
"justMyCode": true,
|
||||
"cwd": "${fileDirname}/.."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
@ -1,7 +1,7 @@
|
||||
"""Reflex CLI to create, run, and deploy apps."""
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import signal
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
@ -164,16 +164,18 @@ def run(
|
||||
telemetry.send(f"run-{env.value}", config.telemetry_enabled)
|
||||
|
||||
# Display custom message when there is a keyboard interrupt.
|
||||
signal.signal(signal.SIGINT, processes.catch_keyboard_interrupt)
|
||||
atexit.register(processes.atexit_handler)
|
||||
|
||||
# Run the frontend and backend together.
|
||||
commands = []
|
||||
if frontend:
|
||||
setup_frontend(Path.cwd())
|
||||
commands.append((frontend_cmd, Path.cwd(), frontend_port))
|
||||
if backend:
|
||||
if backend and env == constants.Env.PROD:
|
||||
commands.append((backend_cmd, app.__name__, backend_host, backend_port))
|
||||
processes.run_concurrently(*commands)
|
||||
with processes.run_concurrently_context(*commands):
|
||||
if env == constants.Env.DEV:
|
||||
backend_cmd(app.__name__, backend_host, int(backend_port))
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
@ -7,6 +7,8 @@ import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import uvicorn
|
||||
|
||||
from reflex import constants
|
||||
from reflex.config import get_config
|
||||
from reflex.utils import console, path_ops, prerequisites, processes
|
||||
@ -95,22 +97,13 @@ def run_backend(
|
||||
port: The app port
|
||||
loglevel: The log level.
|
||||
"""
|
||||
processes.new_process(
|
||||
[
|
||||
"uvicorn",
|
||||
f"{app_name}:{constants.APP_VAR}.{constants.API_VAR}",
|
||||
"--host",
|
||||
host,
|
||||
"--port",
|
||||
str(port),
|
||||
"--log-level",
|
||||
loglevel.value,
|
||||
"--reload",
|
||||
"--reload-dir",
|
||||
app_name.split(".")[0],
|
||||
],
|
||||
run=True,
|
||||
show_logs=True,
|
||||
uvicorn.run(
|
||||
app=f"{app_name}:{constants.APP_VAR}.{constants.API_VAR}",
|
||||
host=host,
|
||||
port=port,
|
||||
log_level=loglevel.value,
|
||||
reload=True,
|
||||
reload_dirs=[app_name.split(".")[0]],
|
||||
)
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ import os
|
||||
import signal
|
||||
import subprocess
|
||||
from concurrent import futures
|
||||
from typing import Callable, List, Optional, Tuple, Union
|
||||
from typing import Callable, Generator, List, Optional, Tuple, Union
|
||||
|
||||
import psutil
|
||||
import typer
|
||||
@ -145,13 +145,23 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
|
||||
return fn(args, **kwargs)
|
||||
|
||||
|
||||
def run_concurrently(*fns: Union[Callable, Tuple]):
|
||||
@contextlib.contextmanager
|
||||
def run_concurrently_context(
|
||||
*fns: Union[Callable, Tuple]
|
||||
) -> Generator[list[futures.Future], None, None]:
|
||||
"""Run functions concurrently in a thread pool.
|
||||
|
||||
|
||||
Args:
|
||||
*fns: The functions to run.
|
||||
|
||||
Yields:
|
||||
The futures for the functions.
|
||||
"""
|
||||
# If no functions are provided, yield an empty list and return.
|
||||
if not fns:
|
||||
yield []
|
||||
return
|
||||
|
||||
# Convert the functions to tuples.
|
||||
fns = [fn if isinstance(fn, tuple) else (fn,) for fn in fns] # type: ignore
|
||||
|
||||
@ -160,11 +170,24 @@ def run_concurrently(*fns: Union[Callable, Tuple]):
|
||||
# Submit the tasks.
|
||||
tasks = [executor.submit(*fn) for fn in fns] # type: ignore
|
||||
|
||||
# Yield control back to the main thread while tasks are running.
|
||||
yield tasks
|
||||
|
||||
# Get the results in the order completed to check any exceptions.
|
||||
for task in futures.as_completed(tasks):
|
||||
task.result()
|
||||
|
||||
|
||||
def run_concurrently(*fns: Union[Callable, Tuple]) -> None:
|
||||
"""Run functions concurrently in a thread pool.
|
||||
|
||||
Args:
|
||||
*fns: The functions to run.
|
||||
"""
|
||||
with run_concurrently_context(*fns):
|
||||
pass
|
||||
|
||||
|
||||
def stream_logs(
|
||||
message: str,
|
||||
process: subprocess.Popen,
|
||||
@ -247,11 +270,6 @@ def show_progress(message: str, process: subprocess.Popen, checkpoints: List[str
|
||||
break
|
||||
|
||||
|
||||
def catch_keyboard_interrupt(signal, frame):
|
||||
"""Display a custom message with the current time when exiting an app.
|
||||
|
||||
Args:
|
||||
signal: The keyboard interrupt signal.
|
||||
frame: The current stack frame.
|
||||
"""
|
||||
def atexit_handler():
|
||||
"""Display a custom message with the current time when exiting an app."""
|
||||
console.log("Reflex app stopped.")
|
||||
|
Loading…
Reference in New Issue
Block a user