mgbam commited on
Commit
b55a0ad
Β·
verified Β·
1 Parent(s): 175724c

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -170
app.py DELETED
@@ -1,170 +0,0 @@
1
- """
2
- CryptoSentinel AI β€” High-performance FastAPI application.
3
-
4
- This is the main entry point that orchestrates the entire application.
5
- - Integrates the asynchronous PriceFetcher for live market data.
6
- - Integrates the asynchronous SentimentAnalyzer for real-time analysis.
7
- - Serves the interactive frontend and provides all necessary API endpoints.
8
- """
9
- import asyncio
10
- import json
11
- from contextlib import asynccontextmanager
12
-
13
- import httpx
14
- from fastapi import FastAPI, Request, BackgroundTasks
15
- from fastapi.responses import HTMLResponse, StreamingResponse
16
- from fastapi.templating import Jinja2Templates
17
- from pydantic import BaseModel, constr
18
-
19
- # ====================================================================
20
- # FIX APPLIED HERE
21
- # ====================================================================
22
- # Use relative imports because price_fetcher and sentiment are in the same 'app' directory.
23
- from .price_fetcher import PriceFetcher
24
- from .sentiment import SentimentAnalyzer
25
- # ====================================================================
26
-
27
-
28
- # --- Pydantic Model for API Input Validation ---
29
-
30
- class SentimentRequest(BaseModel):
31
- """Ensures the text for sentiment analysis is a non-empty string."""
32
- text: constr(strip_whitespace=True, min_length=1)
33
-
34
- # --- Application Lifespan for Resource Management ---
35
-
36
- @asynccontextmanager
37
- async def lifespan(app: FastAPI):
38
- """
39
- Manages application startup and shutdown events using the modern
40
- lifespan context manager.
41
- """
42
- # On startup:
43
- async with httpx.AsyncClient() as client:
44
- # Instantiate and store our services in the application state.
45
- # This makes them accessible in any request handler via `request.app.state`.
46
- app.state.price_fetcher = PriceFetcher(client=client, coins=["bitcoin", "ethereum", "dogecoin"])
47
- app.state.sentiment_analyzer = SentimentAnalyzer(client=client)
48
- app.state.request_counter = 0 # Simple counter for unique SSE event IDs
49
-
50
- # Create a cancellable background task for continuous price updates.
51
- price_update_task = asyncio.create_task(
52
- run_periodic_updates(app.state.price_fetcher, interval_seconds=10)
53
- )
54
-
55
- print("πŸš€ CryptoSentinel AI started successfully.")
56
- yield # The application is now running and ready to accept requests.
57
-
58
- # On shutdown:
59
- print("⏳ Shutting down background tasks...")
60
- price_update_task.cancel()
61
- try:
62
- await price_update_task
63
- except asyncio.CancelledError:
64
- print("Price update task cancelled successfully.")
65
- print("βœ… Shutdown complete.")
66
-
67
- async def run_periodic_updates(fetcher: PriceFetcher, interval_seconds: int):
68
- """A robust asyncio background task that periodically updates prices."""
69
- while True:
70
- await fetcher.update_prices_async()
71
- await asyncio.sleep(interval_seconds)
72
-
73
- # --- FastAPI App Initialization ---
74
-
75
- app = FastAPI(title="CryptoSentinel AI", lifespan=lifespan)
76
- # Assuming your project structure is /app/templates
77
- templates = Jinja2Templates(directory="app/templates")
78
-
79
- # --- API Endpoints ---
80
-
81
- @app.get("/", response_class=HTMLResponse)
82
- async def serve_dashboard(request: Request):
83
- """Serves the main interactive dashboard from `index.html`."""
84
- return templates.TemplateResponse("index.html", {"request": request})
85
-
86
- @app.get("/api/prices", response_class=HTMLResponse)
87
- async def get_prices_fragment(request: Request):
88
- """Returns an HTML fragment with the latest cached crypto prices for HTMX."""
89
- price_fetcher: PriceFetcher = request.app.state.price_fetcher
90
- prices = price_fetcher.get_current_prices()
91
-
92
- html_fragment = ""
93
- for coin, price in prices.items():
94
- # Format the price nicely, handling the initial '--' state
95
- price_str = f"${price:,.2f}" if isinstance(price, (int, float)) else price
96
- html_fragment += f"<div><strong>{coin.capitalize()}:</strong> {price_str}</div>"
97
-
98
- return HTMLResponse(content=html_fragment)
99
-
100
- @app.post("/api/sentiment")
101
- async def analyze_sentiment(
102
- payload: SentimentRequest,
103
- request: Request,
104
- background_tasks: BackgroundTasks
105
- ):
106
- """
107
- Validates and queues a text for sentiment analysis. The heavy lifting is
108
- done in the background to ensure the API responds instantly.
109
- """
110
- analyzer: SentimentAnalyzer = request.app.state.sentiment_analyzer
111
- request.app.state.request_counter += 1
112
- request_id = request.app.state.request_counter
113
-
114
- # The actual API call to Hugging Face will run after this response is sent.
115
- background_tasks.add_task(analyzer.compute_and_publish, payload.text, request_id)
116
-
117
- return HTMLResponse(content="<small>Queued for analysis...</small>")
118
-
119
- @app.get("/api/sentiment/stream")
120
- async def sentiment_stream(request: Request):
121
- """
122
- Establishes a Server-Sent Events (SSE) connection. It efficiently pushes
123
- new sentiment results as HTML fragments to the client as they become available.
124
- """
125
- analyzer: SentimentAnalyzer = request.app.state.sentiment_analyzer
126
-
127
- async def event_generator():
128
- # Clear the initial "waiting..." message on the client.
129
- # hx-swap-oob="innerHTML" swaps this div out-of-band without affecting the target.
130
- yield f"event: sentiment_update\ndata: <div id='sentiment-results' hx-swap-oob='innerHTML'></div>\n\n"
131
-
132
- # Listen for new results from the analyzer's internal queue.
133
- async for result_payload in analyzer.stream_results():
134
- try:
135
- result = result_payload['result']
136
- label = str(result.get('label', 'NEUTRAL')).lower()
137
- score = result.get('score', 0.0) * 100
138
- text = result_payload['text']
139
-
140
- # Dynamically build the HTML fragment to be sent to the client.
141
- html_fragment = f"""
142
- <div>
143
- <blockquote>{text}</blockquote>
144
- <p>
145
- <strong>Result:</strong>
146
- <span class="sentiment-{label}">{label.upper()}</span>
147
- (Confidence: {score:.1f}%)
148
- </p>
149
- </div>
150
- """
151
- # First, process the string to remove newlines. This avoids a
152
- # backslash in the f-string expression, fixing the SyntaxError.
153
- data_payload = html_fragment.replace('\n', '')
154
-
155
- # Then, use the clean variable in the f-string to build the message.
156
- sse_message = f"event: sentiment_update\ndata: {data_payload}\n\n"
157
-
158
- yield sse_message
159
-
160
- except (KeyError, TypeError):
161
- continue # Ignore malformed payloads
162
-
163
- return StreamingResponse(event_generator(), media_type="text/event-stream")
164
-
165
- # --- Main execution block for local development ---
166
- if __name__ == "__main__":
167
- import uvicorn
168
- # This run command assumes you are running `python app.py` from within the /app directory
169
- # or `python -m app.app` from the parent directory.
170
- uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)