"""Helper functions for Habiticalib.""" from dataclasses import asdict, is_dataclass from datetime import date, datetime from enum import Enum import platform from typing import Any import uuid import aiohttp from .const import DEVELOPER_ID, __version__ from .typedefs import Avatar, UserData def join_fields(user_fields: list[str] | str) -> str: """Join user fields into a comma-separated string. This method takes a list of user fields or a single string and returns a string. If a list is provided, it joins the elements with a comma separator. If a string is provided, it returns the string as-is. Parameters ---------- user_fields : list of str or str A list of user field strings or a single user field string. Returns ------- str A comma-separated string of user fields if `user_fields` is a list, or the original string if `user_fields` is a single string. """ return ",".join(user_fields) if isinstance(user_fields, list) else str(user_fields) def get_user_agent() -> str: """Generate User-Agent string. The User-Agent string contains details about the operating system, its version, architecture, the habiticalib version, aiohttp version, and Python version. Returns ------- str A User-Agent string with OS details, library versions, and a project URL. Examples -------- >>> client.get_user_agent() 'Habiticalib/0.0.0 (Windows 11 (10.0.22000); 64bit) aiohttp/3.10.9 Python/3.12.7 +https://github.com/tr4nt0r/habiticalib)' """ os_name = platform.system() os_version = platform.version() os_release = platform.release() arch, _ = platform.architecture() os_info = f"{os_name} {os_release} ({os_version}); {arch}" return ( f"Habiticalib/{__version__} ({os_info}) " f"aiohttp/{aiohttp.__version__} Python/{platform.python_version()} " " +https://github.com/tr4nt0r/habiticalib)" ) def get_x_client(x_client: str | None = None) -> str: """Generate the x-client header string. If a valid `x_client` is provided, it validates the User ID (UUID format). If no `x_client` is provided, it generates a default x-client header string. Parameters ---------- x_client : str or None, optional A client identifier containing a UUID (first 36 characters) or None. If None, a default x-client header will be generated. Returns ------- str The validated x-client header string or habiticalib's default x-client header. Raises ------ ValueError If the provided `x_client` is not in a valid UUID format. Examples -------- >>> get_x_client("123e4567-e89b-12d3-a456-426614174000 - MyHabiticaApp") '123e4567-e89b-12d3-a456-426614174000 - MyHabiticaApp' >>> get_x_client() '4c4ca53f-c059-4ffa-966e-9d29dd405daf - Habiticalib/0.0.0' """ if x_client: try: uuid.UUID(x_client[:36]) except ValueError as e: msg = ( "Invalid User ID provided in x-client. Expected a valid " "UUID format. Please ensure the User ID is correct" ) raise ValueError(msg) from e return x_client return f"{DEVELOPER_ID} - Habiticalib/{__version__}" def extract_avatar(user_data: UserData) -> Avatar: """Extract avatar defs from a user data object.""" return Avatar.from_dict(user_data.to_dict()) def deserialize_task(value: Any) -> Any: # noqa: PLR0911 """Recursively convert Enums to values, dates to ISO strings, UUIDs to strings.""" if is_dataclass(value) and not isinstance(value, type): # Convert dataclass to dict and recursively deserialize return deserialize_task(asdict(value)) if isinstance(value, Enum): return value.value # Convert Enum to its value if isinstance(value, uuid.UUID): return str(value) # Convert UUID to string if isinstance(value, datetime | date): return value.isoformat() # Convert datetime/date to ISO string if isinstance(value, list): # Recursively apply deserialization to each item in the list return [deserialize_task(item) for item in value] if isinstance(value, dict): # Recursively apply deserialization to each key-value pair in the dictionary return {k: deserialize_task(v) for k, v in value.items()} return value # Return other types unchanged