Skip to content

watlowlib.errors

The typed exception hierarchy and ErrorContext dataclass. Every public-facing error inherits from WatlowError; protocol-specific errors carry per-protocol context (Std Bus class/member/instance, Modbus register address / function code) on error.context. See Troubleshooting for the common-error table.

Public surface

watlowlib.errors

Typed exception hierarchy for :mod:watlowlib.

Every library exception inherits from :class:WatlowError and carries a structured :class:ErrorContext. See docs/design.md §8.

ErrorContext selector fields are populated per-protocol:

  • Std Bus failures fill cls / member / instance.
  • Modbus failures fill register_address / function_code.

Wire-level fields (request / response / elapsed_s) are best- effort and may be None for failures raised before I/O.

:class:WatlowCapabilityWarning, :class:WatlowCapabilityError, and :class:WatlowFirmwareError are reserved as part of the planned capability-gate hierarchy (design §5). They are exported for callers that want to except on them without pinning a future minor; the library does not currently emit any of the three. Capability mismatches on writes (e.g. cool-side gains on a heat-only PM) currently surface as :class:WatlowConfigurationError.

ErrorContext dataclass

ErrorContext(
    command_name=None,
    protocol=None,
    port=None,
    address=None,
    parameter_id=None,
    cls=None,
    member=None,
    instance=None,
    register_address=None,
    function_code=None,
    request=None,
    response=None,
    elapsed_s=None,
    extra=_empty_extra(),
)

Structured context attached to every :class:WatlowError.

Fields are best-effort — missing data is None rather than raising.

extra accepts any Mapping and is always frozen into a read-only :class:types.MappingProxyType at construction so the shared empty sentinel can never be mutated through error.context.extra[k] = v.

merged

merged(**updates)

Return a new context with updates overlaid. Unknown keys go to extra.

Source code in src/watlowlib/errors.py
def merged(self, **updates: Any) -> Self:
    """Return a new context with ``updates`` overlaid. Unknown keys go to ``extra``."""
    known: dict[str, Any] = {}
    extra_updates: dict[str, Any] = {}
    for key, value in updates.items():
        if key in _CONTEXT_KNOWN_FIELDS:
            known[key] = value
        else:
            extra_updates[key] = value

    new_extra: Mapping[str, Any] = (
        MappingProxyType({**self.extra, **extra_updates}) if extra_updates else self.extra
    )
    return replace(self, **known, extra=new_extra)

WatlowCapabilityError

WatlowCapabilityError(message='', *, context=None)

Bases: WatlowError

Command is not available on this device / firmware / family.

Reserved for the planned capability-gate hierarchy. The library does not currently raise this — capability mismatches surface as :class:WatlowConfigurationError today (see commands/loop.py).

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowCapabilityWarning

Bases: UserWarning

Reserved warning class — not currently emitted.

Planned use is non-strict family-prior mismatches (attempt the command, warn, update availability from the device's response). The library does not currently emit this; the warning class is exported so callers can pre-register filters.

WatlowConfigurationError

WatlowConfigurationError(message='', *, context=None)

Bases: WatlowError

Configuration-level error (bad args, conflicting settings).

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowConfirmationRequiredError

WatlowConfirmationRequiredError(
    message="", *, context=None
)

Bases: WatlowConfigurationError

A PERSISTENT command was attempted without confirm=True.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowConnectionError

WatlowConnectionError(message='', *, context=None)

Bases: WatlowTransportError

Could not open / lost the connection to the device.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowError

WatlowError(message='', *, context=None)

Bases: Exception

Base class for every :mod:watlowlib exception.

Carries a typed :class:ErrorContext. The message is the human-readable summary; the context is the machine-readable detail.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

with_context

with_context(**updates)

Return a copy of this error with its context updated.

Useful when an inner layer raises and an outer layer wants to enrich the context (for instance adding port or elapsed_s).

Source code in src/watlowlib/errors.py
def with_context(self, **updates: Any) -> Self:
    """Return a copy of this error with its context updated.

    Useful when an inner layer raises and an outer layer wants to enrich
    the context (for instance adding ``port`` or ``elapsed_s``).
    """
    cls = type(self)
    new = cls.__new__(cls)
    new.args = self.args
    try:
        new.__dict__.update(self.__dict__)
    except AttributeError:  # pragma: no cover — no slotted subclass today
        for slot in getattr(cls, "__slots__", ()):
            if hasattr(self, slot):
                object.__setattr__(new, slot, getattr(self, slot))
    new.context = self.context.merged(**updates)
    new.__cause__ = self.__cause__
    new.__context__ = self.__context__
    new.__traceback__ = self.__traceback__
    return new

WatlowFirmwareError

WatlowFirmwareError(message='', *, context=None)

Bases: WatlowCapabilityError

Command is outside the supported firmware window.

Reserved alongside :class:WatlowCapabilityError; not currently emitted by the library.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowFrameError

WatlowFrameError(message='', *, context=None)

Bases: WatlowProtocolError

Bad CRC, bad length, malformed framing, etc.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowModbusError

WatlowModbusError(message='', *, context=None)

Bases: WatlowProtocolError

Base class for Modbus-layer errors.

Wraps every anymodbus exception so callers see one error hierarchy regardless of protocol. __cause__ preserves the original anymodbus exception for callers that need it.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowModbusIllegalDataAddressError

WatlowModbusIllegalDataAddressError(
    message="", *, context=None
)

Bases: WatlowModbusError, WatlowProtocolUnsupportedError

Modbus exception 0x02 — register address not allowed.

Maps to :class:Availability.UNSUPPORTED in the session cache.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowModbusIllegalDataValueError

WatlowModbusIllegalDataValueError(
    message="", *, context=None
)

Bases: WatlowModbusError

Modbus exception 0x03 — bad argument, not absence.

Does not affect availability (the parameter exists; the write value was simply rejected).

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowModbusIllegalFunctionError

WatlowModbusIllegalFunctionError(
    message="", *, context=None
)

Bases: WatlowModbusError, WatlowProtocolUnsupportedError

Modbus exception 0x01 — slave does not implement the function.

Maps to :class:Availability.UNSUPPORTED in the session cache. Inherits :class:WatlowProtocolUnsupportedError so the session's sticky-unsupported handling treats it like Std Bus 0x81 / 0x83.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowModbusSlaveFailureError

WatlowModbusSlaveFailureError(message='', *, context=None)

Bases: WatlowModbusError

Modbus exception 0x04 — unrecoverable slave-side error.

Does not affect availability — non-response is not a refusal of the parameter (per design §5b table).

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowModbusTimeoutError

WatlowModbusTimeoutError(message='', *, context=None)

Bases: WatlowModbusError, WatlowTimeoutError

Modbus request timed out at the protocol layer.

Inherits :class:WatlowTimeoutError so callers with existing timeout-handling code see this as a transport timeout. Does not affect availability.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowNoSuchAttributeError

WatlowNoSuchAttributeError(message='', *, context=None)

Bases: WatlowProtocolError

Standard Bus error 0x83 — valid class, invalid member.

Maps to :class:Availability.UNSUPPORTED in the session cache.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowNoSuchInstanceError

WatlowNoSuchInstanceError(message='', *, context=None)

Bases: WatlowProtocolError

Standard Bus error 0x84 — valid class+member, invalid instance.

The parameter exists but the requested loop / channel does not. Does not affect availability (a different instance may succeed).

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowNoSuchObjectError

WatlowNoSuchObjectError(message='', *, context=None)

Bases: WatlowProtocolError

Standard Bus error 0x81 — invalid class.

The device does not expose the requested object class. Maps to :class:Availability.UNSUPPORTED in the session cache.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowProtocolError

WatlowProtocolError(message='', *, context=None)

Bases: WatlowError

Protocol-level error (framing, parsing, unrecognised reply).

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowProtocolUnsupportedError

WatlowProtocolUnsupportedError(message='', *, context=None)

Bases: WatlowProtocolError

The active protocol cannot satisfy this command on this device.

Sticky for the session: subsequent attempts at the same command short-circuit with this error pre-I/O. Set on Std Bus 0x81 / 0x83 and on Modbus IllegalFunction / IllegalDataAddress per docs/design.md §5b.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowSinkDependencyError

WatlowSinkDependencyError(message='', *, context=None)

Bases: WatlowSinkError

An optional sink extra is not installed.

Raised by sinks behind a [parquet] / [postgres] extra when the backing dependency (pyarrow, asyncpg) is missing at :meth:open time. Instantiating the sink succeeds on bare-core installs; the dependency check is deferred so import-time errors don't break callers that never reach for the extra.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowSinkError

WatlowSinkError(message='', *, context=None)

Bases: WatlowError

Base class for :mod:watlowlib.sinks errors.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowSinkSchemaError

WatlowSinkSchemaError(message='', *, context=None)

Bases: WatlowSinkError

The target sink's existing schema does not match what the row carries.

Raised when a sink configured with create_table=False is pointed at a missing or incompatible backing schema.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowSinkWriteError

WatlowSinkWriteError(message='', *, context=None)

Bases: WatlowSinkError

A sink-backend write failed (CREATE TABLE, INSERT, file IO, ...).

__cause__ preserves the backend-native exception (sqlite3.Error, OSError, ...) for callers that want to inspect it.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowTimeoutError

WatlowTimeoutError(message='', *, context=None)

Bases: WatlowTransportError

A transport read or write timed out.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowTransportError

WatlowTransportError(message='', *, context=None)

Bases: WatlowError

I/O-layer error from the transport.

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT

WatlowValidationError

WatlowValidationError(message='', *, context=None)

Bases: WatlowConfigurationError

Request validation failed before I/O (bad instance, bad value).

Source code in src/watlowlib/errors.py
def __init__(self, message: str = "", *, context: ErrorContext | None = None) -> None:
    super().__init__(message)
    self.context = context if context is not None else _EMPTY_CONTEXT