Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions reflex/utils/exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"):
Expand Down
40 changes: 40 additions & 0 deletions reflex/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Comment on lines +46 to +48
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The else clause will only trigger if the loop completes without a break, meaning we found all parts as directories. However, we need to check if the final path actually exists before returning.

return None


async def run_in_thread(func: Callable) -> Any:
"""Run a function in a separate thread.

Expand Down
17 changes: 6 additions & 11 deletions reflex/utils/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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(),
Expand All @@ -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].")

Expand Down