0.4.0: simpler API — load/validate/DocumentIndex, Template/Form interview, questions and assemble -i - #96
Merged
Conversation
…(deprecated, removed in 0.5.0) load() reads a file as UTF-8 and sets Document.filename and the new Document.path, the base later multi-file resolution can use. parse() is the string form; parse_document() stays as a warning alias. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…e place, say path is groundwork Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…nly what was found - validate() replaces validate_document() (deprecated, removed in 0.5.0). - The two importer callbacks become one resolve= (same shape as assembly's LoadFile); by default the amended original's and the attachment files' definitions are read from beside document.path, sandboxed to its directory (files.file_loader, now shared with the CLI and assembly). - ValidationResult drops the stored errors/warnings/infos (now derived from diagnostics) and the recording methods and used_terms (internal _Recorder). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
… CLI test, changed-in-0.4.0 note Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…thing else; CONFORMANCE limit reworded Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…rts a fault while validating as an internal error Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…ineValues)
The flat analysis fields leave ValidationResult at once, with no
forwarding: result.sections is result.index.sections, the five inline_*
lists are result.index.values.{dates,money,durations,fields,placeholders}.
The recorder now builds the result from its two parts.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…y keyword Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…pler removal test Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…nswers before assembling
Stage 1 of the assembly questions/answers plan.
- load_template/parse_template read a template and its includes once; Template
has questions, problems (what refuses assembly, with the validator's template
rules) and validation (advice).
- Template.form(answers) is a frozen snapshot: questions reached (includes read
where their {{include:}} is), unanswered, blocking, unused, diagnostics,
ready, complete; problem(qid); assemble(). An invalid answer counts as no
answer for reach.
- Question is frozen, has problem(answer), and keeps its placeholders' info.
- assemble, template_questions and needed_questions are deprecated (0.4.0,
removed 0.5.0) and keep their behaviour; legaldown assemble follows the new
API. The template identifiers are worked out once per template.
- tests/test_assembly.py runs every test through both the functions and Template/Form.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
… twice, CLI guard) - Question keeps plain dict default/choices (copied from the declaration), so a default can be offered back as an answer, and a Question can be copied and pickled; None choices is accepted. - condition-reference-unsafe blocks only a template that includes nothing; duration-invalid-unit and directive-duplicate-param block a template. - A file included twice is read in place at its last include, as the old walk decides it. Answers that cannot be copied are reported, not a crash. - Template takes check=; the form-versus-function tests now compare with the deprecated function's ok, output, files and rules; a test pins the reference into an include; CLI tests for the refusal and the internal-error guard. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…-only gate for unsafe references, cyclic answers - directive-duplicate-param and duration-invalid-unit are checked on placeholders outside drafting notes (from the template's own reading), not taken from the validator, which also reports them for notes and other directives. - condition-reference-unsafe is advice only when the template includes fragments; an attachment file hides no section from a reference. - Form copies answers with a memo for cycles, falls back when nested too deep. - The CLI reports a fault while reading the template as an internal error. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
Stage 2 of the assembly questions/answers plan. - hint says what to type (yes or no, a date, an amount and a currency, one of: the choices, ...); from_text turns typed text into the answer assembly takes, or None for the empty text, or raises ValueError saying what to enter. - Strict, not locale-aware; a key before a label, an ambiguous label refused; the amount alone only where the placeholders fix the currency or the unit. - What it returns always passes Question.problem (checked last, and by a randomized test); README documents the interview loop. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…text errors say why, exact key first, README loop keeps decisions open) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…wn questions Stage 3 of the assembly questions/answers plan. - Template.coerce puts right the shapes plain YAML gets wrong (text read as from_text reads it, a money amount given as a number) and never raises; load_answers reads the YAML mapping (AnswersError); the CLI uses both. - Form.as_dict is the JSON-ready form: states, answers, hints, accepts, problems and diagnostics with the question each is about. Question.accepts is the structured hint, to_text the inverse of from_text. - legaldown questions lists what is asked (text or JSON, --check). - The hint and from_text follow the recorded placeholders over a changed currency. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…d descriptions of hostile answers - Form.as_dict is strict JSON: non-finite floats and integers too long to write become text; nested answers are cut at a depth and a node budget; unknown shapes are described shallowly (Question.to_text). - legaldown questions writes UTF-8 bytes like assemble, allow_nan=False, and reports a fault as an internal error; load_answers reports nesting too deep. - coerce cannot raise for an integer too long to write; as_dict's currency and unit follow Question.accepts. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…as empty text Fixes the as_dict test of the previous commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
Stage 4 of the assembly questions/answers plan. - -i asks, on a terminal, for the answers --answers does not give, as the form grows: prompts and errors on stderr, each answer read with Question.from_text, a decision asked again until answered, the empty line leaving a value question to its default or blank, an invalid answer from the file asked again. Without a terminal it lists what must be answered (exit 1), or assembles if nothing must be. The end of the input ends the interview. - --save-answers FILE writes the answers used, with those typed, as YAML in a finally (after Ctrl-C, which assembles nothing and exits 2, or a failure). - Without -i a missing decision says how to be asked. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…nvalid answers dropped, atomic save, line editing) - A text answer holding a lone surrogate is not valid; the terminal's input is asked again when it cannot be decoded; input() gives line editing, with the prompt on stderr. - A template that cannot be assembled asks nothing (and lists no questions without a terminal): its problems come first. - The empty line drops an answer from the file that is not valid, so the default or the blank applies; an accepted default is echoed. - --save-answers writes beside the file and moves it into place. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…m of line editing) input() uses readline only where standard output is the terminal, which assemble's is not, so the earlier change did not give line editing and let arrow keys into answers. A text answer with a C0 control (the tab excepted) or DEL is not valid; _ask is the plain readline() again, with the prompt on stderr. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
The deprecations of this release say 'since 0.4.0, removed in 0.5.0'. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
Contributor
Author
|
@claude review Generated by Claude Code |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
…plain data, one path filter, save-answers keeps what it replaces - answer_problem is §15.7.1 exactly again (a default with a control character is as valid as before); the control-character and UTF-8 rules apply to what is typed (from_text). - Question's placeholder record is an InitVar, not a field: asdict/replace/fields are plain. - files.relative_path: one filter for the paths a LoadFile is asked for (validate and assembly): no absolute path, drive letter, backslash, null byte or '..'. - --save-answers keeps the file's permissions, writes through a symlink, refuses the template's own path; an answer that cannot be written as UTF-8 is an error, not a traceback; the end of the input is said. - README: validate now reads amended originals and attachments (changed in 0.4.0). - Tests: spec-exact text answers, the filter, symlinks, save behaviour; the conformance tests also run the public Template door. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…de the directory Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…f invalid templates documented; hard-link guard - --save-answers writes nothing when no answers file was read and nothing was typed (a typo in the template name, -i without a terminal, Ctrl-C before the first answer), and says so. - The template is also protected against a hard link named as the file to save to. - README: 'Changed in 0.4.0' for legaldown assemble refusing templates with template-rule Errors. - CONFORMANCE.md and the validator __all__ wording. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…, not a traceback Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
…, and replaced by the answers from 3.13 resolve() no longer raises on a link loop in 3.13, so the save succeeds there. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A step-by-step simplification of the external Python API, in three parts. Every old entry point still works and warns (
DeprecationWarning), to be removed in 0.5.0. The version is bumped to 0.4.0.1. Opening a document
load(path)opens a document (UTF-8, BOM and line endings handled) and setsDocument.path;parse(text)is the string form.parse_document.2. Validation: input and output
validate(document, *, final=False, resolve=None). The amended original's and the LegalDown attachment files' definitions are read from besidedocument.path(sandboxed to its directory, one level deep, never the document itself).resolve=replaces the two importer callbacks and has the same shape as assembly'sLoadFile. New shared modulefiles.py(LoadFile,file_loader,relative_path).ValidationResultisdiagnostics+index. The analysis moved toresult.index(DocumentIndex:sections, the lookups,values,is_template,placed_markers).errors/warnings/infosare derived fromdiagnostics; recording methods andused_termsmoved to an internal recorder.validate_document,import_definitions=,import_attachment_definitions=.3. Assembly as an interview
load_template(path)/parse_template(text, resolve=)read a template once.Template.form(answers)is a frozen snapshot:questions(reached, includes read where their{{include:}}is),unanswered,blocking,unused,diagnostics,ready,complete,problem(id),assemble(),as_dict().Question(now a frozen value) getsproblem(answer),from_text(text),to_text(answer),hintandaccepts.Template.coerce(answers)andload_answers(path).legaldown questions(text or JSON,--check),legaldown assemble -i(asks in the terminal; prompts on stderr) and--save-answers FILE(atomic, keeps the file's permissions, never the template itself).assemble,template_questions,needed_questions.Behaviour changes to review
legaldown validatenow reads the amended original and attachment files beside a document, so documents withamendsor attachments can gain real findings (amend-def-override,def-duplicate-id; a missing term moves from Infoamend-term-unresolvableto Erroramend-term-undefined). Noted in the README as "Changed in 0.4.0".legaldown assembleand the newTemplaterefuse a template with Errors in the validator's template rules (§15.7.2 gives assembly a valid template); the deprecatedassemblefunction does not. The CLI also puts the shapes of the answers file right (5000 EUR,no, trimmed text;fee: 5000where the placeholders fix the currency), which wereanswer-invalidbefore.Question.from_text);answer_problemitself is exactly §15.7.1.LoadFile/resolve=loader is only asked for paths within the document's directory (§2.3), including by the deprecatedassemble.{{include:}}is; the deprecated functions ask them after the body.ValidationResultandQuestionchanges are breaking for code that read the old fields or built a result positionally (it is now keyword-only). This is a deliberate hard break, documented in the README.Review history
Each stage was reviewed adversarially by an Opus subagent against the code, with probes (including a real pty for
-i), and the findings fixed before the next stage. A final independent Opus review of the whole PR ran differential checks oflegaldown validate --format jsonagainstmainon every spec fixture (only the amendment cases differ, as documented), found one blocking and several important issues, and confirmed their fixes. The deprecated functions run on the old internal code paths, andtests/test_assembly.pyruns every one of its tests twice, through the functions and throughTemplate/Form, comparingok, output, files and rule ids.Test plan
python -m pytest -q -W error::DeprecationWarning— 1976 passed, 14 skippedLEGALDOWN_FIXTURES_DIR, LegalDown v0.2) — 2409 passed, 40 skipped; theTemplate/Formpath (ungated, and the public door with the template rules on) assembles all assembly fixtures byte for byteruff check src tests🤖 Generated with Claude Code
https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz