oc_chat
Loading...
Searching...
No Matches
ndjson.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# providers/ollama/ndjson.py
4
5"""
6NDJSON (Newline-Delimited JSON) stream parser.
7
8Purpose
9-------
10Reusable utility for parsing chunked NDJSON HTTP responses into a stream
11of dicts. Used by :class:`providers.ollama.client.OllamaClient` and
12reusable by future providers (LM Studio, vLLM).
13
14Responsibilities
15----------------
16- :func:`parse_ndjson_stream` — consume an arbitrary-chunk-size byte
17 iterator, accumulate a line buffer, and yield one dict per complete
18 JSON line. Handles: chunks split across JSON boundaries, blank lines,
19 malformed JSON lines, and final un-terminated lines.
20
21Diagnostics
22-----------
23Domain: OC_CHAT.CLIENT
24Levels:
25 L2 — malformed JSON line logged and skipped
26 L3 — semantic details
27 L4 — deep tracing
28
29Contracts
30---------
31- Input: any ``Iterator[bytes]`` — HTTP response body iterated in
32 arbitrary chunk sizes.
33- Output: ``Iterator[dict]`` — one dict per valid JSON line.
34- A ``json.JSONDecodeError`` on any single line is logged at L2 then
35 skipped; the remaining stream continues normally.
36- An empty iterator yields nothing.
37- A final chunk without a trailing ``\\n`` is flushed after the iterator
38 is exhausted.
39
40Dependencies
41------------
42- oc_diagnostics (structured debug logging via _dbg)
43"""
44from __future__ import annotations
45
46import json
47from collections.abc import Iterator
48
49from oc_diagnostics import _dbg
50
51
52def parse_ndjson_stream(byte_iter: Iterator[bytes]) -> Iterator[dict]:
53 """Parse a chunked NDJSON HTTP response body into a stream of dicts.
54
55 Each element of *byte_iter* may contain zero, one, or multiple
56 ``\\n``-terminated JSON lines, or a partial line that continues in
57 the next element.
58
59 Args:
60 byte_iter: Raw HTTP body chunk iterator (e.g. an iterated
61 ``http.client.HTTPResponse``).
62
63 Yields:
64 Parsed dicts, one per complete, valid JSON line.
65 """
66 func = "parse_ndjson_stream"
67 buf = bytearray()
68
69 for chunk in byte_iter:
70 buf.extend(chunk)
71 while b"\n" in buf:
72 line_bytes, buf = buf.split(b"\n", 1)
73 line = line_bytes.strip()
74 if not line:
75 continue
76 try:
77 yield json.loads(line)
78 except json.JSONDecodeError as exc:
79 _dbg("OC_CHAT.CLIENT", func,
80 f"malformed JSON line ({exc}): {line[:80]!r}", 2)
81
82 # Flush any remaining data (final un-terminated line)
83 line = buf.strip()
84 if line:
85 try:
86 yield json.loads(line)
87 except json.JSONDecodeError as exc:
88 _dbg("OC_CHAT.CLIENT", func,
89 f"malformed final line ({exc}): {line[:80]!r}", 2)
90
91
92__all__ = ["parse_ndjson_stream"]
Iterator[dict] parse_ndjson_stream(Iterator[bytes] byte_iter)
Definition ndjson.py:52