Skip to content

Exceptions

Every database failure Ferro raises is catchable by type. The tree is DBAPI-shaped, rooted at FerroError:

FerroError
├── InterfaceError          the database interface was misused
├── OperationalError        the database or its environment failed
├── DataError               a value could not be decoded or converted
├── IntegrityError          a constraint rejected the statement
│   ├── UniqueViolationError
│   ├── ForeignKeyViolationError
│   ├── NotNullViolationError
│   └── CheckViolationError
└── ModelDoesNotExist       (also a LookupError)

Catch a duplicate insert without matching driver text:

from ferro import UniqueViolationError

try:
    await User(email="[email protected]").save()
except UniqueViolationError as exc:
    print(exc.sqlstate)      # SQLSTATE "23505" on Postgres, result code "2067" on SQLite
    print(exc.constraint)    # violated constraint name (Postgres only)
    print(exc.driver_message)  # original driver text, for logs

ModelDoesNotExist is raised by primary-key lookups like Model.get(pk) when no row matches — use Model.get_or_none(pk) if you prefer None over an exception — and by save() on a persisted instance whose row no longer exists (see Saving: INSERT or UPDATE). It remains a LookupError, so pre-existing except LookupError handlers keep working.

FerroError

Bases: Exception

Root of Ferro's database exception hierarchy.

Attributes:

Name Type Description
driver_message str | None

Original message from the database driver, when the error originated in the database. None otherwise.

sqlstate str | None

Backend error code as reported by the driver: the five-character SQLSTATE (e.g. "23505") on Postgres, the extended result code (e.g. "2067") on SQLite. None when the driver reports none.

constraint str | None

Name of the violated constraint when the backend reports it (Postgres does; SQLite does not). None otherwise.

Source code in src/ferro/exceptions.py
class FerroError(Exception):
    """Root of Ferro's database exception hierarchy.

    Attributes:
        driver_message: Original message from the database driver, when the
            error originated in the database. ``None`` otherwise.
        sqlstate: Backend error code as reported by the driver: the
            five-character SQLSTATE (e.g. ``"23505"``) on Postgres, the
            extended result code (e.g. ``"2067"``) on SQLite. ``None`` when
            the driver reports none.
        constraint: Name of the violated constraint when the backend reports
            it (Postgres does; SQLite does not). ``None`` otherwise.
    """

    driver_message: str | None
    sqlstate: str | None
    constraint: str | None

    def __init__(
        self,
        message: str,
        *,
        driver_message: str | None = None,
        sqlstate: str | None = None,
        constraint: str | None = None,
    ) -> None:
        super().__init__(message)
        self.driver_message = driver_message
        self.sqlstate = sqlstate
        self.constraint = constraint

    def __reduce__(self):
        return (
            self.__class__,
            (str(self),),
            {
                "driver_message": self.driver_message,
                "sqlstate": self.sqlstate,
                "constraint": self.constraint,
            },
        )

Attributes

driver_message = driver_message instance-attribute

sqlstate = sqlstate instance-attribute

constraint = constraint instance-attribute

Functions

__init__(message, *, driver_message=None, sqlstate=None, constraint=None)

Source code in src/ferro/exceptions.py
def __init__(
    self,
    message: str,
    *,
    driver_message: str | None = None,
    sqlstate: str | None = None,
    constraint: str | None = None,
) -> None:
    super().__init__(message)
    self.driver_message = driver_message
    self.sqlstate = sqlstate
    self.constraint = constraint

__reduce__()

Source code in src/ferro/exceptions.py
def __reduce__(self):
    return (
        self.__class__,
        (str(self),),
        {
            "driver_message": self.driver_message,
            "sqlstate": self.sqlstate,
            "constraint": self.constraint,
        },
    )

InterfaceError

Bases: FerroError

The database interface was misused.

Raised for problems on the client side of the conversation: operating on an unknown or uninitialized connection name, driver protocol errors, or invalid connection configuration.

Source code in src/ferro/exceptions.py
class InterfaceError(FerroError):
    """The database interface was misused.

    Raised for problems on the client side of the conversation: operating on
    an unknown or uninitialized connection name, driver protocol errors, or
    invalid connection configuration.
    """

OperationalError

Bases: FerroError

The database or its environment failed.

Raised for failures outside the program's control: the server is unreachable, a connection pool timed out or is closed, or an I/O or TLS error interrupted the conversation.

Source code in src/ferro/exceptions.py
class OperationalError(FerroError):
    """The database or its environment failed.

    Raised for failures outside the program's control: the server is
    unreachable, a connection pool timed out or is closed, or an I/O or TLS
    error interrupted the conversation.
    """

DataError

Bases: FerroError

A value could not be decoded or converted.

Raised when a fetched value cannot be decoded into the expected Python type or a bound value cannot be represented for the column type.

Source code in src/ferro/exceptions.py
class DataError(FerroError):
    """A value could not be decoded or converted.

    Raised when a fetched value cannot be decoded into the expected Python
    type or a bound value cannot be represented for the column type.
    """

IntegrityError

Bases: FerroError

A database constraint rejected the statement.

Source code in src/ferro/exceptions.py
class IntegrityError(FerroError):
    """A database constraint rejected the statement."""

UniqueViolationError

Bases: IntegrityError

A UNIQUE or PRIMARY KEY constraint rejected the statement.

Source code in src/ferro/exceptions.py
class UniqueViolationError(IntegrityError):
    """A UNIQUE or PRIMARY KEY constraint rejected the statement."""

ForeignKeyViolationError

Bases: IntegrityError

A FOREIGN KEY constraint rejected the statement.

Source code in src/ferro/exceptions.py
class ForeignKeyViolationError(IntegrityError):
    """A FOREIGN KEY constraint rejected the statement."""

NotNullViolationError

Bases: IntegrityError

A NOT NULL constraint rejected the statement.

Source code in src/ferro/exceptions.py
class NotNullViolationError(IntegrityError):
    """A NOT NULL constraint rejected the statement."""

CheckViolationError

Bases: IntegrityError

A CHECK constraint rejected the statement.

Source code in src/ferro/exceptions.py
class CheckViolationError(IntegrityError):
    """A CHECK constraint rejected the statement."""

ModelDoesNotExist

Bases: FerroError, LookupError

Raised when :meth:~ferro.models.Model.get finds no row for the primary key, and when :meth:~ferro.models.Model.save on a persisted instance matches no row (the row was deleted underneath, or the primary key was mutated before saving).

Source code in src/ferro/exceptions.py
class ModelDoesNotExist(FerroError, LookupError):
    """Raised when :meth:`~ferro.models.Model.get` finds no row for the primary
    key, and when :meth:`~ferro.models.Model.save` on a persisted instance
    matches no row (the row was deleted underneath, or the primary key was
    mutated before saving)."""

    model: type
    pk: Any

    def __init__(self, model_cls: type, pk: Any) -> None:
        self.model = model_cls
        self.pk = pk
        super().__init__(
            f"No {model_cls.__name__} record found for primary key {pk!r}"
        )

    def __reduce__(self):
        return (self.__class__, (self.model, self.pk))

Attributes

model = model_cls instance-attribute

pk = pk instance-attribute

Functions

__init__(model_cls, pk)

Source code in src/ferro/exceptions.py
def __init__(self, model_cls: type, pk: Any) -> None:
    self.model = model_cls
    self.pk = pk
    super().__init__(
        f"No {model_cls.__name__} record found for primary key {pk!r}"
    )

__reduce__()

Source code in src/ferro/exceptions.py
def __reduce__(self):
    return (self.__class__, (self.model, self.pk))