From 85acbbb16cae6318dffc41219508bb520752fa1c Mon Sep 17 00:00:00 2001 From: Redume Date: Sat, 14 Dec 2024 23:22:44 +0300 Subject: [PATCH] refactor(chart): Move functions to separate files or folders to improve code structure. --- chart/function/create_chart.py | 54 +++++++++ chart/function/gen_unique_name.py | 11 ++ chart/main.py | 190 +++--------------------------- chart/routes/get_chart.py | 34 ++++++ chart/routes/get_chart_period.py | 63 ++++++++++ 5 files changed, 177 insertions(+), 175 deletions(-) create mode 100644 chart/function/create_chart.py create mode 100644 chart/function/gen_unique_name.py create mode 100644 chart/routes/get_chart.py create mode 100644 chart/routes/get_chart_period.py diff --git a/chart/function/create_chart.py b/chart/function/create_chart.py new file mode 100644 index 0000000..1bf7b35 --- /dev/null +++ b/chart/function/create_chart.py @@ -0,0 +1,54 @@ +from datetime import datetime + +from matplotlib import pyplot as plt + +from chart.function.gen_unique_name import generate_unique_name +from ..database.server import create_pool + +async def create_chart(from_currency: str, conv_currency: str, start_date: str, end_date: str) -> (str, None): + pool = await create_pool() + + start_date_obj = datetime.strptime(start_date, '%Y-%m-%d').date() + end_date_obj = datetime.strptime(end_date, '%Y-%m-%d').date() + + async with pool.acquire() as conn: + data = await conn.fetch( + 'SELECT date, rate FROM currency ' + 'WHERE (date BETWEEN $1 AND $2) AND from_currency = $3 AND conv_currency = $4', + start_date_obj, + end_date_obj, + from_currency.upper(), + conv_currency.upper() + ) + + if not data or len(data) <= 1: + return None + + date, rate = [], [] + + for row in data: + date.append(str(row['date'])) + rate.append(row['rate']) + + if rate[0] < rate[-1]: + plt.plot(date, rate, color='green', marker='o') + elif rate[0] > rate[-1]: + plt.plot(date, rate, color='red', marker='o') + else: + plt.plot(date, rate, color='grey') + + plt.xlabel('Date') + plt.ylabel('Rate') + + fig = plt.gcf() + fig.set_size_inches(18.5, 9.5) + + name = await generate_unique_name( + f'{from_currency.upper()}_{conv_currency.upper()}', + datetime.now() + ) + + fig.savefig(f'../charts/{name}.png') + fig.clear() + + return name diff --git a/chart/function/gen_unique_name.py b/chart/function/gen_unique_name.py new file mode 100644 index 0000000..3f29e64 --- /dev/null +++ b/chart/function/gen_unique_name.py @@ -0,0 +1,11 @@ +import datetime +import random +import string + + +async def generate_unique_name(currency_pair: str, date: datetime) -> str: + date_str = date.strftime("%Y%m%d") + random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) + unique_name = f"{currency_pair}_{date_str}_{random_suffix}" + + return unique_name diff --git a/chart/main.py b/chart/main.py index 9e7898e..0d2eaf4 100644 --- a/chart/main.py +++ b/chart/main.py @@ -1,34 +1,16 @@ import os -import psycopg2 + import uvicorn - import yaml -import matplotlib.pyplot as plt -import datetime - -import dateutil.relativedelta -import random -import string - -from fastapi import FastAPI, Response, status, Request -from psycopg2.extras import DictCursor +from fastapi import FastAPI from starlette.staticfiles import StaticFiles -from middleware.plausible_analytics import PlausibleAnalytics +from chart.middleware.plausible_analytics import PlausibleAnalytics +from chart.routes import get_chart, get_chart_period app = FastAPI() - -config = yaml.safe_load(open('../config.yaml')) - -con = psycopg2.connect(host=config['database']['host'], - user=config['database']['user'], - password=config['database']['password'], - database=config['database']['name'], - port=config['database']['port']) - -cur = con.cursor(cursor_factory=DictCursor) -con.autocommit = True +config = yaml.safe_load(open('config.yaml')) if not os.path.exists('../charts'): os.mkdir('../charts') @@ -36,158 +18,16 @@ if not os.path.exists('../charts'): app.mount('/static/charts', StaticFiles(directory='../charts/')) app.middleware('http')(PlausibleAnalytics()) -@app.get("/api/getChart/", status_code=status.HTTP_201_CREATED) -async def get_chart( - response: Response, - request: Request, - from_currency: str = None, - conv_currency: str = None, - start_date: str = None, - end_date: str = None, - ): - - if not from_currency or not conv_currency: - response.status_code = status.HTTP_400_BAD_REQUEST - return { - 'status': status.HTTP_400_BAD_REQUEST, - 'message': 'The from_currency and conv_currency fields are required.', - } - elif not start_date and not end_date: - response.status_code = status.HTTP_400_BAD_REQUEST - return { - 'status': status.HTTP_400_BAD_REQUEST, - 'message': 'The start_date and end_date fields are required.', - } - - - chart = await create_chart(from_currency, conv_currency, start_date, end_date) - - if not chart: - response.status_code = status.HTTP_404_NOT_FOUND - return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND} - - host = request.headers.get("host") - url_schema = request.url.scheme - - return { - 'status': status.HTTP_400_BAD_REQUEST, - 'message': f'{url_schema}://{host}/static/charts/{chart}.png', - } - - -@app.get("/api/getChart/{period}", status_code=status.HTTP_201_CREATED) -async def get_chart_period( - response: Response, - request: Request, - from_currency: str = None, - conv_currency: str = None, - period: str = None, - ): - - if not from_currency or not conv_currency: - response.status_code = status.HTTP_400_BAD_REQUEST - return { - 'status': status.HTTP_400_BAD_REQUEST, - 'message': 'The from_currency and conv_currency fields are required.', - } - - if period not in ['week', 'month', 'quarter', 'year']: - response.status_code = status.HTTP_400_BAD_REQUEST - return {'message': 'Invalid period.', 'status_code': status.HTTP_400_BAD_REQUEST} - - days, month, years = 0, 0, 0 - - if period == 'week': - days = -7 - elif period == 'month': - month = -1 - elif period == 'quarter': - month = -3 - elif period == 'year': - years = -1 - - end_date = datetime.datetime.now() - start_date = end_date + dateutil.relativedelta.relativedelta(months=month, days=days, years=years) - - chart = await create_chart(from_currency, - conv_currency, - start_date.strftime('%Y-%m-%d'), - end_date.strftime('%Y-%m-%d') - ) - - if not chart: - response.status_code = status.HTTP_404_NOT_FOUND - return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND} - - host = request.headers.get("host") - url_schema = request.url.scheme - - return { - 'status': status.HTTP_201_CREATED, - 'message': f'{url_schema}://{host}/static/charts/{chart}.png', - } - - -async def create_chart(from_currency: str, conv_currency: str, start_date: str, end_date: str) -> (str, None): - cur.execute('SELECT date, rate FROM currency WHERE (date BETWEEN %s AND %s) ' - 'AND from_currency = %s AND conv_currency = %s ORDER BY date', - [ - start_date, end_date, - from_currency.upper(), conv_currency.upper() - ]) - - con.commit() - data = cur.fetchall() - - if not data or len(data) <= 1: - return None - - date, rate = [], [] - - for i in range(len(data)): - date.append(str(data[i][0])) - rate.append(data[i][1]) - - if rate[0] < rate[-1]: - plt.plot(date, rate, color='green', marker='o') - elif rate[0] > rate[-1]: - plt.plot(date, rate, color='red', marker='o') - else: - plt.plot(date, rate, color='grey') - - plt.xlabel('Date') - plt.ylabel('Rate') - - fig = plt.gcf() - fig.set_size_inches(18.5, 9.5) - - name = await generate_unique_name( - f'{from_currency.upper()}_{conv_currency.upper()}', - datetime.datetime.now() - ) - - fig.savefig(f'../charts/{name}.png') - fig.clear() - - return name - - -async def generate_unique_name(currency_pair: str, date: datetime) -> str: - date_str = date.strftime("%Y%m%d") - random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) - unique_name = f"{currency_pair}_{date_str}_{random_suffix}" - - return unique_name +app.include_router(get_chart.router) +app.include_router(get_chart_period.router) if __name__ == '__main__': - uvicorn.run(app, - host=config['server']['host'], - port=3030, - ssl_keyfile=config['server']['ssl']['private_key'] - if config['server']['ssl']['work'] - else None, - ssl_certfile=config['server']['ssl']['cert'] - if config['server']['ssl']['work'] - else None - ) + + uvicorn.run( + app, + host=config['server']['host'], + port=3030, + ssl_keyfile=config['server']['ssl']['private_key'] if config['server']['ssl']['work'] else None, + ssl_certfile=config['server']['ssl']['cert'] if config['server']['ssl']['work'] else None + ) diff --git a/chart/routes/get_chart.py b/chart/routes/get_chart.py new file mode 100644 index 0000000..dc2e7b3 --- /dev/null +++ b/chart/routes/get_chart.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, status, Request, Response + +from chart.function.create_chart import create_chart +from chart.routes.get_chart_period import prepare_chart_response + +router = APIRouter() + +@router.get("/api/getChart/", status_code=status.HTTP_201_CREATED) +async def get_chart( + response: Response, + request: Request, + from_currency: str = None, + conv_currency: str = None, + start_date: str = None, + end_date: str = None, + ): + + if not from_currency or not conv_currency: + response.status_code = status.HTTP_400_BAD_REQUEST + return { + 'status': status.HTTP_400_BAD_REQUEST, + 'message': 'The from_currency and conv_currency fields are required.', + } + elif not start_date and not end_date: + response.status_code = status.HTTP_400_BAD_REQUEST + return { + 'status': status.HTTP_400_BAD_REQUEST, + 'message': 'The start_date and end_date fields are required.', + } + + + chart = await create_chart(from_currency, conv_currency, start_date, end_date) + + return await prepare_chart_response(response, request, chart) \ No newline at end of file diff --git a/chart/routes/get_chart_period.py b/chart/routes/get_chart_period.py new file mode 100644 index 0000000..658b2b1 --- /dev/null +++ b/chart/routes/get_chart_period.py @@ -0,0 +1,63 @@ +from datetime import datetime +import dateutil.relativedelta + +from fastapi import APIRouter, status, Request, Response + +from chart.function.create_chart import create_chart + +router = APIRouter() + +@router.get("/api/getChart/{period}", status_code=status.HTTP_201_CREATED) +async def get_chart_period( + response: Response, + request: Request, + from_currency: str = None, + conv_currency: str = None, + period: str = None, + ): + + if not from_currency or not conv_currency: + response.status_code = status.HTTP_400_BAD_REQUEST + return { + 'status': status.HTTP_400_BAD_REQUEST, + 'message': 'The from_currency and conv_currency fields are required.', + } + + if period not in ['week', 'month', 'quarter', 'year']: + response.status_code = status.HTTP_400_BAD_REQUEST + return {'message': 'Invalid period.', 'status_code': status.HTTP_400_BAD_REQUEST} + + days, month, years = 0, 0, 0 + + if period == 'week': + days = -7 + elif period == 'month': + month = -1 + elif period == 'quarter': + month = -3 + elif period == 'year': + years = -1 + + end_date = datetime.now() + start_date = end_date + dateutil.relativedelta.relativedelta(months=month, days=days, years=years) + + chart = await create_chart(from_currency, + conv_currency, + start_date.strftime('%Y-%m-%d'), + end_date.strftime('%Y-%m-%d') + ) + + return await prepare_chart_response(response, request, chart) + +async def prepare_chart_response(response: Response, request: Request, chart_name: str): + if not chart_name: + response.status_code = status.HTTP_404_NOT_FOUND + return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND} + + host = request.headers.get("host") + url_schema = request.url.scheme + + return { + 'status': status.HTTP_201_CREATED, + 'message': f'{url_schema}://{host}/static/charts/{chart_name}.png', + } \ No newline at end of file