"""Custom exceptions for NLM CLI.""" from typing import Any class NLMError(Exception): """Base exception for all NLM errors.""" def __init__(self, message: str, hint: str | None = None) -> None: self.message = message self.hint = hint super().__init__(message) def __str__(self) -> str: if self.hint: return f"{self.message}\n\nHint: {self.hint}" return self.message class AuthenticationError(NLMError): """Raised when authentication fails or credentials are invalid.""" def __init__( self, message: str = "Authentication failed", hint: str = "Run 'nlm login' to authenticate.", ) -> None: super().__init__(message, hint) class NotFoundError(NLMError): """Raised when a requested resource is not found.""" def __init__( self, resource_type: str, resource_id: str, hint: str | None = None, ) -> None: message = f"{resource_type} not found: {resource_id}" if hint is None: hint = ( f"Run 'nlm {resource_type.lower()} list' to see available {resource_type.lower()}s." ) super().__init__(message, hint) self.resource_type = resource_type self.resource_id = resource_id class ValidationError(NLMError): """Raised when input validation fails.""" def __init__( self, message: str, field: str | None = None, hint: str | None = None, ) -> None: if field: message = f"Invalid {field}: {message}" super().__init__(message, hint) self.field = field class NetworkError(NLMError): """Raised when a network request fails.""" def __init__( self, message: str = "Network request failed", hint: str = "Check your internet connection and try again.", status_code: int | None = None, ) -> None: super().__init__(message, hint) self.status_code = status_code class RateLimitError(NLMError): """Raised when rate limit is exceeded.""" def __init__( self, message: str = "Rate limit exceeded", hint: str = "Please wait a moment and try again.", retry_after: int | None = None, ) -> None: super().__init__(message, hint) self.retry_after = retry_after class ConfigError(NLMError): """Raised when configuration is invalid.""" def __init__( self, message: str, hint: str = "Check your configuration at ~/.nlm/config.toml", ) -> None: super().__init__(message, hint) class ProfileNotFoundError(NLMError): """Raised when a profile is not found.""" def __init__(self, profile_name: str) -> None: message = f"Profile not found: {profile_name}" hint = "Run 'nlm login' to create a profile, or use '--profile ' to specify a different profile." super().__init__(message, hint) self.profile_name = profile_name class AccountMismatchError(NLMError): """Raised when trying to save credentials for a different account than what's stored.""" def __init__( self, stored_email: str, new_email: str, profile_name: str, ) -> None: message = ( f"Account mismatch for profile '{profile_name}': " f"stored account is '{stored_email}' but received credentials for '{new_email}'" ) hint = "Use 'nlm login --force' to overwrite with the new account." super().__init__(message, hint) self.stored_email = stored_email self.new_email = new_email self.profile_name = profile_name class FileUploadError(NLMError): """Raised when file upload fails.""" def __init__(self, filename: str, message: str = ""): self.filename = filename super().__init__( f"Failed to upload '{filename}': {message}" if message else f"Failed to upload '{filename}'" ) class FileValidationError(NLMError): """Raised when file validation fails before upload.""" pass def handle_api_error(status_code: int, response_data: dict[str, Any] | None = None) -> NLMError: """Convert API error response to appropriate exception.""" if status_code == 401: return AuthenticationError() if status_code == 403: return AuthenticationError( message="Access denied", hint="Your session may have expired. Run 'nlm login' to re-authenticate.", ) if status_code == 404: return NotFoundError("Resource", "unknown") if status_code == 429: return RateLimitError() if status_code >= 500: return NetworkError( message="NotebookLM server error", hint="The NotebookLM service may be temporarily unavailable. Try again later.", status_code=status_code, ) return NetworkError( message=f"Request failed with status {status_code}", status_code=status_code, )