import datetime
from jinja2 import Environment, FileSystemLoader
from loguru import logger
from typing import Any, Union
from . import TEMPLATE_DIR, SCRIPTS_DIR
#----------------------------------------------------------------------------------------------------------------------#
# region COLOR MAP
#----------------------------------------------------------------------------------------------------------------------#
[docs]
class ColorMap:
""" Color map object for handling the color space of the report """
def __init__(self, config: dict[str, Any]):
""" Initialize a new color map given a config dict
Args:
config (dict[str, Any]): The configuration for this report. Mainly responsible for defining new color
profiles that can later be used
"""
self.config = config
# Default color map - Maps the default bootstrap colors to their respective color names
self.cm = {
'blue': 'primary',
'gray': 'secondary',
'green': 'success',
'red': 'danger',
'yellow': 'warning',
'teal': 'info',
'black': 'dark',
'primary': 'primary',
'secondary': 'secondary',
'success': 'success',
'danger': 'danger',
'warning': 'warning',
'info': 'info',
'dark': 'dark',
'light': 'light',
}
# Create the custom color CSS that will be injected and make the profiles available in the color map
self._make_custom_color_css()
for cc in self.config['custom_colors']:
self.cm[cc] = cc
#---------------#
# Magic Methods #
#---------------#
def __getitem__(self, key: str) -> str:
""" Verify the color name and return the proper color mapping """
return self.verify_and_get_color(key)
#---------#
# Private #
#---------#
def _make_custom_color_css(self):
""" Create the custom color css file """
# Prepare the jinja templater
file_loader = FileSystemLoader(TEMPLATE_DIR)
jinja_env = Environment(loader=file_loader)
# Create CSS string
color_items = [v | {'name': k} for k,v in self.config['custom_colors'].items()]
template = jinja_env.get_template('colors.html')
css = template.render(color_items=color_items)
# Dump to scripts dir
custom_color_file = SCRIPTS_DIR / 'custom_colors.css'
custom_color_file.write_text(css)
#-----#
# API #
#-----#
[docs]
def verify_and_get_color(self, color: str) -> str:
""" Check that the requested color is actually available
Args:
color (str): Color to check
Returns:
str - color name to use
"""
if color not in self.cm:
msg = f'No color `{color}` found. Available: {self.cm.keys()}'
logger.error(msg)
raise RuntimeError(msg)
return self.cm[color]
[docs]
def get_default_color(self, field: str, color_mode: str) -> str:
""" Determine the correct default color for a given color mode and field
Args:
field (str): The type/field the color should be used for
color_mode (str): The color mode of the report, i.e., either `light` or `dark`
Returns:
str - The color to be used
"""
color = self.config['default_colors'].get(field, {}).get(color_mode, None)
if color is None:
msg = f'No default color defined for `{field}` with color mode `{color_mode}`'
logger.error(msg)
raise RuntimeError(msg)
return color
[docs]
def get_site_colors(self, color_mode: str):
""" Get the background color for the website depending on the color mode """
return self.config['site'][color_mode]
#----------------------------------------------------------------------------------------------------------------------#
# region MISC HELPER
#----------------------------------------------------------------------------------------------------------------------#
[docs]
def coerce_to_date(date: Union[str, int, datetime.date, datetime.datetime]) -> datetime.date:
""" Coerce random data formats to a datetime.date object
Args:
date (Union[str, int, datetime.date, datetime.datetime]): The date to coerce
Returns:
datetime.date - The coerce date object
"""
if isinstance(date, datetime.date):
return date
elif isinstance(date, datetime.datetime):
return date.date
elif isinstance(date, int):
date = str(int)
if not isinstance(date, str):
msg = f'Cannot coerce date {date}'
logger.error(msg)
raise RuntimeError(msg)
if len(date) == 6: # noqa: PLR2004
year = int(date[:2])
if year < 50: # noqa: PLR2004
year += 2000
else:
year += 1900
month = int(date[2:4])
day = int(date[4:])
elif len(date) == 8: # noqa: PLR2004
if date.isdigit():
year = int(date[:4])
month = int(date[4:6])
day = int(date[6:])
else:
year = int(date[:2])
if year < 50: # noqa: PLR2004
year += 2000
else:
year += 1900
month = int(date[3:5])
day = int(date[6:])
elif len(date) == 10: # noqa: PLR2004
year = int(date[:4])
month = int(date[5:7])
day = int(date[8:])
return datetime.date(year, month, day)
[docs]
def verify_alignment(align: str) -> str:
""" Check that the requested alignment is actually available
Args:
align (str): The alignment tag that needs to be verified
Returns:
str - The alignment if it is acceptable
"""
alignments = ['left', 'center', 'right']
if align not in alignments:
msg = f'Unknown alignment {align}. Available: {alignments}'
logger.error(msg)
raise RuntimeError(msg)
return align
[docs]
def verify_color_mode(color_mode: str) -> str:
""" Check that the requested alignment is actually available
Args:
color_mode (str): The alignment tag that needs to be verified
Returns:
str - The alignment if it is acceptable
"""
color_modes = ['light', 'dark']
if color_mode not in color_modes:
msg = f'Unknown color_mode {color_mode}. Available: {color_modes}'
logger.error(msg)
raise RuntimeError(msg)
return color_mode
[docs]
def default(var: Any, default: Any) -> Any:
""" Helper function to check kwargs and set them to a default
You could simply use var or default. But for future additions we will use a separat function here
Args:
var (Any): The input variable
default (Any): Default value for the variable
Returns
Any - Either the variable itself, or the default value if var is `None`
"""
if var is None:
return default
else:
return var