Skip to content

refactor(decoders): share memoizing get-or-build cache across adapters#101

Merged
lesnik512 merged 4 commits into
mainfrom
worktree-decoders-shared-memoizing-cache
Jul 13, 2026
Merged

refactor(decoders): share memoizing get-or-build cache across adapters#101
lesnik512 merged 4 commits into
mainfrom
worktree-decoders-shared-memoizing-cache

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Summary

  • Extracts one generic _get_or_build(cache, model, build) into a new decoders/_caching.py, replacing 4 hand-duplicated cache sites across PydanticDecoder/MsgspecDecoder (the object cache and the verdict cache, each duplicated once per decoder).
  • Simplifies decode() in both classes — its try/except TypeError fallback becomes dead code once the shared cache handles the unhashable-model case internally (traced through Seam B's contract to confirm it's genuinely unreachable).
  • Two existing tests that mocked the wrong layer (the whole method, to test decode()'s now-removed fallback) are corrected to mock the cache dict instead.

Behavior change (intentional, reviewed, accepted): can_decode() now returns True (not False) for a genuinely unhashable model that the underlying library can actually build a schema/decoder for (e.g. Annotated[int, []]). This fixes a pre-existing inconsistency — decode() already handled such models correctly via its own fallback, while can_decode() incorrectly rejected them, meaning MissingDecoderError could wrongly fire pre-flight for a model that would have decoded fine. This was caught by task review via an empirical repro, investigated, and explicitly approved as an accepted fix rather than reverted. Two new regression tests (one per decoder, using a genuinely unhashable model through the real non-mocked path) pin the corrected behavior. architecture/decoders.md is updated to document it.

Design: planning/changes/2026-07-13.05-decoders-shared-memoizing-cache.md (updated in-flight to record the finding and the decision)

Test plan

  • just test — 779 passed, 100% coverage
  • just lint-ci — ruff format/check, ty check, planning validator all clean
  • just docs-build — mkdocs --strict clean
  • Task-scoped review (spec + quality): Approved after one fix cycle (Critical finding raised, investigated, resolved with an explicit design decision, re-reviewed clean)
  • Final whole-branch review: Ready to merge = Yes, no Critical/Important findings; independently ruled out a third hidden interaction

Extracts the duplicated memoizing-cache pattern (4 sites: _get_adapter,
_get_msgspec_decoder, and can_decode in both PydanticDecoder and
MsgspecDecoder) into a shared _get_or_build helper in the new
decoders/_caching.py. As a consequence, decode()'s try/except TypeError
fallback in both classes becomes dead code -- the cache now handles the
unhashable-model case internally -- so it's dropped from both.

Corrects two tests that mocked the wrong layer (the whole
_get_adapter/_get_msgspec_decoder method) to instead mock the cache dict,
matching how the sibling can_decode-unhashable tests already do it.

Design: planning/changes/2026-07-13.05-decoders-shared-memoizing-cache.md
…dable models

Task review of c44c80b found the shared memoizing cache changes
can_decode's verdict (False -> True) for a genuinely unhashable,
schema-buildable model (typing.Annotated[int, []]). Verified this
fixes a pre-existing inconsistency rather than introducing a
regression: decode() already handled such models correctly via its
own fallback, while can_decode() incorrectly rejected them. Maintainer
approved accepting the fix; the design doc's Summary, Motivation,
Non-goals, and Testing sections were updated accordingly. Adds one
regression test per decoder pinning the now-consistent behavior.
Final review flagged that decoders.md never documented unhashable-model
behavior, and this branch changes it (can_decode now reflects true
buildability regardless of hashability). Add the missing note per
CLAUDE.md's convention of updating the matching architecture/ capability
file in the same PR that changes its behavior.
@lesnik512 lesnik512 merged commit 2d0d34d into main Jul 13, 2026
6 checks passed
@lesnik512 lesnik512 deleted the worktree-decoders-shared-memoizing-cache branch July 13, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant