Three small context-manager-and-parsing utilities, the exact shapes
context managers and
exceptions just walked. timer() and
atomic_write(path) are context managers you'll write again for real: one measures a block
without ever hiding what happened inside it, the other makes "half-written file on disk" impossible by
construction. repair_json is the function every structured-output LLM feature needs on day
one — models emit JSON wrapped in prose, fenced in markdown, with a trailing comma or a truncated tail
when the response got cut off, and your pipeline has to salvage it or reject it cleanly, never guess.
timer() — a context manager that yields a dict and, on exit however the block
ends, sets d["elapsed"] to the elapsed wall-clock seconds. If the block raises, the
exception must still reach the caller — a timer that swallows errors to "finish measuring" is worse
than no timer at all.atomic_write(path) — yields a writable file handle backed by a temporary file, and
only replaces path with it, via os.replace(), once the block finishes without
raising. An exception before that point must leave path exactly as it was — untouched if it
already existed, absent if it didn't — with no stray temp file left behind either.repair_json(text) — best-effort repair of JSON the way a language model actually emits
it: wrapped in a ```json fence, with a trailing comma before a } or
], with single-quoted strings, or truncated mid-object because the response got cut off.
Return the parsed value on success; raise ValueError when the text genuinely isn't
salvageable — silently returning garbage is the one failure mode worse than raising.The checks are ordinary Python and ship with the page like everything else on a
static site — you could read them in devtools if you wanted. One of them writes a real temporary file and
injects a real exception mid-write to prove atomic_write's guarantee; that filesystem access
works the same way in Pyodide's in-memory filesystem as it does here.
start = time.perf_counter() before the yield, then
d["elapsed"] = time.perf_counter() - start inside a finally wrapped around the
yield. A finally runs whether the block raised or not, and — critically —
never swallows the exception the way an except would if you're not careful to
raise again.tempfile.mkstemp(dir=...) for the temp file, write into it with
os.fdopen(fd, "w") inside a with, then os.replace(tmp, path)
after that with block exits cleanly. Wrap the whole thing in
try/finally and remove the temp file in the finally unless you reached the
replace — a flag variable set right after os.replace is the simplest way to know.json language tag) before trying to parse anything else.,\s*([}\]]) with
\1 removes a comma sitting right before a closer, in objects and arrays alike.ast.literal_eval reads Python-dict-style text (single
quotes, True/False/None) safely, without executing arbitrary
code the way eval would; round-trip the result through
json.loads(json.dumps(...)) so only JSON-safe values survive.ValueError — don't return the raw text, `None`, or a partial guess.