@@ -236,21 +236,43 @@ def __getattr__(self, name: str) -> Any:
236236
237237 @property
238238 def _mapping (self ) -> "RowMapping" :
239- """Read-only dict-like view (column name -> value) over this row.
240-
241- Returns a ``collections.abc.Mapping``; use ``dict(row._mapping)`` for a plain
242- dict, ``row._mapping.items()`` for name/value pairs, and ``iter(row._mapping)``
243- for column names. Names are order-preserving and de-duplicated (last column
244- wins for a repeated name, matching subscript and attribute access).
239+ """Read-only ``dict``-like view (column name -> value) over this row.
240+
241+ Returns a :class:`RowMapping` (a ``collections.abc.Mapping``). Typical use::
242+
243+ row = cursor.fetchone()
244+ dict(row._mapping) # {'id': 1, 'name': 'Alice'}
245+ for name, value in row._mapping.items():
246+ ...
247+ row._mapping["name"] # value by column name
248+ "name" in row._mapping # membership by column name
249+
250+ Semantics and caveats:
251+
252+ - Keys are the result set's column names, order-preserving and
253+ de-duplicated: when a name repeats, one key is kept and the last column
254+ with that name supplies its value (matching ``row[name]`` / ``row.name``).
255+ Every duplicate value stays reachable positionally via ``row[i]``.
256+ - Lookup and membership use the canonical column names exactly, so the key
257+ set, ``in`` and ``[]`` always agree. Unlike ``row[name]`` / ``row.name``,
258+ the view does NOT resolve case-insensitive names or catalog aliases; with
259+ ``lowercase=True`` the keys are the lowercased names.
260+ - ``_mapping`` is a property, so a column literally named ``_mapping`` is
261+ shadowed: read it with ``row["_mapping"]`` or ``row._mapping["_mapping"]``.
245262 """
246263 return RowMapping (self )
247264
248265 def _mapping_keys (self ) -> tuple :
249266 """Canonical, order-preserving column names backing ``_mapping``.
250267
251- Prefers the names snapshotted once by the cursor for the result set. When a
252- row was built without that snapshot, reconstructs names from ``_column_map``
253- (one name per column index); returns ``()`` when neither is available.
268+ Prefers the names snapshotted once by the cursor for the result set, which
269+ preserve the result set's column order. When a row was built without that
270+ snapshot (e.g. a direct ``Row(values, column_map)`` construction), names are
271+ reconstructed from ``_column_map`` in column-index order; that order can
272+ differ from ``_column_map``'s insertion order, so a directly-constructed row
273+ may key differently from an otherwise-equivalent cursor row. Returns ``()``
274+ when neither source is available. Normal cursor fetches always supply the
275+ snapshot.
254276 """
255277 if self ._column_names is not None :
256278 return self ._column_names
@@ -310,10 +332,11 @@ def __repr__(self) -> str:
310332class RowMapping (Mapping ):
311333 """Read-only ``Mapping`` view over a :class:`Row` (column name -> value).
312334
313- Created via :attr:`Row._mapping`. Keys are the row's column names, order-
314- preserving and de-duplicated (last column wins for a repeated name, matching
315- ``row[name]`` / ``row.name``). The view reflects the row it wraps and copies
316- no values.
335+ Created via :attr:`Row._mapping`. Keys are the row's canonical column names,
336+ order-preserving and de-duplicated (last column wins for a repeated name).
337+ Lookup and membership use those names exactly -- no case-insensitive or catalog
338+ alias resolution -- so iteration, ``in`` and ``[]`` always agree. The view
339+ reflects the row it wraps and copies no values.
317340 """
318341
319342 __slots__ = ("_row" ,)
@@ -322,11 +345,13 @@ def __init__(self, row: "Row") -> None:
322345 self ._row = row
323346
324347 def __getitem__ (self , key : str ) -> Any :
325- if isinstance (key , str ):
326- try :
327- return self ._row [key ]
328- except KeyError :
329- raise KeyError (key ) from None
348+ # Restrict lookups to the canonical column names yielded by __iter__ so
349+ # membership and lookup agree with iteration (proper Mapping semantics).
350+ # Row.__getitem__ additionally accepts case-insensitive names and catalog
351+ # aliases, but those are not iterated keys, so the view must not resolve
352+ # them here.
353+ if isinstance (key , str ) and key in self ._row ._mapping_keys ():
354+ return self ._row [key ]
330355 raise KeyError (key )
331356
332357 def __iter__ (self ):
0 commit comments