You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem? Please describe.
I've just finished testing the newly released row-string indexing feature. It's great and extremely handy for cases where a mapping interface is needed. However, I think it can go a bit further: Sometimes we need to pass a true dict into some legacy thing, and here mssql-python could help, by providing a built-in to_dict() function.
Describe the solution you'd like
I would like a function on the Row object that will return a Python dict with all of the row's values.
Describe alternatives you've considered
Interrogating the cursor's description property and producing my own dict is easy enough. But it would certainly be a lot easier, and probably faster, to let mssql-python do the work instead.
Looking a bit more heavily through my use cases, I'd also like to suggest the addition of items() and values() functions, that will behave just like their dict counterparts, in addition to the to_dict() function suggested above.
Hi Adam Machanic (@amachanic), thanks for the feature request! We agree this is a great addition - having a built-in to_dict() is much cleaner than manually zipping cursor.description.
We've raised a PR that adds the following methods to Row:
to_dict() - returns a plain dict mapping column names to values
keys() - column names
values() - column values
items() - (name, value) pairs
"col" in row - membership testing (also respects case-insensitive mode)
These are all lazy, zero cost at fetch time, only run when you call them. We'll update this issue once the PR is merged. Thanks again for the suggestion!
One clarification on the final API - we landed on a single row._mapping view (a read-only collections.abc.Mapping) rather than separate to_dict() / keys() / values() / items() methods on Row directly. Everything you asked for is available through it:
row=cursor.fetchone()
dict(row._mapping) # {'id': 1, 'name': 'Alice'}row._mapping.keys() # column namesrow._mapping.values() # valuesrow._mapping.items() # (name, value) pairsrow._mapping["name"] # value by column name"name"inrow._mapping# membership by column name
Positional/tuple access is unchanged: row[0], row.name, and row["name"] all still work (and row["name"] / row.name honor case-insensitive lowercase mode).
Thanks again for the suggestion!
Jahnvi Thakkar (@jahnvi480) Thank you for the update and your work on this! The leading underscore gives me pause; can you clarify why that's there and what it's intended to convey to users?
Adam Machanic (@amachanic) the underscore is intentional, and _mapping is a fully public, supported API despite how it reads.
The reason is collision-safety. A Row exposes its columns as both attributes (row.customer_id) and mapping keys, so any plain member name we add to Row risks clashing with a real column of the same name. If we called it row.mapping, a query with a column literally named mapping would shadow/ambiguate it. The leading underscore reserves the name in a namespace that won't collide with your column names.
This isn't something we invented - it's the same convention Python's own collections.namedtuple uses. Its public helpers are all underscore-prefixed (_fields, _asdict(), _replace(), _make()), and the docs state the rationale explicitly: "To prevent conflicts with field names, the method and attribute names start with an underscore." SQLAlchemy adopted the identical pattern for its Row._mapping, so it'll also be familiar to anyone coming from there.
So on intent: the underscore here means "reserved, collision-safe row member," not "private/internal, don't touch." Positional access (row[0]), attribute access (row.col), and key access (row["col"]) stay the primary interface; _mapping is the opt-in escape hatch when you want dict semantics - dict(row._mapping), .keys(), .items(), etc.
Activity
github-actions commentedon May 29, 2026
Hi Adam Machanic (@amachanic), thank you for opening this issue!
Our team will review it shortly. We aim to triage all new issues within 24-48 hours and get back to you.
If you have additional information to share, please feel free to update the issue.
Thank you for your patience!
amachanic commentedon May 30, 2026
Looking a bit more heavily through my use cases, I'd also like to suggest the addition of
items()andvalues()functions, that will behave just like their dict counterparts, in addition to theto_dict()function suggested above.jahnvi480 commentedon Jun 1, 2026
Hi Adam Machanic (@amachanic), thanks for the feature request! We agree this is a great addition - having a built-in to_dict() is much cleaner than manually zipping cursor.description.
We've raised a PR that adds the following methods to Row:
These are all lazy, zero cost at fetch time, only run when you call them. We'll update this issue once the PR is merged. Thanks again for the suggestion!
amachanic commentedon Jun 1, 2026
Thank you Jahnvi Thakkar (@jahnvi480)!
FEAT: add read-only row._mapping dict view, remove Row dict methods (#…
FIX: treat absent Row column map as missing key in row._mapping (#606)
FEAT: Add read-only dict-style Row access via row._mapping (#613)
jahnvi480 commentedon Sep 21, 2026
Hello Adam Machanic (@amachanic), the PR has merged to main, so this ships in our next release.
One clarification on the final API - we landed on a single row._mapping view (a read-only collections.abc.Mapping) rather than separate to_dict() / keys() / values() / items() methods on Row directly. Everything you asked for is available through it:
Positional/tuple access is unchanged: row[0], row.name, and row["name"] all still work (and row["name"] / row.name honor case-insensitive lowercase mode).
Thanks again for the suggestion!
amachanic commentedon Sep 21, 2026
Jahnvi Thakkar (@jahnvi480) Thank you for the update and your work on this! The leading underscore gives me pause; can you clarify why that's there and what it's intended to convey to users?
jahnvi480 commentedon Sep 22, 2026
Adam Machanic (@amachanic) the underscore is intentional, and _mapping is a fully public, supported API despite how it reads.
The reason is collision-safety. A Row exposes its columns as both attributes (row.customer_id) and mapping keys, so any plain member name we add to Row risks clashing with a real column of the same name. If we called it row.mapping, a query with a column literally named mapping would shadow/ambiguate it. The leading underscore reserves the name in a namespace that won't collide with your column names.
This isn't something we invented - it's the same convention Python's own collections.namedtuple uses. Its public helpers are all underscore-prefixed (_fields, _asdict(), _replace(), _make()), and the docs state the rationale explicitly: "To prevent conflicts with field names, the method and attribute names start with an underscore." SQLAlchemy adopted the identical pattern for its Row._mapping, so it'll also be familiar to anyone coming from there.
So on intent: the underscore here means "reserved, collision-safe row member," not "private/internal, don't touch." Positional access (row[0]), attribute access (row.col), and key access (row["col"]) stay the primary interface; _mapping is the opt-in escape hatch when you want dict semantics - dict(row._mapping), .keys(), .items(), etc.