Skip to content

Commit 3e25597

Browse files
Make AsyncCursor.callproc sync like its siblings
AsyncCursor exposed three PEP 249 optional extensions that all raise NotSupportedError unconditionally: ``callproc``, ``nextset``, ``scroll``. Two were plain methods and one (``callproc``) was ``async def``, so ``cursor.callproc("x")`` returned a coroutine instead of raising. A caller wrapping the call in a bare ``try ... except NotSupportedError`` would silently see the try block pass and then leak a never-awaited coroutine. The method only ever raises; wrapping in a coroutine provided no value and diverged from the two sibling raisers on the same class plus ``AsyncAdaptedCursor.callproc`` in the SQLAlchemy dialect. Drop ``async``. Update the existing raise test to drop the ``await`` and the stale ``@pytest.mark.asyncio`` marker, and add a guardrail test that inspects all three methods with ``inspect.iscoroutinefunction`` so future drift is caught. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6d2c4f7 commit 3e25597

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

src/dqlitedbapi/aio/cursor.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,17 @@ def setoutputsize(self, size: int, column: int | None = None) -> None:
251251
"""Set output size (no-op for dqlite)."""
252252
pass
253253

254-
async def callproc(
254+
def callproc(
255255
self, procname: str, parameters: Sequence[Any] | None = None
256256
) -> Sequence[Any] | None:
257-
"""PEP 249 optional extension — not supported."""
257+
"""PEP 249 optional extension — not supported.
258+
259+
Sync despite the cursor being async: the method raises
260+
unconditionally, so wrapping it in a coroutine has no value and
261+
would diverge from the sync siblings (``nextset`` / ``scroll``)
262+
and from the SQLAlchemy adapter (``sqlalchemy-dqlite``), which
263+
both expose these as plain methods.
264+
"""
258265
raise NotSupportedError("dqlite does not support stored procedures")
259266

260267
def nextset(self) -> bool | None:

tests/test_async_cursor.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,26 @@ def test_setoutputsize_noop(self) -> None:
111111

112112

113113
class TestOptionalAsyncCursorMethodsRaise:
114-
@pytest.mark.asyncio
115-
async def test_callproc_raises_not_supported(self) -> None:
114+
def test_callproc_raises_not_supported(self) -> None:
116115
from dqlitedbapi.exceptions import NotSupportedError
117116

118117
conn = AsyncConnection("localhost:9001")
119118
cursor = AsyncCursor(conn)
120119
with pytest.raises(NotSupportedError):
121-
await cursor.callproc("some_proc")
120+
cursor.callproc("some_proc")
121+
122+
def test_callproc_nextset_scroll_are_sync(self) -> None:
123+
"""These three PEP 249 optional extensions all unconditionally raise
124+
``NotSupportedError``. They must stay sync so callers can catch the
125+
error with a bare ``try: cursor.callproc(...) except ...`` rather
126+
than accidentally returning a coroutine object that is never awaited.
127+
The adapter in ``sqlalchemy-dqlite`` exposes the same three as sync.
128+
"""
129+
import inspect
130+
131+
assert not inspect.iscoroutinefunction(AsyncCursor.callproc)
132+
assert not inspect.iscoroutinefunction(AsyncCursor.nextset)
133+
assert not inspect.iscoroutinefunction(AsyncCursor.scroll)
122134

123135
def test_nextset_raises_not_supported(self) -> None:
124136
from dqlitedbapi.exceptions import NotSupportedError

0 commit comments

Comments
 (0)