Skip to content

Exceptions

SapRfcError

Bases: Exception

Common base for all saprfclib RFC errors (D-18).

Catching SapRfcError catches every error type the client raises: AbapApplicationError, AbapSystemFailure and CommunicationError.

Source code in src/saprfclib/exceptions.py
class SapRfcError(Exception):
    """Common base for all saprfclib RFC errors (D-18).

    Catching ``SapRfcError`` catches every error type the client raises:
    ``AbapApplicationError``, ``AbapSystemFailure`` and ``CommunicationError``.
    """

AbapApplicationError

Bases: SapRfcError

An ABAP-level application error returned by the called function module (D-15).

Mirrors the RFC_ERROR_INFO ABAP message fields for full pyrfc parity. Every field may be absent in the wire error and therefore defaults to None.

Source code in src/saprfclib/exceptions.py
class AbapApplicationError(SapRfcError):
    """An ABAP-level application error returned by the called function module (D-15).

    Mirrors the ``RFC_ERROR_INFO`` ABAP message fields for full pyrfc parity. Every
    field may be absent in the wire error and therefore defaults to ``None``.
    """

    def __init__(
        self,
        *,
        key: str | None = None,
        msg_class: str | None = None,
        msg_type: str | None = None,
        msg_number: str | None = None,
        msg_v1: str | None = None,
        msg_v2: str | None = None,
        msg_v3: str | None = None,
        msg_v4: str | None = None,
        message: str | None = None,
    ) -> None:
        self.key = key
        self.msg_class = msg_class
        self.msg_type = msg_type
        self.msg_number = msg_number
        self.msg_v1 = msg_v1
        self.msg_v2 = msg_v2
        self.msg_v3 = msg_v3
        self.msg_v4 = msg_v4
        self.message = message
        # Build a useful diagnostic string, omitting absent parts.
        #
        # When the server sends no assembled text the variables are all a caller
        # gets, and dropping them from the string throws away the only part that
        # says what actually went wrong. That is not an edge case: a classic
        # exception on kernel 793 carries the message class, number and variables
        # and NO free text at all -- the client is expected to build the sentence
        # from T100, which this library does not do. Reporting bare
        # 'FOUR_VARIABLES' while holding 'ALPHA1', 'BRAVO2', 'CHARLIE3' and
        # 'DELTA4' unread on the object was the common case, not the rare one.
        parts = [p for p in (key, message) if p]
        if message is None:
            if msg_class and msg_number:
                parts.append(
                    f"message {msg_class}/{msg_number}{f' type {msg_type}' if msg_type else ''}"
                )
            variables = [v for v in (msg_v1, msg_v2, msg_v3, msg_v4) if v]
            if variables:
                parts.append(" ".join(variables))
        super().__init__(": ".join(parts))

AbapSystemFailure

Bases: SapRfcError

An ABAP system failure / short dump on the backend (D-16).

Mirrors the RFC_ERROR_INFO ABAP message fields (same set as :class:AbapApplicationError, minus key which is absent for system failures). Every field may be absent in the wire error and defaults to None.

Source code in src/saprfclib/exceptions.py
class AbapSystemFailure(SapRfcError):
    """An ABAP system failure / short dump on the backend (D-16).

    Mirrors the ``RFC_ERROR_INFO`` ABAP message fields (same set as
    :class:`AbapApplicationError`, minus ``key`` which is absent for system
    failures). Every field may be absent in the wire error and defaults to
    ``None``.
    """

    def __init__(
        self,
        *,
        msg_class: str | None = None,
        msg_type: str | None = None,
        msg_number: str | None = None,
        msg_v1: str | None = None,
        msg_v2: str | None = None,
        msg_v3: str | None = None,
        msg_v4: str | None = None,
        message: str | None = None,
    ) -> None:
        self.msg_class = msg_class
        self.msg_type = msg_type
        self.msg_number = msg_number
        self.msg_v1 = msg_v1
        self.msg_v2 = msg_v2
        self.msg_v3 = msg_v3
        self.msg_v4 = msg_v4
        self.message = message
        super().__init__(message if message is not None else "")

CommunicationError

Bases: SapRfcError

A transport/network-level communication failure (D-17).

original_exception carries the underlying transport error (e.g. an OSError) when the failure originated below the RFC protocol layer.

Source code in src/saprfclib/exceptions.py
class CommunicationError(SapRfcError):
    """A transport/network-level communication failure (D-17).

    ``original_exception`` carries the underlying transport error (e.g. an
    ``OSError``) when the failure originated below the RFC protocol layer.
    """

    def __init__(
        self,
        message: str | None = None,
        *,
        original_exception: BaseException | None = None,
    ) -> None:
        self.message = message
        self.original_exception = original_exception
        super().__init__(message if message is not None else "")

TransactionalError

Bases: SapRfcError

A transactional RFC (tRFC/qRFC/bgRFC) error (D-18 / TRFC-08).

Raised when a TID-store operation fails, a duplicate TID is detected, or any tRFC/qRFC/bgRFC protocol invariant is violated. Subclasses :class:SapRfcError so except saprfclib.SapRfcError still catches it.

Source code in src/saprfclib/exceptions.py
class TransactionalError(SapRfcError):
    """A transactional RFC (tRFC/qRFC/bgRFC) error (D-18 / TRFC-08).

    Raised when a TID-store operation fails, a duplicate TID is detected,
    or any tRFC/qRFC/bgRFC protocol invariant is violated. Subclasses
    :class:`SapRfcError` so ``except saprfclib.SapRfcError`` still catches it.
    """

    def __init__(self, message: str | None = None) -> None:
        self.message = message
        super().__init__(message if message is not None else "")

SncError

Bases: SapRfcError

GSS-API / SNC handshake or frame error (Phase 7 SNC transport).

major and minor carry the OM_uint32 GSS status codes returned by the underlying SNC library. Subclasses :class:SapRfcError so callers can except saprfclib.SapRfcError uniformly (D-18).

Security (threat T-07-CRED): this class is NEVER populated from credential material. The snc_lib path, snc_myname, snc_partnername, GSS tokens, and any name/credential bytes must never enter the message, the major/minor fields, or the repr. Only the two GSS status codes (and, optionally, a caller-supplied non-credential message) are carried.

Source code in src/saprfclib/exceptions.py
class SncError(SapRfcError):
    """GSS-API / SNC handshake or frame error (Phase 7 SNC transport).

    ``major`` and ``minor`` carry the OM_uint32 GSS status codes returned by the
    underlying SNC library. Subclasses :class:`SapRfcError` so callers can
    ``except saprfclib.SapRfcError`` uniformly (D-18).

    Security (threat T-07-CRED): this class is NEVER populated from credential
    material. The ``snc_lib`` path, ``snc_myname``, ``snc_partnername``, GSS
    tokens, and any name/credential bytes must never enter the message, the
    ``major``/``minor`` fields, or the ``repr``. Only the two GSS status codes
    (and, optionally, a caller-supplied non-credential message) are carried.
    """

    def __init__(
        self,
        message: str | None = None,
        *,
        major: int | None = None,
        minor: int | None = None,
    ) -> None:
        self.message = message
        self.major = major
        self.minor = minor
        diagnostic = message or (f"GSS error major=0x{(major or 0):08x} minor=0x{(minor or 0):08x}")
        super().__init__(diagnostic)

WebSocketError

Bases: SapRfcError

WebSocket upgrade, framing, TLS, or HTTP-CONNECT-proxy error (Phase 7 wRFC).

Raised by :mod:saprfclib.ws when the RFC 6455 upgrade fails (bad status, wrong Sec-WebSocket-Accept, or a second redirect), when the HTTP CONNECT proxy tunnel is refused, or when a WebSocket protocol/close error occurs. Subclasses :class:SapRfcError so callers can except saprfclib.SapRfcError uniformly (D-18).

Security (threat T-07-PROXY-CRED): this class is NEVER populated from credential material. ws_proxy_pass and the Proxy-Authorization value must never enter the message or the repr. Proxy failures report only the HTTP status code — never the credential string.

Source code in src/saprfclib/exceptions.py
class WebSocketError(SapRfcError):
    """WebSocket upgrade, framing, TLS, or HTTP-CONNECT-proxy error (Phase 7 wRFC).

    Raised by :mod:`saprfclib.ws` when the RFC 6455 upgrade fails (bad status,
    wrong ``Sec-WebSocket-Accept``, or a second redirect), when the HTTP CONNECT
    proxy tunnel is refused, or when a WebSocket protocol/close error occurs.
    Subclasses :class:`SapRfcError` so callers can ``except saprfclib.SapRfcError``
    uniformly (D-18).

    Security (threat T-07-PROXY-CRED): this class is NEVER populated from
    credential material. ``ws_proxy_pass`` and the ``Proxy-Authorization`` value
    must never enter the message or the ``repr``. Proxy failures report only the
    HTTP status code — never the credential string.
    """

    def __init__(self, message: str | None = None) -> None:
        self.message = message
        super().__init__(message if message is not None else "")

PoolTimeoutError

Bases: SapRfcError

A :class:~saprfclib.pool.ConnectionPool could not lend a connection in time.

Raised by ConnectionPool.acquire() when every connection is in use and the acquire deadline elapses before one is released (POOL-04). Subclasses SapRfcError so callers can except saprfclib.SapRfcError uniformly (D-18).

The structured diagnostic fields aid debugging an exhausted pool:

  • waited — seconds the caller blocked before giving up.
  • discarded — connections found dead-on-ping and replaced during the wait.
  • active — connections currently lent out (len(in_use)).
  • idle — connections sitting idle (len(idle)).
  • max_size — the pool's hard ceiling on total connections.

Security (threat T-05-P03): the diagnostic message carries only these counts and never echoes the connection params (which hold credentials, T-04-CRED).

Source code in src/saprfclib/exceptions.py
class PoolTimeoutError(SapRfcError):
    """A :class:`~saprfclib.pool.ConnectionPool` could not lend a connection in time.

    Raised by ``ConnectionPool.acquire()`` when every connection is in use and the
    acquire deadline elapses before one is released (POOL-04). Subclasses
    ``SapRfcError`` so callers can ``except saprfclib.SapRfcError`` uniformly (D-18).

    The structured diagnostic fields aid debugging an exhausted pool:

    - ``waited`` — seconds the caller blocked before giving up.
    - ``discarded`` — connections found dead-on-ping and replaced during the wait.
    - ``active`` — connections currently lent out (``len(in_use)``).
    - ``idle`` — connections sitting idle (``len(idle)``).
    - ``max_size`` — the pool's hard ceiling on total connections.

    Security (threat T-05-P03): the diagnostic message carries only these counts
    and never echoes the connection ``params`` (which hold credentials, T-04-CRED).
    """

    def __init__(
        self,
        *,
        waited: float,
        discarded: int,
        active: int,
        idle: int,
        max_size: int,
        message: str | None = None,
    ) -> None:
        self.waited = waited
        self.discarded = discarded
        self.active = active
        self.idle = idle
        self.max_size = max_size
        diagnostic = message or (
            f"no pooled connection after {waited:.3f}s; discarded={discarded}; "
            f"active={active} idle={idle} max={max_size}"
        )
        self.message = diagnostic
        super().__init__(diagnostic)