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.

Metadata

Release files for varco-ws 3.2.0

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

Source distribution (sdist)

Source distribution for varco-ws 3.2.0
File Size Uploaded Provenance Actions
varco_ws-3.2.0.tar.gz 52.2 kB With provenance Details for varco_ws-3.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for varco-ws 3.2.0
File Interpreter ABI Platform Actions
varco_ws-3.2.0-py3-none-any.whl Python 3 none any Details for varco_ws-3.2.0-py3-none-any.whl

Total release size: 91.9 kB

Release files / varco_ws-3.2.0.tar.gz

Download URL varco_ws-3.2.0.tar.gz
Size 52.2 kB
Tags Source
SHA-256 checksum
How to use checksums
0eb368bbec3ae0d93615b9d3cad727ab1fbbe522833d88ceb92e07ad7d513bc0
BLAKE2b-256 checksum
How to use checksums
4e7583c7523e0c29542d032915998eb1f487934e942db55ec2672b35bf668248
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 12, 2026.

Transparency log

Release files / varco_ws-3.2.0-py3-none-any.whl

Download URL varco_ws-3.2.0-py3-none-any.whl
Size 39.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0b78bfc9e45c7b333593a08a4c20bcbde646b1cd72011f64002d2f80f3d52eec
BLAKE2b-256 checksum
How to use checksums
80029465809d885e448b904c7b1585911c8554b803b92ef34e65e6a07cd6ef26
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 12, 2026.

Transparency log

Security for varco-ws 3.2.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for varco_ws-3.2.0.tar.gz

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 12, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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