beava._errors
Errors
Two layers, one combined surface. The Python SDK raises three exception classes; the server returns structured wire errors under error.code. Every non-2xx wire response is rewrapped as a Python exception so try/except bv.RegistrationError catches everything that came off the network.
Overview
Beava errors come from two places:
- Client-side — the SDK validates inputs before they hit the wire (e.g. raw
EventDerivationchains that weren't wrapped in@bv.event) and raisesRegistrationErrororBinaryNotFoundErrordirectly.ValidationErroris the structured per-failure record carried byRegistrationError.errors. - Server-side (wire) — every non-2xx response from the data plane carries a JSON body with a stable
error.code. The SDK rewraps that body asRegistrationError(code=..., path=..., message=...); you don't deal with rawhttpxbodies.
The canonical wire-error envelope is a flat object:
{ "error": { "code": "event_not_found", "path": "PageView", "message": "event 'PageView' is not registered" }, "registry_version": 3 }
Some errors carry richer payloads — force_required includes a structured diff, batch_too_large includes the offending count, registration validation can return an errors list of {kind, path, message} tuples. The SDK threads those into RegistrationError.errors as ValidationError instances when present.
HTTP status discipline: 4xx means client mistake (event not registered, schema mismatch, malformed JSON), 5xx means server-side trouble (WAL stall, internal serialization bug). 403 is reserved for reset_disabled_in_production. The Python SDK does not retry — see the FAQ below.
Section 1 of 2
Python exceptions
Three exception classes are exported from the top-level beava module. All three are also accessible as bv.RegistrationError, bv.ValidationError, bv.BinaryNotFoundError.
RegistrationError
The SDK's general-purpose wire-error wrapper. Raised for every non-2xx response from the data plane. Discriminate on code, not on the exception class.
Attributes
code— the stable wire code (e.g."event_not_found","schema_mismatch","unknown_table","reset_disabled","invalid_descriptor"). Catalogued in §Wire error codes below.path— JSON-pointer-style path to the offending field or descriptor (e.g."Transaction.amount"). Empty string when the error is global.message— human-readable description, suitable for logging.errors—list[ValidationError]. Populated when registration validation returns multiple failures; empty for single-error cases.
Raised by
- Any of the seven
Appmethods (register,push,get,batch_get,reset,ping,close) on a non-2xx server response. - Client-side from
app.register(...)when a passed descriptor is a rawEventDerivationchain (code="invalid_descriptor") — does not hit the wire. - Client-side from the transport layer when the server returns a non-JSON body (
code="unparseable_error").
Example
import beava as bv app = bv.App("http://localhost:8080") try: app.push("PageView", {"user_id": "alice"}) except bv.RegistrationError as e: if e.code == "event_not_found": # PageView wasn't registered yet — register and retry. app.register(PageView) app.push("PageView", {"user_id": "alice"}) elif e.code == "invalid_event": # Schema mismatch — log path + message and surface up. log.error("bad event payload at %s: %s", e.path, e.message) raise else: raise # anything else is a programming bug
ValidationError
Frozen dataclass carrying a single validation failure. Not raised on its own — it's the type held in RegistrationError.errors when registration validation produces multiple failures (e.g. five fields with type mismatches across two descriptors).
Attributes
kind— one ofcycle,missing_upstream,schema_mismatch,bad_return_type,unknown_field_type,table_key_invalid,registration_conflict,duplicate_name. The set is exported asbeava._errors.VALIDATION_ERROR_KINDS.path— JSON-pointer-style path (e.g."Transaction.event_time").message— human-readable description.
The dataclass is frozen, so you can use it as a dict key or in sets. Stringification is "[kind] path: message".
BinaryNotFoundError
Raised by embed mode (bv.App() with no URL) when the beava binary cannot be located. Discovery order:
$BEAVA_BINARY— explicit override.beavaon$PATH— released installs../target/debug/beava— local dev-loop convenience.- If none of the above resolves, the exception fires with an install-guidance message.
Network-mode (http://..., tcp://...) Apps never raise this — they don't spawn a binary.
Example
try: with bv.App() as app: app.register(PageView) except bv.BinaryNotFoundError as e: print("install beava: cargo install beava OR set BEAVA_BINARY") raise
Section 2 of 2
Wire error codes
Every non-2xx response from the data plane carries a structured error object. The Python SDK rewraps each as RegistrationError(code=...). The codes below are stable — clients can branch on them. Codes are grouped by trigger; HTTP status is given per code.
Push errors
event_not_found
The pushed event_name isn't in the registry. Register it first (app.register(...)), or check for typos. Catch as RegistrationError(code="event_not_found").
invalid_event
The push body is malformed JSON, fails to deserialize into a Row, or violates the registered event schema (missing required fields, wrong types). The single biggest source of invalid_event: a field type doesn't match the declared schema. Catch as RegistrationError(code="invalid_event").
unknown_field_v0
The push body contains a field not declared on the event's schema (and not in optional_fields). Beava is strict-deny on unknown fields in v0. Add the field to your @bv.event declaration or strip it from the push payload.
unknown_field_event_time_v0
Specialised form of unknown_field_v0 for the reserved event_time / event_time_ms field names. v0 is processing-time only — server clock is the timestamp source. Drop the field.
missing_event_name_in_body
The framed-TCP variant of POST /push couldn't extract event_name from the request frame. Almost always a malformed frame or a stale client. Re-check the SDK version against the server.
invalid_json_body
The request body wasn't valid JSON. Distinct from invalid_event — the bytes never made it to schema validation.
Get / batch_get errors
unknown_table
The requested table isn't in the registry. In batch_get, this can appear per-tuple in the response array — the SDK aggregates and re-raises as a single RegistrationError when any entry failed.
key_not_found
The table is registered, but no events have populated this entity-key yet. The SDK normalizes single-row get calls so that cold-start returns {} rather than raising — you only see key_not_found when calling lower-level feature-query endpoints directly.
key_parse_failure
The supplied entity key doesn't deserialize into the table's declared key shape — typically a composite-key call where the JSON list has the wrong arity or types.
feature_not_found
One of the feature names in the features=[...] filter isn't on the table. The body's missing key lists the offending names. Drop them or re-register the table with the new feature.
batch_too_large
Total work (keys × features) exceeds the per-request limit (10,000). Split the batch on the client.
Register errors
invalid_registration
The register payload failed structural validation (malformed DAG, missing required keys, raw OpNode shape errors). The body's errors list carries one {kind, path, message} per failure; the SDK exposes them via RegistrationError.errors.
unsupported_node_kind
The register payload included a descriptor with a kind the server doesn't accept. v0 accepts "event" and "derivation" only; aggregation tables are produced via kind: "derivation", output_kind: "table".
unbounded_op_in_lifetime_mode
An aggregation that requires a window (velocity, rate, etc.) was declared without one in a context that demands a bounded window. Add window="..." to the op.
force_required
The proposed registry change would destroy state (type change, removed field, etc.). The body carries a structured diff with additive and destructive sections — same shape the server returns under dry_run. Re-call with force=True to commit, or revise the change to be additive-only.
invalid_descriptor
Client-only. Raised by app.register(*descriptors) when one of the descriptors is a raw EventDerivation (a chain expression that wasn't wrapped in @bv.event). The exception message includes the canonical rewrite. Never reaches the wire.
Reset / admin errors
reset_disabled_in_production
The server isn't in test mode (no --test-mode flag, no BEAVA_TEST_MODE=1). /reset is gated to keep production state safe. The SDK surfaces this as RegistrationError(code="reset_disabled") for backward compatibility — handle either string in your except branch if you straddle versions.
Transport / framing errors
unknown_op
The opcode in a TCP frame isn't in the dispatch table. Almost always a stale client; upgrade the SDK.
op_not_implemented
The opcode is recognised but not handled in this build. Distinct from unknown_op (the opcode is outside the dispatch table entirely).
frame_too_large
The TCP frame's declared length exceeds max_frame_bytes. Bump the server's frame ceiling or split the request.
frame_error
Generic protocol-level frame violation (bad length, bad content-type marker, truncated payload).
http_protocol_error
The HTTP/1.1 layer rejected the request before it reached an application handler — typically a malformed request line or oversize headers.
unsupported_content_type
The frame's content_type byte isn't 0x01 (JSON) — the only supported value in v0.
unsupported_media_type
The HTTP Content-Type header isn't application/json. Set the header explicitly on raw curl calls.
unsupported_request_shape
The request body parsed but didn't match any known shape for the endpoint (e.g. /batch_get got an object instead of an array). The body's message field carries a hint.
not_found
The HTTP route doesn't exist. Either a typo on a curl-built request or a version skew.
method_not_allowed
Right path, wrong verb (e.g. GET /push). The body echoes method and path.
Server / durability errors
wal_unavailable
The write-ahead log is taking writes but the durability path is degraded (disk full, fsync stuck, recovery in progress). Retries are safe — beava's contract is that successful writes are WAL-acked.
wal_sync_timeout
A sync-mode push waited longer than its timeout for fsync acknowledgement. The event may or may not be durable — if the server comes back, the event will appear with the next ack-LSN if it was buffered. Treat as an at-most-once retry candidate from the application side.
internal_error
Catch-all for server-side failures the engine couldn't categorize (panicked task, response serialization bug, unexpected I/O). The body's reason field carries a short hint. File a bug with the request payload and the server log line.
SDK-only codes
unparseable_error
Client-only. Generated by the transport layer when the server returns a non-2xx response that isn't valid JSON (a load-balancer 502 page, a TCP RST mid-frame, an unexpected HTML body). The exception's message contains the first 200 bytes of the response so you can diagnose. Never originates server-side.
Common questions
How do I distinguish a 4xx from a 5xx?
The exception doesn't carry the raw HTTP status — only the code. The mapping is documented per-code above (event_not_found is 404, invalid_event is 400, wal_unavailable is 503, etc.). Treat the code as the contract; the status is for HTTP-aware infrastructure (load balancers, monitoring) and isn't part of the SDK's public surface. If you need the status anyway, drop down to the transport layer (app._transport) — but that's a private API and may move.
Does the SDK retry transient failures?
No. Each method is one wire call. Retries belong in your application code — beava's contract is that successful pushes are WAL-acked (durable), so re-pushing on a transport error is safe (idempotent under dedupe_key, an at-least-once duplicate without). For server-side soft failures (wal_unavailable, wal_sync_timeout, internal_error), exponential backoff with jitter is a reasonable default. Don't retry 4xx codes — those are client mistakes that won't fix themselves.
Are these codes stable across versions?
The codes documented above are stable. New codes get added (and that's a minor-version-compatible change); existing codes don't get renamed or repurposed without a major-version bump. If you write an exhaustive switch on e.code, fall through to a default branch — don't panic on unknown codes from a newer server.
Where to go next
Errors don't read in isolation. The two pages you'll cross-reference most:
The seven wire-mapped bv.App methods, each with its own Raises section keyed to the codes above. Start there if you're tracing where a specific error came from.
Full HTTP / TCP request and response shapes per endpoint, with status codes, frame layouts, and content-type matrix. Useful for non-Python clients reading the wire codes raw.
Wire reference →