import os from io import StringIO import json import pandas as pd from datetime import date, timedelta, datetime from typing import Annotated SavePathType = Annotated[str, "File path to save data. If None, data is not saved."] def save_output(data: pd.DataFrame, tag: str, save_path: SavePathType = None) -> None: if save_path: data.to_csv(save_path) print(f"{tag} saved to {save_path}") def get_current_date(): return date.today().strftime("%Y-%m-%d") def decorate_all_methods(decorator): def class_decorator(cls): for attr_name, attr_value in cls.__dict__.items(): if callable(attr_value): setattr(cls, attr_name, decorator(attr_value)) return cls return class_decorator def get_next_weekday(date): if not isinstance(date, datetime): date = datetime.strptime(date, "%Y-%m-%d") if date.weekday() >= 5: days_to_add = 7 - date.weekday() next_weekday = date + timedelta(days=days_to_add) return next_weekday else: return date def safe_read_csv(text, **kwargs): """ Try the default C engine first; on ParserError fall back to python engine and skip bad lines to avoid tokenizing errors from malformed rows. """ try: return pd.read_csv(StringIO(text), **kwargs) except pd.errors.ParserError: return pd.read_csv(StringIO(text), engine='python', on_bad_lines='skip', **kwargs)