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:
return
If 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' block
Effect 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 runs
Expected 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 cleanup
Further 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.
Describe the bug
mssql_python/cursor.py:3046puts a barereturninside afinallyblock. The block belongs to thebatch_generatorgenerator inCursor.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
returnin afinallyblock aSyntaxWarningin Python 3.14. A project that turns warnings into errors cannot importmssql_pythonat all:Version 1.12.0 does not carry this line. Version 1.13.0 does. I compiled
cursor.pyfrom both wheels to confirm that.2. The
returndiscards an exception raised inside the generator.The guard reads:
If
arrow_batch()raises and the guard is true, thereturnreplaces 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
returnis the one path where that intent does not hold.To reproduce
Effect 1:
Effect 2, reduced to the same shape as
batch_generator:Expected behavior
The driver imports under warnings-as-errors. An exception from
arrow_batch()reaches the caller.A
returnis not needed here. If the guard becomes a positive condition around the cleanup body, both effects go away:Further 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.