Skip to content

Commit 0e2d392

Browse files
masenfLendemoradhami3310
authored
Remove app_module_for_backend (#4749)
* Remove app_module_for_backend * Replace app_module_for_backend with ASGI factory on `rx.App.__call__` * Consolidate module spec for uvicorn, gunicorn, and granian * update unit test * granian is funny * 🐶 * possibly * don't compile the app on backend * potentially less hacky solution * use pool executor with functions not returning anything * dang it darglint --------- Co-authored-by: Lendemor <[email protected]> Co-authored-by: Khaleel Al-Adhami <[email protected]>
1 parent 2fcb573 commit 0e2d392

File tree

6 files changed

+58
-54
lines changed

6 files changed

+58
-54
lines changed

reflex/app.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,22 @@ def __call__(self) -> FastAPI:
576576
"""
577577
if not self.api:
578578
raise ValueError("The app has not been initialized.")
579+
580+
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
581+
# before compiling the app in a thread to avoid event loop error (REF-2172).
582+
self._apply_decorated_pages()
583+
584+
compile_future = concurrent.futures.ThreadPoolExecutor(max_workers=1).submit(
585+
self._compile
586+
)
587+
compile_future.add_done_callback(
588+
# Force background compile errors to print eagerly
589+
lambda f: f.result()
590+
)
591+
# Wait for the compile to finish in prod mode to ensure all optional endpoints are mounted.
592+
if is_prod_mode():
593+
compile_future.result()
594+
579595
return self.api
580596

581597
def _add_default_endpoints(self):

reflex/app_module_for_backend.py

Lines changed: 0 additions & 33 deletions
This file was deleted.

reflex/reflex.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import atexit
6+
import concurrent.futures
67
from pathlib import Path
78

89
import typer
@@ -14,6 +15,7 @@
1415
from reflex.custom_components.custom_components import custom_components_cli
1516
from reflex.state import reset_disk_state_manager
1617
from reflex.utils import console, redir, telemetry
18+
from reflex.utils.exec import should_use_granian
1719

1820
# Disable typer+rich integration for help panels
1921
typer.core.rich = None # pyright: ignore [reportPrivateImportUsage]
@@ -203,9 +205,17 @@ def _run(
203205

204206
prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
205207

206-
if frontend:
207-
# Get the app module.
208-
prerequisites.get_compiled_app()
208+
# Get the app module.
209+
app_task = prerequisites.compile_app if frontend else prerequisites.validate_app
210+
211+
# Granian fails if the app is already imported.
212+
if should_use_granian():
213+
compile_future = concurrent.futures.ProcessPoolExecutor(max_workers=1).submit(
214+
app_task
215+
)
216+
compile_future.result()
217+
else:
218+
app_task()
209219

210220
# Warn if schema is not up to date.
211221
prerequisites.check_schema_up_to_date()

reflex/utils/exec.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -200,22 +200,9 @@ def get_app_module():
200200
Returns:
201201
The app module for the backend.
202202
"""
203-
return f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
204-
205-
206-
def get_granian_target():
207-
"""Get the Granian target for the backend.
208-
209-
Returns:
210-
The Granian target for the backend.
211-
"""
212-
import reflex
213-
214-
app_module_path = Path(reflex.__file__).parent / "app_module_for_backend.py"
203+
config = get_config()
215204

216-
return (
217-
f"{app_module_path!s}:{constants.CompileVars.APP}.{constants.CompileVars.API}"
218-
)
205+
return f"{config.module}:{constants.CompileVars.APP}"
219206

220207

221208
def run_backend(
@@ -317,7 +304,8 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
317304
import uvicorn
318305

319306
uvicorn.run(
320-
app=f"{get_app_module()}.{constants.CompileVars.API}",
307+
app=f"{get_app_module()}",
308+
factory=True,
321309
host=host,
322310
port=port,
323311
log_level=loglevel.value,
@@ -341,7 +329,8 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
341329
from granian.server import Server as Granian
342330

343331
Granian(
344-
target=get_granian_target(),
332+
target=get_app_module(),
333+
factory=True,
345334
address=host,
346335
port=port,
347336
interface=Interfaces.ASGI,
@@ -419,6 +408,7 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
419408
*("--host", host),
420409
*("--port", str(port)),
421410
*("--workers", str(_get_backend_workers())),
411+
"--factory",
422412
app_module,
423413
]
424414
if constants.IS_WINDOWS
@@ -482,7 +472,8 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
482472
str(port),
483473
"--interface",
484474
str(Interfaces.ASGI),
485-
get_granian_target(),
475+
"--factory",
476+
get_app_module(),
486477
]
487478
processes.new_process(
488479
command,

reflex/utils/prerequisites.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,15 @@ def get_and_validate_app(reload: bool = False) -> AppInfo:
412412
return AppInfo(app=app, module=app_module)
413413

414414

415+
def validate_app(reload: bool = False) -> None:
416+
"""Validate the app instance based on the default config.
417+
418+
Args:
419+
reload: Re-import the app module from disk
420+
"""
421+
get_and_validate_app(reload=reload)
422+
423+
415424
def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
416425
"""Get the app module based on the default config after first compiling it.
417426
@@ -430,6 +439,16 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
430439
return app_module
431440

432441

442+
def compile_app(reload: bool = False, export: bool = False) -> None:
443+
"""Compile the app module based on the default config.
444+
445+
Args:
446+
reload: Re-import the app module from disk
447+
export: Compile the app for export
448+
"""
449+
get_compiled_app(reload=reload, export=export)
450+
451+
433452
def get_redis() -> Redis | None:
434453
"""Get the asynchronous redis client.
435454

tests/units/test_page.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ def test_page_decorator():
77
def foo_():
88
return text("foo")
99

10+
DECORATED_PAGES.clear()
1011
assert len(DECORATED_PAGES) == 0
1112
decorated_foo_ = page()(foo_)
1213
assert decorated_foo_ == foo_

0 commit comments

Comments
 (0)