get this working with relative imports and hot reload
This commit is contained in:
parent
c9d967e1f5
commit
d32033e210
@ -7,14 +7,13 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
from reflex import constants
|
||||
from reflex.utils import telemetry
|
||||
from reflex.utils.exec import is_prod_mode
|
||||
from reflex.utils.prerequisites import get_app
|
||||
from reflex.utils.prerequisites import get_and_validate_app
|
||||
|
||||
if constants.CompileVars.APP != "app":
|
||||
raise AssertionError("unexpected variable name for 'app'")
|
||||
|
||||
telemetry.send("compile")
|
||||
app_module = get_app(reload=False)
|
||||
app = getattr(app_module, constants.CompileVars.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()
|
||||
@ -30,7 +29,7 @@ if is_prod_mode():
|
||||
# ensure only "app" is exposed.
|
||||
del app_module
|
||||
del compile_future
|
||||
del get_app
|
||||
del get_and_validate_app
|
||||
del is_prod_mode
|
||||
del telemetry
|
||||
del constants
|
||||
|
@ -730,24 +730,36 @@ class Config(Base):
|
||||
"REDIS_URL is required when using the redis state manager."
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _load_via_spec(path: str) -> ModuleType:
|
||||
"""Load a module dynamically using its file path.
|
||||
|
||||
Args:
|
||||
path: The path to the module.
|
||||
|
||||
Returns:
|
||||
The loaded module.
|
||||
"""
|
||||
module_name = Path(path).stem
|
||||
module_path = Path(path).resolve()
|
||||
sys.path.insert(0, str(module_path.parent.parent))
|
||||
spec = importlib.util.spec_from_file_location(module_name, path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
# Set the package name to the parent directory of the module (for relative imports)
|
||||
module.__package__ = module_path.parent.name
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
@property
|
||||
def app_module(self) -> ModuleType | None:
|
||||
"""Get the app module.
|
||||
"""Return the app module if `app_module_path` is set.
|
||||
|
||||
Returns:
|
||||
The app module.
|
||||
"""
|
||||
|
||||
def load_via_spec(path):
|
||||
module_name = Path(path).stem
|
||||
spec = importlib.util.spec_from_file_location(module_name, path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
if self.app_module_path:
|
||||
return load_via_spec(self.app_module_path)
|
||||
return None
|
||||
return (
|
||||
self._load_via_spec(self.app_module_path) if self.app_module_path else None
|
||||
)
|
||||
|
||||
@property
|
||||
def module(self) -> str:
|
||||
|
@ -1759,7 +1759,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
||||
except Exception as ex:
|
||||
state._clean()
|
||||
|
||||
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
app_instance = prerequisites.get_and_validate_app().app
|
||||
|
||||
event_specs = app_instance.backend_exception_handler(ex)
|
||||
|
||||
@ -1871,7 +1871,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
||||
except Exception as ex:
|
||||
telemetry.send_error(ex, context="backend")
|
||||
|
||||
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
app_instance = prerequisites.get_and_validate_app().app
|
||||
|
||||
event_specs = app_instance.backend_exception_handler(ex)
|
||||
|
||||
@ -2383,7 +2383,7 @@ class FrontendEventExceptionState(State):
|
||||
component_stack: The stack trace of the component where the exception occurred.
|
||||
|
||||
"""
|
||||
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
app_instance, _ = prerequisites.get_and_validate_app()
|
||||
app_instance.frontend_exception_handler(Exception(stack))
|
||||
|
||||
|
||||
@ -2422,7 +2422,7 @@ class OnLoadInternalState(State):
|
||||
The list of events to queue for on load handling.
|
||||
"""
|
||||
# Do not app._compile()! It should be already compiled by now.
|
||||
app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
app = prerequisites.get_and_validate_app().app
|
||||
load_events = app.get_load_events(self.router.page.path)
|
||||
if not load_events:
|
||||
self.is_hydrated = True
|
||||
@ -2589,7 +2589,7 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
"""
|
||||
super().__init__(state_instance)
|
||||
# compile is not relevant to backend logic
|
||||
self._self_app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
self._self_app = prerequisites.get_and_validate_app().app
|
||||
self._self_substate_path = tuple(state_instance.get_full_name().split("."))
|
||||
self._self_actx = None
|
||||
self._self_mutable = False
|
||||
@ -3682,7 +3682,7 @@ def get_state_manager() -> StateManager:
|
||||
Returns:
|
||||
The state manager.
|
||||
"""
|
||||
app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
app = prerequisites.get_and_validate_app().app
|
||||
return app.state_manager
|
||||
|
||||
|
||||
|
@ -240,6 +240,19 @@ def run_backend(
|
||||
run_uvicorn_backend(host, port, loglevel)
|
||||
|
||||
|
||||
def get_reload_dirs() -> list[str]:
|
||||
"""Get the reload directories for the backend.
|
||||
|
||||
Returns:
|
||||
The reload directories for the backend.
|
||||
"""
|
||||
config = get_config()
|
||||
reload_dirs = [config.app_name]
|
||||
if app_module_path := config.app_module_path:
|
||||
reload_dirs.append(str(Path(app_module_path).resolve().parent.parent))
|
||||
return reload_dirs
|
||||
|
||||
|
||||
def run_uvicorn_backend(host, port, loglevel: LogLevel):
|
||||
"""Run the backend in development mode using Uvicorn.
|
||||
|
||||
@ -256,7 +269,7 @@ def run_uvicorn_backend(host, port, loglevel: LogLevel):
|
||||
port=port,
|
||||
log_level=loglevel.value,
|
||||
reload=True,
|
||||
reload_dirs=[get_config().app_name],
|
||||
reload_dirs=get_reload_dirs(),
|
||||
)
|
||||
|
||||
|
||||
@ -281,7 +294,7 @@ def run_granian_backend(host, port, loglevel: LogLevel):
|
||||
interface=Interfaces.ASGI,
|
||||
log_level=LogLevels(loglevel.value),
|
||||
reload=True,
|
||||
reload_paths=[Path(get_config().app_name)],
|
||||
reload_paths=get_reload_dirs(),
|
||||
reload_ignore_dirs=[".web"],
|
||||
).serve()
|
||||
except ImportError:
|
||||
|
@ -17,11 +17,13 @@ import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import typing
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Callable, List, Optional
|
||||
from collections import namedtuple
|
||||
|
||||
import httpx
|
||||
import typer
|
||||
@ -42,9 +44,12 @@ from reflex.utils.exceptions import (
|
||||
from reflex.utils.format import format_library_name
|
||||
from reflex.utils.registry import _get_npm_registry
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from reflex.app import App
|
||||
|
||||
CURRENTLY_INSTALLING_NODE = False
|
||||
|
||||
|
||||
AppInfo = namedtuple("AppInfo", ["app", "module"])
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Template:
|
||||
"""A template for a Reflex app."""
|
||||
@ -296,7 +301,6 @@ def get_app(reload: bool = False) -> ModuleType:
|
||||
if not config.app_module
|
||||
else config.app_module
|
||||
)
|
||||
|
||||
if reload:
|
||||
from reflex.state import reload_state_module
|
||||
|
||||
@ -312,19 +316,24 @@ def get_app(reload: bool = False) -> ModuleType:
|
||||
raise
|
||||
|
||||
|
||||
def get_and_validate_app(reload: bool = False):
|
||||
"""Get the app module based on the default config and validate it.
|
||||
def get_and_validate_app(reload: bool = False) -> AppInfo:
|
||||
"""Get the app instance based on the default config and validate it.
|
||||
|
||||
Args:
|
||||
reload: Re-import the app module from disk
|
||||
|
||||
Returns:
|
||||
The app instance and the app module.
|
||||
"""
|
||||
from reflex.app import App
|
||||
|
||||
app_module = get_app(reload=reload)
|
||||
app = getattr(app_module, constants.CompileVars.APP)
|
||||
if not isinstance(app, App):
|
||||
raise RuntimeError("The app object in rxconfig must be an instance of rx.App.")
|
||||
return app
|
||||
raise RuntimeError(
|
||||
"The app instance in the specified app_module_path in rxconfig must be an instance of rx.App."
|
||||
)
|
||||
return AppInfo(app=app, module=app_module)
|
||||
|
||||
|
||||
def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
||||
@ -337,8 +346,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
||||
Returns:
|
||||
The compiled app based on the default config.
|
||||
"""
|
||||
app_module = get_app(reload=reload)
|
||||
app = getattr(app_module, constants.CompileVars.APP)
|
||||
app, app_module = get_and_validate_app(reload=reload)
|
||||
# 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()
|
||||
|
Loading…
Reference in New Issue
Block a user