Guidance for AI agents (Claude Code, etc.) working in this repository.
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.
Source-of-truth planning artifacts live in this repo under docs/:
docs/prd.md— Product Requirements Document (47 FRs, 25 NFRs)docs/architecture.md— 12 architectural decisions, 5 protocol seams, full module layoutdocs/epics.md— 6 epics, 32 stories with Given/When/Then acceptance criteriadocs/product-brief-httpware.md— executive brief (history of how the project was scoped)docs/product-brief-httpware-distillate.md— PRD-ready detail pack with verified facts, API patterns, performance specifics, rejected alternatives, open questions
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.)
This project uses just (task runner) and uv (package manager).
just install # uv lock --upgrade && uv sync --all-extras --frozen --group lint
just lint # eof-fixer + ruff format + ruff check --fix + ty check
just lint-ci # same checks without auto-fixing (used in CI)
just test # uv run pytest (with coverage by default)
just test-branch # pytest with branch coveragejust test passes extra args to pytest:
just test tests/test_client.py
just test tests/test_client.py -k test_get_returns_typed_responseWithout just:
uv run ruff format . && uv run ruff check . --fix && uv run ty check
uv run pytestThese are non-negotiable. CI rejects PRs that violate them.
- No
httpx2leakage:import httpx2/from httpx2is allowed ONLY insidesrc/httpware/transports/httpx2.py. The mapping ofhttpx2exceptions tohttpwareexceptions happens at that single seam. - No
httpx2private API:grep -rE 'httpx2\._' src/httpware/must return zero matches. - No
from __future__ import annotations: Python 3.11+ floor; PEP 604/585 syntax is native. - No
print(): enforced by ruff. - No global logging config: no
logging.basicConfig(), no barelogging.getLogger(). Acquirelogging.getLogger("httpware")orlogging.getLogger(f"httpware.{module}")only. - Type suppressions: use
# ty: ignore[<rule>], never# type: ignoreor# mypy: ignore.
- Modules:
snake_case(client.py,request.py,transports/httpx2.py). - Classes:
PascalCase.Httpis two letters:Httpx2Transport, notHTTPX2Transport. - Methods:
snake_case. Noaprefix on async methods (matchhttpx2);aclose()is the sole exception. - Private symbols:
_leading_underscore. Cross-module private code lives in_internal/. - Imports: absolute paths inside
src/httpware/; relative imports only within the same subpackage. - Docstrings: PEP 257. Module/class/public-method required;
D1(missing docstring) is ignored. - Exception construction: keyword arguments only. Mandatory fields:
status: int,body: bytes,headers: Mapping,json: Any | None,request_method: str,request_url: str.
src/httpware/
├── __init__.py # public exports + __all__
├── client.py # AsyncClient
├── request.py # Request + with_*
├── response.py # Response, StreamResponse
├── errors.py # status-keyed exception hierarchy
├── config.py # Limits, Timeout, ClientConfig, Redactor
├── middleware/ # protocols + built-in middleware
├── transports/ # Transport protocol + Httpx2Transport + RecordedTransport
├── decoders/ # ResponseDecoder protocol + adapters
├── _internal/ # private cross-module helpers
└── py.typed
Story 1.1 ships only the scaffold; subsequent stories add modules.
Five documented internal boundaries. AI agents must respect them — never cross a seam except through its documented protocol.
Middleware ↔ Transport— chain bottom callstransport.__call__.AsyncClient ↔ Middleware— chain composed at construction.AsyncClient ↔ ResponseDecoder— called whenresponse_modelprovided.Httpx2Transport ↔ httpx2— onlytransports/httpx2.pyimportshttpx2.httpware ↔ optional extras— extras imported only inside their dedicated modules.
pytest-asyncioauto mode — async tests do NOT need@pytest.mark.asyncio.- Property-based tests (Hypothesis) for concurrency-sensitive code:
RetryBudget,Bulkhead, retry interleaving. Files namedtest_*_props.py. - Tests for transport-level mocking use
RecordedTransport(shipped with the library); notrespx.
- Check the architecture document (
base-client/docs/architecture.md) before adding a new module or extension point. - Surface ambiguity as a documentation gap rather than improvising.