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.1

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.1
File Size Uploaded
vesta_client-0.1.1.tar.gz 39.5 kB Details

Built distribution (wheel)

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

Total release size: 76.8 kB

Release files / vesta_client-0.1.1.tar.gz

Download URL vesta_client-0.1.1.tar.gz
Size 39.5 kB
Tags Source
SHA-256 checksum
How to use checksums
d8de1d308dcc252e9b7626ce499facff86c63d4314b4d03b882e97567f0f46d8
BLAKE2b-256 checksum
How to use checksums
88dedae5f9a002c2e0a4ec86a2e611bf7c2565b6e9e9e17bc7563af7d9c311c8
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.1-py3-none-any.whl

Download URL vesta_client-0.1.1-py3-none-any.whl
Size 37.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
244960278f91e31d0928fcd98f98114cf927dd6a0c41e1a7316c716811b048c8
BLAKE2b-256 checksum
How to use checksums
c9ae43a38dd0cabace16e21f425678b1a581fd5d7a6d666a413d6d81b057080d
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

0.1.2

2 release files

This release

0.1.1 This release

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