From 8a3cb33e330aa463fc7b15aeb635dd8cc1154219 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 16 Jan 2025 11:15:42 -0800 Subject: [PATCH] CR Suggestions for #4556 (#4644) * reload_dirs: search up from app_module for last directory containing __init__ * Change custom app_module to use an import string * preserve sys.path entries added while loading rxconfig.py --- reflex/config.py | 51 +++++++++++------------------------ reflex/utils/exec.py | 13 +++++++-- reflex/utils/prerequisites.py | 2 +- 3 files changed, 28 insertions(+), 38 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index 65188f8da..e7ee77770 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -10,7 +10,7 @@ import os import sys import threading import urllib.parse -from importlib.util import find_spec, module_from_spec, spec_from_file_location +from importlib.util import find_spec from pathlib import Path from types import ModuleType from typing import ( @@ -606,7 +606,7 @@ class Config(Base): app_name: str # The path to the app module. - app_module_path: Optional[str] = None + app_module_import: Optional[str] = None # The log level to use. loglevel: constants.LogLevel = constants.LogLevel.DEFAULT @@ -730,40 +730,17 @@ 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. - - Raises: - ConfigError: If the module cannot be loaded. - """ - module_name = Path(path).stem - module_path = Path(path).resolve() - sys.path.insert(0, str(module_path.parent.parent)) - spec = spec_from_file_location(module_name, module_path) - if not spec: - raise ConfigError(f"Could not load module from path: {module_path}") - module = 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) # type: ignore - return module - @property def app_module(self) -> ModuleType | None: - """Return the app module if `app_module_path` is set. + """Return the app module if `app_module_import` is set. Returns: The app module. """ return ( - self._load_via_spec(self.app_module_path) if self.app_module_path else None + importlib.import_module(self.app_module_import) + if self.app_module_import + else None ) @property @@ -773,9 +750,8 @@ class Config(Base): Returns: The module name. """ - if self.app_module and self.app_module.__file__: - module_file = Path(self.app_module.__file__) - return f"{module_file.parent.name}.{module_file.stem}" + if self.app_module is not None: + return self.app_module.__name__ return ".".join([self.app_name, self.app_name]) def update_from_env(self) -> dict[str, Any]: @@ -914,7 +890,7 @@ def get_config(reload: bool = False) -> Config: return cached_rxconfig.config with _config_lock: - sys_path = sys.path.copy() + orig_sys_path = sys.path.copy() sys.path.clear() sys.path.append(str(Path.cwd())) try: @@ -922,9 +898,14 @@ def get_config(reload: bool = False) -> Config: return _get_config() except Exception: # If the module import fails, try to import with the original sys.path. - sys.path.extend(sys_path) + sys.path.extend(orig_sys_path) return _get_config() finally: + # Find any entries added to sys.path by rxconfig.py itself. + extra_paths = [ + p for p in sys.path if p not in orig_sys_path and p != str(Path.cwd()) + ] # Restore the original sys.path. sys.path.clear() - sys.path.extend(sys_path) + sys.path.extend(extra_paths) + sys.path.extend(orig_sys_path) diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 22de8b948..6087818d9 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -248,8 +248,17 @@ def get_reload_dirs() -> list[str]: """ 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)) + if config.app_module is not None and config.app_module.__file__: + module_path = Path(config.app_module.__file__).resolve().parent + while module_path.parent.name: + for parent_file in module_path.parent.iterdir(): + if parent_file == "__init__.py": + # go up a level to find dir without `__init__.py` + module_path = module_path.parent + break + else: + break + reload_dirs.append(str(module_path)) return reload_dirs diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index e3db6f52c..38e41986b 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -340,7 +340,7 @@ def get_and_validate_app(reload: bool = False) -> AppInfo: app = getattr(app_module, constants.CompileVars.APP) if not isinstance(app, App): raise RuntimeError( - "The app instance in the specified app_module_path in rxconfig must be an instance of rx.App." + "The app instance in the specified app_module_import in rxconfig must be an instance of rx.App." ) return AppInfo(app=app, module=app_module)