Skip to content

FEAT: Add read-only dict-style Row access via row._mapping - #613

Open
Jahnvi Thakkar (jahnvi480) wants to merge 28 commits into
mainfrom
jahnvi/row-dict-api-606
Open

Jahnvi Thakkar (jahnvi480) wants to merge 28 commits into
mainfrom
jahnvi/row-dict-api-606

Conversation

@jahnvi480

@jahnvi480 Jahnvi Thakkar (jahnvi480) commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

AB#45369

GitHub Issue: #606


Summary

This pull request adds dictionary-style access to the Row class through a single, read-only row._mapping view instead of adding standalone dict methods to Row itself. This keeps the Row tuple/attribute surface clean while making rows fully compatible with Python's mapping protocol.

Enhancements to the Row class (mssql_python/row.py):

  • Added a read-only row._mapping property that returns a RowMapping — a collections.abc.Mapping view of column name → value. RowMapping is exported from the package.
  • Through the standard Mapping protocol, row._mapping supports dict(row._mapping), row._mapping["col"], .keys(), .values(), .items(), the in operator, iteration over column names, and equality comparison with plain dicts.
  • The view is read-only — assignment and deletion raise TypeError — so a row cannot be mutated through it.
  • Column names are order-preserving and de-duplicated (the last column wins for a repeated name), consistent with subscript (row["col"]) and attribute (row.col) access.
  • The cursor snapshots the canonical column names once per result set and shares them with every row, so row._mapping stays correct even after the cursor is reused for another query (previously cursor.description could be stale).

Note: the earlier standalone keys(), values(), items(), and to_dict() methods on Row are intentionally not included; all dict-like access now goes through row._mapping (e.g. dict(row._mapping) replaces row.to_dict()).

Testing improvements:

  • Added comprehensive DB-backed tests for row._mapping covering: dict conversion; keys/values/items; duplicate-key de-duplication; __getitem__/get/in including missing-key KeyError and non-string-key KeyError; read-only enforcement (TypeError on assign/delete); repr and equality; duplicate column names (last-wins); rows produced by fetchall/fetchmany; reflection of converted values (DECIMAL, UNIQUEIDENTIFIER, DATETIME2, NVARCHAR, NULL); custom output converters; the global lowercase setting; and stability across cursor reuse.
  • Preserved the existing no-DB unit tests for Row string-key indexing (row["col"]), including case-insensitive lookup and KeyError/TypeError edge cases.

Copilot AI lite review requested due to automatic review settings June 1, 2026 16:19
@github-actions github-actions Bot added the pr-size: small Minimal code update label Jun 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() to Row, sourced from self._column_map and self._values.
  • Add __contains__ with case-insensitive fallback via self._column_map_lower.
  • Add four new tests in tests/test_001_globals.py exercising the helpers and in operator (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.

Comment thread mssql_python/row.py Outdated
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

100%


🎯 Overall Coverage

83%


📈 Total Lines Covered: 8491 out of 10135
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/init.py (100%)
  • mssql_python/cursor.py (100%)
  • mssql_python/row.py (100%)

Summary

  • Total: 37 lines
  • Missing: 0 lines
  • Coverage: 100%

📋 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

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

@github-actions github-actions Bot added pr-size: medium Moderate update size and removed pr-size: small Minimal code update labels Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perf regression on Row.__init__ from the column_names kwarg - fix suggestion + details inline
two other suggestions on type annotations, docstring)

Comment thread mssql_python/row.py
Comment thread mssql_python/row.py Outdated
Comment thread mssql_python/row.py Outdated
Comment thread mssql_python/row.py Outdated
Comment thread mssql_python/row.py Outdated
Comment thread tests/test_004_cursor.py Outdated
Comment thread mssql_python/row.py Outdated
Copilot AI review requested due to automatic review settings September 18, 2026 09:07
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

PR Performance Report

Performance assessment pending.

Waiting for the matching performance run for head 4f92dddec3b749c9d3aad1bca502ea883feca2db.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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 Row is constructed with column_map=None (the empty-mapping edge case covered by test_row_mapping_none_column_map), row._mapping["missing"] delegates to Row.__getitem__, where membership testing against None raises TypeError. A Mapping must report an absent key with KeyError; 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

Copilot AI review requested due to automatic review settings September 18, 2026 09:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot AI review requested due to automatic review settings September 18, 2026 10:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 through row._mapping (with no to_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

Comment thread mssql_python/row.py Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread mssql_python/row.py
Comment thread mssql_python/row.py
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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

Copilot AI review requested due to automatic review settings September 18, 2026 11:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: large Substantial code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants