"""Telemetry client configuration.""" from __future__ import annotations import os import re from dataclasses import dataclass, field _HEX_64_RE = re.compile(r"^[0-9a-fA-F]{64}$") _UUID_RE = re.compile( r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" ) @dataclass class TelemetryConfig: """Configuration for the telemetry client. Values can be provided directly or loaded from environment variables: - MOSAIC_TELEMETRY_ENABLED -> enabled - MOSAIC_TELEMETRY_SERVER_URL -> server_url - MOSAIC_TELEMETRY_API_KEY -> api_key - MOSAIC_TELEMETRY_INSTANCE_ID -> instance_id """ server_url: str = "" api_key: str = "" instance_id: str = "" enabled: bool = True submit_interval_seconds: float = 300.0 max_queue_size: int = 1000 batch_size: int = 100 request_timeout_seconds: float = 10.0 prediction_cache_ttl_seconds: float = 21600.0 dry_run: bool = False max_retries: int = 3 user_agent: str = field(default="mosaicstack-telemetry-python/0.1.0") def __post_init__(self) -> None: """Load environment variable overrides and validate.""" env_enabled = os.environ.get("MOSAIC_TELEMETRY_ENABLED") if env_enabled is not None: self.enabled = env_enabled.lower() in ("1", "true", "yes") env_url = os.environ.get("MOSAIC_TELEMETRY_SERVER_URL") if env_url and not self.server_url: self.server_url = env_url env_key = os.environ.get("MOSAIC_TELEMETRY_API_KEY") if env_key and not self.api_key: self.api_key = env_key env_instance = os.environ.get("MOSAIC_TELEMETRY_INSTANCE_ID") if env_instance and not self.instance_id: self.instance_id = env_instance # Strip trailing slashes from server_url self.server_url = self.server_url.rstrip("/") def validate(self) -> list[str]: """Validate configuration and return list of errors (empty if valid).""" errors: list[str] = [] if not self.server_url: errors.append("server_url is required") elif not self.server_url.startswith(("http://", "https://")): errors.append("server_url must start with http:// or https://") if not self.api_key: errors.append("api_key is required") elif not _HEX_64_RE.match(self.api_key): errors.append("api_key must be a 64-character hex string") if not self.instance_id: errors.append("instance_id is required") elif not _UUID_RE.match(self.instance_id): errors.append("instance_id must be a valid UUID string") if self.submit_interval_seconds <= 0: errors.append("submit_interval_seconds must be positive") if self.max_queue_size <= 0: errors.append("max_queue_size must be positive") if self.batch_size <= 0 or self.batch_size > 100: errors.append("batch_size must be between 1 and 100") if self.request_timeout_seconds <= 0: errors.append("request_timeout_seconds must be positive") return errors