From 78277af4e9bcb3206002b4c74dca607d9ca27c8e Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 4 Feb 2025 11:28:47 -0800 Subject: [PATCH 1/3] 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 --- reflex/app.py | 16 ++++++++++++++++ reflex/app_module_for_backend.py | 33 -------------------------------- reflex/utils/exec.py | 27 +++++++++----------------- 3 files changed, 25 insertions(+), 51 deletions(-) delete mode 100644 reflex/app_module_for_backend.py diff --git a/reflex/app.py b/reflex/app.py index d9104ece6..ac79ad63b 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -468,6 +468,22 @@ class App(MiddlewareMixin, LifespanMixin): """ if not self.api: raise ValueError("The app has not been initialized.") + + # For py3.9 compatibility when redis is used, we MUST add any decorator pages + # before compiling the app in a thread to avoid event loop error (REF-2172). + self._apply_decorated_pages() + + compile_future = concurrent.futures.ThreadPoolExecutor(max_workers=1).submit( + self._compile + ) + compile_future.add_done_callback( + # Force background compile errors to print eagerly + lambda f: f.result() + ) + # Wait for the compile to finish in prod mode to ensure all optional endpoints are mounted. + if is_prod_mode(): + compile_future.result() + return self.api def _add_default_endpoints(self): diff --git a/reflex/app_module_for_backend.py b/reflex/app_module_for_backend.py deleted file mode 100644 index 28be30410..000000000 --- a/reflex/app_module_for_backend.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Shims the real reflex app module for running backend server (uvicorn or gunicorn). -Only the app attribute is explicitly exposed. -""" - -from concurrent.futures import ThreadPoolExecutor - -from reflex import constants -from reflex.utils.exec import is_prod_mode -from reflex.utils.prerequisites import get_and_validate_app - -if constants.CompileVars.APP != "app": - raise AssertionError("unexpected variable name for 'app'") - -app, app_module = get_and_validate_app(reload=False) -# For py3.9 compatibility when redis is used, we MUST add any decorator pages -# before compiling the app in a thread to avoid event loop error (REF-2172). -app._apply_decorated_pages() -compile_future = ThreadPoolExecutor(max_workers=1).submit(app._compile) -compile_future.add_done_callback( - # Force background compile errors to print eagerly - lambda f: f.result() -) -# Wait for the compile to finish in prod mode to ensure all optional endpoints are mounted. -if is_prod_mode(): - compile_future.result() - -# ensure only "app" is exposed. -del app_module -del compile_future -del get_and_validate_app -del is_prod_mode -del constants -del ThreadPoolExecutor diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 67df7ea91..8ea2ab105 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -195,22 +195,9 @@ def get_app_module(): Returns: The app module for the backend. """ - return f"reflex.app_module_for_backend:{constants.CompileVars.APP}" + config = get_config() - -def get_granian_target(): - """Get the Granian target for the backend. - - Returns: - The Granian target for the backend. - """ - import reflex - - app_module_path = Path(reflex.__file__).parent / "app_module_for_backend.py" - - return ( - f"{app_module_path!s}:{constants.CompileVars.APP}.{constants.CompileVars.API}" - ) + return f"{config.module}:{constants.CompileVars.APP}" def run_backend( @@ -278,7 +265,8 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel): import uvicorn uvicorn.run( - app=f"{get_app_module()}.{constants.CompileVars.API}", + app=f"{get_app_module()}", + factory=True, host=host, port=port, log_level=loglevel.value, @@ -304,7 +292,8 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel): from granian.log import LogLevels # pyright: ignore [reportMissingImports] Granian( - target=get_granian_target(), + target=get_app_module(), + factory=True, address=host, port=port, interface=Interfaces.ASGI, @@ -377,6 +366,7 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel): host, "--port", str(port), + "--factory", app_module, ] if constants.IS_WINDOWS @@ -433,7 +423,8 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel): str(port), "--interface", str(Interfaces.ASGI), - get_granian_target(), + "--factory", + get_app_module(), ] processes.new_process( command, From 0e45cc5b0de1fe1eedc9aec532b5a9f7e9c769e9 Mon Sep 17 00:00:00 2001 From: Lendemor Date: Fri, 7 Feb 2025 00:04:10 +0100 Subject: [PATCH 2/3] update unit test --- tests/units/test_page.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/units/test_page.py b/tests/units/test_page.py index e1dd70905..8c9847cba 100644 --- a/tests/units/test_page.py +++ b/tests/units/test_page.py @@ -7,6 +7,7 @@ def test_page_decorator(): def foo_(): return text("foo") + DECORATED_PAGES.clear() assert len(DECORATED_PAGES) == 0 decorated_foo_ = page()(foo_) assert decorated_foo_ == foo_ From 434e794412acd59dd684ce5184b42bc63da396be Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 14 Feb 2025 18:18:58 -0800 Subject: [PATCH 3/3] granian is funny --- reflex/reflex.py | 5 ++++- reflex/utils/exec.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/reflex/reflex.py b/reflex/reflex.py index 410485551..717509efa 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -15,6 +15,7 @@ from reflex.config import environment, get_config from reflex.custom_components.custom_components import custom_components_cli from reflex.state import reset_disk_state_manager from reflex.utils import console, telemetry +from reflex.utils.exec import should_use_granian # Disable typer+rich integration for help panels typer.core.rich = None # pyright: ignore [reportPrivateImportUsage] @@ -206,7 +207,9 @@ def _run( ) # Get the app module. - prerequisites.get_compiled_app() + if not should_use_granian(): + # Granian fails if the app is already imported. + prerequisites.get_compiled_app() # Warn if schema is not up to date. prerequisites.check_schema_up_to_date() diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 7c0c9953f..3d6233413 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -216,9 +216,6 @@ def run_backend( frontend_present: Whether the frontend is present. """ web_dir = get_web_dir() - # Create a .nocompile file to skip compile for backend. - if web_dir.exists(): - (web_dir / constants.NOCOMPILE_FILE).touch() if not frontend_present: notify_backend() @@ -227,6 +224,9 @@ def run_backend( if should_use_granian(): run_granian_backend(host, port, loglevel) else: + # Create a .nocompile file to skip compile for backend. + if web_dir.exists(): + (web_dir / constants.NOCOMPILE_FILE).touch() run_uvicorn_backend(host, port, loglevel)