Repository navigation
return in a finally block at cursor.py:3046 breaks import on Python 3.14 and discards fetch errors #712
Description
Describe the bug
mssql_python/cursor.py:3046 puts a bare return inside a finally block. The block belongs to the batch_generator generator in Cursor.arrow_reader. The construct arrived in #644 and shipped in 1.13.0.
This has two effects.
1. Python 3.14 refuses to import the driver under warnings-as-errors.
PEP 765 makes return in a finally block a SyntaxWarning in Python 3.14. A project that turns warnings into errors cannot import mssql_python at all:
Exception message:
File "/path/to/site-packages/mssql_python/cursor.py", line 3046
return
^^^^^^
SyntaxError: 'return' in a 'finally' block
Stack trace:
mssql_python/__init__.py:54: from .db_connection import connect, Connection
mssql_python/db_connection.py:9: from mssql_python.connection import Connection, TokenProvider
mssql_python/connection.py:22: from mssql_python.cursor import Cursor
Version 1.12.0 does not carry this line. Version 1.13.0 does. I compiled cursor.py from both wheels to confirm that.
2. The return discards an exception raised inside the generator.
The guard reads:
cur = cursor_ref[0]
cursor_ref[0] = None
if cur is None or cur.closed or cur.hstmt is None:
returnIf arrow_batch() raises and the guard is true, the return replaces the exception with a normal end of iteration. The caller reads a truncated result set and sees no error. I did not confirm that a closed cursor and an in-flight exception occur together in practice. The language semantics hold either way.
The comment above the block states that the teardown runs on "an exception inside the body". The return is the one path where that intent does not hold.
To reproduce
Effect 1:
import warnings
warnings.simplefilter('error')
import mssql_python # SyntaxError: 'return' in a 'finally' blockEffect 2, reduced to the same shape as batch_generator:
def make(guard):
def gen():
try:
yield 1
raise RuntimeError('fetch failed')
finally:
if guard:
return # same shape as cursor.py:3046
print('cleanup ran')
return gen
g = make(True)()
next(g)
try:
next(g)
except RuntimeError:
print('propagated')
except StopIteration:
print('the RuntimeError was discarded') # this branch runs
g = make(False)()
next(g)
try:
next(g)
except RuntimeError:
print('cleanup ran, then propagated') # this branch runsExpected behavior
The driver imports under warnings-as-errors. An exception from arrow_batch() reaches the caller.
A return is not needed here. If the guard becomes a positive condition around the cleanup body, both effects go away:
cur = cursor_ref[0]
cursor_ref[0] = None
if cur is not None and not cur.closed and cur.hstmt is not None:
... # existing cleanupFurther technical details
Python version: 3.14.5
mssql-python version: 1.13.0
SQL Server version: Azure SQL Database
Operating system: macOS 26.6.1
Additional context
The import failure blocks every test that imports the driver, because our test suite treats warnings as errors. A per-message warning filter works as a local workaround.
Activity
github-actions commentedon Aug 10, 2026
Hi J Rob Gant (@rgant), thank you for opening this issue!
Our team will review it shortly. We aim to triage all new issues within 24-48 hours and get back to you.
If you have additional information to share, please feel free to update the issue.
Thank you for your patience!
subrata-ms commentedon Aug 12, 2026
Root Cause Analysis
The regression was introduced in mssql-python 1.13.0 by PR #644, which added explicit cleanup behavior to Cursor.arrow_reader().
As part of that change, batch_generator gained a try/finally block. Its defensive cleanup guard used a bare return when the cursor or statement handle was unavailable.
A return inside finally has two consequences:
Python 3.14 emits a PEP 765 SyntaxWarning. Applications treating warnings as errors cannot compile or import the module from source.
If arrow_batch() raises while the guard is true, the return overrides the active exception. Iteration ends with StopIteration, potentially presenting a truncated result set as successful.
Version 1.12.0 was unaffected because its Arrow batch generator had no cleanup finally block. The issue is limited to the control-flow statement used by the cleanup guard, rather than the Arrow fetching or ODBC cleanup operations themselves.
FIX: Preserve Arrow reader fetch exceptions (#712) (#718)
subrata-ms commentedon Aug 18, 2026
The issue is addressed and fix should be available as part of upcoming mssql-python driver release 1.14.0.