* 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
This commit is contained in:
parent
dc34c9a4b4
commit
8a3cb33e33
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user