feat: pydantic as an optional extra (0.3.0)#21
Merged
Conversation
Item A (pydantic moves to [project.optional-dependencies], guard the import like msgspec, fail-fast at AsyncClient.__init__ when decoder=None and the extra is missing, drop the top-level PydanticDecoder re-export) + Item D (empty/malformed payload tests for PydanticDecoder) + a full README freshness pass. Single PR, breaking, ships as 0.3.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 tasks decomposed test-first. Output is a 7-commit feature branch covering: import_checker flag + decoders/pydantic.py guard, lazy fail-fast default decoder in client.py, top-level re-export drop + isolation subprocess test, malformed-payload tests, pyproject extras move + version bump, README freshness pass + engineering.md updates, 0.3.0 release notes. Implements planning/specs/2026-06-04-pydantic-optional-extra-design.md.
…init__ Adds is_pydantic_installed to _internal/import_checker.py; guards the pydantic import in decoders/pydantic.py the same way msgspec is guarded; PydanticDecoder.__init__ raises ImportError with the install hint when the extra is missing. New tests in tests/test_optional_extras_pydantic_missing.py cover both the PydanticDecoder fail-fast and the explicit-decoder escape hatch. Drops the submodule-level __all__ in decoders/pydantic.py. Part of the 0.3.0 pydantic-optional-extra work (planning/specs/2026-06-04-pydantic-optional-extra-design.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the unconditional top-level PydanticDecoder import from client.py. Adds _default_pydantic_decoder() that checks is_pydantic_installed up front and raises ImportError immediately when AsyncClient(decoder=None) is constructed without the pydantic extra. Explicit decoder= arguments bypass the check. Restores the AsyncClient fail-fast test in tests/test_optional_extras_pydantic_missing.py that was deferred from the previous commit so per-commit greenness holds. Part of the 0.3.0 pydantic-optional-extra work (planning/specs/2026-06-04-pydantic-optional-extra-design.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PydanticDecoder is no longer re-exported from httpware/__init__.py. Consumers import it from httpware.decoders.pydantic instead, mirroring how MsgspecDecoder is already accessed. test_public_api.py moves the symbol from expected to removed; tests/test_decoders_pydantic.py uses the submodule import path; a new subprocess test in tests/test_optional_extras_isolation.py guards against pydantic being re-introduced as a transitive load. Breaking change for callers using `from httpware import PydanticDecoder`. Part of the 0.3.0 pydantic-optional-extra work (planning/specs/2026-06-04-pydantic-optional-extra-design.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Unit 1 implementer renamed `content` → `_content` to silence ARG002, which broke structural matching against ResponseDecoder.decode (`ty` rejects the name divergence because `content` is keyword-callable). Keep the protocol-matching name and silence ARG002 with a per-line justification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rtion The removed-symbols set now contains 0.1 removals (Request, Response, ClientConfig, etc.) and the 0.3 removal PydanticDecoder. The "0.1 symbols still exposed" diagnostic would misattribute a future leak. Drop the version tag — the set is "removed", regardless of when. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Parametrized tests for empty bytes, null literal, empty object, malformed JSON, and invalid UTF-8 against both a primitive int and a BaseModel subclass. Pins current pydantic.ValidationError surface so a future pydantic upgrade that changes the error type fails visibly instead of silently changing semantics. Closes the "empty/malformed payload tests" item from planning/deferred-work.md. Part of the 0.3.0 pydantic-optional-extra work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pydantic moves from [project] dependencies to [project.optional-dependencies]. Install httpware[pydantic] to keep the default-decoder UX; the all extra now bundles pydantic, msgspec, and otel. Breaking change. See planning/releases/0.3.0.md for the full migration story. Part of the 0.3.0 pydantic-optional-extra work. Also applies ruff formatting fixes to two test files from prior units that were already unformatted on this branch (required to pass lint-ci). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README rewrites the post-pivot blurb, replaces the stale "0.1.0 alpha" status with 0.3.0, drops the RecordedTransport reference, and adds the [pydantic] extra to install instructions. engineering.md §1 retracts "Pydantic ships as the default"; §3 Seam C adds the isolation-test verification rule; §7 spells out the guarded-import pattern explicitly. Part of the 0.3.0 pydantic-optional-extra work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the pydantic-as-optional-extra breaking change with two migration paths (install the extra, or import PydanticDecoder from the submodule). Notes the malformed-payload tests, isolation-test addition, and README freshness pass. Part of the 0.3.0 pydantic-optional-extra work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous regex anchored `^from pydantic` matched zero lines because the imports now live inside `if import_checker.is_pydantic_installed:` blocks (i.e., indented). Update the example command to match indented import lines and filter out the import_checker module itself. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lesnik512
added a commit
that referenced
this pull request
Jun 4, 2026
PR #21 (`feat/v0.3-pydantic-optional`, tag 0.3.0) shipped both deferred-work items it tracked (pydantic-import guard + malformed-payload tests). Move the spec, plan, and the leftover v0.2-retro-housekeeping spec into planning/archive/, and migrate the two closed items into a new "Closed by the 0.3.0 release" section in deferred-work.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
pydanticfrom a required[project] dependencyto an opt-in[project.optional-dependencies]extra (pip install httpware[pydantic]).httpware.PydanticDecoderre-export; consumers import fromhttpware.decoders.pydanticinstead — mirrors the existingMsgspecDecoderpattern.AsyncClient(decoder=None)without thepydanticextra installed now raisesImportErrorat__init__with a clear install hint.b"",b"null",b"{}", invalid JSON, invalid UTF-8) via parametrized tests, so a future pydantic upgrade that changes the error surface fails visibly.README.mdandplanning/engineering.mdup to date for the 0.3.0 framing (drops stale0.1.0 alphastatus +RecordedTransportreference).0.3.0.Breaking changes documented in
planning/releases/0.3.0.mdwith two migration paths.Spec & plan
planning/specs/2026-06-04-pydantic-optional-extra-design.mdplanning/plans/2026-06-04-pydantic-optional-extra-plan.mdTest plan
just lint-ciclean (eof-fixer + ruff format + ruff check + ty check)just testgreen: 130 passed, 100% coveragegrep -rnE 'from pydantic|import pydantic' src/httpware/ | grep -v import_checkerreturns exactly one indented line (the guarded import indecoders/pydantic.py)tests/test_optional_extras_isolation.pysubprocess-tests thatimport httpwaredoes not transitively load pydanticpip install httpware(no extras) —from httpware import AsyncClientworks;AsyncClient()raisesImportErrorwith thehttpware[pydantic]hintpip install httpware[pydantic]—AsyncClient()works, defaultPydanticDecoderis constructedfrom httpware import PydanticDecoderfails withImportError: cannot import name 'PydanticDecoder' from 'httpware'(confirms the breaking change is visible)🤖 Generated with Claude Code