Skip to content

Share the corpus builders' cache and file helpers - #690

Merged
derek73 merged 4 commits into
masterfrom
refactor/corpus-build-helpers
Oct 17, 2026
Merged

derek73 merged 4 commits into
masterfrom
refactor/corpus-build-helpers

Conversation

@derek73

@derek73 derek73 commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Summary

This is the follow-up to #689's /simplify pass. Three labeled-corpus builders each carried their own copies of the same helpers:

Helper Copies before
corpus_lines CLDR, GND, Wikidata
fields_lines CLDR, GND, Wikidata
cache_dir GND, Wikidata
check-before-cache, atomic cached JSON fetch GND, Wikidata

tools/differential/corpus_build.py now holds them once:

  • cache_dir(name)
  • parse_json
  • cached_json(cache, key, label, fetch, check, refresh)
  • corpus_lines
  • fields_lines
  • write_corpus

Each builder keeps only what is its own: its query, its response predicate and its renderings.

  • Import: nothing in tools/differential/ imported a sibling before. Each builder appends its own directory to sys.path before importing. That directory is usually sys.path[0] when a builder runs as a script, but not under -P/PYTHONSAFEPATH, nor when the tests load a builder by path. It is appended rather than inserted, so it cannot shadow a stdlib or installed module. The trade-off: an earlier corpus_build on sys.path would win, and none exists.
  • GND's corpus_lines becomes the shape-aware one CLDR and Wikidata used. It writes the same lines for GND's rows, all of which carry "shape": None. Only the test's hand-built rows lacked the key, and they gain it. The shared function stays strict, so a builder that forgets the shape fails rather than emitting untagged lines.
  • Cache keys and paths are unchanged, so existing caches are still hit. Wikidata's damaged-cache message now names the URL, as GND's did.
  • write_corpus writes the pair through .part files. Both are built and written aside before either is renamed into place, and the .part files are removed on failure. A refused row (a duplicate rendering, a bad shape) or a failed write therefore leaves the committed pair as it was. Only a crash between the two renames could leave them out of step. The old builders wrote the corpus file first, then the fields file.
  • Sort order: an untagged line sorts before the tagged ones of its text. The old key tied shape 0 with None, leaving that order to set iteration. No builder writes shape 0, so no output moves.
  • README: "Adding a source" now points new builders at corpus_build.py, and the Wikidata cache path names its wikidata/ directory.

Test plan

  • All three corpora rebuild byte-identical: CLDR re-fetched from GitHub, GND and Wikidata from the cache.

  • uv run pytest: 19,251 passed

  • New tests/v2/test_corpus_build.py pins each helper's contract:

    • each builder loads in a fresh -P interpreter. In the test process, the first builder loaded had been finding the module for the other two, so a builder that forgot its own sys.path step still passed;
    • the shape TypeError;
    • the untagged-first order, over forty texts, since one text passed the old key by luck;
    • write_corpus: a refused row, and a write that fails partway, each leave the old pair and no .part file;
    • the atomic cache write, by fault injection.

    Each test was checked against a mutation of its code and fails.

  • uv run mypy, uv run ruff check

  • Three review rounds:

    • the first found no behavior change;
    • the second (tests, comments, silent failures) led to the fixes above;
    • the third, over the fix commits, found that "both or neither" held only for a refused row, which the .part writes now extend to a failed write.

    The deliberate behavior changes don't move output: the pair is never half-written, and the tie-break is deterministic.

🤖 Generated with Claude Code

build_cldr_corpus.py, build_gnd_corpus.py and build_wikidata_corpus.py
each carried their own corpus_lines and fields_lines, and the two that
query a live service each their own cache_dir and check-before-cache,
atomic-write fetch. corpus_build.py now holds them once; each builder
appends its own directory to sys.path (the tests load builders by path)
and imports it. GND's corpus_lines becomes the shape-aware one, which
writes the same lines for its shape-less rows; its test's hand-built
rows gain the shape key every real row has.

All three corpora rebuild byte-identical (CLDR re-fetched from GitHub,
GND and Wikidata from the cache, whose keys are unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73 derek73 self-assigned this Oct 17, 2026
@codecov

codecov Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.03%. Comparing base (4375b01) to head (beac25d).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #690   +/-   ##
=======================================
  Coverage   99.03%   99.03%           
=======================================
  Files          46       46           
  Lines        4541     4541           
=======================================
  Hits         4497     4497           
  Misses         44       44           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

derek73 and others added 3 commits October 16, 2026 19:06
Review of #690:
- write_corpus builds both files before writing either, so a refused
  row leaves the committed pair as it was (the old builders wrote the
  corpus first, and the docstring claimed 'always together');
- test_corpus_build.py loads each builder in a fresh -P interpreter
  (the test process's first builder had been finding the module for
  the other two), and pins the shape TypeError, write_corpus, and the
  atomic cache write by fault injection;
- an untagged line sorts before the tagged ones of its text, so the
  order no longer rests on set iteration when shape 0 ties with None;
- the sys.path comment said appending prevents shadowing; it prevents
  shadowing the stdlib, at the cost of an earlier corpus_build winning;
  load_tool's 'no import-time side effects' now names the one there is;
  the Wikidata cache path names its wikidata/ directory.

All three corpora rebuild byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its negative control passed: with one text, the old key's tie between
shape 0 and None happened to break the right way in set order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second review of #690's fix: 'both or neither' held for a refused row
but not for a write that fails partway (disk full, interrupt). Both
files now go to .part files, removed again on failure, before either
is renamed into place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73 derek73 added this to the 2.5 milestone Oct 17, 2026
@derek73
derek73 merged commit 3e3ad58 into master Oct 17, 2026
10 checks passed
@derek73
derek73 deleted the refactor/corpus-build-helpers branch October 17, 2026 02:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant