FIX: fetchone/fetchmany/fetchall type-checking under ty (#620)#631
Merged
Conversation
Catalog/metadata helpers reassigned cursor.fetchone/fetchmany/fetchall to closures on the instance, which made static type checkers infer a union of bound method and unbound function and report a spurious missing 'self' argument at call sites. Route the catalog column map (including lowercase and specialized aliases) through the existing cached-map state that the standard fetch methods already consume, so metadata rows pick up catalog column names without any per-call method reassignment. Adds a regression test ensuring the fetch methods are never shadowed on the instance.
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/cursor.pyLines 1637-1645 1637 # Some catalog helpers expose additional friendly aliases (e.g. ODBC 2.x
1638 # vs 3.x column names) via a specialized mapping. Merge them in so they
1639 # resolve to the same column indices.
1640 if specialized_mapping:
! 1641 column_map.update(specialized_mapping)
1642
1643 # Route the map through the shared fetch path. The standard fetchone/
1644 # fetchmany/fetchall methods read these cached maps when constructing Row
1645 # objects, so metadata rows pick up the catalog column names without any📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 59.9%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 76.3%
mssql_python.row.py: 76.9%
mssql_python.__init__.py: 77.3%
mssql_python.pybind.connection.connection.cpp: 77.3%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.logging.py: 85.5%
mssql_python.connection.py: 85.6%🔗 Quick Links
|
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refactors how catalog/metadata result sets set up column name mappings in mssql_python/cursor.py to avoid dynamically reassigning fetchone/fetchmany/fetchall on the cursor instance (which can confuse static type checkers like ty). It also adds a regression test to ensure fetch methods are never shadowed as instance attributes.
Changes:
- Refactored
_prepare_metadata_result_setto build/cache a column name → index map instead of wrapping/replacing fetch methods. - Removed now-obsolete “restore original fetch methods” logic from
execute. - Added a regression test asserting fetch methods remain class methods (not instance attributes) across metadata helpers and normal
execute.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
mssql_python/cursor.py |
Stops runtime reassignment of fetch methods by routing metadata column mappings through the shared cached map path. |
tests/test_004_cursor.py |
Adds a regression test to prevent fetch methods being shadowed on the cursor instance (GH #620). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ase=True (#620) _prepare_metadata_result_set() hard-coded _cached_column_map_lower = None. When lowercase=True the description column names are already lowercased, so forcing the lowercase map to None dropped the Row case-insensitive fallback and broke original-cased lookups (row.TABLE_NAME / row["TABLE_NAME"]) on catalog result sets - a regression vs the previous wrapper-based code. Build the lowercase map the same way execute() does (including any specialized aliases). Also remove the now-dead self._column_map attribute (nothing reads it; fetch methods use _cached_column_map) and add a lowercase=True regression test for catalog column access.
sumitmsft
reviewed
Jun 11, 2026
sumitmsft
reviewed
Jun 11, 2026
sumitmsft
previously approved these changes
Jun 11, 2026
sumitmsft
left a comment
Contributor
There was a problem hiding this comment.
approved with minor changes
sumitmsft
reviewed
Jun 11, 2026
subrata-ms
previously approved these changes
Jun 11, 2026
cursor.py: _prepare_metadata_result_set() iterated self.description directly. If DDBCSQLDescribeCol fails (swallowed above) and no fallback_description is supplied (getTypeInfo() is the only such caller), description becomes None and the loop raised TypeError. Iterate `self.description or ()` so the cursor yields no rows instead. Not a pre-existing regression, but cheap to harden here. test: the GH-620 shadowing test only compared row values (cursor.fetchall() == [[1]]), which would not notice if the getTypeInfo() column-name cache leaked into the following SELECT. Fetch the row and assert row.one == 1 and that row.TYPE_NAME / row["TYPE_NAME"] raise, locking in the per-query cache rebuild.
subrata-ms
approved these changes
Jun 11, 2026
bewithgaurav
approved these changes
Jun 11, 2026
Merged
gargsaumya
added a commit
that referenced
this pull request
Jun 12, 2026
[AB#45750](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/45750) Release mssql-python v1.9.0. Bumps `__version__`, `setup.py` version, and updates `PyPI_Description.md` with this release's customer-facing changes. ### Summary #### Enhancements - **Row Objects in Bulk Copy** - `bulkcopy` now accepts `Row` objects (and lists) directly, converting each row to a tuple automatically (#615). - **Cached Parameter Type Resolution for NULLs** - `SQLDescribeParam` results are cached per statement when binding `NULL` parameters, removing redundant server round-trips and fixing incorrect type fallbacks for all-NULL columns and VARBINARY types (#614). #### Bug Fixes - **macOS / Linux Import Failure** - simdutf is now always statically linked via FetchContent, fixing import/symbol failures on machines without simdutf at the CI build path (#608). Resolves #607, #628. - **executemany Large Decimal Handling** - Fixed a `SQL_C_NUMERIC` type mismatch when inserting `Decimal` values outside the SQL Server `MONEY` range via `executemany` (#611). Resolves #609. - **Exception Pickling** - DB-API exception subclasses and `ConnectionStringParseError` now implement `__reduce__` for correct pickle/unpickle round-trips (#616). Resolves #587. - **PRINT Messages in nextset()** - Diagnostic messages from subsequent result sets are captured on `SQL_SUCCESS_WITH_INFO` during `nextset()` (#618). Resolves #612. - **Row Objects in executemany DAE Path** - `executemany` converts `Row` objects to tuples in the DAE fallback path, fixing `varchar(max)` writes (#630). Resolves #629. - **Static Type-Checking of Fetch Methods** - `fetchone`/`fetchmany`/`fetchall` are no longer reassigned as instance attributes, fixing type-checking under `ty` (#631). Resolves #620. #### Version bump - `mssql_python/__init__.py`: `__version__` 1.8.0 → 1.9.0 - `setup.py`: version 1.8.0 → 1.9.0 - `PyPI_Description.md`: updated "What's new" section to v1.9.0 Bundled `mssql_py_core` version unchanged (0.1.4).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Work Item / Issue Reference
Summary
This pull request refactors how column name mappings are handled for catalog/metadata result sets in the
mssql_python/cursor.pymodule, specifically to avoid dynamically reassigning fetch methods (likefetchone,fetchmany, andfetchall) as instance attributes. This change improves compatibility with static type checkers and ensures cleaner, more predictable method behavior. A regression test is also added to prevent future regressions.Refactoring of column mapping and fetch method handling:
_prepare_metadata_result_setmethod to build and cache the column name to index map (_column_map) directly, merging in specialized aliases when provided, and removed all logic that dynamically wraps or reassigns fetch methods on the instance. This ensures fetch methods remain class methods and improves static type checking compatibility. [1] [2]__init__method of the cursor to initialize the new_column_mapattribute, and clarified the usage of cached column maps.executethat restored original fetch methods, since fetch methods are no longer replaced at runtime.Testing and regression prevention:
test_fetch_methods_not_shadowed_on_instanceto verify that fetch methods are never shadowed as instance attributes, preventing regressions related to GH cursor.fetchall, fetchmany and fetchone fail to type-check underty#620 and ensuring compatibility with static type checkers.