Skip to main content

varco-ws

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.0.0.tar.gz (37.3 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.0.0-py3-none-any.whl (25.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: varco_ws-3.0.0.tar.gz
  • Upload date:
  • Size: 37.3 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.0.0.tar.gz
Algorithm Hash digest
SHA256 0b896f09424a626dc699caaf436cefb91a45eeff44820f519cf00a8f8756e052
MD5 8c6aff4ad920ec6fb116509738ec4260
BLAKE2b-256 e8d004d5456bb104c4c23d5ee557e7bd0888685748d0b7e6507a62dd2ff13671

See more details on using hashes here.

Provenance

The following attestation bundles were made for varco_ws-3.0.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.0.0-py3-none-any.whl.

File metadata

  • Download URL: varco_ws-3.0.0-py3-none-any.whl
  • Upload date:
  • Size: 25.3 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.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c5f9fa9329ac8bd9aadea91d9c68bf8ea2425fe8daa0bcb09aed80a63c202052
MD5 29b55f8a80b7cad80b43934c2f1b49a3
BLAKE2b-256 d764959ce342d1578b3765e71ca8587f485fb1ea03dc167373e1aa0b2f869b9c

See more details on using hashes here.

Provenance

The following attestation bundles were made for varco_ws-3.0.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

3.2.0

2 files

3.1.0

2 files

This release

3.0.0 This release

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