abhisheksan commited on
Commit
01aa5c0
·
verified ·
1 Parent(s): 4852779

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +247 -247
app.py CHANGED
@@ -1,248 +1,248 @@
1
- import asyncio
2
- import os
3
- from datetime import datetime
4
- from typing import Dict, List, Optional
5
-
6
- from dotenv import load_dotenv
7
- from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, status
8
- from fastapi.middleware.cors import CORSMiddleware
9
- from loguru import logger
10
-
11
- # --- UPDATED IMPORTS ---
12
- from analysis_service import AnalysisService
13
- # No longer need RssTwitterService
14
- from models import (ConflictAnalysis, HealthCheck, SubredditSource, TensionLevel,
15
- UpdateRequest)
16
-
17
- # Load environment variables from .env file
18
- load_dotenv()
19
-
20
- # Configure logging
21
- os.makedirs("logs", exist_ok=True)
22
- logger.add("logs/app.log", rotation="500 MB", level=os.getenv("LOG_LEVEL", "INFO"))
23
-
24
- # Global readiness flag
25
- app_ready = False
26
-
27
- # Create FastAPI application
28
- app = FastAPI(
29
- title="WesternFront API",
30
- description="AI-powered conflict tracker for monitoring India-Pakistan tensions using Reddit data",
31
- version="2.0.0" # Version bump for new data source
32
- )
33
-
34
- # Add CORS middleware
35
- app.add_middleware(
36
- CORSMiddleware,
37
- allow_origins=["*"], # Adjust this for production
38
- allow_credentials=True,
39
- allow_methods=["*"],
40
- allow_headers=["*"],
41
- )
42
-
43
- # --- UPDATED: Services ---
44
- # The AnalysisService now manages the RedditService internally
45
- analysis_service = AnalysisService()
46
-
47
- # In-memory store for latest analysis
48
- latest_analysis: Optional[ConflictAnalysis] = None
49
- last_update_time: Optional[datetime] = None
50
-
51
-
52
- async def get_analysis_service() -> AnalysisService:
53
- """Dependency to get the Analysis service."""
54
- return analysis_service
55
-
56
-
57
- @app.on_event("startup")
58
- async def startup_event():
59
- """Initialize services on startup."""
60
- global app_ready
61
-
62
- logger.info("Starting up WesternFront API v2.0")
63
-
64
- try:
65
- # Initialize Gemini AI and the internal Reddit service
66
- analysis_service.initialize_gemini()
67
- analysis_service.reddit_service.initialize()
68
-
69
- # Schedule first update in background
70
- asyncio.create_task(update_analysis_task("startup"))
71
-
72
- # Set up periodic update task
73
- asyncio.create_task(periodic_update())
74
-
75
- # Mark application as ready to accept requests
76
- app_ready = True
77
- logger.info("Application ready to accept requests")
78
-
79
- except Exception as e:
80
- logger.error(f"Error during startup: {e}")
81
- app_ready = False
82
-
83
-
84
- @app.on_event("shutdown")
85
- async def shutdown_event():
86
- """Clean up resources on shutdown."""
87
- logger.info("Shutting down WesternFront API")
88
- if analysis_service and hasattr(analysis_service, 'close'):
89
- await analysis_service.close()
90
-
91
-
92
- async def update_analysis_task(trigger: str = "scheduled") -> None:
93
- """Task to update the conflict analysis using the AnalysisService."""
94
- global latest_analysis, last_update_time
95
-
96
- try:
97
- logger.info(f"Starting analysis update (trigger: {trigger})")
98
-
99
- # --- REFACTORED: The analysis_service now handles everything ---
100
- analysis = await analysis_service.generate_conflict_analysis(trigger=trigger)
101
-
102
- if analysis:
103
- latest_analysis = analysis
104
- last_update_time = datetime.now()
105
- logger.info(f"Analysis updated successfully. Tension level: {analysis.tension_level}")
106
- else:
107
- logger.warning("Failed to generate new analysis. No relevant data might be available.")
108
-
109
- except Exception as e:
110
- logger.error(f"Error in update_analysis_task: {str(e)}")
111
-
112
-
113
- async def periodic_update() -> None:
114
- """Periodically update the analysis."""
115
- update_interval = int(os.getenv("UPDATE_INTERVAL_MINUTES", 60))
116
-
117
- while True:
118
- try:
119
- await asyncio.sleep(update_interval * 60)
120
- await update_analysis_task("scheduled")
121
- except Exception as e:
122
- logger.error(f"Error in periodic_update: {str(e)}")
123
- await asyncio.sleep(300) # Wait 5 minutes if there was an error
124
-
125
-
126
- @app.get("/", response_model=Dict)
127
- async def root():
128
- """Root endpoint with basic information about the API."""
129
- return {
130
- "name": "WesternFront API",
131
- "description": "AI-powered conflict tracker for India-Pakistan tensions using Reddit data",
132
- "version": "2.0.0",
133
- "status": "ready" if app_ready else "initializing"
134
- }
135
-
136
-
137
- @app.get("/ready")
138
- async def readiness_check():
139
- """Readiness check endpoint."""
140
- if not app_ready:
141
- raise HTTPException(status_code=503, detail="Application is starting up")
142
- return {"status": "ready", "timestamp": datetime.now().isoformat()}
143
-
144
-
145
- @app.get("/health", response_model=HealthCheck)
146
- async def health_check():
147
- """Health check endpoint."""
148
- # --- UPDATED: Check Reddit service instead of Twitter ---
149
- reddit_initialized = analysis_service.reddit_service.reddit is not None
150
- gemini_initialized = analysis_service.model is not None
151
-
152
- return HealthCheck(
153
- status="healthy" if app_ready else "initializing",
154
- version="2.0.0",
155
- timestamp=datetime.now(),
156
- last_update=last_update_time,
157
- components_status={
158
- "reddit_service": reddit_initialized,
159
- "analysis_service": gemini_initialized
160
- }
161
- )
162
-
163
- # The HEAD /health endpoint is a bit redundant with FastAPI, so it can be removed for simplicity
164
- # unless you have a specific use case for it.
165
-
166
- @app.get("/analysis", response_model=Optional[ConflictAnalysis])
167
- async def get_latest_analysis():
168
- """Get the latest conflict analysis."""
169
- if not latest_analysis:
170
- raise HTTPException(
171
- status_code=status.HTTP_404_NOT_FOUND,
172
- detail="No analysis available yet. Try triggering an update."
173
- )
174
- return latest_analysis
175
-
176
-
177
- @app.post("/analysis/update", response_model=Dict)
178
- async def trigger_update(request: UpdateRequest):
179
- """Trigger an analysis update."""
180
- if request.force:
181
- # --- UPDATED: Clear Reddit service cache ---
182
- analysis_service.reddit_service.in_memory_cache.clear()
183
- logger.info("Cache cleared for forced refresh.")
184
-
185
- # Start update in background
186
- asyncio.create_task(update_analysis_task("manual"))
187
-
188
- return {
189
- "message": "Analysis update triggered",
190
- "timestamp": datetime.now().isoformat(),
191
- "force_refresh": request.force
192
- }
193
-
194
-
195
- # --- UPDATED: Now manages subreddit sources ---
196
- @app.get("/sources", response_model=List[SubredditSource])
197
- async def get_subreddit_sources(
198
- analysis: AnalysisService = Depends(get_analysis_service)
199
- ):
200
- """Get the current list of subreddit sources."""
201
- return analysis.get_sources()
202
-
203
-
204
- # --- UPDATED: Now manages subreddit sources ---
205
- @app.post("/sources", response_model=Dict)
206
- async def update_subreddit_sources(
207
- sources: List[SubredditSource],
208
- analysis: AnalysisService = Depends(get_analysis_service)
209
- ):
210
- """Update the list of subreddit sources."""
211
- analysis.update_sources(sources)
212
- return {
213
- "message": "Subreddit sources updated",
214
- "count": len(sources)
215
- }
216
-
217
-
218
- @app.get("/keywords", response_model=List[str])
219
- async def get_search_keywords(
220
- analysis: AnalysisService = Depends(get_analysis_service)
221
- ):
222
- """Get the current search keywords."""
223
- return analysis.get_search_keywords()
224
-
225
-
226
- @app.post("/keywords", response_model=Dict)
227
- async def update_search_keywords(
228
- keywords: List[str],
229
- analysis: AnalysisService = Depends(get_analysis_service)
230
- ):
231
- """Update the search keywords."""
232
- analysis.update_search_keywords(keywords)
233
- return {
234
- "message": "Search keywords updated",
235
- "count": len(keywords)
236
- }
237
-
238
-
239
- @app.get("/tension-levels", response_model=List[str])
240
- async def get_tension_levels():
241
- """Get the available tension levels."""
242
- return [level.value for level in TensionLevel]
243
-
244
- # --- REMOVED: /rss-feeds endpoint is no longer applicable ---
245
-
246
- if __name__ == "__main__":
247
- import uvicorn
248
  uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
 
1
+ import asyncio
2
+ import os
3
+ from datetime import datetime
4
+ from typing import Dict, List, Optional
5
+
6
+ from dotenv import load_dotenv
7
+ from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, status
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from loguru import logger
10
+
11
+ # --- UPDATED IMPORTS ---
12
+ from analysis_service import AnalysisService
13
+ # No longer need RssTwitterService
14
+ from models import (ConflictAnalysis, HealthCheck, SubredditSource, TensionLevel,
15
+ UpdateRequest)
16
+
17
+ # Load environment variables from .env file
18
+ load_dotenv()
19
+
20
+ # Configure logging
21
+ os.makedirs("logs", exist_ok=True)
22
+ logger.add("logs/app.log", rotation="500 MB", level=os.getenv("LOG_LEVEL", "INFO"))
23
+
24
+ # Global readiness flag
25
+ app_ready = False
26
+
27
+ # Create FastAPI application
28
+ app = FastAPI(
29
+ title="WesternFront API",
30
+ description="AI-powered conflict tracker for monitoring India-Pakistan tensions using Reddit data",
31
+ version="2.0.0" # Version bump for new data source
32
+ )
33
+
34
+ # Add CORS middleware
35
+ app.add_middleware(
36
+ CORSMiddleware,
37
+ allow_origins=["*"], # Adjust this for production
38
+ allow_credentials=True,
39
+ allow_methods=["*"],
40
+ allow_headers=["*"],
41
+ )
42
+
43
+ # --- UPDATED: Services ---
44
+ # The AnalysisService now manages the RedditService internally
45
+ analysis_service = AnalysisService()
46
+
47
+ # In-memory store for latest analysis
48
+ latest_analysis: Optional[ConflictAnalysis] = None
49
+ last_update_time: Optional[datetime] = None
50
+
51
+
52
+ async def get_analysis_service() -> AnalysisService:
53
+ """Dependency to get the Analysis service."""
54
+ return analysis_service
55
+
56
+
57
+ @app.on_event("startup")
58
+ async def startup_event():
59
+ """Initialize services on startup."""
60
+ global app_ready
61
+
62
+ logger.info("Starting up WesternFront API v2.0")
63
+
64
+ try:
65
+ # Initialize Gemini AI and the internal Reddit service
66
+ analysis_service.initialize_gemini()
67
+ analysis_service.reddit_service.initialize()
68
+
69
+ # Schedule first update in background
70
+ asyncio.create_task(update_analysis_task("startup"))
71
+
72
+ # Set up periodic update task
73
+ asyncio.create_task(periodic_update())
74
+
75
+ # Mark application as ready to accept requests
76
+ app_ready = True
77
+ logger.info("Application ready to accept requests")
78
+
79
+ except Exception as e:
80
+ logger.error(f"Error during startup: {e}")
81
+ app_ready = False
82
+
83
+
84
+ @app.on_event("shutdown")
85
+ async def shutdown_event():
86
+ """Clean up resources on shutdown."""
87
+ logger.info("Shutting down WesternFront API")
88
+ if analysis_service and hasattr(analysis_service, 'close'):
89
+ await analysis_service.close()
90
+
91
+
92
+ async def update_analysis_task(trigger: str = "scheduled") -> None:
93
+ """Task to update the conflict analysis using the AnalysisService."""
94
+ global latest_analysis, last_update_time
95
+
96
+ try:
97
+ logger.info(f"Starting analysis update (trigger: {trigger})")
98
+
99
+ # --- REFACTORED: The analysis_service now handles everything ---
100
+ analysis = await analysis_service.generate_conflict_analysis(trigger=trigger)
101
+
102
+ if analysis:
103
+ latest_analysis = analysis
104
+ last_update_time = datetime.now()
105
+ logger.info(f"Analysis updated successfully. Tension level: {analysis.tension_level}")
106
+ else:
107
+ logger.warning("Failed to generate new analysis. No relevant data might be available.")
108
+
109
+ except Exception as e:
110
+ logger.error(f"Error in update_analysis_task: {str(e)}")
111
+
112
+
113
+ async def periodic_update() -> None:
114
+ """Periodically update the analysis."""
115
+ update_interval = int(os.getenv("UPDATE_INTERVAL_MINUTES", 60))
116
+
117
+ while True:
118
+ try:
119
+ await asyncio.sleep(update_interval * 60)
120
+ await update_analysis_task("scheduled")
121
+ except Exception as e:
122
+ logger.error(f"Error in periodic_update: {str(e)}")
123
+ await asyncio.sleep(300) # Wait 5 minutes if there was an error
124
+
125
+
126
+ @app.get("/", response_model=Dict)
127
+ async def root():
128
+ """Root endpoint with basic information about the API."""
129
+ return {
130
+ "name": "WesternFront API",
131
+ "description": "AI-powered conflict tracker for India-Pakistan tensions using Reddit data",
132
+ "version": "2.0.0",
133
+ "status": "ready" if app_ready else "initializing"
134
+ }
135
+
136
+
137
+ @app.get("/ready")
138
+ async def readiness_check():
139
+ """Readiness check endpoint."""
140
+ if not app_ready:
141
+ raise HTTPException(status_code=503, detail="Application is starting up")
142
+ return {"status": "ready", "timestamp": datetime.now().isoformat()}
143
+
144
+ @app.head("/health", response_model=HealthCheck)
145
+ @app.get("/health", response_model=HealthCheck)
146
+ async def health_check():
147
+ """Health check endpoint."""
148
+ # --- UPDATED: Check Reddit service instead of Twitter ---
149
+ reddit_initialized = analysis_service.reddit_service.reddit is not None
150
+ gemini_initialized = analysis_service.model is not None
151
+
152
+ return HealthCheck(
153
+ status="healthy" if app_ready else "initializing",
154
+ version="2.0.0",
155
+ timestamp=datetime.now(),
156
+ last_update=last_update_time,
157
+ components_status={
158
+ "reddit_service": reddit_initialized,
159
+ "analysis_service": gemini_initialized
160
+ }
161
+ )
162
+
163
+ # The HEAD /health endpoint is a bit redundant with FastAPI, so it can be removed for simplicity
164
+ # unless you have a specific use case for it.
165
+
166
+ @app.get("/analysis", response_model=Optional[ConflictAnalysis])
167
+ async def get_latest_analysis():
168
+ """Get the latest conflict analysis."""
169
+ if not latest_analysis:
170
+ raise HTTPException(
171
+ status_code=status.HTTP_404_NOT_FOUND,
172
+ detail="No analysis available yet. Try triggering an update."
173
+ )
174
+ return latest_analysis
175
+
176
+
177
+ @app.post("/analysis/update", response_model=Dict)
178
+ async def trigger_update(request: UpdateRequest):
179
+ """Trigger an analysis update."""
180
+ if request.force:
181
+ # --- UPDATED: Clear Reddit service cache ---
182
+ analysis_service.reddit_service.in_memory_cache.clear()
183
+ logger.info("Cache cleared for forced refresh.")
184
+
185
+ # Start update in background
186
+ asyncio.create_task(update_analysis_task("manual"))
187
+
188
+ return {
189
+ "message": "Analysis update triggered",
190
+ "timestamp": datetime.now().isoformat(),
191
+ "force_refresh": request.force
192
+ }
193
+
194
+
195
+ # --- UPDATED: Now manages subreddit sources ---
196
+ @app.get("/sources", response_model=List[SubredditSource])
197
+ async def get_subreddit_sources(
198
+ analysis: AnalysisService = Depends(get_analysis_service)
199
+ ):
200
+ """Get the current list of subreddit sources."""
201
+ return analysis.get_sources()
202
+
203
+
204
+ # --- UPDATED: Now manages subreddit sources ---
205
+ @app.post("/sources", response_model=Dict)
206
+ async def update_subreddit_sources(
207
+ sources: List[SubredditSource],
208
+ analysis: AnalysisService = Depends(get_analysis_service)
209
+ ):
210
+ """Update the list of subreddit sources."""
211
+ analysis.update_sources(sources)
212
+ return {
213
+ "message": "Subreddit sources updated",
214
+ "count": len(sources)
215
+ }
216
+
217
+
218
+ @app.get("/keywords", response_model=List[str])
219
+ async def get_search_keywords(
220
+ analysis: AnalysisService = Depends(get_analysis_service)
221
+ ):
222
+ """Get the current search keywords."""
223
+ return analysis.get_search_keywords()
224
+
225
+
226
+ @app.post("/keywords", response_model=Dict)
227
+ async def update_search_keywords(
228
+ keywords: List[str],
229
+ analysis: AnalysisService = Depends(get_analysis_service)
230
+ ):
231
+ """Update the search keywords."""
232
+ analysis.update_search_keywords(keywords)
233
+ return {
234
+ "message": "Search keywords updated",
235
+ "count": len(keywords)
236
+ }
237
+
238
+
239
+ @app.get("/tension-levels", response_model=List[str])
240
+ async def get_tension_levels():
241
+ """Get the available tension levels."""
242
+ return [level.value for level in TensionLevel]
243
+
244
+ # --- REMOVED: /rss-feeds endpoint is no longer applicable ---
245
+
246
+ if __name__ == "__main__":
247
+ import uvicorn
248
  uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)