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
- Assign one of exactly True, False, or "LEGACY" (uppercase)
- Map string config values before assignment: on/true/1 -> True, off/false/0 -> False, legacy -> "LEGACY"
- 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
- Never feed stringly-typed config straight into the setter; map it once at config load
- Use 'is True' / 'is False' checks in your own validation so ints don't slip through as bools
- Write the literal as "LEGACY" — exact uppercase — and cover it with a unit test
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
- autocommit must be True, False, or 'LEGACY'
- no SQL statements to execute
- retryFetch: attempts must be a finite integer >= 1, got ${at
- You can only execute one statement at a time
- query timeout must be non-negative
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/bf58f1ef812e3769.
Report an issue: GitHub.