mirror of
https://github.com/Redume/Kekkai.git
synced 2025-02-23 12:43:12 +03:00
refactor(chart): Added docstring in each file and function. Fixed long lines
This commit is contained in:
parent
c5d3d9fe9b
commit
ed8cf91558
7 changed files with 190 additions and 30 deletions
|
@ -1,9 +1,27 @@
|
||||||
import asyncpg
|
"""
|
||||||
import yaml
|
This module initializes the database connection pool using asyncpg.
|
||||||
|
|
||||||
config = yaml.safe_load(open("config.yaml", "r"))
|
It reads database configuration from a YAML file and creates a connection pool
|
||||||
|
that can be used throughout the application for database operations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncpg
|
||||||
|
|
||||||
|
from chart.utils.load_config import load_config
|
||||||
|
|
||||||
|
config = load_config('config.yaml')
|
||||||
|
|
||||||
async def create_pool() -> asyncpg.pool.Pool:
|
async def create_pool() -> asyncpg.pool.Pool:
|
||||||
|
"""
|
||||||
|
Creates and returns a connection pool for the PostgreSQL database.
|
||||||
|
|
||||||
|
The function uses configuration settings loaded from a YAML file to
|
||||||
|
establish the connection pool. The pool allows multiple database
|
||||||
|
connections to be reused efficiently across the application.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
asyncpg.pool.Pool: The connection pool object.
|
||||||
|
"""
|
||||||
pool = await asyncpg.create_pool(
|
pool = await asyncpg.create_pool(
|
||||||
user=config['database']['user'],
|
user=config['database']['user'],
|
||||||
password=config['database']['password'],
|
password=config['database']['password'],
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
"""
|
||||||
|
This module provides functionality to generate currency rate charts
|
||||||
|
based on historical data retrieved from the database.
|
||||||
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from matplotlib import pyplot as plt
|
from matplotlib import pyplot as plt
|
||||||
|
@ -5,7 +10,29 @@ from matplotlib import pyplot as plt
|
||||||
from chart.function.gen_unique_name import generate_unique_name
|
from chart.function.gen_unique_name import generate_unique_name
|
||||||
from ..database.server import create_pool
|
from ..database.server import create_pool
|
||||||
|
|
||||||
async def create_chart(from_currency: str, conv_currency: str, start_date: str, end_date: str) -> (str, None):
|
async def create_chart(
|
||||||
|
from_currency: str,
|
||||||
|
conv_currency: str,
|
||||||
|
start_date: str,
|
||||||
|
end_date: str
|
||||||
|
) -> (str, None):
|
||||||
|
"""
|
||||||
|
Generates a line chart of currency rates for a given date range.
|
||||||
|
|
||||||
|
The chart shows the exchange rate trend between `from_currency` and
|
||||||
|
`conv_currency` within the specified `start_date` and `end_date` range.
|
||||||
|
The generated chart is saved as a PNG file, and the function returns the
|
||||||
|
file name. If data is invalid or insufficient, the function returns `None`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
from_currency (str): The base currency (e.g., "USD").
|
||||||
|
conv_currency (str): The target currency (e.g., "EUR").
|
||||||
|
start_date (str): The start date in the format 'YYYY-MM-DD'.
|
||||||
|
end_date (str): The end date in the format 'YYYY-MM-DD'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str | None: The name of the saved chart file, or `None` if the operation fails.
|
||||||
|
"""
|
||||||
pool = await create_pool()
|
pool = await create_pool()
|
||||||
|
|
||||||
if not validate_date(start_date) or not validate_date(end_date):
|
if not validate_date(start_date) or not validate_date(end_date):
|
||||||
|
@ -58,6 +85,15 @@ async def create_chart(from_currency: str, conv_currency: str, start_date: str,
|
||||||
|
|
||||||
|
|
||||||
def validate_date(date_str: str) -> bool:
|
def validate_date(date_str: str) -> bool:
|
||||||
|
"""
|
||||||
|
Validates whether the provided string is a valid date in the format 'YYYY-MM-DD'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date_str (str): The date string to validate.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: `True` if the string is a valid date, `False` otherwise.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
datetime.strptime(date_str, '%Y-%m-%d')
|
datetime.strptime(date_str, '%Y-%m-%d')
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -1,9 +1,23 @@
|
||||||
|
"""
|
||||||
|
This module provides a function to generate a unique name for chart files.
|
||||||
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
|
||||||
async def generate_unique_name(currency_pair: str, date: datetime) -> str:
|
async def generate_unique_name(currency_pair: str, date: datetime) -> str:
|
||||||
|
"""
|
||||||
|
Generates a unique name for a chart file based on the currency pair,
|
||||||
|
current date, and a random suffix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
currency_pair (str): A string representing the currency pair (e.g., "USD_EUR").
|
||||||
|
date (datetime.datetime): The current datetime object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A unique name in the format "CURRENCYPAIR_YYYYMMDD_RANDOMSUFFIX".
|
||||||
|
"""
|
||||||
date_str = date.strftime("%Y%m%d")
|
date_str = date.strftime("%Y%m%d")
|
||||||
random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
|
random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
|
||||||
unique_name = f"{currency_pair}_{date_str}_{random_suffix}"
|
unique_name = f"{currency_pair}_{date_str}_{random_suffix}"
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
|
"""
|
||||||
|
This is the main application file for the chart service using FastAPI.
|
||||||
|
|
||||||
|
The application serves static files, provides endpoints for generating charts,
|
||||||
|
and integrates with Plausible Analytics for tracking usage.
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
import yaml
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from starlette.staticfiles import StaticFiles
|
from starlette.staticfiles import StaticFiles
|
||||||
|
|
||||||
from chart.middleware.plausible_analytics import PlausibleAnalytics
|
from chart.middleware.plausible_analytics import PlausibleAnalytics
|
||||||
from chart.routes import get_chart, get_chart_period
|
from chart.routes import get_chart, get_chart_period
|
||||||
|
from chart.utils.load_config import load_config
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
config = yaml.safe_load(open('config.yaml'))
|
config = load_config('config.yaml')
|
||||||
|
|
||||||
if not os.path.exists('../charts'):
|
if not os.path.exists('../charts'):
|
||||||
os.mkdir('../charts')
|
os.mkdir('../charts')
|
||||||
|
@ -23,11 +29,16 @@ app.include_router(get_chart_period.router)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
app,
|
app,
|
||||||
host=config['server']['host'],
|
host=config['server']['host'],
|
||||||
port=3030,
|
port=3030,
|
||||||
ssl_keyfile=config['server']['ssl']['private_key'] if config['server']['ssl']['work'] else None,
|
ssl_keyfile=
|
||||||
ssl_certfile=config['server']['ssl']['cert'] if config['server']['ssl']['work'] else None
|
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
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,25 @@
|
||||||
import httpx
|
"""
|
||||||
import yaml
|
This module provides middleware for integrating Plausible Analytics
|
||||||
|
into a FastAPI application.
|
||||||
from user_agents import parse as ua_parse
|
"""
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
config = yaml.safe_load(open('../config.yaml'))
|
import httpx
|
||||||
|
from user_agents import parse as ua_parse
|
||||||
|
|
||||||
|
from chart.utils.load_config import load_config
|
||||||
|
|
||||||
|
config = load_config('config.yaml')
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
class PlausibleAnalytics:
|
class PlausibleAnalytics:
|
||||||
|
"""
|
||||||
|
Middleware for sending events to Plausible Analytics.
|
||||||
|
|
||||||
|
This middleware intercepts each incoming request, collects metadata such as
|
||||||
|
user-agent, request path, and response status, and sends it as an event to
|
||||||
|
Plausible Analytics.
|
||||||
|
"""
|
||||||
async def __call__(self, request, call_next):
|
async def __call__(self, request, call_next):
|
||||||
response = await call_next(request)
|
response = await call_next(request)
|
||||||
|
|
||||||
|
@ -23,8 +36,10 @@ class PlausibleAnalytics:
|
||||||
"props": {
|
"props": {
|
||||||
"method": request.method,
|
"method": request.method,
|
||||||
"statusCode": response.status_code,
|
"statusCode": response.status_code,
|
||||||
"browser": f"{user_agent_parsed.browser.family} {user_agent_parsed.browser.version_string}",
|
"browser": f"{user_agent_parsed.browser.family} "
|
||||||
"os": f"{user_agent_parsed.os.family} {user_agent_parsed.os.version_string}",
|
f"{user_agent_parsed.browser.version_string}",
|
||||||
|
"os": f"{user_agent_parsed.os.family} "
|
||||||
|
f"{user_agent_parsed.os.version_string}",
|
||||||
"source": request.headers.get('referer', 'direct'),
|
"source": request.headers.get('referer', 'direct'),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -40,7 +55,12 @@ class PlausibleAnalytics:
|
||||||
"User-Agent": request.headers.get('user-agent', 'unknown'),
|
"User-Agent": request.headers.get('user-agent', 'unknown'),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
print(f"Request error while sending event to Plausible: {e}")
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
print(f"HTTP status error while sending event to Plausible: {e}")
|
||||||
|
# pylint: disable=broad-exception-caught
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error sending event to Plausible: {e}")
|
print(f"Unexpected error sending event to Plausible: {e}")
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -1,34 +1,60 @@
|
||||||
|
"""
|
||||||
|
This module contains the route for retrieving a chart based on a given currency pair and date range.
|
||||||
|
It defines the `/api/getChart/` endpoint that processes requests for generating charts.
|
||||||
|
"""
|
||||||
from fastapi import APIRouter, status, Request, Response
|
from fastapi import APIRouter, status, Request, Response
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from chart.function.create_chart import create_chart
|
from chart.function.create_chart import create_chart
|
||||||
from chart.routes.get_chart_period import prepare_chart_response
|
from chart.routes.get_chart_period import prepare_chart_response
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
class ChartRequestParams(BaseModel):
|
||||||
|
"""
|
||||||
|
A Pydantic model that represents the request parameters for generating a chart.
|
||||||
|
|
||||||
|
This model is used to validate and group the request parameters:
|
||||||
|
from_currency, conv_currency, start_date, and end_date.
|
||||||
|
"""
|
||||||
|
from_currency: str
|
||||||
|
conv_currency: str
|
||||||
|
start_date: str
|
||||||
|
end_date: str
|
||||||
|
|
||||||
@router.get("/api/getChart/", status_code=status.HTTP_201_CREATED)
|
@router.get("/api/getChart/", status_code=status.HTTP_201_CREATED)
|
||||||
async def get_chart(
|
async def get_chart(
|
||||||
response: Response,
|
response: Response,
|
||||||
request: Request,
|
request: Request,
|
||||||
from_currency: str = None,
|
params: ChartRequestParams
|
||||||
conv_currency: str = None,
|
|
||||||
start_date: str = None,
|
|
||||||
end_date: str = None,
|
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Fetches a chart for a given currency pair and date range.
|
||||||
|
|
||||||
if not from_currency or not conv_currency:
|
:param response: The response object used for returning the HTTP response.
|
||||||
|
:param request: The request object containing details about the incoming request.
|
||||||
|
:param params: Contains the request parameters:
|
||||||
|
from_currency, conv_currency, start_date, and end_date.
|
||||||
|
:return: A chart or an error message if the request is invalid.
|
||||||
|
"""
|
||||||
|
if not params.from_currency or not params.conv_currency:
|
||||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||||
return {
|
return {
|
||||||
'status': status.HTTP_400_BAD_REQUEST,
|
'status': status.HTTP_400_BAD_REQUEST,
|
||||||
'message': 'The from_currency and conv_currency fields are required.',
|
'message': 'The from_currency and conv_currency fields are required.',
|
||||||
}
|
}
|
||||||
|
|
||||||
elif not start_date and not end_date:
|
if not params.start_date or not params.end_date:
|
||||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||||
return {
|
return {
|
||||||
'status': status.HTTP_400_BAD_REQUEST,
|
'status': status.HTTP_400_BAD_REQUEST,
|
||||||
'message': 'The start_date and end_date fields are required.',
|
'message': 'The start_date and end_date fields are required.',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chart = await create_chart(
|
||||||
chart = await create_chart(from_currency, conv_currency, start_date, end_date)
|
params.from_currency,
|
||||||
|
params.conv_currency,
|
||||||
|
params.start_date,
|
||||||
|
params.end_date
|
||||||
|
)
|
||||||
return await prepare_chart_response(response, request, chart)
|
return await prepare_chart_response(response, request, chart)
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
|
"""
|
||||||
|
This module defines the route for fetching a chart for a specific currency pair and period.
|
||||||
|
|
||||||
|
It includes the endpoint `/api/getChart/{period}` which allows users to request a chart for
|
||||||
|
a given currency pair (from_currency and conv_currency)
|
||||||
|
over a specified time period (week, month, quarter, or year).
|
||||||
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import dateutil.relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from fastapi import APIRouter, status, Request, Response
|
from fastapi import APIRouter, status, Request, Response
|
||||||
|
|
||||||
|
@ -15,7 +22,20 @@ async def get_chart_period(
|
||||||
conv_currency: str = None,
|
conv_currency: str = None,
|
||||||
period: str = None,
|
period: str = None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Fetches a chart for a given currency pair and a specific period.
|
||||||
|
|
||||||
|
The period can be one of the following: 'week', 'month', 'quarter', 'year'.
|
||||||
|
Based on the selected period, it calculates the start date and retrieves the chart data.
|
||||||
|
|
||||||
|
:param response: The response object used to set status and message.
|
||||||
|
:param request: The request object used to retrieve details of the incoming request.
|
||||||
|
:param from_currency: The base currency in the pair (e.g., 'USD').
|
||||||
|
:param conv_currency: The target currency in the pair (e.g., 'EUR').
|
||||||
|
:param period: The time period for which the chart is requested
|
||||||
|
(e.g., 'week', 'month', 'quarter', 'year').
|
||||||
|
:return: A response containing the chart URL or an error message if parameters are invalid.
|
||||||
|
"""
|
||||||
if not from_currency or not conv_currency:
|
if not from_currency or not conv_currency:
|
||||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||||
return {
|
return {
|
||||||
|
@ -39,7 +59,7 @@ async def get_chart_period(
|
||||||
years = -1
|
years = -1
|
||||||
|
|
||||||
end_date = datetime.now()
|
end_date = datetime.now()
|
||||||
start_date = end_date + dateutil.relativedelta.relativedelta(months=month, days=days, years=years)
|
start_date = end_date + relativedelta(months=month, days=days, years=years)
|
||||||
|
|
||||||
chart = await create_chart(
|
chart = await create_chart(
|
||||||
from_currency,
|
from_currency,
|
||||||
|
@ -51,7 +71,22 @@ async def get_chart_period(
|
||||||
return await prepare_chart_response(response, request, chart)
|
return await prepare_chart_response(response, request, chart)
|
||||||
|
|
||||||
|
|
||||||
async def prepare_chart_response(response: Response, request: Request, chart_name: str):
|
async def prepare_chart_response(
|
||||||
|
response: Response,
|
||||||
|
request: Request,
|
||||||
|
chart_name: str
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Prepares the response to return the URL of the generated chart.
|
||||||
|
|
||||||
|
If the chart data is not found, it returns a 404 error with an appropriate message.
|
||||||
|
Otherwise, it returns a URL to access the chart image.
|
||||||
|
|
||||||
|
:param response: The response object used to set status and message.
|
||||||
|
:param request: The request object used to retrieve details of the incoming request.
|
||||||
|
:param chart_name: The name of the generated chart (used to build the URL).
|
||||||
|
:return: A dictionary with the chart URL or an error message if no chart is found.
|
||||||
|
"""
|
||||||
if not chart_name:
|
if not chart_name:
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND}
|
return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND}
|
||||||
|
|
Loading…
Add table
Reference in a new issue