Skip to content

Commit c1df81e

Browse files
Enhance Backend restructuring codebase (#11)
1 parent d3d59bd commit c1df81e

File tree

9 files changed

+179
-157
lines changed

9 files changed

+179
-157
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
GROQ_API_KEY =
1+
GROQ_API_KEY =
2+
REDIS_URL =

app.py

Lines changed: 5 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,8 @@
1-
# API imports
21
from fastapi import FastAPI, Request, Depends
3-
import groq
4-
import os
5-
import dotenv
6-
import datetime
7-
import requests
8-
92
from fastapi.middleware.cors import CORSMiddleware
10-
from fastapi.responses import HTMLResponse
11-
from fastapi.templating import Jinja2Templates
12-
from fastapi_cache import FastAPICache
13-
from fastapi_cache.backends.redis import RedisBackend
14-
from contextlib import asynccontextmanager
15-
import json
16-
from redis import asyncio as aioredis
17-
# Custom imports
18-
from topStocks import get_top_stocks, get_stock
19-
from ask import groq_chat
20-
from stockNews import fetch_news
21-
from agents import multi_ai
22-
from agno.agent import RunResponse
23-
24-
dotenv.load_dotenv()
25-
templates = Jinja2Templates(directory="templates")
26-
27-
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
28-
groq_client = groq.Client(api_key=GROQ_API_KEY)
29-
30-
if not GROQ_API_KEY:
31-
raise ValueError("Please provide a GROQ API key")
32-
REDIS_URL = os.getenv("REDIS_URL")
33-
34-
@asynccontextmanager
35-
async def lifespan(_: FastAPI):
36-
redis_client = None
37-
38-
try:
39-
redis_client = aioredis.from_url(REDIS_URL, encoding="utf-8", decode_responses=True)
40-
FastAPICache.init(RedisBackend(redis_client), prefix="fastapi-cache")
41-
print("✅ Redis cache initialized successfully!")
42-
yield
43-
except Exception as e:
44-
print(f"❌ Redis Connection Error: {e}")
45-
yield
46-
finally:
47-
try:
48-
await FastAPICache.clear()
49-
if redis_client:
50-
await redis_client.close()
51-
print("🔴 Redis connection closed!")
52-
except Exception as e:
53-
print(f"❌ Error while closing Redis: {e}")
54-
3+
from utils.redisCache import lifespan, get_cache
4+
from routes.stockRoutes import router as stock_router
5+
from routes.agentRoutes import router as agent_router
556

567
app = FastAPI(lifespan=lifespan)
578
app.add_middleware(
@@ -62,110 +13,8 @@ async def lifespan(_: FastAPI):
6213
allow_headers=["*"],
6314
)
6415

65-
@app.get("/")
66-
@app.head("/")
67-
async def read_root(request: Request):
68-
text = "Investo-glow Backend API Server"
69-
return templates.TemplateResponse("base.html",{"request":request, "text": text})
70-
71-
def get_cache():
72-
return FastAPICache.get_backend()
73-
74-
@app.get("/top-stocks")
75-
async def read_top_stocks(cache: RedisBackend = Depends(get_cache)):
76-
cache_key = "top_stocks"
77-
cached_result = await cache.get(cache_key)
78-
if cached_result:
79-
return json.loads(cached_result)
80-
81-
top_stocks = ['AAPL', 'MSFT', 'AMZN', 'GOOGL']
82-
stocks = " ".join(top_stocks)
83-
stocks_info = get_top_stocks(stocks)
84-
85-
await cache.set(cache_key, json.dumps(stocks_info), 10)
86-
return stocks_info
87-
88-
@app.get("/stock-news")
89-
async def stock_news(cache: RedisBackend = Depends(get_cache)):
90-
cache_key = "stock_news"
91-
cached_result = await cache.get(cache_key)
92-
if cached_result:
93-
return json.loads(cached_result)
94-
news_stack = fetch_news()
95-
await cache.set(cache_key, json.dumps(news_stack), 300)
96-
return news_stack
97-
98-
@app.get("/stocks/{name}")
99-
async def read_stock(name: str, cache: RedisBackend = Depends(get_cache)):
100-
cache_key = "stock_{name}"
101-
cached_result = await cache.get(cache_key)
102-
if cached_result:
103-
return json.loads(cached_result)
104-
stock_info = get_stock(name)
105-
await cache.set(cache_key, json.dumps(stock_info), 10)
106-
return stock_info
107-
108-
109-
@app.get("health/") # Changed to GET since it's retrieving status
110-
async def health_check():
111-
try:
112-
return {
113-
"status": "healthy",
114-
"timestamp": datetime.datetime.now().isoformat(),
115-
"uptime": "OK",
116-
"api": {
117-
"groq_api": "connected" if GROQ_API_KEY else "not configured",
118-
},
119-
"ip": requests.get('https://api.ipify.org').text,
120-
"services": {
121-
"top_stocks": app.url_path_for("read_top_stocks"),
122-
"chat": app.url_path_for("chat"),
123-
"agent": app.url_path_for("ask"),
124-
},
125-
}
126-
127-
except Exception as e:
128-
return {
129-
"status": "unhealthy",
130-
"timestamp": datetime.datetime.now().isoformat(),
131-
"error": str(e)
132-
}
133-
134-
@app.get("/chat")
135-
def chat(query: str):
136-
"""
137-
API endpoint to handle user investment-related questions and return AI-generated insights.
138-
"""
139-
if not query:
140-
return {"error": "Query parameter is required"}
141-
142-
try:
143-
response = groq_client.chat.completions.create(
144-
model="llama-3.3-70b-versatile",
145-
messages=[{"role": "system", "content": "You are an AI investment assistant."},
146-
{"role": "user", "content": query}]
147-
)
148-
149-
answer = response.choices[0].message.content
150-
return {"question": query, "answer": answer}
151-
152-
except Exception as e:
153-
return {"error": str(e)}
16+
app.include_router(stock_router)
17+
app.include_router(agent_router)
15418

15519

156-
@app.get("/agent")
157-
def ask(query: str):
158-
"""
159-
API endpoint to handle user investment-related questions and return AI-generated insights.
160-
"""
161-
if not query:
162-
return {"error": "Query parameter is required"}
163-
164-
try:
165-
response: RunResponse = multi_ai.run(query)
166-
answer = response.content
16720

168-
return {"question": query, "answer": answer}
169-
170-
except Exception as e:
171-
return {"error": str(e)}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

routes/agentRoutes.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
import datetime
3+
import requests
4+
import groq
5+
from fastapi import FastAPI, APIRouter
6+
from fastapi.responses import HTMLResponse
7+
from fastapi.templating import Jinja2Templates
8+
from agno.agent import RunResponse
9+
from controllers.agents import multi_ai
10+
import dotenv
11+
12+
router = APIRouter()
13+
14+
dotenv.load_dotenv()
15+
templates = Jinja2Templates(directory="templates")
16+
17+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
18+
groq_client = groq.Client(api_key=GROQ_API_KEY)
19+
20+
if not GROQ_API_KEY:
21+
raise ValueError("Please provide a GROQ API key")
22+
23+
@router.get("health/") # Changed to GET since it's retrieving status
24+
async def health_check():
25+
try:
26+
return {
27+
"status": "healthy",
28+
"timestamp": datetime.datetime.now().isoformat(),
29+
"uptime": "OK",
30+
"api": {
31+
"groq_api": "connected" if GROQ_API_KEY else "not configured",
32+
},
33+
"ip": requests.get('https://api.ipify.org').text,
34+
"services": {
35+
"top_stocks": router.url_path_for("read_top_stocks"),
36+
"chat": router.url_path_for("chat"),
37+
"agent": router.url_path_for("ask"),
38+
},
39+
}
40+
41+
except Exception as e:
42+
return {
43+
"status": "unhealthy",
44+
"timestamp": datetime.datetime.now().isoformat(),
45+
"error": str(e)
46+
}
47+
48+
@router.get("/chat")
49+
def chat(query: str):
50+
"""
51+
API endpoint to handle user investment-related questions and return AI-generated insights.
52+
"""
53+
if not query:
54+
return {"error": "Query parameter is required"}
55+
56+
try:
57+
response = groq_client.chat.completions.create(
58+
model="llama-3.3-70b-versatile",
59+
messages=[{"role": "system", "content": "You are an AI investment assistant."},
60+
{"role": "user", "content": query}]
61+
)
62+
63+
answer = response.choices[0].message.content
64+
return {"question": query, "answer": answer}
65+
66+
except Exception as e:
67+
return {"error": str(e)}
68+
69+
70+
@router.get("/agent")
71+
def ask(query: str):
72+
"""
73+
API endpoint to handle user investment-related questions and return AI-generated insights.
74+
"""
75+
if not query:
76+
return {"error": "Query parameter is required"}
77+
78+
try:
79+
response: RunResponse = multi_ai.run(query)
80+
answer = response.content
81+
82+
return {"question": query, "answer": answer}
83+
84+
except Exception as e:
85+
return {"error": str(e)}

routes/stockRoutes.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from fastapi import APIRouter, Depends, Request
2+
from fastapi_cache import FastAPICache
3+
from fastapi_cache.backends.redis import RedisBackend
4+
from utils.redisCache import get_cache
5+
from controllers.topStocks import get_top_stocks, get_stock
6+
from controllers.stockNews import fetch_news
7+
import json
8+
from fastapi.templating import Jinja2Templates
9+
10+
templates = Jinja2Templates(directory="templates")
11+
router = APIRouter()
12+
13+
@router.get("/top-stocks")
14+
async def read_top_stocks(cache: RedisBackend = Depends(get_cache)):
15+
cache_key = "top_stocks"
16+
cached_result = await cache.get(cache_key)
17+
if cached_result:
18+
return json.loads(cached_result)
19+
20+
top_stocks = ['AAPL', 'MSFT', 'AMZN', 'GOOGL']
21+
stocks = " ".join(top_stocks)
22+
stocks_info = get_top_stocks(stocks)
23+
24+
await cache.set(cache_key, json.dumps(stocks_info), 10)
25+
return stocks_info
26+
27+
@router.get("/stock-news")
28+
async def stock_news(cache: RedisBackend = Depends(get_cache)):
29+
cache_key = "stock_news"
30+
cached_result = await cache.get(cache_key)
31+
if cached_result:
32+
return json.loads(cached_result)
33+
news_stack = fetch_news()
34+
await cache.set(cache_key, json.dumps(news_stack), 300)
35+
return news_stack
36+
37+
@router.get("/stocks/{name}")
38+
async def read_stock(name: str, cache: RedisBackend = Depends(get_cache)):
39+
cache_key = "stock_{name}"
40+
cached_result = await cache.get(cache_key)
41+
if cached_result:
42+
return json.loads(cached_result)
43+
stock_info = get_stock(name)
44+
await cache.set(cache_key, json.dumps(stock_info), 10)
45+
return stock_info
46+
47+
@router.get("/")
48+
@router.head("/")
49+
async def read_root(request: Request):
50+
text = "Investo-glow Backend API Server"
51+
return templates.TemplateResponse("base.html",{"request":request, "text": text})

utils/redisCache.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from fastapi_cache.backends.redis import RedisBackend
2+
from contextlib import asynccontextmanager
3+
from redis import asyncio as aioredis
4+
from fastapi_cache import FastAPICache
5+
from fastapi import FastAPI
6+
import os
7+
import dotenv
8+
9+
dotenv.load_dotenv()
10+
11+
REDIS_URL = os.getenv("REDIS_URL")
12+
13+
@asynccontextmanager
14+
async def lifespan(_: FastAPI):
15+
redis_client = None
16+
17+
try:
18+
redis_client = aioredis.from_url(REDIS_URL, encoding="utf-8", decode_responses=True)
19+
FastAPICache.init(RedisBackend(redis_client), prefix="fastapi-cache")
20+
print("✅ Redis cache initialized successfully!")
21+
yield
22+
23+
except Exception as e:
24+
print(f"❌ Redis Connection Error: {e}")
25+
yield
26+
finally:
27+
try:
28+
await FastAPICache.clear()
29+
if redis_client:
30+
await redis_client.close()
31+
print("🔴 Redis connection closed!")
32+
except Exception as e:
33+
print(f"❌ Error while closing Redis: {e}")
34+
35+
def get_cache():
36+
return FastAPICache.get_backend()

0 commit comments

Comments
 (0)