Skip to content

Commit a5482f2

Browse files
lesnik512claude
andcommitted
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>
1 parent b62623f commit a5482f2

3 files changed

Lines changed: 9 additions & 12 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
pip install httpware # core only — no decoder
1818
pip install httpware[pydantic] # + PydanticDecoder (the default-decoder path)
1919
pip install httpware[msgspec] # + MsgspecDecoder
20-
pip install httpware[all] # everything declared above (pydantic, msgspec, otel)
20+
pip install httpware[all] # everything declared above (pydantic, msgspec)
2121
```
2222

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. 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.
2424

2525
## Quickstart
2626

planning/engineering.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ The 0.1.0 seams numbered 1 (Middleware↔Transport) and 4 (Transport↔httpx2) h
4141
### Seam C: `httpware ↔ optional extras`
4242

4343
- **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.
4545
- **Rule:** never import an extra at package top-level. The package must import cleanly when the extra is not installed.
4646
- **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.
4747

@@ -94,16 +94,17 @@ src/httpware/
9494

9595
## 7. Optional-extras pattern
9696

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`:
9898

9999
```toml
100100
[project.optional-dependencies]
101101
pydantic = ["pydantic>=2"]
102102
msgspec = ["msgspec>=0.18"]
103-
otel = ["opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20"]
104103
```
105104

106-
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.)
107108

108109
Caller-facing pattern: consumers select the implementation by passing it explicitly, e.g., `AsyncClient(decoder=PydanticDecoder())`. There is no auto-detection or implicit registry.
109110

@@ -125,7 +126,7 @@ Post-pivot, the roadmap has three categories. Topic slugs in `planning/specs/` a
125126
- **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).
126127
- **Remaining:** `3-5` `Bulkhead`, `3-6` extension-slot docs.
127128
- **Epic 4 — Streaming:** `4-3` `AsyncClient.stream` context manager (forwards to `httpx2.AsyncClient.stream`; no `StreamResponse` type).
128-
- **Epic 5 — Observability:** `5-1` Layer 1 middleware hooks, `5-2` wire into resilience middlewares, `5-4` OpenTelemetry middleware (`otel` extra), `5-5` logging policy CI grep.
129+
- **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.
129130
- **Epic 6 — Ship v1.0:** `6-2` docs site (`mkdocs`), `6-3` benchmarks, `6-5` release flow (Trusted Publishers + Sigstore).
130131
- **Carry-forward decoder:** `1-6` msgspec decoder via extras — second `ResponseDecoder` adapter, already implemented; verified surviving in the pivot.
131132
- **Middleware protocol:** `2-1` and `2-2` already implemented in the pivot (protocol, chain, phase decorators).

pyproject.toml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ dependencies = [
3434
[project.optional-dependencies]
3535
pydantic = ["pydantic>=2.0,<3.0"]
3636
msgspec = ["msgspec>=0.18"]
37-
otel = [
38-
"opentelemetry-api>=1.20",
39-
"opentelemetry-sdk>=1.20",
40-
]
41-
all = ["httpware[pydantic,msgspec,otel]"]
37+
all = ["httpware[pydantic,msgspec]"]
4238

4339
[project.urls]
4440
repository = "https://github.com/modern-python/httpware"

0 commit comments

Comments
 (0)