Danialebrat commited on
Commit
d9a47f6
Β·
1 Parent(s): 93a0411

Adding app.py wrapper

Browse files
Files changed (1) hide show
  1. app.py +149 -0
app.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Root app.py – HuggingFace Spaces entry point.
3
+
4
+ Presents a landing page that lets users pick between the two dashboards,
5
+ then dynamically loads the chosen sub-app without touching its source code.
6
+
7
+ Run locally : streamlit run app.py
8
+ HF Spaces : set "app_file: app.py" in README.md front-matter (default)
9
+ """
10
+
11
+ import sys
12
+ import importlib.util
13
+ from pathlib import Path
14
+
15
+ import streamlit as st
16
+
17
+ # ── Paths ──────────────────────────────────────────────────────────────────────
18
+ ROOT = Path(__file__).resolve().parent
19
+
20
+ _SUB_APPS: dict[str, Path] = {
21
+ "sentiment": ROOT / "visualization",
22
+ "brand": ROOT / "visualization_brand_sentiment",
23
+ }
24
+
25
+ # Module-name prefixes that belong to the sub-apps and must be evicted from
26
+ # sys.modules when switching dashboards (both apps share identical package names:
27
+ # data, utils, components, agents, visualizations).
28
+ _SUB_APP_MODULE_PREFIXES = (
29
+ "data", "utils", "components", "agents", "visualizations", "_subapp",
30
+ )
31
+
32
+ # ── Page config (must be the very first Streamlit call) ────────────────────────
33
+ st.set_page_config(
34
+ page_title="Musora Analytics Suite",
35
+ page_icon="πŸ“Š",
36
+ layout="wide",
37
+ initial_sidebar_state="expanded",
38
+ )
39
+
40
+ # After we've set the page config, prevent sub-apps from overriding it.
41
+ # Both sub-apps call st.set_page_config() at module level; patching the
42
+ # function on the streamlit module object makes those calls no-ops.
43
+ import streamlit as _st_mod # same object as `st`
44
+ _st_mod.set_page_config = lambda *a, **kw: None
45
+
46
+
47
+ # ── Helpers ────────────────────────────────────────────────────────────────────
48
+
49
+ def _activate_app_path(app_key: str) -> None:
50
+ """
51
+ Swap out the active sub-app path in sys.path and clear any stale
52
+ module cache entries so both apps' identically-named packages resolve
53
+ to the correct source tree.
54
+ """
55
+ # Remove every sub-app directory that may already be on the path.
56
+ for path in _SUB_APPS.values():
57
+ s = str(path)
58
+ while s in sys.path:
59
+ sys.path.remove(s)
60
+
61
+ # Insert the target sub-app directory at position 0 (highest priority).
62
+ sys.path.insert(0, str(_SUB_APPS[app_key]))
63
+
64
+ # Evict cached modules that originated from any sub-app so that the
65
+ # next import resolves from the newly active path.
66
+ for mod_name in list(sys.modules.keys()):
67
+ if any(
68
+ mod_name == prefix or mod_name.startswith(prefix + ".")
69
+ for prefix in _SUB_APP_MODULE_PREFIXES
70
+ ):
71
+ del sys.modules[mod_name]
72
+
73
+
74
+ def _load_and_run(app_key: str) -> None:
75
+ """
76
+ Dynamically load the selected sub-app module and invoke its main().
77
+
78
+ Notes
79
+ -----
80
+ * exec_module() runs the module's top-level code (config loading,
81
+ auth check, CSS injection, etc.) exactly as Streamlit would.
82
+ * main() is called explicitly because the sub-apps guard it with
83
+ ``if __name__ == "__main__"``, which is False under importlib.
84
+ * StopException (raised by st.stop() inside the auth gate) is
85
+ re-raised so Streamlit's runner can handle it correctly.
86
+ """
87
+ _activate_app_path(app_key)
88
+
89
+ app_path = _SUB_APPS[app_key] / "app.py"
90
+ spec = importlib.util.spec_from_file_location("_subapp", app_path)
91
+ mod = importlib.util.module_from_spec(spec)
92
+
93
+ try:
94
+ spec.loader.exec_module(mod) # runs module-level code (auth, CSS …)
95
+ if hasattr(mod, "main"):
96
+ mod.main() # renders the full dashboard
97
+ except Exception as exc: # noqa: BLE001
98
+ # Re-raise Streamlit's internal StopException (triggered by st.stop())
99
+ # so the runner knows to halt rendering cleanly.
100
+ if "Stop" in type(exc).__name__:
101
+ raise
102
+ st.error(f"Dashboard error: {exc}")
103
+ st.exception(exc)
104
+
105
+
106
+ # ── Routing ────────────────────────────────────────────────────────────────────
107
+
108
+ choice: str | None = st.session_state.get("app_choice")
109
+
110
+ if choice is None:
111
+ # ── Landing page ───────────────────────────────────────────────────────────
112
+ st.title("πŸ“Š Musora Analytics Suite")
113
+ st.markdown("Select a dashboard to get started.")
114
+ st.markdown("---")
115
+
116
+ col1, col2 = st.columns(2, gap="large")
117
+
118
+ with col1:
119
+ st.subheader("Musora Sentiment Dashboard")
120
+ st.markdown(
121
+ "Analyse social-media comment sentiment across YouTube, "
122
+ "Facebook, and other platforms. Includes reply-required "
123
+ "triage and global date/platform filters."
124
+ )
125
+ if st.button("Open β†’", key="btn_sentiment", use_container_width=True):
126
+ st.session_state.app_choice = "sentiment"
127
+ st.rerun()
128
+
129
+ with col2:
130
+ st.subheader("Sabian Brand Sentiment")
131
+ st.markdown(
132
+ "Track brand sentiment for Sabian products across Musora "
133
+ "forums and YouTube. Covers competitor mentions, purchase "
134
+ "intent, and demographic breakdowns."
135
+ )
136
+ if st.button("Open β†’", key="btn_brand", use_container_width=True):
137
+ st.session_state.app_choice = "brand"
138
+ st.rerun()
139
+
140
+ else:
141
+ # ── Sub-app: inject a "back" button at the top of the sidebar ─────────────
142
+ with st.sidebar:
143
+ if st.button("← Home", key="back_home", use_container_width=True):
144
+ st.session_state.app_choice = None
145
+ st.rerun()
146
+ st.markdown("---")
147
+
148
+ # ── Load and render the chosen dashboard ───────────────────────────────────
149
+ _load_and_run(choice)