Skip to content

Commit 7988519

Browse files
lesnik512claude
andcommitted
docs: import planning bundle from base-client; make httpware self-contained
- Copy product-brief-httpware.md, product-brief-httpware-distillate.md, prd.md, architecture.md, epics.md from sibling base-client/docs/ - Updated CLAUDE.md to point at local docs/ rather than ../base-client/docs/ - Updated story 1.1 input_documents to relative paths - httpx2 dependency tightened from >=2.0.0b1 to >=2.0.0 in all planning docs (matches httpware/pyproject.toml; httpx2 v2.0.0 GA shipped 2026-05-12) Future bmad workflow sessions can now run from httpware/ without needing the base-client repo on disk. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 19030de commit 7988519

7 files changed

Lines changed: 3142 additions & 9 deletions

File tree

‎CLAUDE.md‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ Guidance for AI agents (Claude Code, etc.) working in this repository.
66

77
`httpware` is a Python async HTTP client framework for building resilient service clients. It supersedes `community-of-python/base-client` and ships under the `modern-python` org. The framework owns the abstraction layer above the underlying HTTP client (`httpx2` by default); consumers never import the transport.
88

9-
**Source-of-truth planning artifacts** live in the sibling `base-client` repo (until httpware is fully self-hosted):
9+
**Source-of-truth planning artifacts** live in this repo under `docs/`:
1010

11-
- `base-client/docs/prd.md` — Product Requirements Document (47 FRs, 25 NFRs)
12-
- `base-client/docs/architecture.md` — 12 architectural decisions, 5 protocol seams, full module layout
13-
- `base-client/docs/epics.md` — 6 epics, 32 stories with Given/When/Then acceptance criteria
11+
- `docs/prd.md` — Product Requirements Document (47 FRs, 25 NFRs)
12+
- `docs/architecture.md` — 12 architectural decisions, 5 protocol seams, full module layout
13+
- `docs/epics.md` — 6 epics, 32 stories with Given/When/Then acceptance criteria
14+
- `docs/product-brief-httpware.md` — executive brief (history of how the project was scoped)
15+
- `docs/product-brief-httpware-distillate.md` — PRD-ready detail pack with verified facts, API patterns, performance specifics, rejected alternatives, open questions
1416

15-
Stories under active dev live in `httpware/docs/stories/`.
17+
Stories under active dev live in `docs/stories/`. (Initial planning artifacts were authored in the predecessor repo `community-of-python/base-client` and copied here at the end of Story 1.1; both copies are kept in sync until base-client is archived.)
1618

1719
## Commands
1820

‎docs/architecture.md‎

Lines changed: 1034 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/epics.md‎

Lines changed: 823 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/prd.md‎

Lines changed: 692 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/product-brief-httpware-distillate.md‎

Lines changed: 455 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/product-brief-httpware.md‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
title: "Product Brief: httpware"
3+
status: "complete"
4+
created: "2026-05-11"
5+
updated: "2026-05-12"
6+
inputs:
7+
- "Conversation with maintainer (Artur Shiriev / krenix512@proton.me)"
8+
- "Source scan: /Users/kevinsmith/src/pypi/base-client"
9+
- "Source scan: github.com/community-of-python/circuit-breaker-box"
10+
- "GitHub API: encode/httpx, encode/httpx Discussion #3784, jawah/niquests, pydantic/httpx2"
11+
- "Survey of openai-python, anthropic-sdk-python, stripe-python, hvac"
12+
- "Survey of reqwest, ky, OkHttp, Polly, resilience4j, gobreaker, purgatory, pybreaker"
13+
---
14+
15+
# Product Brief: httpware
16+
17+
## Executive Summary
18+
19+
`httpware` is a new Python async HTTP client framework for building resilient service clients. It supersedes `base-client` (community-of-python), which will be deprecated, and ships under the `modern-python` org.
20+
21+
The library exists because every Python team building backend service clients hits the same wall: the underlying HTTP client leaks its types through your public API, retries and circuit breakers are bolted on as separate libraries with poor composition, and the test story forces consumers to learn transport-level mocking. The dominant async HTTP client was released-stalled for 17 months under `encode/httpx`, then forked to `pydantic/httpx2` on 2026-05-11 with stewardship picked up by Pydantic Services — `httpware` ships on the stewardship-renewed `httpx2` line.
22+
23+
`httpware` solves the underlying design problems: the public API is **transport-agnostic** — the underlying HTTP client is an implementation detail behind a small `Transport` protocol, swappable from httpx2 to niquests (or anything else) without consumer changes. Resilience is a **composable middleware concern**: retries, timeouts, bulkheads, and a Finagle-style **retry budget** ship built-in; circuit breakers and other failure-policy primitives have a stable extension point and a reference design but are deliberately out of v1.0 (see Scope). Tests use a `RecordedTransport` and never see the underlying client.
24+
25+
Now is the right time because `base-client`'s current design (httpx types leaking through the public API, dependency on `httpx._client` private modules, a circuit-breaker library with verified critical bugs) is unsalvageable without breaking changes. The `httpx2` transition is a forcing function: rebuilding on the stewardship-renewed line is the moment to also fix the architectural debt and missing resilience primitives in one motion.
26+
27+
## The Problem
28+
29+
Three pain points motivate this work, in order of severity.
30+
31+
**1. The httpx → httpx2 transition.** `encode/httpx` 0.28.1 shipped 2024-12-06 — no release for 17+ months. The issue tracker was disabled. On 2026-02-27 the lead maintainer publicly stated they were stepping back from community engagement (Discussion #3784). On **2026-05-11** Pydantic Services forked the project as `pydantic/httpx2`, restored community channels, and released `v2.0.0b1` the same day. The strategic concern is now resolved — but every `base-client` consumer is still pinned to `encode/httpx`. Moving to the stewardship-renewed line is itself a breaking change for downstream services if we don't isolate the dependency. Rebuilding the wrapper is the moment to do it right.
32+
33+
**2. `base-client`'s public API leaks httpx everywhere.** Today, `BaseClient` exposes `httpx.AsyncClient` as a public dataclass field; method signatures take and return `httpx.Request` / `httpx.Response`; error classes hold `httpx.Response`; the implementation imports from `httpx._client` and `httpx._types` (private modules). Tests assert against 19 specific httpx exception types via `respx.mock(side_effect=...)`. **Every consumer is tightly coupled to httpx, including via httpx's private API.** Migrating to httpx2 — or anything else — is a breaking change for every downstream service that uses `base-client`.
34+
35+
**3. Resilience is bolted on poorly.** `base-client` depends on `circuit-breaker-box`, which a source-level read confirms has **five critical or high-severity bugs**: no half-open state at all (recovery thundering herd), first failure not counted in retries, Redis backend refreshes the TTL on every increment (the breaker never auto-recovers under sustained load), non-atomic in-memory increment, and an off-by-one in the availability check. It is not a faithful circuit-breaker implementation. Separately, no popular Python library currently ships a **retry budget** — the single most effective control against retry storms, well-understood at Finagle/Envoy scale and absent from Python entirely. The Python ecosystem has the right *parts* (`tenacity`, `purgatory`, `pybreaker`) but no canonical framework that composes them with a coherent ordering, observability, and async semantics.
36+
37+
## The Solution
38+
39+
`httpware` is a small, opinionated framework with six design pillars:
40+
41+
1. **Own the abstractions.** `httpware.Request`, `httpware.Response`, `httpware.Transport`, `httpware.Middleware` are first-class types defined by the library. The underlying HTTP client (`httpx2` by default) sits behind the `Transport` protocol. No consumer code ever imports `httpx2`.
42+
43+
2. **Single-call, typed-response API.** `await client.get("/users/1", response_model=User)` returns a typed `User`. No two-step prepare/send, no fluent builder. Matches the Stainless pattern that openai-python and anthropic-python proved at scale.
44+
45+
3. **First-class middleware, onion model.** Every request flows through `Observability → RetryBudget → Retry → [extension slot] → Bulkhead → Timeout → Transport`. Users add custom middleware (auth refresh, tracing, signing, or a third-party circuit breaker) on the same axis. Built-in primitives are themselves middleware — composable, replaceable, removable.
46+
47+
4. **Pluggable validation.** `response_model=` accepts any type — pydantic is the default validator, but a `ResponseDecoder` protocol lets users plug in `msgspec`, `attrs`, plain dataclasses, or anything else. The library does not hard-couple to one validation library, avoiding the same leakage problem we're solving for the transport.
48+
49+
5. **Retry budget as the flagship resilience feature.** The single thing that turns retry from a footgun into a safe default. Token-bucket admission control over the whole client's retry traffic (Finagle defaults: 20% retry ratio + 10/sec floor + 10s TTL). Caps retry storms before they happen; degrades gracefully when the budget is exhausted. Almost no Python library ships this — `httpware` makes it on by default.
50+
51+
6. **Tests speak the library's language, not the transport's.** A `RecordedTransport({(method, url): Response})` is the primary test path. Consumers never write respx routes and never assert against httpx2 exception types. Mocking is a 3-line fixture.
52+
53+
## What Makes This Different
54+
55+
The Python landscape has three rough cohorts:
56+
57+
- **Stainless-generated SDKs** (openai-python, anthropic-python): excellent typed responses, granular exception hierarchy, but deliberately no middleware system — retries are hand-rolled inside the request loop, circuit breakers are not supported, and the library exists only because it's generated from an OpenAPI spec. Not a framework for building your own service clients.
58+
- **Raw httpx2 / niquests**: low-level transport. No resilience built in. Tests use transport-level mocking. Public API tied to the chosen client.
59+
- **`base-client` and similar thin wrappers**: shims that re-expose the underlying client's types and bolt on one or two resilience libraries with poor composition.
60+
61+
`httpware` occupies the gap: a **framework** with the Stainless typed-response ergonomics, plus first-class middleware-based resilience (which Stainless deliberately omits), plus genuine transport-agnosticism. The retry budget is a category-leading feature. The honest moat is design quality and the combination — none of the pieces are individually novel, but the combination doesn't exist in Python today.
62+
63+
The fuller positioning: **`httpware` is to Python what Polly is to .NET and resilience4j is to the JVM** — a canonical resilience-first HTTP framework. Python has no equivalent category leader; the transport-agnostic abstraction is the proof point that lets `httpware` stand independent of any one underlying client's fate.
64+
65+
## Who This Serves
66+
67+
**Primary**: Backend Python teams in `modern-python` and partner orgs building async service-to-service clients (FastAPI services calling other internal or third-party APIs). Several teams already depend on `base-client`; they need a path forward.
68+
69+
**Secondary**: Teams building **LLM and AI-gateway clients**. AI service traffic is the highest-volume, highest-failure HTTP workload in Python in 2026: long streaming responses, aggressive rate-limits, retry storms when a provider degrades, multi-vendor failover. The middleware model is literally what teams hand-roll on top of openai/anthropic SDKs today — `httpware` makes it a one-line declaration.
70+
71+
**Tertiary**: The wider Python community building service clients on PyPI. `httpware` is open-source and credible as a default choice for new projects that today would reach for `httpx2 + tenacity + a-circuit-breaker-library + a custom wrapper`.
72+
73+
**Success for a user looks like**: defining a service client in under 50 lines that gets resilient retries, retry budgeting, observability, and typed responses for free; swapping the underlying HTTP client (httpx2 → niquests, when the time comes) with a single-line change; writing tests that don't know what an `httpx2.Request` is.
74+
75+
## Success Criteria
76+
77+
**v1.0 release criteria**
78+
79+
- All public API surface uses `httpware.*` types. `grep -r 'import httpx2' httpware/ examples/ tests/` returns zero hits outside the `httpware.transports.httpx2` module.
80+
- Built-in middleware ships and is documented: Retry, RetryBudget, Bulkhead, Timeout, Observability (with OpenTelemetry instrumentation).
81+
- Middleware extension point documented and validated by a reference circuit-breaker middleware (built atop `purgatory` or equivalent) shipped as an example or companion package, not as a v1.0 dependency.
82+
- `RecordedTransport` covers the common test patterns from current `base-client` consumers (success, error status, exception, retry behavior).
83+
- `ResponseDecoder` protocol shipped with pydantic and msgspec adapters.
84+
- At least one consumer service inside `modern-python` is migrated from `base-client` to `httpware` and running in production.
85+
86+
**Adoption criteria (6 months post-1.0)**
87+
88+
- All known `base-client` consumers migrated; `base-client` archived.
89+
- ≥3 external (non-`modern-python`) projects on PyPI depending on `httpware`.
90+
91+
## Scope
92+
93+
**In scope for v1.0**
94+
95+
- Async-only API (no sync facade).
96+
- `Transport` protocol + default `Httpx2Transport`. (Niquests transport: nice-to-have, not blocking.)
97+
- Single-call request API with `response_model=` typed responses.
98+
- `ResponseDecoder` protocol — pluggable validator. Default pydantic adapter; msgspec adapter shipped. User can plug attrs, dataclasses, or anything else.
99+
- Middleware system (onion model with phase-shortcut helpers) and a documented extension point in the resilience layer (the "extension slot" between Retry and Bulkhead) where third-party middleware (e.g. circuit breakers) cleanly compose.
100+
- Built-in middleware: Retry (full-jitter exponential backoff, idempotency-aware), RetryBudget (Finagle defaults), Bulkhead (`asyncio.Semaphore`-based), Timeout, Observability hooks with first-class OpenTelemetry semantic-convention support.
101+
- Exception hierarchy keyed by status (`NotFoundError`, `RateLimitedError`, etc.) with plain fields (`status: int`, `body: bytes`, `headers`, `json`). No transport-typed objects on exceptions.
102+
- `RecordedTransport` for tests.
103+
- `with_options(...)` returning a new client sharing the pool.
104+
- Streaming via `async with client.stream(...) as resp`.
105+
- Security defaults: TLS verification on, configurable secret-redaction hook on outgoing logs/spans, documented CVE disclosure channel.
106+
- Python 3.11+ (drop 3.10 to use `TaskGroup`).
107+
108+
**Explicitly out of v1.0 (designed for, not implemented in v1.0)**
109+
110+
- **In-house circuit breaker.** The middleware extension slot is the contract — a circuit-breaker middleware can be plugged in without library changes. A reference implementation (likely wrapping `purgatory`) ships as an example or companion package. The library's design notes capture the intended state machine (3-state, sliding-window, slow-call, jittered half-open) so the future implementation has a target.
111+
- **Sync API.** Async-only at v1.0. Sync support, if it comes, ships as a parallel class hierarchy (à la Stainless). Users with mixed sync/async codebases (Celery workers, scripts, migrations) keep using `requests` or httpx2 synchronously for now.
112+
- **Niquests transport.** The `Transport` protocol guarantees it's a future-proof addition; shipping it is post-v1.0.
113+
- **Distributed resilience-state coordination** beyond a single Redis backend. No gossip protocols.
114+
- **OpenAPI-driven codegen.**
115+
- **Backwards-compatibility shim for `base-client`.** Migration is documented but not automated.
116+
117+
## Technical Approach
118+
119+
A short engineering note (not the executive read):
120+
121+
The reference implementation is a **clean greenfield rewrite**, not a fork of `base-client`. Default `Transport` wraps `httpx2.AsyncClient` and adapts types at the seam. The default `ResponseDecoder` is a pydantic adapter that caches `TypeAdapter` instances per `response_model` and uses `validate_json(content)` rather than `validate_python(json())` — roughly 2× parse-and-validate throughput, and a fix for a documented performance footgun in current `base-client`. A msgspec adapter ships alongside for users who want msgspec's faster path. Default `Limits` and `Timeout` shipped by the library are sensible for service workloads (`Timeout(connect=5, read=30, write=30, pool=5)`, `Limits(max_connections=100, max_keepalive=20)`), not the `Timeout(1)` in current `base-client` examples.
122+
123+
## Vision (2-3 years)
124+
125+
`httpware` becomes the default choice in the `modern-python` ecosystem for any service that talks to another service over HTTP. It earns adoption outside the org because no other Python library combines its typed-response ergonomics, first-class resilience, and transport-agnosticism.
126+
127+
The transport-agnostic design isn't strategic insurance against any one client's fate (the httpx → httpx2 transition demonstrated that the ecosystem can self-correct), but it remains durable value: any consumer of `httpware` rides out future transport changes without code rewrites, and a future `NiquestsTransport` (or any other backend) drops into the same `Transport` slot.

‎docs/stories/1-1-project-scaffold-and-tooling.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ status: review
77
created: 2026-05-12
88
completed: 2026-05-13
99
input_documents:
10-
- /Users/kevinsmith/src/pypi/base-client/docs/prd.md
11-
- /Users/kevinsmith/src/pypi/base-client/docs/architecture.md
12-
- /Users/kevinsmith/src/pypi/base-client/docs/epics.md
10+
- docs/prd.md
11+
- docs/architecture.md
12+
- docs/epics.md
1313
---
1414

1515
# Story 1.1: Project scaffold and tooling
@@ -83,7 +83,7 @@ input_documents:
8383

8484
## Dev Notes
8585

86-
**Architecture references** (all in `/Users/kevinsmith/src/pypi/base-client/docs/`):
86+
**Architecture references** (all in `docs/`):
8787

8888
- `architecture.md` § Starter Template Evaluation — selected starter is `uv init --lib` + port from `modern-python/modern-di`
8989
- `architecture.md` § Project Structure & Boundaries — full directory tree and root-file enumeration

0 commit comments

Comments
 (0)