""" Helper functions for the API. Provides utility functions for data transformation, processing, and other common tasks. """ import uuid from datetime import datetime, timedelta from typing import Any, Dict, List, Optional import json def generate_uuid() -> str: """ Generate a new UUID string. Returns: str: A new UUID string. """ return str(uuid.uuid4()) def format_date(date_obj: datetime) -> str: """ Format a datetime object as a date string (YYYY-MM-DD). Args: date_obj: The datetime object to format. Returns: str: The formatted date string. """ return date_obj.strftime('%Y-%m-%d') def format_datetime(datetime_obj: datetime) -> str: """ Format a datetime object as an ISO string. Args: datetime_obj: The datetime object to format. Returns: str: The formatted datetime string. """ return datetime_obj.isoformat() def parse_date(date_str: str) -> Optional[datetime]: """ Parse a date string into a datetime object. Args: date_str: The date string to parse (YYYY-MM-DD). Returns: Optional[datetime]: The parsed datetime or None if invalid. """ if not date_str: return None try: return datetime.fromisoformat(date_str) except ValueError: return None def parse_datetime(datetime_str: str) -> Optional[datetime]: """ Parse a datetime string into a datetime object. Args: datetime_str: The datetime string to parse. Returns: Optional[datetime]: The parsed datetime or None if invalid. """ if not datetime_str: return None try: return datetime.fromisoformat(datetime_str) except ValueError: return None def to_camel_case(snake_str: str) -> str: """ Convert a snake_case string to camelCase. Args: snake_str: The snake_case string to convert. Returns: str: The camelCase string. """ components = snake_str.split('_') return components[0] + ''.join(x.title() for x in components[1:]) def to_snake_case(camel_str: str) -> str: """ Convert a camelCase string to snake_case. Args: camel_str: The camelCase string to convert. Returns: str: The snake_case string. """ import re # Use regex to find all capital letters and insert underscore before them s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str) return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() def convert_keys_to_camel_case(data: Dict[str, Any]) -> Dict[str, Any]: """ Convert all dictionary keys from snake_case to camelCase. Args: data: The dictionary with snake_case keys. Returns: Dict[str, Any]: A new dictionary with camelCase keys. """ if not isinstance(data, dict): return data result = {} for key, value in data.items(): if isinstance(value, dict): value = convert_keys_to_camel_case(value) elif isinstance(value, list): value = [ convert_keys_to_camel_case(item) if isinstance(item, dict) else item for item in value ] result[to_camel_case(key)] = value return result def convert_keys_to_snake_case(data: Dict[str, Any]) -> Dict[str, Any]: """ Convert all dictionary keys from camelCase to snake_case. Args: data: The dictionary with camelCase keys. Returns: Dict[str, Any]: A new dictionary with snake_case keys. """ if not isinstance(data, dict): return data result = {} for key, value in data.items(): if isinstance(value, dict): value = convert_keys_to_snake_case(value) elif isinstance(value, list): value = [ convert_keys_to_snake_case(item) if isinstance(item, dict) else item for item in value ] result[to_snake_case(key)] = value return result def filter_none_values(data: Dict[str, Any]) -> Dict[str, Any]: """ Remove all None values from a dictionary. Args: data: The dictionary to filter. Returns: Dict[str, Any]: A new dictionary without None values. """ return {k: v for k, v in data.items() if v is not None} def get_week_start_end(date: datetime) -> tuple: """ Get the start and end dates of the week containing the given date. Args: date: The date to get the week for. Returns: tuple: (start_date, end_date) of the week. """ # Monday is 0 and Sunday is 6 start = date - timedelta(days=date.weekday()) end = start + timedelta(days=6) return start, end def get_month_start_end(date: datetime) -> tuple: """ Get the start and end dates of the month containing the given date. Args: date: The date to get the month for. Returns: tuple: (start_date, end_date) of the month. """ start = date.replace(day=1) # Get the last day by going to next month and subtracting one day if date.month == 12: end = datetime(date.year + 1, 1, 1) - timedelta(days=1) else: end = datetime(date.year, date.month + 1, 1) - timedelta(days=1) return start, end def get_date_range(start_date: str, end_date: str) -> List[str]: """ Get a list of date strings between start_date and end_date (inclusive). Args: start_date: The start date string (YYYY-MM-DD). end_date: The end date string (YYYY-MM-DD). Returns: List[str]: List of date strings in the range. """ start = parse_date(start_date) end = parse_date(end_date) if not start or not end: return [] if start > end: return [] date_list = [] current = start while current <= end: date_list.append(format_date(current)) current += timedelta(days=1) return date_list def date_diff_in_days(start_date: str, end_date: str) -> int: """ Calculate the difference in days between two date strings. Args: start_date: The start date string (YYYY-MM-DD). end_date: The end date string (YYYY-MM-DD). Returns: int: The number of days between the dates. Returns 0 if dates are invalid. """ start = parse_date(start_date) end = parse_date(end_date) if not start or not end: return 0 return (end - start).days def dict_to_json(data: Dict[str, Any]) -> str: """ Convert a dictionary to a JSON string. Args: data: The dictionary to convert. Returns: str: The JSON string. """ return json.dumps(data, default=str) def json_to_dict(json_str: str) -> Dict[str, Any]: """ Convert a JSON string to a dictionary. Args: json_str: The JSON string to convert. Returns: Dict[str, Any]: The dictionary. Returns empty dict if JSON is invalid. """ try: return json.loads(json_str) except (json.JSONDecodeError, TypeError): return {} def paginate_results(items: List[Any], page: int = 1, page_size: int = 10) -> Dict[str, Any]: """ Paginate a list of items. Args: items: The list of items to paginate. page: The page number (1-based). page_size: The number of items per page. Returns: Dict[str, Any]: A dictionary with pagination info and results. """ if page < 1: page = 1 if page_size < 1: page_size = 10 total_items = len(items) total_pages = (total_items + page_size - 1) // page_size start_idx = (page - 1) * page_size end_idx = min(start_idx + page_size, total_items) return { 'page': page, 'page_size': page_size, 'total_items': total_items, 'total_pages': total_pages, 'has_previous': page > 1, 'has_next': page < total_pages, 'items': items[start_idx:end_idx] }