FEAT: Add read-only dict-style Row access via row._mapping - #613
Jahnvi Thakkar (jahnvi480) wants to merge 28 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds dict-like helpers (keys(), values(), items(), to_dict()) and __contains__ to the Row class so rows behave more like mappings, addressing issue #606. New unit tests cover each helper and the case-insensitive membership path.
Changes:
- Add
keys(),values(),items(),to_dict()toRow, sourced fromself._column_mapandself._values. - Add
__contains__with case-insensitive fallback viaself._column_map_lower. - Add four new tests in
tests/test_001_globals.pyexercising the helpers andinoperator (case-sensitive and case-insensitive).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| mssql_python/row.py | Implements the new mapping-style methods and membership protocol on Row. |
| tests/test_001_globals.py | Adds tests for to_dict, keys/values/items, and __contains__ (case-sensitive + insensitive). |
Key concern: in real cursor-built rows, _column_map stores each column under both its original-cased name and a lowercase alias, so keys()/items()/to_dict() will emit each column twice and len(keys()) != len(values()). The new tests use a hand-built column_map without the lowercase aliases, so they don't catch this — see the inline comment for details and suggested directions.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.performance_counter.hpp: 0.7%
mssql_python.pybind.logger_bridge.cpp: 57.9%
mssql_python.pybind.ddbc_bindings.h: 64.1%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 77.7%
mssql_python.pybind.connection.connection_pool.cpp: 81.8%
mssql_python.row.py: 83.4%
mssql_python.logging.py: 86.2%
mssql_python.pooling.py: 90.1%
mssql_python.pybind.py_type_cache.hpp: 91.6%🔗 Quick Links
|
…reusability, add integration tests
…lowercase aliases
Gaurav Sharma (bewithgaurav)
left a comment
There was a problem hiding this comment.
perf regression on Row.__init__ from the column_names kwarg - fix suggestion + details inline
two other suggestions on type annotations, docstring)
PR Performance ReportPerformance assessment pending. Waiting for the matching performance run for head |
There was a problem hiding this comment.
🔵 Needs a closer look
RowMapping.__getitem__ can raise TypeError instead of the required KeyError for rows constructed without a column map.
Review details
Suppressed comments (1)
mssql_python/row.py:327
- When a
Rowis constructed withcolumn_map=None(the empty-mapping edge case covered bytest_row_mapping_none_column_map),row._mapping["missing"]delegates toRow.__getitem__, where membership testing againstNoneraisesTypeError. AMappingmust report an absent key withKeyError; handle this no-column-map case while translating the lookup failure.
if isinstance(key, str):
try:
return self._row[key]
except KeyError:
raise KeyError(key) from None
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🟢 Approval recommended
The implementation, exports, type stubs, cursor snapshots, and tests are internally consistent with no blocking issues found.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
Please re-review
Sumit Sarabhai (sumitmsft)
left a comment
There was a problem hiding this comment.
Re-reviewed at 63b199ea. The previously reported empty-mapping issue is fixed: missing-key indexing raises KeyError, .get() returns the expected default, and membership returns False. The regression test now covers these operations.
Reviewed the PR for correctness, security, reliability, performance, test coverage, repository conventions, and applicable architecture and design specifications. No actionable issues were identified. The implementation is consistent with repository standards and the stated API contract. No separate approved design was accessible for verification.
Validation: rebuilt the native extension; all 19 focused tests passed against local SQL Server. Additional live checks passed for duplicate labels, nextset(), saved-row stability, and metadata mappings. Black passed across 93 files. Cross-platform ADO, coverage, and performance checks were still pending at the end of the re-review; this approval does not replace those checks.
Recommendation: Approve
There was a problem hiding this comment.
🟡 Changes recommended
RowMapping accepts keys absent from iteration, and the linked issue’s previously announced Row-level API is not implemented.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
mssql_python/row.py:246
- The linked #606 discussion says the promised API is
Row.to_dict(),Row.keys(),Row.values(),Row.items(), and"col" in row, but this PR explicitly omits all of those and exposes them only throughrow._mapping(with noto_dict). If this is an intentional replacement, update the issue/acceptance criteria and call out the API change; otherwise the implementation does not deliver the scope previously announced for #606.
@property
def _mapping(self) -> "RowMapping":
"""Read-only dict-like view (column name -> value) over this row.
Returns a ``collections.abc.Mapping``; use ``dict(row._mapping)`` for a plain
dict, ``row._mapping.items()`` for name/value pairs, and ``iter(row._mapping)``
for column names. Names are order-preserving and de-duplicated (last column
wins for a repeated name, matching subscript and attribute access).
"""
return RowMapping(self)
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
Gaurav Sharma (bewithgaurav)
left a comment
There was a problem hiding this comment.
this addresses dictionary conversion and named-field iteration through the agreed mapping view, without adding common method names to the row itself. two nonblocking suggestions on fallback ordering and user-facing documentation. approving.
RowMapping.__getitem__ delegated to Row.__getitem__, which resolves case-insensitive names (lowercase mode) and catalog aliases that __iter__ never yields, violating the Mapping contract (e.g. 'MixedCase' in row._mapping was True while list(row._mapping) had only 'mixedcase'). Restrict the view's lookup/membership to the canonical _mapping_keys() so keys, 'in' and '[]' always agree; Row-level case-insensitive access is unchanged. Also document duplicate-label, reserved-name, and fallback-ordering caveats, and add lowercase + catalog-alias regressions.
77a6874
There was a problem hiding this comment.
🟢 Approval recommended
The mapping implementation, cursor snapshotting, exports, typing, and tests are internally consistent with no blocking defects found.
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🟢 Approval recommended
The implementation, public exports, cursor integration, type stubs, and test coverage are consistent with the stated mapping contract.
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Lite
Work Item / Issue Reference
Summary
This pull request adds dictionary-style access to the
Rowclass through a single, read-onlyrow._mappingview instead of adding standalone dict methods toRowitself. This keeps theRowtuple/attribute surface clean while making rows fully compatible with Python's mapping protocol.Enhancements to the
Rowclass (mssql_python/row.py):row._mappingproperty that returns aRowMapping— acollections.abc.Mappingview of column name → value.RowMappingis exported from the package.Mappingprotocol,row._mappingsupportsdict(row._mapping),row._mapping["col"],.keys(),.values(),.items(), theinoperator, iteration over column names, and equality comparison with plaindicts.TypeError— so a row cannot be mutated through it.row["col"]) and attribute (row.col) access.row._mappingstays correct even after the cursor is reused for another query (previouslycursor.descriptioncould be stale).Note: the earlier standalone
keys(),values(),items(), andto_dict()methods onRoware intentionally not included; all dict-like access now goes throughrow._mapping(e.g.dict(row._mapping)replacesrow.to_dict()).Testing improvements:
row._mappingcovering: dict conversion;keys/values/items; duplicate-key de-duplication;__getitem__/get/inincluding missing-keyKeyErrorand non-string-keyKeyError; read-only enforcement (TypeErroron assign/delete);reprand equality; duplicate column names (last-wins); rows produced byfetchall/fetchmany; reflection of converted values (DECIMAL, UNIQUEIDENTIFIER, DATETIME2, NVARCHAR, NULL); custom output converters; the globallowercasesetting; and stability across cursor reuse.Rowstring-key indexing (row["col"]), including case-insensitive lookup andKeyError/TypeErroredge cases.