tursodatabase/turso · error · ProgrammingError

batch mode must be one of {sorted(_BATCH_MODES)} or None, go

Error message

batch mode must be one of {sorted(_BATCH_MODES)} or None, got {mode!r}

What it means

In the embedded Python bindings, Connection.batch() accepts an optional mode naming the transaction-control BEGIN statement for the batch. The mode string is looked up in the _BATCH_MODES dict (case-insensitively); if it isn't one of the recognized names, _batch_begin_sql raises a ProgrammingError before any SQL runs. The error text lists the valid modes, so it is purely a client-side argument-validation error.

Source

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

    "concurrent": "BEGIN CONCURRENT",
}

_TRANSACTION_CONTROL_KEYWORDS = {
    "BEGIN",
    "COMMIT",
    "END",
    "ROLLBACK",
    "SAVEPOINT",
    "RELEASE",
}


def _batch_begin_sql(mode: Optional[str]) -> Optional[str]:
    if mode is None:
        return None
    begin_sql = _BATCH_MODES.get(str(mode).lower())
    if begin_sql is None:
        raise ProgrammingError(
            f"batch mode must be one of {sorted(_BATCH_MODES)} or None, got {mode!r}"
        )
    return begin_sql


def _normalize_batch_statements(
    statements: Iterable[Any],
) -> list[tuple[str, Sequence[Any] | Mapping[str, Any]]]:
    """Normalize batch input to (sql, parameters) pairs. Accepts SQL
    strings and (sql, parameters) pairs."""
    normalized = []
    for index, statement in enumerate(statements):
        if isinstance(statement, str):
            normalized.append((statement, ()))
            continue
        if (
            isinstance(statement, (tuple, list))
            and len(statement) == 2

View on GitHub (pinned to c1e5928725)

Solutions

  1. Use exactly one of the modes printed in the message (lowercase-insensitive): e.g. 'deferred', 'immediate', 'exclusive' as supported by _BATCH_MODES, or None for no transaction.
  2. Normalize before passing: str(mode).strip().lower(), since matching is done on the lowercased string but whitespace/typos still fail.
  3. If you have a custom enum, pass mode.value rather than the enum object.
  4. Check the installed version's _BATCH_MODES dict (bindings/python/turso/lib.py) if upgrading — supported modes may differ from docs.

Example fix

# before
conn.batch(stmts, mode="BEGIN")
# after
conn.batch(stmts, mode="deferred")  # or mode=None
Defensive patterns

Strategy: validation

Validate before calling

_VALID_MODES = {"deferred", "immediate", "exclusive"}  # intersect with _BATCH_MODES for your version
mode = str(mode).strip().lower() if mode is not None else None
assert mode is None or mode in _VALID_MODES, f"bad batch mode: {mode!r}"

Type guard

def is_valid_batch_mode(mode: object) -> bool:
    from turso.lib import _BATCH_MODES
    return mode is None or str(mode).lower() in _BATCH_MODES

Try / catch

try:
    conn.batch(stmts, mode=mode)
except ProgrammingError as e:
    log.warning("bad batch mode %r: %s", mode, e)
    conn.batch(stmts, mode=None)  # safe fallback: no wrapping transaction

Prevention

When it happens

Trigger: Calling batch(statements, mode=...) with a mode string not in _BATCH_MODES, e.g. mode="BEGIN", mode="transaction", mode="immediate " with stray characters, or passing a non-string type that lowercases to an unknown name.

Common situations: Porting code from another driver whose batch/transaction API uses different mode vocabulary ('deferred'/'immediate'/'exclusive' vs this library's names), typos like 'IMMEDIATE ' or 'write', and passing an enum member whose str() isn't a plain mode name.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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