Skip to content

Commit 9e4d50b

Browse files
Collapse redundant OSError-subclass tuples to isinstance(e, OSError)
ConnectionError, BrokenPipeError, and TimeoutError are all OSError subclasses, so listing them next to OSError in an isinstance tuple or except clause is redundant. The single OSError check also picks up ConnectionResetError, ConnectionAbortedError, ConnectionRefusedError, InterruptedError, and socket.gaierror that the narrower enumeration silently missed. Cleans up is_disconnect and both do_ping except tuples (the inner cursor.execute branch and the finally cursor.close branch). Adds a defensive parametrised test plus a socket.gaierror pin so a future regression that re-enumerates subclasses cannot silently shrink the coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1452410 commit 9e4d50b

2 files changed

Lines changed: 43 additions & 4 deletions

File tree

src/sqlalchemydqlite/base.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,13 @@ def is_disconnect(self, e: Any, connection: Any, cursor: Any) -> bool:
360360
if isinstance(getattr(e, "__cause__", None), _client_exc.DqliteConnectionError):
361361
return True
362362
# Underlying OS-level transport failures (socket RST, broken pipe,
363-
# DNS, connect refused) surface as these stdlib types.
364-
if isinstance(e, (ConnectionError, BrokenPipeError, TimeoutError, OSError)):
363+
# DNS, connect refused, connection timeout). ``ConnectionError``,
364+
# ``BrokenPipeError``, and ``TimeoutError`` are all ``OSError``
365+
# subclasses, so a single ``OSError`` check covers every stdlib
366+
# transport-error shape (including ``ConnectionResetError`` /
367+
# ``ConnectionAbortedError`` / ``ConnectionRefusedError`` /
368+
# ``socket.gaierror`` that a narrower enumeration would miss).
369+
if isinstance(e, OSError):
365370
return True
366371
# ``dqlitedbapi.Connection`` / ``Cursor`` raise ``InterfaceError``
367372
# when operated on after ``close()``; match the narrow
@@ -404,7 +409,6 @@ def do_ping(self, dbapi_connection: Any) -> bool:
404409
_dbapi_exc.InterfaceError,
405410
_client_exc.DqliteConnectionError,
406411
OSError,
407-
TimeoutError,
408412
):
409413
return False
410414
finally:
@@ -422,7 +426,6 @@ def do_ping(self, dbapi_connection: Any) -> bool:
422426
_dbapi_exc.InterfaceError,
423427
_client_exc.DqliteConnectionError,
424428
OSError,
425-
TimeoutError,
426429
) as exc:
427430
logger.debug(
428431
"do_ping: cursor.close failed (%s); proceeding",

tests/test_dialect_dialect_config.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,42 @@ def test_is_disconnect_false_for_non_leader_codes(self, code: int) -> None:
348348
e = dqliteclient.exceptions.OperationalError(code, "application error")
349349
assert dialect.is_disconnect(e, None, None) is False
350350

351+
@pytest.mark.parametrize(
352+
"exc_cls",
353+
[
354+
OSError,
355+
ConnectionError,
356+
BrokenPipeError,
357+
TimeoutError,
358+
ConnectionResetError,
359+
ConnectionAbortedError,
360+
ConnectionRefusedError,
361+
InterruptedError,
362+
],
363+
)
364+
def test_every_oserror_subclass_is_disconnect(self, exc_cls: type[BaseException]) -> None:
365+
"""Every stdlib OSError subclass classifies as a disconnect.
366+
367+
Guards against a future regression that replaces the single
368+
``isinstance(e, OSError)`` check with an explicit subclass
369+
enumeration — which would silently miss ``ConnectionResetError``
370+
/ ``ConnectionAbortedError`` / ``ConnectionRefusedError`` /
371+
``InterruptedError`` and the ``socket.gaierror`` /
372+
``socket.herror`` DNS-failure shapes.
373+
"""
374+
dialect = DqliteDialect()
375+
assert dialect.is_disconnect(exc_cls("x"), None, None) is True
376+
377+
def test_socket_gaierror_is_disconnect(self) -> None:
378+
"""``socket.gaierror`` is an OSError subclass used for DNS
379+
resolution failures — exactly the "DNS" case the branch
380+
comment promises to cover.
381+
"""
382+
import socket
383+
384+
dialect = DqliteDialect()
385+
assert dialect.is_disconnect(socket.gaierror("name or service"), None, None) is True
386+
351387

352388
class TestSupportsSaneRowcountFlags:
353389
"""Pin the ``supports_sane_rowcount`` quartet on the dialect class

0 commit comments

Comments
 (0)