beava/ SDK reference/ Quickstart
v0 SDK reference · 5 min

Install. Push. Query.

Install the beava server, install the Python SDK, push your first event and query the feature — in five minutes.

Pick a path

Most readers want the install path below; if you'd rather skip the server entirely and run beava in-process under your Python interpreter, embed mode is a one-line alternative.

This is the SDK reference. For the why — vision, concepts, recipes — see Docs. For tutorials, Learn.

01 Install beava

pip install beava drops a platform wheel from PyPI. The wheel ships the SDK and the Rust server binary together (~4 MB, polars / ruff / uv pattern). If you'd rather run the server in a container, Docker is the alternative.

terminal
pip install beava

# SDK + server binary land together; `beava` is on PATH
beava --data-dir ./.beava/
# → HTTP listen   : 127.0.0.1:8080
# → TCP listen    : 127.0.0.1:8081 (enabled=true)

The wheel comes from PyPI. The bundled beava binary lands in your Python user-scripts dir (~/.local/bin/beava on Linux, ~/Library/Python/<ver>/bin/beava on macOS), so embed mode (bv.App() with no URL) finds it without extra setup. Pin a specific version with pip install beava==0.0.0. Add --memory-only to skip the WAL entirely.

terminal
# persistent volume keeps your features across restarts
docker run -d --name beava \
  -p 8080:8080 -p 8081:8081 \
  -v beava-data:/data \
  beavadev/beava:latest

# SDK install — same `pip install beava` works inside docker
# the bundled binary on PATH; pass an explicit URL to `bv.App(url=...)`.
curl -fsSL https://raw.githubusercontent.com/beava-dev/beava/main/scripts/install.sh | sh

# verify
curl -X POST http://localhost:8080/ping
# → {"pong":true,"registry_version":0}

Port 8080 is the HTTP data plane (push, get, batch_get, register, ping). Port 8081 is the framed-TCP fast-path (sub-millisecond JSON). Admin endpoints (/health, /ready, /metrics, /registry) live on a separate sidecar port that you opt into via the --http-addr / --config flags.

Tip. beava ships static. There's no JVM to size, no broker to configure, no schema registry to register against. If curl -X POST :8080/ping returns {"pong":true,"registry_version":0}, you're done.

02 Declare your first feature

beava is declarative. You write a Python class for the event source and a function for the table (the per-entity feature you want to query). Every operator works the same way: group_by(...).agg(name=bv.<op>(...)).

site_features.py 12 lines
import beava as bv

@bv.event
class PageView:
    user_id: str
    path: str

@bv.table(key="user_id")
def UserStats(pv: PageView):
    return pv.group_by("user_id").agg(
        visits=bv.count(window="1h"),
    )

Register it once. The server picks it up live — no restart.

terminal
python -c "import beava as bv; from site_features import *; \
  bv.App('http://localhost:8080').register(PageView, UserStats)"
# → {"status": "ok", "registry_version": 1, "added": ["PageView", "UserStats"]}

03 Push events, read the feature

beava speaks HTTP — every operation is a single request. You can drive it from Python or from curl; the wire is identical.

request · push event
curl -X POST \
  http://localhost:8080/push \
  -H "content-type: application/json" \
  -d '{
    "event": "PageView",
    "data": {
      "user_id": "alice",
      "path":    "/home"
    }
  }'
response · 4 ms 200 OK
{
  "ack_lsn": 62,
  "idempotent_replay": false,
  "registry_version": 1
}

Now ask beava for the running feature:

request · read feature
curl -X POST \
  http://localhost:8080/get \
  -H "content-type: application/json" \
  -d '{
    "table": "UserStats",
    "key":   "alice"
  }'
response · 0.6 ms 200 OK
{ "visits": 2 }

The /get response is the row itself — a flat dict of feature name → value. Cold-start (no events for that key) returns {}, not an error — a cold key is a key with no data.

App parameters

Every bv.App(...) accepts the same shape. Pass an explicit URL or rely on embed mode (no URL → spawn local binary on ephemeral ports).

url string | None

Server URL. http://... / https://... for HTTP transport, tcp://... for the custom-framed TCP fast-path. None (the default) for embed mode — the SDK locates the beava binary on $PATH and spawns it.

default None

timeout float

Per-request transport-level I/O timeout in seconds.

default 30.0

test_mode bool

Embed-mode only. Sets BEAVA_TEST_MODE=1 in the spawned binary's env so test-only opcodes (OP_RESET) are accepted. Setting test_mode=True against a network URL emits a UserWarning and is ignored.

default False

Common questions

Embed mode — what's the lifecycle?

bv.App() (no URL) spawns the beava binary on ephemeral ports inside a context manager. The subprocess is torn down on __exit__. Calling any wire method on an embed-mode App outside with raises RuntimeError.

with bv.App() as app:
    app.register(PageView, UserStats)
    app.push("PageView", {"user_id": "alice", "path": "/home"})
    print(app.get("UserStats", "alice"))

Each spawn allocates a unique tmpdir under $TMPDIR/beava-embed-<pid>-<ms>-<hex>/; cleaned up via atexit.

What does the server hold? Does it persist events?

The server holds per-entity aggregates — counters, velocities, sketches, last-seen — keyed by entity. It does not persist the raw events themselves; it persists the aggregates over them via WAL + periodic snapshot.

If you need event replay, keep your raw events somewhere else (S3, your warehouse). beava is the live-feature server, not the event log.

Cold-start for an unknown key — is that an error?

No. app.get("UserStats", "newuser") returns {} (empty dict) with HTTP 200 when no events have ever been pushed for that key. This matches the Redis-shaped contract: a cold key is just a key with no data.

unknown_table IS an error — that means the table name isn't registered.

How does this compare to Redis?

Redis is a key-value store you can build rolling counters on top of (with Lua + ZSETs + a lot of care). beava is a server that already is rolling counters — and velocities, leaderboards, last-N-seen, sketches — without you assembling the primitives.

Where to go next

You've got beava running and a counter ticking. Two reasonable next stops: