mgbam commited on
Commit
526c84c
·
verified ·
1 Parent(s): 3bccb62

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -49
app.py CHANGED
@@ -1,54 +1,111 @@
1
- from fastapi import FastAPI
2
- from fastapi.responses import HTMLResponse, JSONResponse
3
- import requests
4
- import uvicorn
5
-
6
- # Initialize FastAPI app
7
- app = FastAPI()
8
-
9
- # API endpoint for getting cryptocurrency prices from CoinGecko
10
- API_URL = 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,dogecoin&vs_currencies=usd'
11
-
12
- # HTML template with HTMX integration
13
- HTML_TEMPLATE = """
14
- <!DOCTYPE html>
15
- <html lang="en">
16
- <head>
17
- <meta charset="UTF-8">
18
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
- <script src="https://unpkg.com/htmx.org"></script>
20
- <title>Crypto Price Dashboard</title>
21
- </head>
22
- <body>
23
- <h1>Cryptocurrency Prices</h1>
24
- <div id="prices" hx-get="/api/prices" hx-trigger="every 10s">
25
- <div>Loading...</div>
26
- </div>
27
- <script>
28
- document.addEventListener('htmx:afterRequest', function(event) {
29
- const pricesDiv = document.getElementById('prices');
30
- const data = JSON.parse(event.detail.xhr.responseText);
31
- pricesDiv.innerHTML = `
32
- <div>Bitcoin: $${data.bitcoin.usd}</div>
33
- <div>Ethereum: $${data.ethereum.usd}</div>
34
- <div>Dogecoin: $${data.dogecoin.usd}</div>
35
- `;
36
- });
37
- </script>
38
- </body>
39
- </html>
40
- """
 
41
 
42
  @app.get("/", response_class=HTMLResponse)
43
- def read_root():
44
- return HTML_TEMPLATE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- @app.get("/api/prices", response_class=JSONResponse)
47
- def get_prices():
48
- response = requests.get(API_URL)
49
- response.raise_for_status()
50
- return response.json()
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
 
52
  if __name__ == "__main__":
53
- # Run with: uvicorn this_file_name:app --host 0.0.0.0 --port 7860 --reload
54
- uvicorn.run("this_file_name:app", host="0.0.0.0", port=7860, reload=True)
 
 
1
+ import httpx
2
+ from fastapi import FastAPI, Request, HTTPException
3
+ from fastapi.responses import HTMLResponse
4
+ from fastapi.templating import Jinja2Templates
5
+ from pydantic import BaseModel, ValidationError
6
+ from contextlib import asynccontextmanager
7
+
8
+ # --- Configuration ---
9
+ # Centralized configuration makes the app easier to modify.
10
+ COINGECKO_API_URL = "https://api.coingecko.com/api/v3/simple/price"
11
+ TARGET_COINS = ["bitcoin", "ethereum", "dogecoin"]
12
+ TARGET_CURRENCY = "usd"
13
+
14
+ # --- Pydantic Models for Data Validation ---
15
+ # This ensures the API response from CoinGecko matches our expectations.
16
+ class PriceData(BaseModel):
17
+ usd: float
18
+
19
+ # The full response is a dictionary mapping coin names (str) to their PriceData.
20
+ # Example: {"bitcoin": {"usd": 65000.0}}
21
+ ApiResponse = dict[str, PriceData]
22
+
23
+ # --- Application State and Lifespan Management ---
24
+ # Using a lifespan event is the modern way to manage resources like HTTP clients.
25
+ # This client will be created on startup and closed gracefully on shutdown.
26
+ app_state = {}
27
+
28
+ @asynccontextmanager
29
+ async def lifespan(app: FastAPI):
30
+ # On startup: create a single, reusable httpx client.
31
+ app_state["http_client"] = httpx.AsyncClient()
32
+ yield
33
+ # On shutdown: close the client.
34
+ await app_state["http_client"].aclose()
35
+
36
+ # --- FastAPI App Initialization ---
37
+ app = FastAPI(lifespan=lifespan)
38
+ templates = Jinja2Templates(directory="templates")
39
+
40
+
41
+ # --- API Endpoints ---
42
 
43
  @app.get("/", response_class=HTMLResponse)
44
+ async def serve_home_page(request: Request):
45
+ """Serves the main HTML page which will then trigger HTMX calls."""
46
+ return templates.TemplateResponse("index.html", {"request": request})
47
+
48
+ @app.get("/api/prices", response_class=HTMLResponse)
49
+ async def get_prices_ui():
50
+ """
51
+ This endpoint is called by HTMX.
52
+ It fetches crypto data and returns an HTML FRAGMENT to be swapped into the page.
53
+ """
54
+ try:
55
+ client: httpx.AsyncClient = app_state["http_client"]
56
+
57
+ # Build the request parameters dynamically
58
+ params = {
59
+ "ids": ",".join(TARGET_COINS),
60
+ "vs_currencies": TARGET_CURRENCY
61
+ }
62
+
63
+ response = await client.get(COINGECKO_API_URL, params=params)
64
+ response.raise_for_status() # Raise an exception for 4xx/5xx errors
65
+
66
+ # Validate the received data against our Pydantic model
67
+ prices = ApiResponse(**response.json())
68
+
69
+ # This is a simple but effective way to render an HTML fragment.
70
+ # For larger fragments, you would use another Jinja2 template.
71
+ html_fragment = ""
72
+ for coin, data in prices.items():
73
+ html_fragment += f"<div><strong>{coin.capitalize()}:</strong> ${data.usd:,.2f}</div>"
74
+
75
+ return HTMLResponse(content=html_fragment)
76
+
77
+ except httpx.RequestError as e:
78
+ # Handle network-related errors
79
+ return HTMLResponse(content=f"<div class='error'>Network error: Could not connect to CoinGecko.</div>", status_code=503)
80
+ except ValidationError as e:
81
+ # Handle cases where CoinGecko's API response changes unexpectedly
82
+ return HTMLResponse(content=f"<div class='error'>Invalid API response from data source.</div>", status_code=502)
83
+ except Exception as e:
84
+ # Generic catch-all for other unexpected errors
85
+ return HTMLResponse(content=f"<div class='error'>An unexpected error occurred.</div>", status_code=500)
86
+
87
 
88
+ @app.get("/api/forecast/{coin_id}")
89
+ async def get_forecast(coin_id: str):
90
+ """
91
+ Placeholder for a real forecasting model.
92
+ A real implementation would involve loading a pre-trained model,
93
+ fetching historical data, and running a prediction.
94
+ """
95
+ if coin_id not in TARGET_COINS:
96
+ raise HTTPException(status_code=404, detail=f"Forecast not available for '{coin_id}'")
97
+
98
+ # In a real app, this would be the output of your ML model.
99
+ mock_forecast = {
100
+ "coin_id": coin_id,
101
+ "prediction_in_24h": "up",
102
+ "confidence": 0.78,
103
+ "detail": "This is a mock forecast. A real implementation requires a data science model."
104
+ }
105
+ return mock_forecast
106
 
107
+ # --- Main execution (for development) ---
108
  if __name__ == "__main__":
109
+ # To run: uvicorn main:app --reload --port 7860
110
+ import uvicorn
111
+ uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True)