diff --git a/examples/jiter/README.md b/examples/jiter/README.md new file mode 100644 index 0000000..16eeab7 --- /dev/null +++ b/examples/jiter/README.md @@ -0,0 +1,18 @@ +# jiter Examples + +Each sub-directory contains a self-contained example. The order in +which the examples are to appear is specified in `order.json` (an +array of directory names in the expected order). + +In each example directory you'll find: + +* `config.toml` - must conform to the specification outlined here: + https://docs.pyscript.net/latest/user-guide/configuration/ This is + parsed and ultimately turned into a JSON representation as part of + the package's API object. +* `setup.py` - Python code for contextual and environmental setup, + NOT SEEN BY THE END USER, but is run before the `code.py` code is + evaluated. Allows us to create useful (IPython) shims, avoid + repeating boilerplate and whatnot. +* `code.py` - the actual code added to the editor which forms the + practical example of using the package. diff --git a/examples/jiter/from_json_basics/code.py b/examples/jiter/from_json_basics/code.py new file mode 100644 index 0000000..1c051b5 --- /dev/null +++ b/examples/jiter/from_json_basics/code.py @@ -0,0 +1,54 @@ +""" +A first look at jiter, a fast JSON parser from the Pydantic team. + +The whole API is essentially one function: `jiter.from_json`. It takes +a `bytes` object (not a `str`) and returns native Python values, just +like the standard library's `json.loads`. See the project page for +details: https://github.com/pydantic/jiter +""" +from IPython.core.display import display, HTML + +import jiter + + +# jiter.from_json expects bytes -- note the leading `b`. +weather_report = b""" +{ + "station": "Kew Gardens", + "recorded_at": "2026-04-12T09:00:00Z", + "temperature_c": 14.2, + "humidity_pct": 71, + "observers": ["Ada", "Grace", "Lin"], + "is_raining": false +} +""" + +heading("1. Parse JSON bytes into a Python dict") +note( + "jiter returns ordinary Python values: dicts, lists, strings, " + "numbers, booleans, and None. Below, we parse a small weather " + "report and pull values out by key." +) + +report = jiter.from_json(weather_report) + +display(report, append=True) +note( + f"Station: {report['station']}. " + f"Temperature: {report['temperature_c']} °C. " + f"Observers: {', '.join(report['observers'])}." +) + +heading("2. The string cache") +note( + "By default, jiter caches parsed strings to speed up repeated " + "keys and values. You can inspect and reset that cache." +) + +# Parse a few records that share keys, so the cache gets a workout. +for _ in range(5): + jiter.from_json(weather_report) + +note(f"String cache size after parsing: {jiter.cache_usage()} bytes.") +jiter.cache_clear() +note(f"After cache_clear(): {jiter.cache_usage()} bytes.") diff --git a/examples/jiter/from_json_basics/config.toml b/examples/jiter/from_json_basics/config.toml new file mode 100644 index 0000000..9fecf48 --- /dev/null +++ b/examples/jiter/from_json_basics/config.toml @@ -0,0 +1 @@ +packages = ["jiter"] diff --git a/examples/jiter/from_json_basics/setup.py b/examples/jiter/from_json_basics/setup.py new file mode 100644 index 0000000..b4f3ee1 --- /dev/null +++ b/examples/jiter/from_json_basics/setup.py @@ -0,0 +1,41 @@ +""" +Shim IPython's display API onto PyScript so example code written in a +Jupyter/IPython idiom runs unmodified in the browser. +""" + +import sys +import types +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + """Wrap pyscript.display so output lands in the example target.""" + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +ipython = types.ModuleType("IPython") +core = types.ModuleType("IPython.core") +core_display = types.ModuleType("IPython.core.display") +core_display.display = display +core_display.HTML = HTML +ipython.core = core +core.display = core_display +ipython.get_ipython = lambda: None +ipython.display = core_display +sys.modules["IPython"] = ipython +sys.modules["IPython.core"] = core +sys.modules["IPython.core.display"] = core_display +sys.modules["IPython.display"] = core_display + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) diff --git a/examples/jiter/order.json b/examples/jiter/order.json new file mode 100644 index 0000000..f2acbe0 --- /dev/null +++ b/examples/jiter/order.json @@ -0,0 +1,5 @@ +[ + "from_json_basics", + "partial_json_streaming", + "precision_and_duplicates" +] diff --git a/examples/jiter/partial_json_streaming/code.py b/examples/jiter/partial_json_streaming/code.py new file mode 100644 index 0000000..8bd0859 --- /dev/null +++ b/examples/jiter/partial_json_streaming/code.py @@ -0,0 +1,46 @@ +# --------------------------------------------------------------------- +# Partial JSON: parsing a stream that hasn't finished arriving. +# --------------------------------------------------------------------- + +heading("Parsing partial JSON as it streams in") +note( + "When you're reading JSON from a network response (think: an LLM " + "token stream), you often want to render what you have so far. " + "jiter's partial_mode handles a truncated payload " + "without raising." +) + +# Imagine these are progressively longer prefixes of a single response +# arriving over the wire, one chunk at a time. +full_response = ( + b'{"model": "demo-1", "choices": [' + b'{"role": "assistant", "content": "Hello, traveller! Welcome to jiter."}' + b']}' +) +checkpoints = [full_response[:n] for n in (20, 55, 95, len(full_response))] + +heading("Default: incomplete input is an error", level=3) +try: + jiter.from_json(checkpoints[1]) +except ValueError as exc: + note(f"Got ValueError: {exc}") + +heading("partial_mode=True: drop the trailing incomplete value", level=3) +note( + "Useful when you want a clean, fully-formed object so far. The " + "still-arriving last field is silently omitted." +) +for i, chunk in enumerate(checkpoints, start=1): + parsed = jiter.from_json(chunk, partial_mode=True) + display(HTML(f"After chunk {i} ({len(chunk)} bytes):"), append=True) + display(parsed, append=True) + +heading('partial_mode="trailing-strings": keep the in-flight string', level=3) +note( + "Perfect for showing a token-by-token assistant reply: the " + "partially-received string is included as-is." +) +for i, chunk in enumerate(checkpoints, start=1): + parsed = jiter.from_json(chunk, partial_mode="trailing-strings") + display(HTML(f"After chunk {i}:"), append=True) + display(parsed, append=True) diff --git a/examples/jiter/partial_json_streaming/config.toml b/examples/jiter/partial_json_streaming/config.toml new file mode 100644 index 0000000..9fecf48 --- /dev/null +++ b/examples/jiter/partial_json_streaming/config.toml @@ -0,0 +1 @@ +packages = ["jiter"] diff --git a/examples/jiter/partial_json_streaming/setup.py b/examples/jiter/partial_json_streaming/setup.py new file mode 100644 index 0000000..cedc558 --- /dev/null +++ b/examples/jiter/partial_json_streaming/setup.py @@ -0,0 +1,20 @@ +"""Lighter setup for the second example: same names, no IPython shim.""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display(*args, **kwargs, target=__pyscript_display_target__) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +import jiter diff --git a/examples/jiter/precision_and_duplicates/code.py b/examples/jiter/precision_and_duplicates/code.py new file mode 100644 index 0000000..9e08eb4 --- /dev/null +++ b/examples/jiter/precision_and_duplicates/code.py @@ -0,0 +1,59 @@ +# --------------------------------------------------------------------- +# Numeric precision and strict-mode options. +# --------------------------------------------------------------------- +import jiter +from decimal import Decimal + + +heading("Decimals and duplicate-key detection") +note( + "When JSON carries money or scientific values, the default " + "float conversion can lose precision. jiter lets " + "you opt into Decimal output. Separately, " + "catch_duplicate_keys turns a silent overwrite " + "into a loud error." +) + +# A ledger entry whose amount has more digits than a 64-bit float +# can faithfully store. +ledger_entry = b'{"account": "ACME-001", "amount": 1234567890.1234567890}' + +heading("Default: amount comes back as a float", level=3) +default_parse = jiter.from_json(ledger_entry) +display(default_parse, append=True) +note( + f"Type: {type(default_parse['amount']).__name__}. " + f"Notice the trailing digits have been rounded." +) + +heading('float_mode="decimal": exact decimal arithmetic', level=3) +decimal_parse = jiter.from_json(ledger_entry, float_mode="decimal") +display(decimal_parse, append=True) +note( + f"Type: {type(decimal_parse['amount']).__name__}. " + f"Now we can sum many of these without drift." +) + +# Add up 1000 copies to show Decimal stays exact. +total = sum( + jiter.from_json(ledger_entry, float_mode="decimal")["amount"] + for _ in range(1000) +) +note(f"Sum of 1000 entries (Decimal): {total}") + +heading("Catching duplicate keys", level=3) +note( + "By default, JSON parsers silently let later keys win. That can " + "hide bugs in upstream data. Pass catch_duplicate_keys=True " + "to fail fast instead." +) + +suspect_payload = b'{"user_id": 1, "user_id": 2, "name": "Robin"}' + +permissive = jiter.from_json(suspect_payload) +note(f"Permissive parse (last value wins): {permissive}") + +try: + jiter.from_json(suspect_payload, catch_duplicate_keys=True) +except ValueError as exc: + note(f"Strict parse raised ValueError: {exc}") diff --git a/examples/jiter/precision_and_duplicates/config.toml b/examples/jiter/precision_and_duplicates/config.toml new file mode 100644 index 0000000..9fecf48 --- /dev/null +++ b/examples/jiter/precision_and_duplicates/config.toml @@ -0,0 +1 @@ +packages = ["jiter"] diff --git a/examples/jiter/precision_and_duplicates/setup.py b/examples/jiter/precision_and_duplicates/setup.py new file mode 100644 index 0000000..0bf3fc9 --- /dev/null +++ b/examples/jiter/precision_and_duplicates/setup.py @@ -0,0 +1,18 @@ +"""Lighter setup: same names, no IPython shim.""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display(*args, **kwargs, target=__pyscript_display_target__) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) +