Source code for sqlspec.exceptions

from collections.abc import Generator
from contextlib import contextmanager
from typing import Any, Final

STACK_SQL_PREVIEW_LIMIT: Final[int] = 120

__all__ = (
    "CheckViolationError",
    "ConfigResolverError",
    "DataError",
    "DatabaseConnectionError",
    "DialectNotSupportedError",
    "FileNotFoundInStorageError",
    "ForeignKeyViolationError",
    "ImproperConfigurationError",
    "IntegrityError",
    "InvalidVersionFormatError",
    "MigrationError",
    "MissingDependencyError",
    "MultipleResultsFoundError",
    "NotFoundError",
    "NotNullViolationError",
    "OperationalError",
    "OutOfOrderMigrationError",
    "RepositoryError",
    "SQLBuilderError",
    "SQLConversionError",
    "SQLFileNotFoundError",
    "SQLFileParseError",
    "SQLParsingError",
    "SQLSpecError",
    "SerializationError",
    "StackExecutionError",
    "StorageCapabilityError",
    "StorageOperationFailedError",
    "TransactionError",
    "UniqueViolationError",
)


class SQLSpecError(Exception):
    """Base exception class for SQLSpec exceptions."""

    detail: str

    def __init__(self, *args: Any, detail: str = "") -> None:
        """Initialize SQLSpecError.

        Args:
            *args: args are converted to :class:`str` before passing to :class:`Exception`
            detail: detail of the exception.
        """
        str_args = [str(arg) for arg in args if arg]
        if not detail:
            if str_args:
                detail, *str_args = str_args
            elif hasattr(self, "detail"):
                detail = self.detail
        self.detail = detail
        super().__init__(*str_args)

    def __repr__(self) -> str:
        if self.detail:
            return f"{self.__class__.__name__} - {self.detail}"
        return self.__class__.__name__

    def __str__(self) -> str:
        return " ".join((*self.args, self.detail)).strip()


class MissingDependencyError(SQLSpecError, ImportError):
    """Raised when a required dependency is not installed."""

    def __init__(self, package: str, install_package: str | None = None) -> None:
        super().__init__(
            f"Package {package!r} is not installed but required. You can install it by running "
            f"'pip install sqlspec[{install_package or package}]' to install sqlspec with the required extra "
            f"or 'pip install {install_package or package}' to install the package separately"
        )


class BackendNotRegisteredError(SQLSpecError):
    """Raised when a requested storage backend key is not registered."""

    def __init__(self, backend_key: str) -> None:
        super().__init__(f"Storage backend '{backend_key}' is not registered. Please register it before use.")


class ConfigResolverError(SQLSpecError):
    """Exception raised when config resolution fails."""


class SQLParsingError(SQLSpecError):
    """Issues parsing SQL statements."""

    def __init__(self, message: str | None = None) -> None:
        if message is None:
            message = "Issues parsing SQL statement."
        super().__init__(message)


class SQLBuilderError(SQLSpecError):
    """Issues Building or Generating SQL statements."""

    def __init__(self, message: str | None = None) -> None:
        if message is None:
            message = "Issues building SQL statement."
        super().__init__(message)


class SQLConversionError(SQLSpecError):
    """Issues converting SQL statements."""

    def __init__(self, message: str | None = None) -> None:
        if message is None:
            message = "Issues converting SQL statement."
        super().__init__(message)


class ImproperConfigurationError(SQLSpecError):
    """Raised when configuration is invalid or incomplete."""


class DialectNotSupportedError(SQLBuilderError):
    """Raised when a SQL dialect does not support a specific feature."""


class SerializationError(SQLSpecError):
    """Encoding or decoding of an object failed."""


class RepositoryError(SQLSpecError):
    """Base repository exception type."""


class IntegrityError(RepositoryError):
    """Data integrity error."""


class NotFoundError(RepositoryError):
    """An identity does not exist."""


class MultipleResultsFoundError(RepositoryError):
    """A single database result was required but more than one were found."""


class UniqueViolationError(IntegrityError):
    """A unique constraint was violated."""


class ForeignKeyViolationError(IntegrityError):
    """A foreign key constraint was violated."""


class CheckViolationError(IntegrityError):
    """A check constraint was violated."""


class NotNullViolationError(IntegrityError):
    """A not-null constraint was violated."""


class DatabaseConnectionError(SQLSpecError):
    """Database connection error (invalid credentials, network failure, etc.)."""


class TransactionError(SQLSpecError):
    """Transaction error (rollback, deadlock, serialization failure)."""


class DataError(SQLSpecError):
    """Invalid data type or format for database operation."""


[docs] class StackExecutionError(SQLSpecError): """Raised when a statement stack operation fails."""
[docs] def __init__( self, operation_index: int, sql: str, original_error: Exception, *, adapter: str | None = None, mode: str = "fail-fast", native_pipeline: bool | None = None, downgrade_reason: str | None = None, ) -> None: pipeline_state = "enabled" if native_pipeline else "disabled" adapter_label = adapter or "unknown-adapter" preview = " ".join(sql.strip().split()) if len(preview) > STACK_SQL_PREVIEW_LIMIT: preview = f"{preview[: STACK_SQL_PREVIEW_LIMIT - 3]}..." detail = ( f"Stack operation {operation_index} failed on {adapter_label} " f"(mode={mode}, pipeline={pipeline_state}) sql={preview}" ) super().__init__(detail) self.operation_index = operation_index self.sql = sql self.original_error = original_error self.adapter = adapter self.mode = mode self.native_pipeline = native_pipeline self.downgrade_reason = downgrade_reason
def __str__(self) -> str: base = super().__str__() return f"{base}: {self.original_error}" if self.original_error else base
class OperationalError(SQLSpecError): """Operational database error (timeout, disk full, resource limit).""" class StorageOperationFailedError(SQLSpecError): """Raised when a storage backend operation fails (e.g., network, permission, API error).""" class StorageCapabilityError(SQLSpecError): """Raised when a requested storage bridge capability is unavailable.""" def __init__(self, message: str, *, capability: str | None = None, remediation: str | None = None) -> None: parts = [message] if capability: parts.append(f"(capability: {capability})") if remediation: parts.append(remediation) detail = " ".join(parts) super().__init__(detail) self.capability = capability self.remediation = remediation class FileNotFoundInStorageError(StorageOperationFailedError): """Raised when a file or object is not found in the storage backend.""" class SQLFileNotFoundError(SQLSpecError): """Raised when a SQL file cannot be found.""" def __init__(self, name: str, path: "str | None" = None) -> None: """Initialize the error. Args: name: Name of the SQL file. path: Optional path where the file was expected. """ message = f"SQL file '{name}' not found at path: {path}" if path else f"SQL file '{name}' not found" super().__init__(message) self.name = name self.path = path class SQLFileParseError(SQLSpecError): """Raised when a SQL file cannot be parsed.""" def __init__(self, name: str, path: str, original_error: "Exception") -> None: """Initialize the error. Args: name: Name of the SQL file. path: Path to the SQL file. original_error: The underlying parsing error. """ message = f"Failed to parse SQL file '{name}' at {path}: {original_error}" super().__init__(message) self.name = name self.path = path self.original_error = original_error class MigrationError(SQLSpecError): """Base exception for migration-related errors.""" class InvalidVersionFormatError(MigrationError): """Raised when a migration version format is invalid. Invalid formats include versions that don't match sequential (0001) or timestamp (YYYYMMDDHHmmss) patterns, or timestamps with invalid dates. """ class OutOfOrderMigrationError(MigrationError): """Raised when an out-of-order migration is detected in strict mode. Out-of-order migrations occur when a pending migration has a timestamp earlier than already-applied migrations, typically from late-merging branches. """ @contextmanager def wrap_exceptions( wrap_exceptions: bool = True, suppress: "type[Exception] | tuple[type[Exception], ...] | None" = None ) -> Generator[None, None, None]: """Context manager for exception handling with optional suppression. Args: wrap_exceptions: If True, wrap exceptions in RepositoryError. If False, let them pass through. suppress: Exception type(s) to suppress completely (like contextlib.suppress). If provided, these exceptions are caught and ignored. """ try: yield except Exception as exc: if suppress is not None and ( (isinstance(suppress, type) and isinstance(exc, suppress)) or (isinstance(suppress, tuple) and isinstance(exc, suppress)) ): return if isinstance(exc, SQLSpecError): raise if wrap_exceptions is False: raise msg = "An error occurred during the operation." raise RepositoryError(detail=msg) from exc