327 lines
7.8 KiB
Python
327 lines
7.8 KiB
Python
"""
|
|
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]
|
|
} |