Skip to content

feat: pydantic as an optional extra (0.3.0)#21

Merged
lesnik512 merged 12 commits into
mainfrom
feat/v0.3-pydantic-optional
Jun 4, 2026
Merged

feat: pydantic as an optional extra (0.3.0)#21
lesnik512 merged 12 commits into
mainfrom
feat/v0.3-pydantic-optional

Conversation

@lesnik512

@lesnik512 lesnik512 commented Jun 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Moves pydantic from a required [project] dependency to an opt-in [project.optional-dependencies] extra (pip install httpware[pydantic]).
  • Drops the top-level httpware.PydanticDecoder re-export; consumers import from httpware.decoders.pydantic instead — mirrors the existing MsgspecDecoder pattern.
  • AsyncClient(decoder=None) without the pydantic extra installed now raises ImportError at __init__ with a clear install hint.
  • Pins pydantic-core behavior on malformed payloads (b"", b"null", b"{}", invalid JSON, invalid UTF-8) via parametrized tests, so a future pydantic upgrade that changes the error surface fails visibly.
  • Brings README.md and planning/engineering.md up to date for the 0.3.0 framing (drops stale 0.1.0 alpha status + RecordedTransport reference).
  • Version bumped to 0.3.0.

Breaking changes documented in planning/releases/0.3.0.md with two migration paths.

Spec & plan

Test plan

  • just lint-ci clean (eof-fixer + ruff format + ruff check + ty check)
  • just test green: 130 passed, 100% coverage
  • grep -rnE 'from pydantic|import pydantic' src/httpware/ | grep -v import_checker returns exactly one indented line (the guarded import in decoders/pydantic.py)
  • tests/test_optional_extras_isolation.py subprocess-tests that import httpware does not transitively load pydantic
  • In a fresh venv: pip install httpware (no extras) — from httpware import AsyncClient works; AsyncClient() raises ImportError with the httpware[pydantic] hint
  • In a fresh venv: pip install httpware[pydantic]AsyncClient() works, default PydanticDecoder is constructed
  • In a fresh venv: from httpware import PydanticDecoder fails with ImportError: cannot import name 'PydanticDecoder' from 'httpware' (confirms the breaking change is visible)

🤖 Generated with Claude Code

lesnik512 and others added 12 commits June 4, 2026 22:35
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 lesnik512 self-assigned this Jun 4, 2026
@lesnik512 lesnik512 merged commit 1222c64 into main Jun 4, 2026
5 checks passed
@lesnik512 lesnik512 deleted the feat/v0.3-pydantic-optional branch June 4, 2026 20:33
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>
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