The resilience and observability configs are dataclasses with no env-facing layer, so every
consumer writes the same settings models and maps them by hand.
What happens
clientwright ships ClientConfig, CircuitBreakerConfig, ObservabilityConfig, NativeOptions,
CallOptions and friends as dataclasses. There are no pydantic models in the package:
import pkgutil, importlib, inspect
from pydantic import BaseModel
import clientwright
found = []
for m in pkgutil.walk_packages(clientwright.__path__, prefix="clientwright."):
mod = importlib.import_module(m.name)
found += [n for n, o in vars(mod).items()
if inspect.isclass(o) and issubclass(o, BaseModel)]
found # []
Why it matters
The configuration a REST client actually needs per environment — base URL, connect and total
timeouts, pool sizing, HTTP/2, retry and circuit-breaker policy — is exactly the part that differs
between dev, stage and prod, and therefore exactly the part that has to come from the environment.
With only dataclasses, each consumer writes its own pydantic layer and its own mapping onto yours.
In our monorepo that layer sits alongside a dozen client sections and is the last piece of a
settings library we have otherwise been able to delete: everything else now comes from the library
that reads it — sqlalchemy-foundation-kit, redis-client-kit, aiokafka-foundation-kit,
omni-box, pg-partsmith, idempotency-kit, deadline-budget, servicewright. clientwright
and grpc-client-kit are the two that still need a local model.
Precedent: servicewright#22 raised the same gap for the observability sections and was resolved by
shipping the models in 0.7.0.
A specific hazard worth designing against
Consumer-written sections tend to subclass BaseSettings, which makes each one scrape the
environment with no prefix — a bare BASE_URL in a pod can reach a nested client section the
parent settings did not fill. Shipping plain BaseModel sections, reachable only through their
parent, removes that for everyone. servicewright hit the same thing before 0.7.0.
What would help
Some sanctioned path from environment to ClientConfig. Pydantic models mirroring the dataclasses,
a from_settings() constructor, or a documented protocol you accept would all work for us. What we
care about is that there is exactly one such path, owned here, so the mapping of your field set is
not re-derived in every repo that uses the library.
Versions
clientwright 0.2.2, pydantic 2.13, pydantic-settings 2.13, CPython 3.14.
The resilience and observability configs are dataclasses with no env-facing layer, so every
consumer writes the same settings models and maps them by hand.
What happens
clientwrightshipsClientConfig,CircuitBreakerConfig,ObservabilityConfig,NativeOptions,CallOptionsand friends as dataclasses. There are no pydantic models in the package:Why it matters
The configuration a REST client actually needs per environment — base URL, connect and total
timeouts, pool sizing, HTTP/2, retry and circuit-breaker policy — is exactly the part that differs
between dev, stage and prod, and therefore exactly the part that has to come from the environment.
With only dataclasses, each consumer writes its own pydantic layer and its own mapping onto yours.
In our monorepo that layer sits alongside a dozen client sections and is the last piece of a
settings library we have otherwise been able to delete: everything else now comes from the library
that reads it —
sqlalchemy-foundation-kit,redis-client-kit,aiokafka-foundation-kit,omni-box,pg-partsmith,idempotency-kit,deadline-budget,servicewright.clientwrightand
grpc-client-kitare the two that still need a local model.Precedent: servicewright#22 raised the same gap for the observability sections and was resolved by
shipping the models in 0.7.0.
A specific hazard worth designing against
Consumer-written sections tend to subclass
BaseSettings, which makes each one scrape theenvironment with no prefix — a bare
BASE_URLin a pod can reach a nested client section theparent settings did not fill. Shipping plain
BaseModelsections, reachable only through theirparent, removes that for everyone. servicewright hit the same thing before 0.7.0.
What would help
Some sanctioned path from environment to
ClientConfig. Pydantic models mirroring the dataclasses,a
from_settings()constructor, or a documented protocol you accept would all work for us. What wecare about is that there is exactly one such path, owned here, so the mapping of your field set is
not re-derived in every repo that uses the library.
Versions
clientwright0.2.2, pydantic 2.13, pydantic-settings 2.13, CPython 3.14.