Error handling
Every failure raises a subclass of StrataError, so you can catch a family or a
specific kind:
from stratadb.errors import NotFoundError, StrataError
try:
db.branches.merge("nope", "default")
except NotFoundError as err:
print(err.code)
except StrataError:
raise
not_found.engine.branch
There are sixteen subclasses, and they map to the classes in the shared error registry rather than being invented by the SDK:
AccessDeniedError, AlreadyExistsError, AmbiguousCommitError,
ConflictError, CorruptionError, DataLossError,
FailedPreconditionError, HistoryUnavailableError, InternalError,
InvalidArgumentError, IoError, NotFoundError, ResourceExhaustedError,
SerializationError, UnavailableError, UnsupportedError.
Branch on the code, not the message
Messages are written for people and change. err.code is a stable identifier
and is the thing to compare:
except StrataError as err:
if err.code == "not_found.engine.branch":
...
Each error also carries err.hint, which is a sentence telling you what to do,
and err.ref, a link to that code’s page.
One caveat on ref. Codes raised by the engine resolve; codes raised by the SDK
itself, the ones with .sdk. in them, currently 404. code and hint are
reliable in both cases. This is tracked as strata-python issue 87.
Did the write apply?
This is the field that makes the tree worth using. When a write fails, the question is whether it took effect, and the answer is on the exception:
except StrataError as err:
err.commit_outcome
Most codes answer never or not_applicable, which means your database is
untouched and a retry is safe. A small number answer maybe_committed, and
those are the only ones where retrying blindly can double-apply.
Error codes names them and explains the handling contract. It is worth reading once before you write a retry loop.
Retrying
err.retry_policy says whether a retry can work at all:
nevermeans the same call will fail the same way. Fix the input.after_state_changemeans it can work once something else changes, such as a branch reaching a different status.- Others are safe to retry with backoff.
So a retry loop that reads both fields rather than catching broadly:
for attempt in range(3):
try:
db.branches.merge("work", "default")
break
except StrataError as err:
if err.retry_policy == "never" or err.commit_outcome == "maybe_committed":
raise
time.sleep(2**attempt)
Errors that teach
Two are worth knowing because they catch real mistakes rather than bad input.
db.state raises unsupported.sdk.state_removed, because the state-cell
primitive was removed in V1 and the attribute was kept as a signpost to
db.kv and db.json.
Using a handle after close(), or across an os.fork(), raises
failed_precondition.sdk.handle_closed and
failed_precondition.sdk.fork_not_supported respectively. Neither is undefined
behaviour. Opening databases covers
the rules both are enforcing.
Where to look next
Error codes is the full contract, and every /e/ page
carries the retry policy and commit outcome for one code.