SSE Client with Reconnect Parser

Parse text/event-stream frames into structured events and wrap the HTTP read loop with bounded reconnect attempts and backoff.

PythonData Fetching

Code

snippet
import json
import time
import urllib.error
import urllib.request
from collections.abc import Iterable, Iterator


def parse_sse(lines: Iterable[str]) -> Iterator[dict[str, str]]:
    event: dict[str, str] = {}
    data: list[str] = []
    for raw in lines:
        line = raw.rstrip("\r\n")
        if not line:
            if data:
                event["data"] = "\n".join(data)
                yield event
            event, data = {}, []
            continue
        if line.startswith(":"):
            continue
        field, _, value = line.partition(":")
        value = value.lstrip(" ")
        if field == "data":
            data.append(value)
        elif field:
            event[field] = value
    if data:
        event["data"] = "\n".join(data)
        yield event


def iter_sse(
    url: str, retries: int = 3, backoff: float = 0.5
) -> Iterator[dict[str, str]]:
    attempt = 0
    while True:
        try:
            request = urllib.request.Request(
                url, headers={"Accept": "text/event-stream"}
            )
            with urllib.request.urlopen(request, timeout=30) as response:
                yield from parse_sse(line.decode("utf-8") for line in response)
            return
        except (OSError, urllib.error.URLError):
            attempt += 1
            if attempt > retries:
                raise
            time.sleep(backoff * attempt)


FIXTURE = """event: build
id: 7
data: {"status": "ok"}
data: second line
"""


if __name__ == "__main__":
    events = list(parse_sse(FIXTURE.splitlines()))
    print(events)
    for event in events:
        first_line = event["data"].splitlines()[0]
        print(event.get("event", "message"), json.loads(first_line))