Skip to main content

vesta-client (Python)

Python client library for the Vesta protocol.

Installation

pip install vesta-client

Usage

import asyncio
from vesta_client import VestaConnection, create_event, load_or_create_identity

async def main():
    client_id = load_or_create_identity("myapp-main-alice")

    conn = VestaConnection(
        server_url="ws://localhost:5150/ws",
        client_id=client_id,
        channels=["myapp/chat"],
    )

    conn.on_event(lambda msg: print(f"Event: {msg.event.event_type}"))
    conn.on_connected(lambda welcome: print(f"Connected to {welcome.server_id}"))

    await conn.connect()

    # Publish
    event = create_event(
        channel_id="myapp/chat",
        client_id=client_id,
        event_type="app.chat.message",
        payload={"text": "Hello!", "username": "alice"},
    )
    await conn.publish(event)

    # Keep running
    await asyncio.Event().wait()

asyncio.run(main())

API

VestaConnection

Async WebSocket connection with auto-reconnect.

Constructor

VestaConnection(
    server_url: str,
    client_id: str,
    channels: list[str],
    auto_reconnect: bool = True,
    initial_reconnect_delay: float = 1.0,
    max_reconnect_delay: float = 30.0,
    last_sequences: dict[str, int] | None = None,
    public_key: str | None = None,
    identity: VestaIdentity | None = None,  # enables device-group helpers
    local_store: ClientEventStore | None = None,  # offline outbox + event cache
    relay_directory: RelayDirectory | None = None,  # manifest verification + relay failover
)

Methods

  • await connect() — Open connection and handshake
  • await disconnect() — Gracefully close
  • await dispose() — Permanently dispose
  • await publish(event) — Publish a VestaEvent
  • await subscribe(channel_id, from_sequence=None) — Subscribe
  • await unsubscribe(channel_id) — Unsubscribe
  • await fetch(channel_id, from_sequence, to_sequence=None, limit=None) — Fetch history
  • update_sequence(channel_id, sequence) — Update catch-up position
  • await delete_channel(channel_id) — Soft-delete a channel
  • await register_app(app_id) — Register an app namespace (needed when the relay requires app registration)
  • set_user_relay_override(url) / clear_user_relay_override() — Persist or clear the user's manual relay choice (requires relay_directory)
  • await switch_relay(url) — Switch to a specific relay from the current candidate list and reconnect

Device group helpers (require identity in constructor):

  • await create_device_group(device_name=None) — Create a new group, publish an announce, return group_id
  • await link_device(group_id, target_public_key, reason=None) — Vouch for another device
  • await join_device_group(group_id, device_name=None) — Announce this device joining an existing group
  • await unlink_device(group_id, target_public_key, reason=None) — Remove a device from the group
  • await get_device_group_members(group_id, timeout=5.0) — Replay the identity channel and return current membership as DeviceGroup

Event callbacks

  • on_event(callback) — Real-time event received
  • on_events_batch(callback) — Batch of events received
  • on_ack(callback) — Publish acknowledged
  • on_error(callback) — Raw server error
  • on_limited(callback) — Semantic "your app is being limited" signal — see "Limit notices" below
  • on_connected(callback) — Connection established
  • on_reconnected(callback) — Fired instead of/alongside on_connected on a subsequent WELCOME
  • on_disconnected(callback) — Connection lost
  • on_relay_switched(callback) — The active relay changed
  • on_manifest_applied(callback) — A newer owner-signed relay manifest was adopted

Limit notices

When the relay refuses a publish (quota, rate limit, unregistered app, ACL), the raw error is still delivered via on_error, but classify_error_code(code) (also run internally) tells you whether it's worth surfacing to the user and whether retrying can ever succeed:

conn.on_limited(lambda notice: print(notice.code, notice.message, "transient:", notice.is_transient))

When local_store is configured and the limit is not transient (e.g. QUOTA_EXCEEDED, UNKNOWN_APP, ACCESS_DENIED), the matching outbox entry is automatically marked "rejected" via mark_outbox_rejected so it is dead-lettered instead of retried forever.

Offline & persistence

ClientEventStore caches received events and queues outbox publishes made while disconnected. The package ships InMemoryClientEventStore (non-persistent) and SqliteClientEventStore (stdlib sqlite3, durable across restarts):

from vesta_client import SqliteClientEventStore

local_store = SqliteClientEventStore("my-app-cache.db")
conn = VestaConnection(server_url="ws://localhost:5150/ws", client_id=client_id,
                       channels=["myapp/chat"], local_store=local_store)

Projection snapshots

Every built-in reducer (AppendOnlyLog, LwwRegister, LwwMap) supports snapshot() / restore() so a projection can resume from its last sequence instead of replaying the whole channel on cold start. Persist snapshots with a ProjectionStore — InMemoryProjectionStore or SqliteProjectionStore:

from vesta_client import LwwMap, restore_projection, save_projection
from vesta_client.projection_store import SqliteProjectionStore

store = SqliteProjectionStore("my-app-snapshots.db")
presence = LwwMap(project)

await restore_projection(store, channel_id, "presence", presence)
await conn.fetch(channel_id, presence.last_sequence + 1)
# ...later, e.g. on shutdown:
await save_projection(store, channel_id, "presence", presence)

A reducer that hasn't opted in raises SnapshotNotSupportedError — override snapshot() / _restore_state() on a custom EventReducer subclass to add support.

Relay independence

RelayDirectory resolves an ordered relay candidate list from a user override, the latest verified owner-signed manifest, and the app's compiled-in defaults (VestaAppConfig). Attach one to get manifest verification/adoption and set_user_relay_override() / clear_user_relay_override():

from vesta_client import RelayDirectory, VestaAppConfig, FileRelayOverrideStore, FileManifestStore

app_config = VestaAppConfig(app_id="myapp", owner_public_key="...", default_relays=["wss://relay.example/ws"])
relay_directory = RelayDirectory(
    app_config,
    FileRelayOverrideStore("~/.vesta/relays/myapp.override.json"),
    FileManifestStore("~/.vesta/relays/myapp.manifest.json"),
)

conn = VestaConnection(
    relays=relay_directory.resolve_candidates(),
    client_id=client_id, channels=["myapp/chat"],
    relay_directory=relay_directory,
)

InMemoryRelayOverrideStore / InMemoryManifestStore are also available for tests or transient sessions.

Federation (server-to-server discovery)

When every relay in the candidate list is failing and no fresher manifest is available, FederationClient asks any reachable discovery-enabled relay which relays host your app:

from vesta_client import FederationClient

federation = FederationClient(app_config)
base = FederationClient.to_federation_base_url("wss://relay.example/ws")  # "https://relay.example/"
relays = await federation.discover_relays_for_app(base)
# Show-only: the user adopts one manually via conn.set_user_relay_override(url).

Every descriptor is verified (self-signature) and cross-checked against the app's owner (owner_client_id must match derive_client_id(app_config.owner_public_key)) — a relay cannot spoof hosting your app under a different owner. Uses the stdlib urllib.request internally — no extra dependency.

create_event(channel_id, client_id, event_type, payload, **kwargs)

Create a VestaEvent with a UUID and current timestamp.

load_or_create_identity(prefix)

Persist a stable clientId in ~/.vesta/{prefix}-identity.json.

Metadata

Release files for vesta-client 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vesta-client 0.1.2
File Size Uploaded
vesta_client-0.1.2.tar.gz 39.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for vesta-client 0.1.2
File Interpreter ABI Platform
vesta_client-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 76.8 kB

Release files / vesta_client-0.1.2.tar.gz

Download URL vesta_client-0.1.2.tar.gz
Size 39.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e579e194bcb82e09ce1c6ce656b4aa3a0b2886df74d3719fb0b58d077d7897b2
BLAKE2b-256 checksum
How to use checksums
48c3fd373aa13cadf600ddbbaca7b8b2d78b6860052c4998288bad70e8b071f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / vesta_client-0.1.2-py3-none-any.whl

Download URL vesta_client-0.1.2-py3-none-any.whl
Size 37.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d54c8f1f38d275bc9ee7c030e91c71014b8672fd93d3e855c39dd5608d02b87c
BLAKE2b-256 checksum
How to use checksums
556cfcfc6a7b795221807599b65081f7f24e5bb517b85c0c2c2f4a042e0356c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.6

2 release files

0.1.4

2 release files

0.1.3

2 release files

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page