from dataclasses import dataclass, field from typing import Dict, Set, Union from urllib.parse import urlparse @dataclass class SynapseConfig: """Store and validate configuration for Synapse API connection.""" homeserver: str access_token: str blacklist: Set[str] = field(default_factory=set) def __post_init__(self) -> None: """Validate Synapse config after initialization.""" if not self.homeserver.strip(): raise ValueError("Homeserver URL cannot be empty") parsed = urlparse(self.homeserver) if not all([parsed.scheme, parsed.netloc]): raise ValueError(f"Invalid homeserver URL: {self.homeserver}") if not self.access_token.strip(): raise ValueError("Access token cannot be empty") if not isinstance(self.blacklist, set): raise TypeError("Blacklist must be a set") for username in self.blacklist: if not isinstance(username, str) or not username.strip(): raise ValueError("Blacklist contains invalid username") @property def base_url(self) -> str: """Get normalized base URL with trailing slash.""" return self.homeserver.rstrip('/') + '/_synapse/admin' @property def headers(self) -> Dict[str, str]: """Generate request headers with authentication.""" return { "Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json", "Accept": "application/json" } def add_to_blacklist(self, *usernames: str) -> None: """Add one or more username to the blacklist.""" for username in usernames: username = username.strip() if not username: raise ValueError("Username cannot be empty") self.blacklist.add(username) def remove_from_blacklist(self, *usernames: str) -> None: """Remove one or more username from the blacklist.""" for username in usernames: self.blacklist.discard(username) @classmethod def from_dict(cls, data: Dict[str, str]) -> "SynapseConfig": """Create from dictionary with validation.""" try: blacklist = set(data.get("blacklist", [])) return cls( homeserver=str(data["homeserver"]), access_token=str(data["access_token"]), blacklist=blacklist ) except KeyError as e: raise ValueError(f"Missing required field: {e}") from e except (TypeError, ValueError) as e: raise ValueError(f"Invalid data type: {e}") from e def to_dict(self) -> Dict[str, Union[str, Set[str]]]: """Serialize to dictionary without sensitive exposure.""" return { "homeserver": self.homeserver, "access_token": self.access_token, "blacklist": sorted(self.blacklist) } def __str__(self) -> str: """Safe representation excluding access token.""" return (f"SynapseConfig(homeserver={self.homeserver!r}," f"blacklist={len(self.blacklist)})")