You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
chore(deps): drop otel optional extra (YAGNI — no code uses it)
The `otel` extra declared `opentelemetry-api>=1.20` + `opentelemetry-sdk>=1.20`
but zero httpware code imported them. It existed as a placeholder for Epic 5
(Observability) which hasn't started.
Three downsides as currently declared:
1. Misleads users: `pip install httpware[otel]` advertises OTel integration
that doesn't exist.
2. `httpware[all]` pulled ~30MB of OTel deps that no code used.
3. Pre-committed version bounds may not match what Epic 5 eventually needs
(might want opentelemetry-instrumentation rather than the SDK).
Drop now; add back at the same time as the code that uses it lands. This is
the same pattern pydantic/msgspec extras follow.
- pyproject.toml: remove `otel = [...]` block; drop otel from `all` extra.
- planning/engineering.md: update §3 Seam C contract (drop preemptive otel
example), §7 optional-extras pattern (note the lesson), §8 Epic 5-4
description.
- README.md: drop "(pydantic, msgspec, otel)" parenthetical from the [all]
description and the "otel extra is declared but unshipped" line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`AsyncClient()` with no `decoder=` argument defaults to constructing a `PydanticDecoder`; that path requires the `pydantic` extra and raises `ImportError` at `AsyncClient.__init__` if it is missing. The `otel` extra is declared but the OpenTelemetry middleware (Epic 5) has not shipped yet.
23
+
`AsyncClient()` with no `decoder=` argument defaults to constructing a `PydanticDecoder`; that path requires the `pydantic` extra and raises `ImportError` at `AsyncClient.__init__` if it is missing.
Copy file name to clipboardExpand all lines: planning/engineering.md
+6-5Lines changed: 6 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,7 +41,7 @@ The 0.1.0 seams numbered 1 (Middleware↔Transport) and 4 (Transport↔httpx2) h
41
41
### Seam C: `httpware ↔ optional extras`
42
42
43
43
-**Where:**`pyproject.toml` extras (`[project.optional-dependencies]`) ↔ the adapter modules that import them.
44
-
-**Contract:** each optional dependency is imported only inside its own dedicated module (e.g., `pydantic` in `decoders/pydantic.py`; `msgspec` in `decoders/msgspec.py`; `opentelemetry`in `middleware/observability/otel.py` when Epic 5 lands).
44
+
-**Contract:** each optional dependency is imported only inside its own dedicated module (e.g., `pydantic` in `decoders/pydantic.py`; `msgspec` in `decoders/msgspec.py`). Future extras (e.g., OpenTelemetry under Epic 5) follow the same pattern, with the extra declared in `pyproject.toml` at the same time the code that uses it lands — not earlier.
45
45
-**Rule:** never import an extra at package top-level. The package must import cleanly when the extra is not installed.
46
46
-**Verification:**`tests/test_optional_extras_isolation.py` runs a fresh-subprocess `import httpware` and asserts that neither pydantic nor msgspec ends up in `sys.modules`. New extras must add the same isolation test.
47
47
@@ -94,16 +94,17 @@ src/httpware/
94
94
95
95
## 7. Optional-extras pattern
96
96
97
-
`httpware` core has a small dependency set. Capabilities that pull in heavyweight dependencies (`pydantic`, `msgspec`, `opentelemetry`) live behind extras declared in `pyproject.toml`:
97
+
`httpware` core has a small dependency set. Capabilities that pull in heavyweight dependencies (`pydantic`, `msgspec`) live behind extras declared in `pyproject.toml`:
Each extra's code lives in a single dedicated module (`decoders/pydantic.py`, `decoders/msgspec.py`, `middleware/observability/otel.py` when Epic 5 lands). The `import` of the extra happens **inside** that module behind an `is_<extra>_installed` guard from `_internal/import_checker.py` — never at package top level. This way, `import httpware` works cleanly without the extras installed, and the seam stays observable: `grep -rnE 'from pydantic|import pydantic' src/httpware/ | grep -v import_checker` returns exactly one indented line (the guarded import in `decoders/pydantic.py`), and the same is true for `msgspec`.
105
+
Each extra's code lives in a single dedicated module (`decoders/pydantic.py`, `decoders/msgspec.py`). The `import` of the extra happens **inside** that module behind an `is_<extra>_installed` guard from `_internal/import_checker.py` — never at package top level. This way, `import httpware` works cleanly without the extras installed, and the seam stays observable: `grep -rnE 'from pydantic|import pydantic' src/httpware/ | grep -v import_checker` returns exactly one indented line (the guarded import in `decoders/pydantic.py`), and the same is true for `msgspec`.
106
+
107
+
New extras are added at the same time as the code that uses them — never preemptively. (An `otel` extra existed pre-0.4 but was removed once we noticed it was advertising functionality that didn't exist. Epic 5 will reintroduce it when the OpenTelemetry middleware actually lands.)
107
108
108
109
Caller-facing pattern: consumers select the implementation by passing it explicitly, e.g., `AsyncClient(decoder=PydanticDecoder())`. There is no auto-detection or implicit registry.
109
110
@@ -125,7 +126,7 @@ Post-pivot, the roadmap has three categories. Topic slugs in `planning/specs/` a
125
126
-**Shipped in v0.4 slice 1:**`Retry` middleware + Finagle-style `RetryBudget` token bucket + `attempt_timeout=` parameter (folded-in 3-1). See [`planning/specs/2026-06-05-retry-and-retry-budget-design.md`](specs/2026-06-05-retry-and-retry-budget-design.md) and [`planning/plans/2026-06-05-retry-and-retry-budget-plan.md`](plans/2026-06-05-retry-and-retry-budget-plan.md).
-**Epic 5 — Observability:**`5-1` Layer 1 middleware hooks, `5-2` wire into resilience middlewares, `5-4` OpenTelemetry middleware (will declare the `otel` extra at the same time the code lands), `5-5` logging policy CI grep.
0 commit comments