diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 60ea2c901cd..5f917a5c877 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -23,6 +23,7 @@ from reflex.environment import environment from reflex.utils import console, path_ops from reflex.utils.decorator import once +from reflex.utils.misc import get_module_path from reflex.utils.prerequisites import get_web_dir # For uvicorn windows bug fix (#2335) @@ -323,15 +324,12 @@ def get_app_file() -> Path: if current_working_dir not in sys.path: # Add the current working directory to sys.path sys.path.insert(0, current_working_dir) - module_spec = importlib.util.find_spec(get_app_module()) - if module_spec is None: - msg = f"Module {get_app_module()} not found. Make sure the module is installed." + app_module = get_app_module() + module_path = get_module_path(app_module) + if module_path is None: + msg = f"Module {app_module} not found. Make sure the module is installed." raise ImportError(msg) - file_name = module_spec.origin - if file_name is None: - msg = f"Module {get_app_module()} not found. Make sure the module is installed." - raise ImportError(msg) - return Path(file_name).resolve() + return module_path def get_app_instance_from_file() -> str: @@ -396,8 +394,10 @@ def get_reload_paths() -> Sequence[Path]: """ config = get_config() reload_paths = [Path.cwd()] - if (spec := importlib.util.find_spec(config.module)) is not None and spec.origin: - module_path = Path(spec.origin).resolve().parent + app_module = config.module + module_path = get_module_path(app_module) + if module_path is not None: + module_path = module_path.parent while module_path.parent.name and _has_child_file(module_path, "__init__.py"): if _has_child_file(module_path, "rxconfig.py"): diff --git a/reflex/utils/misc.py b/reflex/utils/misc.py index ee03ba6f1b4..bff6f5cc87c 100644 --- a/reflex/utils/misc.py +++ b/reflex/utils/misc.py @@ -9,6 +9,46 @@ from typing import Any +def get_module_path(module_name: str) -> Path | None: + """Check if a module exists and return its path. + + This function searches for a module by navigating through the module hierarchy + in each path of sys.path, checking for both .py files and packages with __init__.py. + + Args: + module_name: The name of the module to search for (e.g., "package.submodule"). + + Returns: + The path to the module file if found, None otherwise. + """ + parts = module_name.split(".") + + # Check each path in sys.path + for path in sys.path: + current_path = Path(path) + + # Navigate through the module hierarchy + for i, part in enumerate(parts): + potential_file = current_path / (part + ".py") + potential_dir = current_path / part + potential_init = current_path / part / "__init__.py" + + if potential_file.is_file(): + # We encountered a file, but we can't continue deeper + if i == len(parts) - 1: + return potential_file + return None # Can't continue deeper + if potential_dir.is_dir() and potential_init.is_file(): + # It's a package, so we can continue deeper + current_path = potential_dir + else: + break # Path doesn't exist, break out of the loop + else: + return current_path / "__init__.py" # Made it through all parts + + return None + + async def run_in_thread(func: Callable) -> Any: """Run a function in a separate thread. diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index ca290f0ed12..de150fcf46a 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -40,6 +40,7 @@ from reflex.environment import environment from reflex.utils import console, net, path_ops, processes, redir from reflex.utils.exceptions import SystemPackageMissingError +from reflex.utils.misc import get_module_path from reflex.utils.registry import get_npm_registry if typing.TYPE_CHECKING: @@ -348,14 +349,11 @@ def _check_app_name(config: Config): ) raise RuntimeError(msg) - from reflex.utils.misc import with_cwd_in_syspath + from reflex.utils.misc import get_module_path, with_cwd_in_syspath with with_cwd_in_syspath(): - try: - mod_spec = importlib.util.find_spec(config.module) - except ModuleNotFoundError: - mod_spec = None - if mod_spec is None: + module_path = get_module_path(config.module) + if module_path is None: msg = f"Module {config.module} not found. " if config.app_module_import is not None: msg += f"Ensure app_module_import='{config.app_module_import}' in rxconfig.py matches your folder structure." @@ -740,14 +738,11 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel): sys.path.insert(0, str(Path.cwd())) config = get_config() - module_path = importlib.util.find_spec(config.module) + module_path = get_module_path(config.module) if module_path is None: console.error(f"Could not find module {config.module}.") raise click.exceptions.Exit(1) - if not module_path.origin: - console.error(f"Could not find origin for module {config.module}.") - raise click.exceptions.Exit(1) console.info(f"Renaming app directory to {new_app_name}.") process_directory( Path.cwd(), @@ -756,7 +751,7 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel): exclude_dirs=[constants.Dirs.WEB, constants.Dirs.APP_ASSETS], ) - rename_path_up_tree(Path(module_path.origin), config.app_name, new_app_name) + rename_path_up_tree(module_path, config.app_name, new_app_name) console.success(f"App directory renamed to [bold]{new_app_name}[/bold].")