abhisheksan commited on
Commit
0873402
·
verified ·
1 Parent(s): 723dc31

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -100
app.py CHANGED
@@ -2,11 +2,12 @@ import asyncio
2
  import os
3
  from datetime import datetime
4
  from typing import Dict, List, Optional
5
- from fastapi.responses import JSONResponse, HTMLResponse
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
  from analysis_service import AnalysisService
12
  from twitter_service import RssTwitterService
@@ -20,9 +21,6 @@ load_dotenv()
20
  os.makedirs("logs", exist_ok=True)
21
  logger.add("logs/app.log", rotation="500 MB", level=os.getenv("LOG_LEVEL", "INFO"))
22
 
23
- # Global readiness flag
24
- app_ready = False
25
-
26
  # Create FastAPI application
27
  app = FastAPI(
28
  title="WesternFront API",
@@ -46,10 +44,14 @@ analysis_service = AnalysisService()
46
  # In-memory store for latest analysis
47
  latest_analysis: Optional[ConflictAnalysis] = None
48
  last_update_time: Optional[datetime] = None
49
- background_task = None # Store task reference to cancel if needed
50
 
51
- # Feature flags
52
- AUTO_UPDATE_ENABLED = os.getenv("AUTO_UPDATE_ENABLED", "false").lower() in ("true", "1", "t")
 
 
 
 
 
53
 
54
 
55
  async def get_twitter_service() -> RssTwitterService:
@@ -65,51 +67,45 @@ async def get_analysis_service() -> AnalysisService:
65
  @app.on_event("startup")
66
  async def startup_event():
67
  """Initialize services on startup."""
68
-
69
  logger.info("Starting up WesternFront API")
70
 
71
- try:
72
- # Initialize Twitter service
73
- twitter_initialized = await twitter_service.initialize()
74
- if not twitter_initialized:
75
- logger.warning("Twitter service initialization failed. Some features may not work.")
76
-
77
- # Initialize Gemini AI service
78
- analysis_service.initialize()
79
-
80
- # Set analysis service's twitter service reference
81
- analysis_service.twitter_service = twitter_service
82
-
83
- logger.info("Application ready to accept requests")
84
-
85
- # Only start background tasks if auto-update is enabled
86
- if AUTO_UPDATE_ENABLED:
87
- logger.info("Auto-update enabled. Starting background tasks.")
88
- # Use scheduled_update_task to avoid continuous execution
89
- asyncio.create_task(scheduled_update_task())
90
- else:
91
- logger.info("Auto-update disabled. Background analysis will not run automatically.")
92
-
93
- except Exception as e:
94
- logger.error(f"Error during startup: {e}")
 
 
 
 
 
 
 
95
 
96
 
97
  @app.on_event("shutdown")
98
  async def shutdown_event():
99
  """Clean up resources on shutdown."""
100
- global background_task
101
-
102
  logger.info("Shutting down WesternFront API")
103
-
104
- # Cancel any running background tasks
105
- if background_task and not background_task.done():
106
- logger.info("Cancelling running background task")
107
- background_task.cancel()
108
- try:
109
- await background_task
110
- except asyncio.CancelledError:
111
- pass
112
-
113
  if twitter_service and hasattr(twitter_service, 'close'):
114
  await twitter_service.close()
115
  if analysis_service and hasattr(analysis_service, 'close'):
@@ -147,68 +143,29 @@ async def update_analysis_task(trigger: str = "scheduled") -> None:
147
  logger.error(f"Error in update_analysis_task: {str(e)}")
148
 
149
 
150
- async def scheduled_update_task() -> None:
151
- """Run a single scheduled update then exit."""
152
- global background_task
153
 
154
- try:
155
- # Wait a few seconds after startup before first update
156
- await asyncio.sleep(10)
157
- background_task = asyncio.create_task(update_analysis_task("startup"))
158
- await background_task
159
- except Exception as e:
160
- logger.error(f"Error in scheduled_update_task: {str(e)}")
161
 
162
- app_ready = True # Force this to True for Hugging Face Spaces
163
 
164
- # Update the root endpoint to be Hugging Face Spaces friendly
165
- @app.get("/", response_class=HTMLResponse)
166
- async def root():
167
- """Root endpoint designed for Hugging Face Spaces."""
168
- html_content = """
169
- <!DOCTYPE html>
170
- <html>
171
- <head>
172
- <title>WesternFront API</title>
173
- <meta property="og:title" content="WesternFront API">
174
- <meta property="og:description" content="AI-powered conflict tracker for India-Pakistan tensions">
175
- </head>
176
- <body>
177
- <h1>WesternFront API</h1>
178
- <p>API is running successfully! Access the API documentation at <a href="/docs">/docs</a></p>
179
- <p>Version: 1.1.0</p>
180
- <p>Status: Ready</p>
181
- </body>
182
- </html>
183
- """
184
- return HTMLResponse(content=html_content)
185
-
186
- # Add a Hugging Face Spaces specific health check endpoint
187
- @app.get("/health-check")
188
- async def hf_health_check():
189
- """Health check endpoint specifically for Hugging Face Spaces."""
190
- return JSONResponse(status_code=200, content={"status": "healthy"})
191
-
192
  @app.get("/", response_model=Dict)
193
  async def root():
194
  """Root endpoint with basic information about the API."""
195
  return {
196
  "name": "WesternFront API",
197
  "description": "AI-powered conflict tracker for India-Pakistan tensions",
198
- "version": "1.1.0",
199
- "status": "ready" if app_ready else "initializing",
200
- "auto_update": AUTO_UPDATE_ENABLED
201
  }
202
 
203
 
204
- @app.get("/ready")
205
- async def readiness_check():
206
- """Readiness check endpoint for probes and monitoring."""
207
- if not app_ready:
208
- raise HTTPException(status_code=503, detail="Application is starting up")
209
- return {"status": "ready", "timestamp": datetime.now().isoformat()}
210
-
211
-
212
  @app.get("/health", response_model=HealthCheck)
213
  async def health_check():
214
  """Health check endpoint."""
@@ -216,14 +173,13 @@ async def health_check():
216
  gemini_initialized = analysis_service.model is not None
217
 
218
  return HealthCheck(
219
- status="healthy" if app_ready else "initializing",
220
  version="1.1.0",
221
  timestamp=datetime.now(),
222
  last_update=last_update_time,
223
  components_status={
224
  "twitter_service": twitter_initialized,
225
- "analysis_service": gemini_initialized,
226
- "auto_update": AUTO_UPDATE_ENABLED
227
  }
228
  )
229
 
@@ -241,20 +197,20 @@ async def get_latest_analysis():
241
 
242
  @app.post("/analysis/update", response_model=Dict)
243
  async def trigger_update(
244
- request: UpdateRequest,
245
  background_tasks: BackgroundTasks
246
  ):
247
  """Trigger an analysis update."""
248
  if request.force:
249
  # Clear cache to force fresh tweets
250
  twitter_service.in_memory_cache.clear()
251
-
252
  # Add update task to background tasks
253
  background_tasks.add_task(update_analysis_task, "manual")
254
 
255
  return {
256
  "message": "Analysis update triggered",
257
- "timestamp": datetime.now().isoformat(),
258
  "force_refresh": request.force
259
  }
260
 
@@ -307,6 +263,32 @@ async def get_tension_levels():
307
  return [level.value for level in TensionLevel]
308
 
309
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  @app.get("/rss-feeds", response_model=Dict[str, str])
311
  async def get_registered_rss_feeds(
312
  twitter: RssTwitterService = Depends(get_twitter_service)
 
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
+ from pydantic import BaseModel
11
 
12
  from analysis_service import AnalysisService
13
  from twitter_service import RssTwitterService
 
21
  os.makedirs("logs", exist_ok=True)
22
  logger.add("logs/app.log", rotation="500 MB", level=os.getenv("LOG_LEVEL", "INFO"))
23
 
 
 
 
24
  # Create FastAPI application
25
  app = FastAPI(
26
  title="WesternFront API",
 
44
  # In-memory store for latest analysis
45
  latest_analysis: Optional[ConflictAnalysis] = None
46
  last_update_time: Optional[datetime] = None
 
47
 
48
+ # Define model for RSS feed registration
49
+ class RssFeedRegistration(BaseModel):
50
+ twitter_handle: str
51
+ rss_url: str
52
+
53
+ class RssFeedBatch(BaseModel):
54
+ feeds: Dict[str, str]
55
 
56
 
57
  async def get_twitter_service() -> RssTwitterService:
 
67
  @app.on_event("startup")
68
  async def startup_event():
69
  """Initialize services on startup."""
 
70
  logger.info("Starting up WesternFront API")
71
 
72
+ # Initialize services
73
+ twitter_initialized = await twitter_service.initialize()
74
+ if not twitter_initialized:
75
+ logger.warning("Twitter service initialization failed. Some features may not work.")
76
+
77
+ # Set analysis service's twitter service reference
78
+ analysis_service.twitter_service = twitter_service
79
+
80
+ # Initialize Gemini AI service
81
+ gemini_initialized = analysis_service.initialize()
82
+ if not gemini_initialized:
83
+ logger.warning("Gemini AI initialization failed. Analysis features may not work.")
84
+
85
+ # Load RSS feeds from environment variable if available
86
+ rss_feeds_env = os.getenv("RSS_FEEDS")
87
+ if rss_feeds_env:
88
+ try:
89
+ import json
90
+ feeds = json.loads(rss_feeds_env)
91
+ if isinstance(feeds, dict):
92
+ twitter_service.register_rss_feed_batch(feeds)
93
+ logger.info(f"Loaded {len(feeds)} RSS feeds from environment variables")
94
+ except Exception as e:
95
+ logger.error(f"Failed to load RSS feeds from environment variables: {str(e)}")
96
+
97
+ # Schedule first update
98
+ background_tasks = BackgroundTasks()
99
+ background_tasks.add_task(update_analysis_task)
100
+
101
+ # Set up periodic update task
102
+ asyncio.create_task(periodic_update())
103
 
104
 
105
  @app.on_event("shutdown")
106
  async def shutdown_event():
107
  """Clean up resources on shutdown."""
 
 
108
  logger.info("Shutting down WesternFront API")
 
 
 
 
 
 
 
 
 
 
109
  if twitter_service and hasattr(twitter_service, 'close'):
110
  await twitter_service.close()
111
  if analysis_service and hasattr(analysis_service, 'close'):
 
143
  logger.error(f"Error in update_analysis_task: {str(e)}")
144
 
145
 
146
+ async def periodic_update() -> None:
147
+ """Periodically update the analysis."""
148
+ update_interval = int(os.getenv("UPDATE_INTERVAL_MINUTES", 60))
149
 
150
+ while True:
151
+ try:
152
+ await asyncio.sleep(update_interval * 60) # Convert to seconds
153
+ await update_analysis_task("scheduled")
154
+ except Exception as e:
155
+ logger.error(f"Error in periodic_update: {str(e)}")
156
+ await asyncio.sleep(300) # Wait 5 minutes if there was an error
157
 
 
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  @app.get("/", response_model=Dict)
160
  async def root():
161
  """Root endpoint with basic information about the API."""
162
  return {
163
  "name": "WesternFront API",
164
  "description": "AI-powered conflict tracker for India-Pakistan tensions",
165
+ "version": "1.1.0"
 
 
166
  }
167
 
168
 
 
 
 
 
 
 
 
 
169
  @app.get("/health", response_model=HealthCheck)
170
  async def health_check():
171
  """Health check endpoint."""
 
173
  gemini_initialized = analysis_service.model is not None
174
 
175
  return HealthCheck(
176
+ status="healthy",
177
  version="1.1.0",
178
  timestamp=datetime.now(),
179
  last_update=last_update_time,
180
  components_status={
181
  "twitter_service": twitter_initialized,
182
+ "analysis_service": gemini_initialized
 
183
  }
184
  )
185
 
 
197
 
198
  @app.post("/analysis/update", response_model=Dict)
199
  async def trigger_update(
200
+ request: UpdateRequest,
201
  background_tasks: BackgroundTasks
202
  ):
203
  """Trigger an analysis update."""
204
  if request.force:
205
  # Clear cache to force fresh tweets
206
  twitter_service.in_memory_cache.clear()
207
+
208
  # Add update task to background tasks
209
  background_tasks.add_task(update_analysis_task, "manual")
210
 
211
  return {
212
  "message": "Analysis update triggered",
213
+ "timestamp": datetime.now(),
214
  "force_refresh": request.force
215
  }
216
 
 
263
  return [level.value for level in TensionLevel]
264
 
265
 
266
+ @app.post("/rss-feed", response_model=Dict)
267
+ async def register_rss_feed(
268
+ feed: RssFeedRegistration,
269
+ twitter: RssTwitterService = Depends(get_twitter_service)
270
+ ):
271
+ """Register an RSS feed for a Twitter handle."""
272
+ twitter.register_rss_feed(feed.twitter_handle, feed.rss_url)
273
+ return {
274
+ "message": f"RSS feed registered for {feed.twitter_handle}",
275
+ "twitter_handle": feed.twitter_handle
276
+ }
277
+
278
+
279
+ @app.post("/rss-feeds", response_model=Dict)
280
+ async def register_rss_feeds(
281
+ feeds: RssFeedBatch,
282
+ twitter: RssTwitterService = Depends(get_twitter_service)
283
+ ):
284
+ """Register multiple RSS feeds at once."""
285
+ twitter.register_rss_feed_batch(feeds.feeds)
286
+ return {
287
+ "message": "RSS feeds registered",
288
+ "count": len(feeds.feeds)
289
+ }
290
+
291
+
292
  @app.get("/rss-feeds", response_model=Dict[str, str])
293
  async def get_registered_rss_feeds(
294
  twitter: RssTwitterService = Depends(get_twitter_service)