tursodatabase/turso · error · ProgrammingError

autocommit must be True, False, or 'LEGACY'

Error message

autocommit must be True, False, or 'LEGACY'

What it means

ProgrammingError raised by the Connection.autocommit setter (connection.py:102-106) when the assigned value is not one of exactly True, False, or the case-sensitive string "LEGACY". This follows the Python 3.12+ sqlite3 autocommit contract: True means the server runs every statement in autocommit, False means explicit BEGIN/COMMIT control, and "LEGACY" keeps implicit transactions driven by isolation_level. The check uses 'in', so 1 and 0 slip through as True/False, but "legacy", "on", "off", and None do not.

Source

Thrown at serverless/python/turso_serverless/connection.py:105

                sql, args=params, named_args=named_params, want_rows=want_rows
            )
        except RuntimeError as e:
            raise _classify_error(e) from None

    @property
    def in_transaction(self) -> bool:
        """Whether an explicit transaction is open, from the server's answer
        as of the most recently completed statement."""
        return not self._session.autocommit

    @property
    def autocommit(self) -> object | bool:
        return self._autocommit_mode

    @autocommit.setter
    def autocommit(self, val: object | bool) -> None:
        if val not in (True, False, "LEGACY"):
            raise ProgrammingError("autocommit must be True, False, or 'LEGACY'")
        self._autocommit_mode = val

    def close(self) -> None:
        if self._closed:
            return
        try:
            # Closing the stream rolls back any open transaction server-side,
            # matching sqlite3: uncommitted changes are lost on close.
            self._session.close()
        finally:
            self._closed = True

    def commit(self) -> None:
        self._ensure_open()
        if self.in_transaction:
            self._execute_stmt("COMMIT", want_rows=False)

    def rollback(self) -> None:

View on GitHub (pinned to bad083fafb)

Solutions

  1. Assign one of exactly True, False, or "LEGACY" (uppercase)
  2. Map string config values before assignment: on/true/1 -> True, off/false/0 -> False, legacy -> "LEGACY"
  3. Validate the value at config load time so the failure names the bad key, not the driver setter

Example fix

// before
conn.autocommit = os.environ["DB_AUTOCOMMIT"]  # "on" -> ProgrammingError

// after
_mapping = {"on": True, "off": False, "legacy": "LEGACY"}
conn.autocommit = _mapping[os.environ["DB_AUTOCOMMIT"].lower()]
Defensive patterns

Strategy: type-guard

Validate before calling

_AUTOCOMMIT_MAP = {"on": True, "off": False, "legacy": "LEGACY", "true": True, "false": False}

raw = os.environ["DB_AUTOCOMMIT"]
conn.autocommit = _AUTOCOMMIT_MAP[raw.lower()]  # raises KeyError on the config key, not inside the driver

Type guard

def is_valid_autocommit(val: object) -> bool:
    """Exactly the three values the setter accepts ('LEGACY' is case-sensitive)."""
    return val is True or val is False or (isinstance(val, str) and val == "LEGACY")

Prevention

When it happens

Trigger: Assigning conn.autocommit = "on" / "off" / "legacy" (wrong case or string form), None, or a config value read as a string. Loading the setting from an env var or JSON/YAML config and assigning it verbatim is the classic trigger.

Common situations: Porting configuration from other drivers (psycopg-style toggles, libsql string flags); env-var driven config (os.environ["AUTOCOMMIT"]); deploying the same code across sqlite3 (which accepts only these three values too, so errors usually come from stringly-typed config); typos in the LEGACY literal.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/bf58f1ef812e3769. Report an issue: GitHub.