Skip to content

Commit 05610e1

Browse files
Raise InterfaceError when fetching without a result set
PEP 249 requires fetch methods to raise Error when no result set exists (before any execute or after DML). Check _description is None as the signal for "no result set available." Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 29271e1 commit 05610e1

File tree

3 files changed

+26
-11
lines changed

3 files changed

+26
-11
lines changed

src/dqlitedbapi/aio/cursor.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,16 @@ async def executemany(
125125
self._rowcount = total_affected
126126
return self
127127

128+
def _check_result_set(self) -> None:
129+
if self._description is None:
130+
raise InterfaceError("No result set: execute a query before fetching")
131+
128132
async def fetchone(self) -> tuple[Any, ...] | None:
129133
"""Fetch the next row of a query result set."""
130134
self._check_closed()
135+
self._check_result_set()
131136

132-
if not self._rows or self._row_index >= len(self._rows):
137+
if self._row_index >= len(self._rows):
133138
return None
134139

135140
row = self._rows[self._row_index]
@@ -139,6 +144,7 @@ async def fetchone(self) -> tuple[Any, ...] | None:
139144
async def fetchmany(self, size: int | None = None) -> list[tuple[Any, ...]]:
140145
"""Fetch the next set of rows of a query result."""
141146
self._check_closed()
147+
self._check_result_set()
142148

143149
if size is None:
144150
size = self._arraysize
@@ -155,6 +161,7 @@ async def fetchmany(self, size: int | None = None) -> list[tuple[Any, ...]]:
155161
async def fetchall(self) -> list[tuple[Any, ...]]:
156162
"""Fetch all remaining rows of a query result."""
157163
self._check_closed()
164+
self._check_result_set()
158165

159166
result = self._rows[self._row_index :]
160167
self._row_index = len(self._rows)

src/dqlitedbapi/cursor.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,16 @@ async def _executemany_async(
139139
total_affected += self._rowcount
140140
self._rowcount = total_affected
141141

142+
def _check_result_set(self) -> None:
143+
if self._description is None:
144+
raise InterfaceError("No result set: execute a query before fetching")
145+
142146
def fetchone(self) -> tuple[Any, ...] | None:
143147
"""Fetch the next row of a query result set."""
144148
self._check_closed()
149+
self._check_result_set()
145150

146-
if not self._rows or self._row_index >= len(self._rows):
151+
if self._row_index >= len(self._rows):
147152
return None
148153

149154
row = self._rows[self._row_index]
@@ -153,6 +158,7 @@ def fetchone(self) -> tuple[Any, ...] | None:
153158
def fetchmany(self, size: int | None = None) -> list[tuple[Any, ...]]:
154159
"""Fetch the next set of rows of a query result."""
155160
self._check_closed()
161+
self._check_result_set()
156162

157163
if size is None:
158164
size = self._arraysize
@@ -169,6 +175,7 @@ def fetchmany(self, size: int | None = None) -> list[tuple[Any, ...]]:
169175
def fetchall(self) -> list[tuple[Any, ...]]:
170176
"""Fetch all remaining rows of a query result."""
171177
self._check_closed()
178+
self._check_result_set()
172179

173180
result = self._rows[self._row_index :]
174181
self._row_index = len(self._rows)

tests/test_cursor.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,23 @@ def test_fetchone_on_closed_cursor_raises(self) -> None:
4343
with pytest.raises(InterfaceError, match="Cursor is closed"):
4444
cursor.fetchone()
4545

46-
def test_fetchone_no_results(self) -> None:
46+
def test_fetchone_without_execute_raises(self) -> None:
4747
conn = Connection("localhost:9001")
4848
cursor = Cursor(conn)
49-
result = cursor.fetchone()
50-
assert result is None
49+
with pytest.raises(InterfaceError, match="No result set"):
50+
cursor.fetchone()
5151

52-
def test_fetchall_no_results(self) -> None:
52+
def test_fetchall_without_execute_raises(self) -> None:
5353
conn = Connection("localhost:9001")
5454
cursor = Cursor(conn)
55-
result = cursor.fetchall()
56-
assert result == []
55+
with pytest.raises(InterfaceError, match="No result set"):
56+
cursor.fetchall()
5757

58-
def test_fetchmany_no_results(self) -> None:
58+
def test_fetchmany_without_execute_raises(self) -> None:
5959
conn = Connection("localhost:9001")
6060
cursor = Cursor(conn)
61-
result = cursor.fetchmany(5)
62-
assert result == []
61+
with pytest.raises(InterfaceError, match="No result set"):
62+
cursor.fetchmany(5)
6363

6464
def test_context_manager(self) -> None:
6565
conn = Connection("localhost:9001")
@@ -71,6 +71,7 @@ def test_iterator(self) -> None:
7171
conn = Connection("localhost:9001")
7272
cursor = Cursor(conn)
7373
cursor._rows = [(1, "a"), (2, "b"), (3, "c")]
74+
cursor._description = [("id", None, None, None, None, None, None)]
7475

7576
results = list(cursor)
7677
assert results == [(1, "a"), (2, "b"), (3, "c")]

0 commit comments

Comments
 (0)