Ensure local Modal runtime autoload

#34
app.py CHANGED
@@ -5,9 +5,11 @@ from fastapi import Request
5
  from fastapi.responses import JSONResponse, RedirectResponse
6
 
7
  from dream_customs import zerogpu # noqa: F401
 
8
  from dream_customs.ui.app import build_demo
9
 
10
 
 
11
  demo = build_demo()
12
 
13
 
 
5
  from fastapi.responses import JSONResponse, RedirectResponse
6
 
7
  from dream_customs import zerogpu # noqa: F401
8
+ from dream_customs.runtime_env import auto_load_runtime_env_json
9
  from dream_customs.ui.app import build_demo
10
 
11
 
12
+ auto_load_runtime_env_json()
13
  demo = build_demo()
14
 
15
 
dream_customs/runtime_env.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+
6
+ SPACE_ID = "build-small-hackathon/dream-customs"
7
+ DEFAULT_RUNTIME_ENV_JSON = Path("/tmp/dream-customs-runtime.json")
8
+ RUNTIME_ENV_KEYS = {
9
+ "DREAM_CUSTOMS_TEXT_ENDPOINT",
10
+ "DREAM_CUSTOMS_VISION_ENDPOINT",
11
+ "DREAM_CUSTOMS_ASR_ENDPOINT",
12
+ "DREAM_CUSTOMS_HOSTED_TOKEN",
13
+ }
14
+
15
+
16
+ def runtime_env_json_path() -> Path:
17
+ return Path(os.getenv("DREAM_CUSTOMS_RUNTIME_ENV_JSON", str(DEFAULT_RUNTIME_ENV_JSON)))
18
+
19
+
20
+ def _runtime_env_autoload_enabled() -> bool:
21
+ value = os.getenv("DREAM_CUSTOMS_DISABLE_RUNTIME_ENV_JSON", "").strip().lower()
22
+ return value not in {"1", "true", "yes", "on"}
23
+
24
+
25
+ def load_runtime_env_json(path: Path) -> dict:
26
+ if not path.exists():
27
+ return {"loaded": False, "path": str(path), "reason": "missing"}
28
+ try:
29
+ data = json.loads(path.read_text(encoding="utf-8"))
30
+ except (OSError, json.JSONDecodeError) as exc:
31
+ return {"loaded": False, "path": str(path), "reason": exc.__class__.__name__}
32
+
33
+ loaded_keys = []
34
+ for key in sorted(RUNTIME_ENV_KEYS):
35
+ value = str(data.get(key, "")).strip()
36
+ if value:
37
+ os.environ[key] = value
38
+ loaded_keys.append(key)
39
+ return {
40
+ "loaded": bool(loaded_keys),
41
+ "path": str(path),
42
+ "configured_keys": loaded_keys,
43
+ }
44
+
45
+
46
+ def auto_load_runtime_env_json() -> dict:
47
+ if not _runtime_env_autoload_enabled():
48
+ return {"loaded": False, "reason": "disabled"}
49
+ if os.getenv("PYTEST_CURRENT_TEST") and not os.getenv("DREAM_CUSTOMS_RUNTIME_ENV_JSON"):
50
+ return {"loaded": False, "reason": "pytest"}
51
+ return load_runtime_env_json(runtime_env_json_path())
52
+
53
+
54
+ def configured_env(space_id: str = SPACE_ID) -> dict:
55
+ return {
56
+ "space_id": os.getenv("SPACE_ID", space_id),
57
+ "text_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_TEXT_ENDPOINT", "").strip()),
58
+ "vision_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_VISION_ENDPOINT", "").strip()),
59
+ "asr_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_ASR_ENDPOINT", "").strip()),
60
+ "hosted_token_configured": bool(os.getenv("DREAM_CUSTOMS_HOSTED_TOKEN", "").strip()),
61
+ }
scripts/local_space_mirror.py CHANGED
@@ -9,16 +9,17 @@ import os
9
  import sys
10
  from pathlib import Path
11
 
12
-
13
  ROOT = Path(__file__).resolve().parents[1]
14
- SPACE_ID = "build-small-hackathon/dream-customs"
15
- DEFAULT_RUNTIME_ENV_JSON = Path("/tmp/dream-customs-runtime.json")
16
- RUNTIME_ENV_KEYS = {
17
- "DREAM_CUSTOMS_TEXT_ENDPOINT",
18
- "DREAM_CUSTOMS_VISION_ENDPOINT",
19
- "DREAM_CUSTOMS_ASR_ENDPOINT",
20
- "DREAM_CUSTOMS_HOSTED_TOKEN",
21
- }
 
 
22
 
23
 
24
  def _repo_root_on_path() -> None:
@@ -27,37 +28,6 @@ def _repo_root_on_path() -> None:
27
  sys.path.insert(0, root)
28
 
29
 
30
- def _configured_env() -> dict:
31
- return {
32
- "space_id": os.getenv("SPACE_ID", SPACE_ID),
33
- "text_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_TEXT_ENDPOINT", "").strip()),
34
- "vision_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_VISION_ENDPOINT", "").strip()),
35
- "asr_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_ASR_ENDPOINT", "").strip()),
36
- "hosted_token_configured": bool(os.getenv("DREAM_CUSTOMS_HOSTED_TOKEN", "").strip()),
37
- }
38
-
39
-
40
- def load_runtime_env_json(path: Path) -> dict:
41
- if not path.exists():
42
- return {"loaded": False, "path": str(path), "reason": "missing"}
43
- try:
44
- data = json.loads(path.read_text(encoding="utf-8"))
45
- except (OSError, json.JSONDecodeError) as exc:
46
- return {"loaded": False, "path": str(path), "reason": exc.__class__.__name__}
47
-
48
- loaded_keys = []
49
- for key in sorted(RUNTIME_ENV_KEYS):
50
- value = str(data.get(key, "")).strip()
51
- if value:
52
- os.environ[key] = value
53
- loaded_keys.append(key)
54
- return {
55
- "loaded": bool(loaded_keys),
56
- "path": str(path),
57
- "configured_keys": loaded_keys,
58
- }
59
-
60
-
61
  def mirror_manifest(host: str, port: int) -> dict:
62
  _repo_root_on_path()
63
  from dream_customs.defaults import DEFAULT_ASR_BACKEND, DEFAULT_TEXT_BACKEND, DEFAULT_VISION_BACKEND
@@ -72,7 +42,7 @@ def mirror_manifest(host: str, port: int) -> dict:
72
  "vision": DEFAULT_VISION_BACKEND,
73
  "asr": DEFAULT_ASR_BACKEND,
74
  },
75
- "env": _configured_env(),
76
  "notes": [
77
  "Uses the same app.py/build_demo path as the Hugging Face Space.",
78
  "Missing hosted endpoint secrets fall back to deterministic demo behavior.",
 
9
  import sys
10
  from pathlib import Path
11
 
 
12
  ROOT = Path(__file__).resolve().parents[1]
13
+ root = str(ROOT)
14
+ if root not in sys.path:
15
+ sys.path.insert(0, root)
16
+
17
+ from dream_customs.runtime_env import (
18
+ DEFAULT_RUNTIME_ENV_JSON,
19
+ SPACE_ID,
20
+ configured_env,
21
+ load_runtime_env_json,
22
+ )
23
 
24
 
25
  def _repo_root_on_path() -> None:
 
28
  sys.path.insert(0, root)
29
 
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def mirror_manifest(host: str, port: int) -> dict:
32
  _repo_root_on_path()
33
  from dream_customs.defaults import DEFAULT_ASR_BACKEND, DEFAULT_TEXT_BACKEND, DEFAULT_VISION_BACKEND
 
42
  "vision": DEFAULT_VISION_BACKEND,
43
  "asr": DEFAULT_ASR_BACKEND,
44
  },
45
+ "env": configured_env(SPACE_ID),
46
  "notes": [
47
  "Uses the same app.py/build_demo path as the Hugging Face Space.",
48
  "Missing hosted endpoint secrets fall back to deterministic demo behavior.",
tests/test_local_space_mirror.py CHANGED
@@ -1,10 +1,20 @@
1
  import json
 
 
 
2
 
3
  from scripts.local_space_mirror import load_runtime_env_json, mirror_manifest
4
  from scripts.smoke_local_space_mirror import inspect_config
5
 
6
 
7
- def test_local_space_mirror_manifest_matches_space_entrypoint():
 
 
 
 
 
 
 
8
  manifest = mirror_manifest("127.0.0.1", 7862)
9
 
10
  assert manifest["mode"] == "local-space-mirror"
@@ -61,6 +71,58 @@ def test_local_space_mirror_loads_runtime_env_json_without_printing_values(tmp_p
61
  assert "super-secret-token" not in serialized
62
 
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  def test_local_space_mirror_config_smoke_requires_composer_debug_markers():
65
  config = {
66
  "title": "Dream QA",
 
1
  import json
2
+ import os
3
+ import subprocess
4
+ import sys
5
 
6
  from scripts.local_space_mirror import load_runtime_env_json, mirror_manifest
7
  from scripts.smoke_local_space_mirror import inspect_config
8
 
9
 
10
+ def test_local_space_mirror_manifest_matches_space_entrypoint(monkeypatch):
11
+ for key in [
12
+ "DREAM_CUSTOMS_TEXT_ENDPOINT",
13
+ "DREAM_CUSTOMS_VISION_ENDPOINT",
14
+ "DREAM_CUSTOMS_ASR_ENDPOINT",
15
+ "DREAM_CUSTOMS_HOSTED_TOKEN",
16
+ ]:
17
+ monkeypatch.delenv(key, raising=False)
18
  manifest = mirror_manifest("127.0.0.1", 7862)
19
 
20
  assert manifest["mode"] == "local-space-mirror"
 
71
  assert "super-secret-token" not in serialized
72
 
73
 
74
+ def test_app_import_auto_loads_local_runtime_env_json(tmp_path):
75
+ runtime_json = tmp_path / "runtime.json"
76
+ runtime_json.write_text(
77
+ json.dumps(
78
+ {
79
+ "DREAM_CUSTOMS_TEXT_ENDPOINT": "https://example.test/text",
80
+ "DREAM_CUSTOMS_VISION_ENDPOINT": "https://example.test/vision",
81
+ "DREAM_CUSTOMS_ASR_ENDPOINT": "https://example.test/asr",
82
+ "DREAM_CUSTOMS_HOSTED_TOKEN": "super-secret-token",
83
+ }
84
+ ),
85
+ encoding="utf-8",
86
+ )
87
+ env = os.environ.copy()
88
+ env.pop("PYTEST_CURRENT_TEST", None)
89
+ for key in [
90
+ "DREAM_CUSTOMS_TEXT_ENDPOINT",
91
+ "DREAM_CUSTOMS_VISION_ENDPOINT",
92
+ "DREAM_CUSTOMS_ASR_ENDPOINT",
93
+ "DREAM_CUSTOMS_HOSTED_TOKEN",
94
+ ]:
95
+ env.pop(key, None)
96
+ env["DREAM_CUSTOMS_RUNTIME_ENV_JSON"] = str(runtime_json)
97
+ result = subprocess.run(
98
+ [
99
+ sys.executable,
100
+ "-c",
101
+ (
102
+ "import json, os; import app; "
103
+ "print(json.dumps({key: bool(os.getenv(key)) for key in "
104
+ "['DREAM_CUSTOMS_TEXT_ENDPOINT','DREAM_CUSTOMS_VISION_ENDPOINT',"
105
+ "'DREAM_CUSTOMS_ASR_ENDPOINT','DREAM_CUSTOMS_HOSTED_TOKEN']}))"
106
+ ),
107
+ ],
108
+ check=True,
109
+ capture_output=True,
110
+ text=True,
111
+ env=env,
112
+ )
113
+
114
+ loaded = json.loads(result.stdout.strip())
115
+
116
+ assert loaded == {
117
+ "DREAM_CUSTOMS_TEXT_ENDPOINT": True,
118
+ "DREAM_CUSTOMS_VISION_ENDPOINT": True,
119
+ "DREAM_CUSTOMS_ASR_ENDPOINT": True,
120
+ "DREAM_CUSTOMS_HOSTED_TOKEN": True,
121
+ }
122
+ assert "https://example.test" not in result.stdout
123
+ assert "super-secret-token" not in result.stdout
124
+
125
+
126
  def test_local_space_mirror_config_smoke_requires_composer_debug_markers():
127
  config = {
128
  "title": "Dream QA",