Skip to main content

varco-ws

PyPI version WebSocket and Server-Sent Events (SSE) push adapters for the varco event system.

WebSocketEventBus and SSEEventBus are not standalone AbstractEventBus implementations — they are push adapters that subscribe to an existing AbstractEventBus (Kafka, Redis, NATS, in-memory, …) and forward matching events to connected browser clients. The underlying bus still owns routing, retries, and DLQ; varco_ws only handles the last hop to the browser.

Adapter Protocol Direction Best for
WebSocketEventBus WebSocket bidirectional (push side only used here) real-time UIs needing full-duplex
SSEEventBus Server-Sent Events server → client only simple, proxy/CDN-friendly live feeds

Installation

uv add varco-ws
# or: pip install varco-ws

varco_ws depends only on varco_core — no third-party broker client. It is framework-agnostic; the examples below use FastAPI/Starlette because that is the common pairing with the rest of varco.


Quick start — WebSocket

from varco_ws import WebSocketEventBus
from varco_core.event import Event


class OrderPlacedEvent(Event):
    __event_type__ = "order.placed"
    order_id: str


# bus is any AbstractEventBus (KafkaEventBus, RedisEventBus, InMemoryEventBus, ...)
ws_bus = WebSocketEventBus(bus, event_type=OrderPlacedEvent, channel="orders")


@app.on_event("startup")
async def startup() -> None:
    await ws_bus.start()  # subscribes to the underlying bus


@app.websocket("/ws/orders")
async def orders_ws(websocket: WebSocket) -> None:
    await websocket.accept()
    async with ws_bus.connect(websocket):
        await asyncio.sleep(3600)  # keep the connection open until the client disconnects


@app.on_event("shutdown")
async def shutdown() -> None:
    await ws_bus.stop()

Each event is serialized to JSON and pushed to every connected client:

{"event_type": "order.placed", "event_id": "...", "data": {"order_id": "abc"}}

Quick start — Server-Sent Events

from fastapi.responses import StreamingResponse
from varco_ws import SSEEventBus

sse_bus = SSEEventBus(bus, event_type=OrderPlacedEvent, channel="orders")
await sse_bus.start()


@app.get("/events/orders")
async def orders_sse(request: Request):
    async def generate():
        async with sse_bus.subscribe() as stream:
            async for message in stream:
                if await request.is_disconnected():
                    break
                yield message

    return StreamingResponse(generate(), media_type="text/event-stream")

Constructor reference

Both adapters share the same shape:

WebSocketEventBus(
    bus: AbstractEventBus,        # required — the underlying event bus
    *,
    event_type: type[Event] = Event,      # default: subscribe to all event types
    channel: str = "*",                    # default: subscribe to all channels
    max_queue_size: int = 100,             # per-client outbound queue depth (0 = unbounded)
    backpressure_policy: BackpressurePolicy = BackpressurePolicy.DROP_OLDEST,
)

SSEEventBus(
    bus: AbstractEventBus,
    *,
    event_type: type[Event] = Event,
    channel: str = "*",
    max_queue_size: int = 100,
    # SSEEventBus has no backpressure_policy — a full subscriber queue blocks
    # the put() call (natural backpressure) rather than dropping/disconnecting.
)

WebSocketEventBus.connect(websocket, *, connection_id=None, max_queue_size=None, backpressure_policy=None) and SSEEventBus.subscribe() are both async context managers that register/unregister the client automatically, even on exception.

Backpressure policy (WebSocket only)

BackpressurePolicy governs what happens when a slow client's outbound queue fills up:

Policy Effect
DROP_OLDEST (default) discard the oldest buffered message to make room for the new one — best for live feeds where freshness matters more than completeness
DROP_NEWEST discard the incoming message; queue contents are preserved — best when clients must receive events in order from the start
BLOCK await until space is available — guarantees delivery but can stall other clients if many are slow
DISCONNECT eject the client immediately once its queue is full

Each client drains from its own asyncio.Queue via a dedicated background task — a slow client never blocks delivery to other clients or the bus handler itself.


Lifecycle

Both adapters must be explicitly started and stopped — they are not started automatically, even when DI-managed:

await ws_bus.start()  # subscribes to the underlying AbstractEventBus
await ws_bus.stop()  # cancels the subscription and disconnects all clients

async with ws_bus:  # WebSocketEventBus also supports async context-manager use
    ...

DI integration

WebSocketEventBus and SSEEventBus are @Singleton-decorated and inject AbstractEventBus — they self-register when container.scan("varco_ws", recursive=True) is called. An AbstractEventBus implementation must already be registered in the container before scanning varco_ws.

from varco_redis.di import bootstrap as redis_bootstrap
from varco_ws.di import bootstrap as ws_bootstrap

redis_bootstrap()  # registers AbstractEventBus
ws_bootstrap()  # scans varco_ws, finds both adapters

ws_bus = container.get(WebSocketEventBus)
sse_bus = container.get(SSEEventBus)


# Start/stop in the FastAPI lifespan handler — the container never calls
# start()/stop() itself.
@asynccontextmanager
async def lifespan(app):
    await ws_bus.start()
    await sse_bus.start()
    yield
    await ws_bus.stop()
    await sse_bus.stop()

The scan-discovered singletons subscribe to all events on all channels (event_type=Event, channel="*"). For a per-channel adapter, use bind_websocket_adapter() / bind_sse_adapter() instead:

from varco_ws.di import bootstrap, bind_websocket_adapter, bind_sse_adapter
from myapp.events import OrderEvent

bootstrap(container)
bind_websocket_adapter(container, event_type=OrderEvent, channel="orders")
bind_sse_adapter(container, event_type=OrderEvent, channel="orders")

orders_ws = container.get(WebSocketEventBus)  # per-channel singleton
orders_sse = container.get(SSEEventBus)  # per-channel singleton

Running tests

uv run pytest varco_ws/tests/

No external broker is required — varco_ws tests exercise the adapters against InMemoryEventBus.


Caveats

  • Not thread-safe — use each adapter instance from a single event loop.
  • WebSocketEventBus/SSEEventBus are push sidecars, not AbstractEventBus implementations — they cannot be passed anywhere an AbstractEventBus is expected. Use the underlying bus for service-to-service messaging.
  • Memory grows with connected clients × max_queue_size; size accordingly for high fan-out.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

varco_ws-3.2.0.tar.gz (52.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

varco_ws-3.2.0-py3-none-any.whl (39.6 kB view details)

Uploaded Python 3

File details

Details for the file varco_ws-3.2.0.tar.gz.

File metadata

  • Download URL: varco_ws-3.2.0.tar.gz
  • Upload date:
  • Size: 52.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for varco_ws-3.2.0.tar.gz
Algorithm Hash digest
SHA256 0eb368bbec3ae0d93615b9d3cad727ab1fbbe522833d88ceb92e07ad7d513bc0
MD5 60c048d54b2bdfde29b9644bdb17af10
BLAKE2b-256 4e7583c7523e0c29542d032915998eb1f487934e942db55ec2672b35bf668248

See more details on using hashes here.

Provenance

The following attestation bundles were made for varco_ws-3.2.0.tar.gz:

Publisher: release.yml on edoardoscarpaci/varco

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file varco_ws-3.2.0-py3-none-any.whl.

File metadata

  • Download URL: varco_ws-3.2.0-py3-none-any.whl
  • Upload date:
  • Size: 39.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for varco_ws-3.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0b78bfc9e45c7b333593a08a4c20bcbde646b1cd72011f64002d2f80f3d52eec
MD5 70c0a282480ffd62c763b4a497bdc5fe
BLAKE2b-256 80029465809d885e448b904c7b1585911c8554b803b92ef34e65e6a07cd6ef26

See more details on using hashes here.

Provenance

The following attestation bundles were made for varco_ws-3.2.0-py3-none-any.whl:

Publisher: release.yml on edoardoscarpaci/varco

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

3.2.0 This release

2 files

3.1.0

2 files

3.0.0

2 files

2.1.0

2 files

2.0.0

2 files

1.0.6

2 files

0.1.0

2 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