Skip to content

Commit f109ba0

Browse files
committed
feat(chart)!: Graphs are now rendered directly through query params, without creating a file.
- The default format is now jpeg instead of png - Files are now not saved to the folder
1 parent b41e341 commit f109ba0

File tree

5 files changed

+22
-86
lines changed

5 files changed

+22
-86
lines changed

chart/function/create_chart.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55
from datetime import datetime
66
from typing import Optional
7+
from io import BytesIO
78

89
from matplotlib import pyplot as plt
910
from scipy.interpolate import make_interp_spline
@@ -13,11 +14,10 @@
1314
from database.server import Database
1415
from utils.config.load_config import load_config
1516
from utils.config.get_dsn import get_dsn
16-
from function.gen_unique_name import generate_unique_name
1717

1818
config = load_config('config.hjson')
1919

20-
async def create_chart(currency: Currency, db: Database) -> Optional[str]:
20+
async def create_chart(currency: Currency, db: Database) -> Optional[BytesIO]:
2121
"""
2222
Generates a currency exchange rate chart based on historical data.
2323
@@ -93,12 +93,9 @@ async def create_chart(currency: Currency, db: Database) -> Optional[str]:
9393
ax.spines['top'].set_visible(False)
9494
ax.spines['right'].set_visible(False)
9595

96-
name = await generate_unique_name(
97-
f'{currency.from_currency.upper()}_{currency.conv_currency.upper()}',
98-
datetime.now()
99-
)
100-
101-
plt.savefig(f'../charts/{name}.png')
96+
buf = BytesIO()
97+
fig.savefig(buf, format="jpeg", bbox_inches="tight")
10298
plt.close(fig)
99+
buf.seek(0)
103100

104-
return name
101+
return buf

chart/function/gen_unique_name.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

chart/main.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@
44
The application serves static files, provides endpoints for generating charts,
55
and integrates with Plausible Analytics for tracking usage.
66
"""
7-
8-
import os
9-
107
import uvicorn
118
from fastapi import FastAPI
129
from fastapi.exceptions import RequestValidationError
13-
from starlette.staticfiles import StaticFiles
1410

1511
from utils.lifespan import lifespan
1612
from utils.config.load_config import load_config
@@ -22,15 +18,11 @@
2218
app = FastAPI(lifespan=lifespan)
2319
config = load_config('config.hjson')
2420

25-
if not os.path.exists('../charts'):
26-
os.mkdir('../charts')
27-
2821
app.add_exception_handler(
2922
RequestValidationError,
3023
custom_validation_exception
3124
)
3225

33-
app.mount('/static/charts', StaticFiles(directory='../charts/'))
3426
app.middleware('http')(PlausibleAnalytics())
3527

3628
app.include_router(get_chart.router)

chart/routes/get_chart.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
from datetime import datetime
99

1010
from fastapi import APIRouter, status, Depends, HTTPException, Request
11+
from fastapi.responses import StreamingResponse
1112

1213
from schemas.currency import Currency
1314
from function.create_chart import create_chart
14-
from .get_chart_period import prepare_chart_response
1515

1616
router = APIRouter()
1717

18-
@router.get('/api/getChart/', status_code=status.HTTP_201_CREATED)
19-
async def get_chart(req: Request, currency: Currency = Depends()) -> dict:
18+
@router.get('/api/getChart/', status_code=status.HTTP_200_OK)
19+
async def get_chart(req: Request, currency: Currency = Depends()) -> StreamingResponse:
2020
"""
2121
Endpoint for retrieving a currency exchange rate chart.
2222
@@ -56,6 +56,12 @@ async def get_chart(req: Request, currency: Currency = Depends()) -> dict:
5656
detail="The start_date cannot be later than the end_date."
5757
)
5858

59-
data = await create_chart(currency, req.app.state.db)
59+
chart = await create_chart(currency, req.app.state.db)
6060

61-
return await prepare_chart_response(req, data)
61+
if chart is None:
62+
raise HTTPException(
63+
status_code=status.HTTP_400_BAD_REQUEST,
64+
detail='No data found.'
65+
)
66+
67+
return StreamingResponse(chart, media_type="image/jpeg")

chart/routes/get_chart_period.py

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@
99
from dateutil.relativedelta import relativedelta
1010

1111
from fastapi import APIRouter, status, Request, HTTPException, Depends
12+
from fastapi.responses import StreamingResponse
1213

1314
from function.create_chart import create_chart
1415
from schemas.currency import Currency
1516

1617
# pylint: disable=duplicate-code
1718
router = APIRouter()
1819

19-
@router.get("/api/getChart/{period}", status_code=status.HTTP_201_CREATED)
20+
@router.get("/api/getChart/{period}", status_code=status.HTTP_200_OK)
2021
async def get_chart_period(
2122
req: Request,
2223
currency: Currency = Depends()
23-
) -> dict:
24+
) -> StreamingResponse:
2425
"""
2526
Fetches a chart for a given currency pair and a specific period.
2627
@@ -36,8 +37,6 @@ async def get_chart_period(
3637
:param conv_currency: The target currency in the pair (e.g., 'EUR').
3738
:param period: The time period for which the chart is requested
3839
(e.g., 'week', 'month', 'quarter', 'year').
39-
:return: A response containing the chart URL or an error message
40-
if parameters are invalid.
4140
"""
4241
if not currency.from_currency or not currency.conv_currency:
4342
raise HTTPException(
@@ -66,39 +65,10 @@ async def get_chart_period(
6665

6766
chart = await create_chart(currency, req.app.state.db)
6867

69-
if not chart:
68+
if chart is None:
7069
raise HTTPException(
7170
status_code=status.HTTP_400_BAD_REQUEST,
7271
detail='No data found.'
7372
)
7473

75-
return await prepare_chart_response(req, chart)
76-
77-
async def prepare_chart_response(req: Request, chart_name: str) -> dict:
78-
"""
79-
Prepares the response to return the URL of the generated chart.
80-
81-
If the chart data is not found,
82-
it returns a 404 error with an appropriate message.
83-
Otherwise, it returns a URL to access the chart image.
84-
85-
:param response: The response object used to set status and message.
86-
:param request: The request object used
87-
to retrieve details of the incoming request.
88-
:param chart_name: The name of the generated chart
89-
(used to build the URL).
90-
:return: A dictionary with the chart URL or an error message
91-
if no chart is found.
92-
"""
93-
if not chart_name:
94-
raise HTTPException(
95-
status_code=status.HTTP_400_BAD_REQUEST,
96-
detail='No data found.'
97-
)
98-
99-
host = req.headers.get("host")
100-
url_scheme = req.headers.get("X-Forwarded-Proto", req.url.scheme)
101-
102-
return {
103-
'detail': f'{url_scheme}://{host}/static/charts/{chart_name}.png',
104-
}
74+
return StreamingResponse(chart, media_type="image/jpeg")

0 commit comments

Comments
 (0)