Skip to content

Commit 162e5ef

Browse files
flcromFarhanAliRazamasenf
authored
fail loudly when the app package has no __init__.py (#7304)
* fail loudly when the app package has no __init__.py * Create missing app package markers before backend import * Test package marker creation and existing-file safety * Update bugfix note for automatic package markers * Guard package marker creation for namespace and read-only apps * Preserve nested regular package imports * Use module target for Granian backend imports * Preserve app module validation for Granian target * Restore sys.path after missing-module test --------- Co-authored-by: Farhan Ali Raza <62690310+FarhanAliRaza@users.noreply.github.com> Co-authored-by: Masen Furer <m_github@0x26.net>
1 parent e84c5be commit 162e5ef

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

‎news/7258.bugfix.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Load backend apps by their configured module name so state updates work even when the app package has no `__init__.py`.

‎reflex/utils/exec.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,8 @@ def get_app_instance_from_file() -> str:
553553
Returns:
554554
The app module for the backend.
555555
"""
556-
return f"{get_app_file()}:{constants.CompileVars.APP}"
556+
get_app_file()
557+
return get_app_instance()
557558

558559

559560
def run_backend(
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Regression for a backend app whose package has no __init__.py."""
2+
3+
import importlib
4+
import sys
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
from reflex.utils import exec as exec_utils
10+
11+
12+
@pytest.mark.parametrize("installed", [False, True])
13+
def test_granian_target_keeps_configured_module_without_writing_marker(
14+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, installed: bool
15+
):
16+
from granian._internal import prepare_import
17+
18+
root = tmp_path / "installed" if installed else tmp_path
19+
module = "installed_app_7304" if installed else "myapp.myapp"
20+
module_file = root.joinpath(*module.split(".")).with_suffix(".py")
21+
module_file.parent.mkdir(parents=True)
22+
module_file.write_text("app = object()\n")
23+
monkeypatch.chdir(tmp_path)
24+
monkeypatch.syspath_prepend(str(root))
25+
original_path = sys.path.copy()
26+
if installed:
27+
from reflex_base.config import Config
28+
29+
config = Config(app_name="myapp", app_module_import=module)
30+
monkeypatch.setattr(exec_utils, "get_config", lambda: config)
31+
else:
32+
monkeypatch.setattr(exec_utils, "get_app_module", lambda: module)
33+
34+
target, _ = exec_utils.get_app_instance_from_file().split(":", 1)
35+
assert prepare_import(target) == module
36+
sys.modules.pop(module, None)
37+
try:
38+
assert importlib.import_module(target).app is not None
39+
assert not (module_file.parent / "__init__.py").exists()
40+
finally:
41+
sys.modules.pop(module, None)
42+
for index in range(1, len(module.split("."))):
43+
sys.modules.pop(".".join(module.split(".")[:index]), None)
44+
sys.path[:] = original_path
45+
46+
47+
def test_granian_target_rejects_missing_module(
48+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
49+
):
50+
"""The module target still provides the existing missing-module error."""
51+
monkeypatch.chdir(tmp_path)
52+
monkeypatch.syspath_prepend(str(tmp_path))
53+
monkeypatch.setattr(exec_utils, "get_app_module", lambda: "missing.app")
54+
with pytest.raises(ImportError, match=r"Module missing\.app not found"):
55+
exec_utils.get_app_instance_from_file()
56+
57+
58+
def test_granian_target_adds_project_root_to_sys_path(
59+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
60+
):
61+
"""An installed console script may not put the project root on sys.path."""
62+
app_dir = tmp_path / "local_app_7304"
63+
app_dir.mkdir()
64+
(app_dir / "app.py").write_text("app = object()\n")
65+
monkeypatch.chdir(tmp_path)
66+
monkeypatch.setattr(
67+
sys, "path", [entry for entry in sys.path if entry != str(tmp_path)]
68+
)
69+
monkeypatch.setattr(exec_utils, "get_app_module", lambda: "local_app_7304.app")
70+
71+
target, _ = exec_utils.get_app_instance_from_file().split(":", 1)
72+
try:
73+
assert importlib.import_module(target).app is not None
74+
finally:
75+
sys.modules.pop(target, None)
76+
sys.modules.pop("local_app_7304", None)

0 commit comments

Comments
 (0)