Skip to content

Commit c7fae0b

Browse files
Use public API in _get_server_version_info and add error handling
Replace internal .dbapi_connection access with connection.exec_driver_sql() and wrap in try/except to gracefully fall back to (3, 0, 0) on failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e5cadf2 commit c7fae0b

2 files changed

Lines changed: 50 additions & 8 deletions

File tree

src/sqlalchemydqlite/base.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,13 @@ def _get_server_version_info(self, connection: Any) -> tuple[int, ...]:
107107
108108
dqlite uses SQLite internally, so we return SQLite version.
109109
"""
110-
cursor = connection.connection.dbapi_connection.cursor()
111-
cursor.execute("SELECT sqlite_version()")
112-
row = cursor.fetchone()
113-
cursor.close()
114-
115-
if row:
116-
version_str = row[0]
117-
return tuple(int(x) for x in version_str.split("."))
110+
try:
111+
result = connection.exec_driver_sql("SELECT sqlite_version()")
112+
version_str = result.scalar()
113+
if version_str:
114+
return tuple(int(x) for x in version_str.split("."))
115+
except Exception:
116+
pass
118117
return (3, 0, 0)
119118

120119

tests/test_dialect.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,49 @@ def test_create_async_engine(self) -> None:
9393
assert engine.dialect.driver == "dqlitedbapi_aio"
9494

9595

96+
class TestGetServerVersionInfo:
97+
def test_does_not_access_dbapi_connection_directly(self) -> None:
98+
"""_get_server_version_info should use exec_driver_sql, not internal attributes."""
99+
import ast
100+
import inspect
101+
import textwrap
102+
103+
source = textwrap.dedent(inspect.getsource(DqliteDialect._get_server_version_info))
104+
tree = ast.parse(source)
105+
106+
# Check that it doesn't access .dbapi_connection
107+
for node in ast.walk(tree):
108+
if isinstance(node, ast.Attribute) and node.attr == "dbapi_connection":
109+
raise AssertionError(
110+
"_get_server_version_info accesses .dbapi_connection directly; "
111+
"should use connection.exec_driver_sql() instead"
112+
)
113+
114+
def test_returns_fallback_on_error(self) -> None:
115+
"""Should return (3, 0, 0) if the query fails."""
116+
from unittest.mock import MagicMock
117+
118+
dialect = DqliteDialect()
119+
mock_conn = MagicMock()
120+
mock_conn.exec_driver_sql.side_effect = Exception("connection broken")
121+
122+
result = dialect._get_server_version_info(mock_conn)
123+
assert result == (3, 0, 0)
124+
125+
def test_parses_version_string(self) -> None:
126+
"""Should parse a version string like '3.39.4' into a tuple."""
127+
from unittest.mock import MagicMock
128+
129+
dialect = DqliteDialect()
130+
mock_conn = MagicMock()
131+
mock_result = MagicMock()
132+
mock_result.scalar.return_value = "3.39.4"
133+
mock_conn.exec_driver_sql.return_value = mock_result
134+
135+
result = dialect._get_server_version_info(mock_conn)
136+
assert result == (3, 39, 4)
137+
138+
96139
class TestURLParsing:
97140
def test_parse_basic_url(self) -> None:
98141
url = URL.create("dqlite", host="localhost", port=9001, database="test")

0 commit comments

Comments
 (0)