""" CryptoSentinel Pro — High-performance FastAPI application. This is the main entry point that orchestrates the entire application. - Integrates an asynchronous PriceFetcher for live market data. - Integrates a sophisticated GeminiAnalyzer for deep text analysis. - Implements an automated pipeline to fetch, analyze, and stream top crypto news. - Serves the interactive frontend and provides all necessary API endpoints. """ import asyncio import json import os from contextlib import asynccontextmanager from typing import Optional, Union import httpx # =================== FIX APPLIED HERE (1 of 2) =================== from fastapi import FastAPI, Request, BackgroundTasks, Form # ================================================================= from fastapi.responses import HTMLResponse, StreamingResponse from fastapi.templating import Jinja2Templates from pydantic import BaseModel, constr # Import our modular, asynchronous service classes from .price_fetcher import PriceFetcher from .gemini_analyzer import GeminiAnalyzer from newsapi import NewsApiClient # --- Application Lifespan for Resource Management --- @asynccontextmanager async def lifespan(app: FastAPI): """ Manages application startup and shutdown events using the modern lifespan context manager. """ async with httpx.AsyncClient() as client: # Instantiate and store all services in the application state. app.state.price_fetcher = PriceFetcher(client=client, coins=["bitcoin", "ethereum", "dogecoin"]) app.state.gemini_analyzer = GeminiAnalyzer(client=client) app.state.news_api = NewsApiClient(api_key=os.getenv("NEWS_API_KEY")) # Create separate queues for the two real-time feeds app.state.sentiment_queue: asyncio.Queue = asyncio.Queue() app.state.news_queue: asyncio.Queue = asyncio.Queue() # Create cancellable background tasks for periodic updates. price_task = asyncio.create_task( run_periodic_updates(app.state.price_fetcher, interval_seconds=30) ) news_task = asyncio.create_task( run_periodic_news_analysis(app, interval_seconds=900) # Run every 15 minutes ) print("🚀 CryptoSentinel Pro started successfully.") yield print("⏳ Shutting down background tasks...") price_task.cancel() news_task.cancel() try: await asyncio.gather(price_task, news_task) except asyncio.CancelledError: print("Background tasks cancelled successfully.") print("✅ Shutdown complete.") async def run_periodic_updates(fetcher: PriceFetcher, interval_seconds: int): """A robust asyncio background task that periodically updates prices.""" while True: await fetcher.update_prices_async() await asyncio.sleep(interval_seconds) async def run_periodic_news_analysis(app: FastAPI, interval_seconds: int): """Fetches, analyzes, and queues top crypto news periodically.""" while True: print("📰 Fetching latest crypto news for automated analysis...") try: top_headlines = app.state.news_api.get_everything( q='bitcoin OR ethereum OR crypto OR blockchain', language='en', sort_by='publishedAt', page_size=5 ) analyzer: GeminiAnalyzer = app.state.gemini_analyzer for article in top_headlines.get('articles', []): title = article.get('title') if title and "[Removed]" not in title: analysis = await analyzer.analyze_text(title) analysis['url'] = article.get('url') await app.state.news_queue.put(analysis) except Exception as e: print(f"❌ Error during news fetching or analysis: {e}") await asyncio.sleep(interval_seconds) # --- FastAPI App Initialization --- app = FastAPI(title="CryptoSentinel Pro", lifespan=lifespan) templates = Jinja2Templates(directory="templates") # --- HTML Rendering Helper --- def render_analysis_card(payload: dict, is_news: bool = False) -> str: s = payload text_to_show = s.get('summary', 'Analysis failed or not available.') if is_news: url = s.get('url', '#') text_to_show = f'{s.get("summary", "N/A")}' impact_class = f"impact-{s.get('impact', 'low').lower()}" sentiment_class = f"sentiment-{s.get('sentiment', 'neutral').lower()}" return f"""
{text_to_show}