Repository navigation
Phase 2 of the API redesign: readers are a real type - #60
Open
kelsayed17 wants to merge 22 commits into
Open
kelsayed17 wants to merge 22 commits into
kelsayed17 wants to merge 22 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ting line numbers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iance note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct, the snapshot renders readers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… finds annotations, honest chaining checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, paginate checks expect= first, coverage of as_json Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urrogates, docstrings current per task Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate walk, and the last stale references Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ery codec reader, and finish the sweeps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r for every reader Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urrent shape-error prose, tests first Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eadgate.errors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From Python 3.14, json.loads and msgspec recurse until the real C stack runs out, so whether 100_000 levels overflow depends on the platform: on the ubuntu and macos runners they parsed, and three tests failed with coverage at 99%. The tests now make the parse raise RecursionError mid-decode (a json.loads parse_float hook, a msgspec __post_init__, a mint that raises it), so they pass the same way everywhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Phase 2 of the public API redesign: section 2, Readers, of the design spec. The plan is
specs/2026-11-05-phase-2-readers.md.A reader is now a real type. A wrong
expect=fails twice: it is a type error, and it raisesTypeErrorbefore anything is sent. The builders for readers and writers now share one vocabulary. The two refusal checks are split by what each one sees. A body nested too deeply for its parser now raises a shape error, where it used to raise a rawRecursionError.Breaking changes
Readerwas a type alias, so any callable worked asexpect=Readeris a final class. A hand-written reader isReader(fn), or@Readerover adefexpect=str,expect=Modelor a bare function was acceptedTypeError(expect= takes a Reader or an Expect (got the class str)). Wrap it inReader(...)or use a builderExpect(ok),answers=,Expect.onandcache.conditionaltook any callableReaderwithTypeErrorExpect(ok, {404: a, 409: b})with answers of different types inferred their union.on(), or spell outExpect[A | B | None]decoder_for(codec, T, refusal_check=...)reader_for(codec, T, refuse=...)encoder_for(codec, T)writer_for(codec, T)headgate.msgspec.decoder/.encoder, and pydantic'sheadgate.msgspec.reader/.writer, and pydantic'sbody_reader(decode, refusal_check=...)body_reader(decode, refuse=...)json_reader(decode, allow_empty_body=True)body_reader(decode, empty=b"[]")json_writerEncodedBodyHttpOptions(refusal_check=...)HttpOptions(response_check=...)RefusalCheckResponseCheckfrom headgate import RefusedErrorfrom headgate.errors import RefusedErrorAdded
as_json, a readyReader[object]built on the standard library'sjson. It accepts only strict RFC 8259 JSON. It refuses non-UTF-8 bodies, a BOM, UTF-16,NaN/Infinity, numbers out of range and lone surrogates, each with aDecodeErrorthat never includes the body.Reader'sreprnames what it reads, soExpect(...)prints something useful.Fixed
ResponseParseError, or the status's error on a declared non-2xx. Before, it raised a rawRecursionError, whichever reader read it. Every reader reports it asnested too deeply.Verification
Each task was reviewed against the plan, and the whole branch was reviewed once more at the end. That final review's fixes are in the last commit.
🤖 Generated with Claude Code