Skip to content

Commit a144ead

Browse files
Inherit dqliteclient.ProtocolError from the wire-layer one
Two unrelated classes named ProtocolError lived in dqlitewire and dqliteclient with no class relationship. A caller writing ``except ProtocolError:`` caught only the one whose module they imported, which is an ergonomic trap at layering boundaries. Make the client class inherit from both DqliteError (unchanged) and dqlitewire.exceptions.ProtocolError so ``except dqlitewire.ProtocolError`` catches both variants and ``except dqliteclient.DqliteError`` keeps working. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e261652 commit a144ead

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

src/dqliteclient/exceptions.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Exceptions for dqlite client."""
22

3+
from dqlitewire.exceptions import ProtocolError as _WireProtocolError
4+
35

46
class DqliteError(Exception):
57
"""Base exception for dqlite client errors."""
@@ -9,8 +11,18 @@ class DqliteConnectionError(DqliteError):
911
"""Error establishing or maintaining connection."""
1012

1113

12-
class ProtocolError(DqliteError):
13-
"""Protocol-level error."""
14+
class ProtocolError(DqliteError, _WireProtocolError):
15+
"""Protocol-level error.
16+
17+
Inherits from both the client's ``DqliteError`` hierarchy (so
18+
``except DqliteError`` still catches it) and the wire-layer
19+
``dqlitewire.exceptions.ProtocolError`` (so a caller who wants to
20+
catch protocol-level problems at *any* layer can write
21+
``except dqlitewire.ProtocolError`` and get both the wire- and
22+
client-level variants). Without the dual parent, the two identically
23+
named classes caught only half the surface depending on which module
24+
the caller imported.
25+
"""
1426

1527

1628
class InterfaceError(DqliteError):

tests/test_connection.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,3 +1035,39 @@ async def hang_forever() -> None:
10351035
# No outer timeout: the internal 0.5s wait_for expires, the
10361036
# resulting TimeoutError is suppressed, and the call returns.
10371037
await conn._abort_protocol()
1038+
1039+
1040+
class TestProtocolErrorHierarchy:
1041+
"""Cross-layer exception catching works: dqliteclient.ProtocolError
1042+
is a subclass of BOTH dqliteclient.DqliteError and
1043+
dqlitewire.exceptions.ProtocolError, so callers can catch either
1044+
ancestor and get both variants.
1045+
"""
1046+
1047+
def test_client_protocol_error_subclass_of_wire_version(self) -> None:
1048+
import dqlitewire.exceptions
1049+
from dqliteclient.exceptions import ProtocolError
1050+
1051+
assert issubclass(ProtocolError, dqlitewire.exceptions.ProtocolError)
1052+
1053+
def test_client_protocol_error_subclass_of_dqlite_error(self) -> None:
1054+
from dqliteclient.exceptions import DqliteError, ProtocolError
1055+
1056+
assert issubclass(ProtocolError, DqliteError)
1057+
1058+
def test_except_wire_protocol_error_catches_client_variant(self) -> None:
1059+
import pytest as _pytest
1060+
1061+
import dqlitewire.exceptions
1062+
from dqliteclient.exceptions import ProtocolError
1063+
1064+
with _pytest.raises(dqlitewire.exceptions.ProtocolError):
1065+
raise ProtocolError("boom")
1066+
1067+
def test_except_dqlite_error_still_catches_client_variant(self) -> None:
1068+
import pytest as _pytest
1069+
1070+
from dqliteclient.exceptions import DqliteError, ProtocolError
1071+
1072+
with _pytest.raises(DqliteError):
1073+
raise ProtocolError("boom")

0 commit comments

Comments
 (0)