Summary
In mssql-python 1.6.0, Row no longer supports string-key indexing (row["ColumnName"]). This worked in 1.5.0 and is not called out as a breaking change in the v1.6.0 release notes.
Repro
import mssql_python
conn = mssql_python.connect("<your conn str>")
cur = conn.cursor()
cur.execute("SELECT 1 AS ProductID, 'foo' AS Name")
row = cur.fetchone()
print(row[0]) # works
print(row.ProductID) # works (attribute access)
print(row["ProductID"]) # TypeError on 1.6.0; worked on 1.5.0
Output on 1.6.0
1
1
Traceback (most recent call last):
File "probe.py", line N, in <module>
print(row["ProductID"])
~~~^^^^^^^^^^^^^
TypeError: list indices must be integers or slices, not str
Versions
- mssql-python 1.6.0 (verified via
pip show mssql-python)
- Python 3.14 on Windows
- Server: Fabric SQL endpoint (not relevant; reproes with any
SELECT ... AS Name)
The same script returns 1 on 1.5.0.
Why this matters
- Silent regression. The exception message (
list indices must be integers or slices) reads like a Python typo, not a deliberate API change. Users will spend time debugging their query and column aliases before they suspect the driver.
- Not in release notes. The v1.6.0 notes list 1 enhancement and 6 bug fixes, none of which mention Row access semantics or list any "Breaking Changes" section. v0.14.0 set the precedent that breaking changes get their own section and migration notes — v1.6.0 should follow that convention.
- Public documentation uses this pattern. The official mssql-python quickstart on Microsoft Learn currently shows
print(f"Inserted Product ID : {result['ProductID']}") (source). Anyone following the quickstart on 1.6.0 hits this on the last code block. (A docs fix to use attribute access is already staged in our v1.6.0 doc PR, but the broader user base relying on row["col"] in their own code is the real concern.)
- Attribute access isn't a complete substitute.
row.ColumnName only works when the column alias is a valid Python identifier. For aliases with spaces, hyphens, reserved words, or computed/unnamed columns, string-key access was the only ergonomic option short of positional indexing.
Ask
Either:
- Restore
Row.__getitem__(str) (preferred — matches pyodbc Row semantics and 1.5.0 behavior), or
- Document this as a breaking change in the v1.6.0 release notes with a migration recipe (switch to attribute or positional access; for non-identifier column names, suggest
dict(zip([d[0] for d in cur.description], row)) or similar).
Happy to send a PR for either path if it would help.
Related context
Summary
In mssql-python 1.6.0,
Rowno longer supports string-key indexing (row["ColumnName"]). This worked in 1.5.0 and is not called out as a breaking change in the v1.6.0 release notes.Repro
Output on 1.6.0
Versions
pip show mssql-python)SELECT ... AS Name)The same script returns
1on 1.5.0.Why this matters
list indices must be integers or slices) reads like a Python typo, not a deliberate API change. Users will spend time debugging their query and column aliases before they suspect the driver.print(f"Inserted Product ID : {result['ProductID']}")(source). Anyone following the quickstart on 1.6.0 hits this on the last code block. (A docs fix to use attribute access is already staged in our v1.6.0 doc PR, but the broader user base relying onrow["col"]in their own code is the real concern.)row.ColumnNameonly works when the column alias is a valid Python identifier. For aliases with spaces, hyphens, reserved words, or computed/unnamed columns, string-key access was the only ergonomic option short of positional indexing.Ask
Either:
Row.__getitem__(str)(preferred — matches pyodbc Row semantics and 1.5.0 behavior), ordict(zip([d[0] for d in cur.description], row))or similar).Happy to send a PR for either path if it would help.
Related context
result['ProductID']; no duplicates found (only cursor.execute() with reset_cursor=False raises ProgrammingError: Invalid cursor state #506 which is the unrelatedreset_cursor=Falsefix).