tursodatabase/turso · error · KeyError

KeyError(key)

Error message

KeyError(key)

What it means

Row.__getitem__ resolves string keys through an index built from the result set's column names; integer and slice keys go straight to the underlying data tuple. A string that is not one of the selected column names raises KeyError(key). The row only knows the names the SELECT actually returned, not the full table schema.

Source

Thrown at bindings/python/turso/lib.py:1168

        obj._data = data
        # Build mapping from column name to index
        desc = cursor.description or ()
        obj._keys = tuple(col[0] for col in desc)
        obj._index = {name: idx for idx, name in enumerate(obj._keys)}
        return obj

    def keys(self) -> list[str]:
        return list(self._keys)

    def __getitem__(self, key: int | str | slice, /) -> Any:
        if isinstance(key, slice):
            return self._data[key]
        if isinstance(key, int):
            return self._data[key]
        # key is column name
        idx = self._index.get(key)
        if idx is None:
            raise KeyError(key)
        return self._data[idx]

    def __hash__(self) -> int:
        return hash((self._keys, self._data))

    def __iter__(self) -> Iterator[Any]:
        return iter(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __eq__(self, value: object, /) -> bool:
        if not isinstance(value, Row):
            return NotImplemented  # type: ignore[return-value]
        return self._keys == value._keys and self._data == value._data

    def __ne__(self, value: object, /) -> bool:
        if not isinstance(value, Row):

View on GitHub (pinned to c1e5928725)

Solutions

  1. Print row.keys() once and use the exact names the query returned
  2. Alias computed columns in the SELECT list (SELECT count(*) AS n FROM t)
  3. Select explicit columns instead of * so the name set is stable
  4. Access by integer index when names are unstable

Example fix

# before
cur.execute("SELECT count(*) FROM t")
row = cur.fetchone()
row["total"]  # KeyError('total')

# after
cur.execute("SELECT count(*) AS total FROM t")
row = cur.fetchone()
row["total"]  # works
Defensive patterns

Strategy: validation

Validate before calling

def column_or(row, name, default=None):
    return row[name] if name in row.keys() else default

Type guard

def has_column(row, name: str) -> bool:
    return name in row.keys()

Try / catch

try:
    value = row["total"]
except KeyError:
    value = row[row.keys()[0]]  # or log row.keys() and fix the SELECT

Prevention

When it happens

Trigger: row['typo'] after a normal query; a name that differs from the SELECT list, such as an unaliased aggregate or expression column; a column dropped or renamed by a schema migration while the code still selects *.

Common situations: SELECT * across a migration that renames a column; joins returning one bare name for two same-named columns; trusting keys from external JSON instead of row.keys(); case mismatches with the query text.

Related errors


AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-20). Data as JSON: /api/errors/265e4595cd7000e8. Report an issue: GitHub.