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,