Run backend from the main thread in dev mode (#1653)

This commit is contained in:
Masen Furer 2023-08-23 16:52:54 -07:00 committed by GitHub
parent 96a6ab95a2
commit 81fd9d1e9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 30 deletions

28
docs/DEBUGGING.md Normal file
View 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}/.."
}
]
}
```

View File

@ -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()

View File

@ -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]],
)

View File

@ -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.")