Skip to content

Commit 9cbe664

Browse files
committed
Drop is_leaf property
1 parent 1df904d commit 9cbe664

7 files changed

Lines changed: 7 additions & 69 deletions

File tree

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,7 @@ mode (RFC 6902 JSON Patch over NDJSON).
171171

172172
Each event carries the raw op (`event.op`) and an independent deep copy of the
173173
document so far (`event.snapshot`). For the common case of reacting to one
174-
field at a time, use the demux properties: `event.is_leaf` skips structural
175-
ops (root seed, empty-container init), `event.field` is the JSON Pointer with
174+
field at a time, use the demux properties: `event.field` is the JSON Pointer with
176175
the leading `/` stripped (`"intent"`, `"steps/0"`, `"address/city"`), and
177176
`event.value` is the op's value.
178177

@@ -200,8 +199,6 @@ async def main() -> None:
200199
input="I was charged twice this month, please refund the duplicate.",
201200
)
202201
async for event in stream:
203-
if not event.is_leaf:
204-
continue
205202
match event.field:
206203
case "intent":
207204
print(f"dispatching to {event.value} queue")

docs/client.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,9 @@ Each `PatchEvent` carries:
232232
folds one into a object in place, returning the (possibly new) root.
233233
- `event.snapshot` — an independent deep copy of the JSON object built up to
234234
and including this op
235-
- `event.is_leaf` / `event.field` / `event.value` convenience demux for
236-
the common case of reacting to one field at a time. `is_leaf` is `True`
237-
for non-structural adds (skipping the root seed and empty-container init
238-
ops); `field` is the JSON Pointer with the leading `/` stripped
239-
(`"intent"`, `"steps/0"`, `"address/city"`).
235+
- `event.field` / `event.value``field` is the JSON Pointer with the leading `/` stripped
236+
(`"intent"`, `"steps/0"`, `"address/city"`). `value` contains the current field content,
237+
including empty lists `[]` or dictionary `{}` values.
240238

241239
```python
242240
import asyncio
@@ -258,8 +256,6 @@ async def main():
258256
input="I was charged twice this month, please refund the duplicate.",
259257
)
260258
async for event in stream:
261-
if not event.is_leaf:
262-
continue
263259
match event.field:
264260
case "intent":
265261
asyncio.create_task(dispatch_to_queue(event.value))

examples/stream_fanout.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ async def main() -> None:
7676
max_tokens=400,
7777
)
7878
async for event in stream:
79-
if not event.is_leaf:
79+
if not event.value:
8080
continue
8181
elapsed_ms = int((time.monotonic() - started) * 1000)
8282
if event.field.startswith("steps/"):

examples/stream_field_printer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async def main() -> None:
3434
input="Generate a profile for a senior backend engineer.",
3535
)
3636
async for event in stream:
37-
if not event.is_leaf:
37+
if not event.value:
3838
continue
3939
print(f"{event.field:>24} = {event.value!r}")
4040
finally:

examples/stream_hitl_approval.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ async def main() -> None:
7474
max_tokens=300,
7575
)
7676
async for event in stream:
77-
if not event.is_leaf:
78-
continue
7977
match event.field:
8078
case "action":
8179
proposed_action = event.value

src/dottxt/streaming.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,13 @@ class PatchEvent:
3737
is an independent deep copy, so callers may stash events without later
3838
ops mutating earlier snapshots.
3939
40-
The ``is_leaf`` / ``field`` / ``value`` properties demux the op for the
40+
The ``field`` / ``value`` properties demux the op for the
4141
common pattern of reacting to one structured-output field at a time.
4242
"""
4343

4444
op: dict[str, Any]
4545
snapshot: dict[str, Any] | list[Any]
4646

47-
@property
48-
def is_leaf(self) -> bool:
49-
"""True iff this op contributes a single leaf value.
50-
51-
False for the root seed (``path == ""``), for empty-container init
52-
ops (``value`` is ``{}`` or ``[]``), and for any op that is not an
53-
``add``.
54-
"""
55-
return (
56-
self.op.get("op") == "add"
57-
and self.op.get("path", "") != ""
58-
and self.op.get("value") not in ({}, [])
59-
)
60-
6147
@property
6248
def field(self) -> str:
6349
"""JSON Pointer for this op with the leading ``/`` stripped.

tests/test_streaming.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,6 @@
1111
from dottxt.streaming import PatchEvent, PatchStreamError, apply_add, stream
1212

1313

14-
def test_patch_event_is_leaf_for_top_level_leaf() -> None:
15-
"""A non-empty add at a non-root path is a leaf."""
16-
event = PatchEvent(
17-
op={"op": "add", "path": "/intent", "value": "billing"},
18-
snapshot={"intent": "billing"},
19-
)
20-
assert event.is_leaf is True
21-
assert event.field == "intent"
22-
assert event.value == "billing"
23-
24-
25-
def test_patch_event_is_leaf_false_for_root_seed() -> None:
26-
"""The root seed op is structural, not a leaf."""
27-
event = PatchEvent(op={"op": "add", "path": "", "value": {}}, snapshot={})
28-
assert event.is_leaf is False
29-
assert event.field == ""
30-
31-
32-
def test_patch_event_is_leaf_false_for_empty_container() -> None:
33-
"""Empty-object and empty-array seed ops are structural."""
34-
obj_seed = PatchEvent(
35-
op={"op": "add", "path": "/address", "value": {}}, snapshot={"address": {}}
36-
)
37-
arr_seed = PatchEvent(
38-
op={"op": "add", "path": "/steps", "value": []}, snapshot={"steps": []}
39-
)
40-
assert obj_seed.is_leaf is False
41-
assert arr_seed.is_leaf is False
42-
43-
4414
def test_patch_event_field_for_array_index_and_nested_path() -> None:
4515
"""Array indices and nested object paths keep their joined segments."""
4616
arr = PatchEvent(
@@ -55,15 +25,6 @@ def test_patch_event_field_for_array_index_and_nested_path() -> None:
5525
assert nested.field == "address/city"
5626

5727

58-
def test_patch_event_is_leaf_handles_falsy_primitives() -> None:
59-
"""Falsy primitives (0, "", False) are still leaves, only {} / [] are structural."""
60-
for value in (0, "", False, None):
61-
event = PatchEvent(
62-
op={"op": "add", "path": "/x", "value": value}, snapshot={"x": value}
63-
)
64-
assert event.is_leaf is True, value
65-
66-
6728
def test_apply_add_replaces_root_for_empty_path() -> None:
6829
"""An op with ``path == ""`` replaces the document root."""
6930
assert apply_add(None, "", {}) == {}

0 commit comments

Comments
 (0)