refactor(chart): Added docstring in each file and function. Fixed long lines

This commit is contained in:
Данил 2024-12-15 10:51:32 +03:00
parent c5d3d9fe9b
commit ed8cf91558
7 changed files with 190 additions and 30 deletions

View file

@ -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:
"""
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(
user=config['database']['user'],
password=config['database']['password'],
@ -14,4 +32,4 @@ async def create_pool() -> asyncpg.pool.Pool:
max_size=20
)
return pool
return pool

View file

@ -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 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 ..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()
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:
"""
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:
datetime.strptime(date_str, '%Y-%m-%d')
return True

View file

@ -1,9 +1,23 @@
"""
This module provides a function to generate a unique name for chart files.
"""
import datetime
import random
import string
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")
random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
unique_name = f"{currency_pair}_{date_str}_{random_suffix}"

View file

@ -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 uvicorn
import yaml
from fastapi import FastAPI
from starlette.staticfiles import StaticFiles
from chart.middleware.plausible_analytics import PlausibleAnalytics
from chart.routes import get_chart, get_chart_period
from chart.utils.load_config import load_config
app = FastAPI()
config = yaml.safe_load(open('config.yaml'))
config = load_config('config.yaml')
if not os.path.exists('../charts'):
os.mkdir('../charts')
@ -23,11 +29,16 @@ 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
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
)

View file

@ -1,12 +1,25 @@
import httpx
import yaml
from user_agents import parse as ua_parse
"""
This module provides middleware for integrating Plausible Analytics
into a FastAPI application.
"""
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:
"""
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):
response = await call_next(request)
@ -23,8 +36,10 @@ class PlausibleAnalytics:
"props": {
"method": request.method,
"statusCode": response.status_code,
"browser": f"{user_agent_parsed.browser.family} {user_agent_parsed.browser.version_string}",
"os": f"{user_agent_parsed.os.family} {user_agent_parsed.os.version_string}",
"browser": f"{user_agent_parsed.browser.family} "
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'),
},
}
@ -40,7 +55,12 @@ class PlausibleAnalytics:
"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:
print(f"Error sending event to Plausible: {e}")
print(f"Unexpected error sending event to Plausible: {e}")
return response

View file

@ -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 pydantic import BaseModel
from chart.function.create_chart import create_chart
from chart.routes.get_chart_period import prepare_chart_response
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)
async def get_chart(
response: Response,
request: Request,
from_currency: str = None,
conv_currency: str = None,
start_date: str = None,
end_date: str = None,
params: ChartRequestParams
):
"""
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
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:
if not params.start_date or not params.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)
chart = await create_chart(
params.from_currency,
params.conv_currency,
params.start_date,
params.end_date
)
return await prepare_chart_response(response, request, chart)

View file

@ -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
import dateutil.relativedelta
from dateutil.relativedelta import relativedelta
from fastapi import APIRouter, status, Request, Response
@ -15,7 +22,20 @@ async def get_chart_period(
conv_currency: 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:
response.status_code = status.HTTP_400_BAD_REQUEST
return {
@ -39,7 +59,7 @@ async def get_chart_period(
years = -1
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(
from_currency,
@ -51,7 +71,22 @@ async def get_chart_period(
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:
response.status_code = status.HTTP_404_NOT_FOUND
return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND}