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
{{ message }}
Repository navigation
Commit b00487c
Browse filesBrowse the repository at this point in the historyBrowse files
* fix: keep notes and attributes when pickling or copying any ClientError
One __reduce__ on ClientError rebuilds from args without calling __init__
and restores __dict__, replacing _KeywordReduceMixin and the StatusError
reconstructor. add_note() notes and user-set attributes now survive pickle,
copy.copy and copy.deepcopy for every error class.
* fix!: make STATUS_TO_EXCEPTION read-only
STATUS_TO_EXCEPTION is now a types.MappingProxyType. Code that assigned to
it to remap a status now gets TypeError; catch the error in a middleware
and raise your own class instead.
* fix: mask every query value in error messages and events
redact_url now replaces the value of every query and fragment parameter
with REDACTED and keeps the keys, instead of masking an allowlist of
sensitive names, so presigned-URL signatures and other unlisted secrets
no longer reach messages, reprs, log records or span events. Userinfo is
cut at the last @ so a raw password containing @ is stripped whole.
* fix: keep body content out of the DecodeError message
The message is now 'failed to decode response into <model>: <type>' with
the error count appended when the original exception has error_count().
A non-class model such as X | None is shown by repr, which stops the
AttributeError on Python 3.11 to 3.13. exc.original is unchanged.
* fix: let phase decorator reprs handle callables without __qualname__
repr() of a middleware built by before_request, after_response, on_error
or their async siblings no longer raises AttributeError for a
functools.partial or a callable instance; it falls back to repr(f).
* docs: describe redaction as masking every query value
Copy file name to clipboardExpand all lines: docs/errors.md
+9-8Lines changed: 9 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,7 +49,7 @@ ClientError (catch-all for anything httpware raises)
49
49
50
50
Only statuses from 400 to 599 raise. Any other status returns the response unchanged.
51
51
52
-
The numbered rows are exported as `STATUS_TO_EXCEPTION`, a `Mapping[int, type[StatusError]]`, so you can look up a class in code with `STATUS_TO_EXCEPTION.get(404)`. The two fallback rows are not in the mapping.
52
+
The numbered rows are exported as `STATUS_TO_EXCEPTION`, a `Mapping[int, type[StatusError]]`, so you can look up a class in code with `STATUS_TO_EXCEPTION.get(404)`. The two fallback rows are not in the mapping. The mapping is read-only, and assigning to it raises `TypeError`. To raise your own class for a status, catch the httpware error in a middleware and raise yours from it.
53
53
54
54
## Catching strategies
55
55
@@ -112,7 +112,7 @@ exc.response.request.url # the failing URL (httpx2.URL)
112
112
exc.response.request.method # the HTTP method
113
113
```
114
114
115
-
The exception's `repr` and message strip `user:pass@` userinfo and mask the values of known-sensitive query and URL-fragment parameters (`api_key`, `apikey`, `access_token`, `refresh_token`, `token`, `secret`, `client_secret`, `password`, `passwd`, `pwd`, `auth`, `authorization`, `sig`, `signature`, `key`, `private_key`, `session`, `sessionid`, `x-api-key`) as `REDACTED`, keeping the keys. Values under other names are left as they are, so avoid putting other secrets in query strings. Request headers such as `Authorization` and `Cookie` are never redacted, and `exc.response.request.headers` returns them in full.
115
+
The exception's `repr` and message strip `user:pass@` userinfo and replace every query-parameter value with `REDACTED`, keeping the keys, so `?page=2&X-Amz-Signature=abc` shows as `?page=REDACTED&X-Amz-Signature=REDACTED`. Fragment values get the same treatment, and a fragment without `=`, like `#section-3`, is kept. The unredacted URL is still on `exc.response.request.url`. Request headers such as `Authorization` and `Cookie` are never redacted, and `exc.response.request.headers` returns them in full.
116
116
117
117
## Resilience-error payloads
118
118
@@ -149,19 +149,20 @@ Fields:
149
149
-`model: type`: the type passed as `response_model=`.
150
150
-`original: BaseException`: the decoder's own exception, such as `pydantic.ValidationError`, `msgspec.ValidationError` or `msgspec.DecodeError`. It is also `exc.__cause__`.
151
151
152
+
The message names the model and the type of `original`, and adds the error count when `original` has an `error_count()` method, as `pydantic.ValidationError` does:
153
+
154
+
failed to decode response into User: ValidationError (2 errors)
155
+
156
+
A model that is not a class, such as `User | None` or `list[User]`, appears as its `repr`. The message never quotes the body. `str(exc.original)` can: a pydantic `ValidationError` echoes the input values it rejected, so check what it holds before you log it.
157
+
152
158
```python
153
159
from httpware import AsyncClient, DecodeError
154
160
155
161
156
162
try:
157
163
user =await client.get("/users/1", response_model=User)
Copy file name to clipboardExpand all lines: docs/middleware.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,7 +13,7 @@ Middleware is for behavior that applies to every call through a client. Other ca
13
13
- Side effects that don't need httpware's exceptions or chain order, including on post-redirect hops: use `event_hooks` on an `httpx2` client you build yourself and pass as `httpx2_client=`. Middleware and phase decorators run in the httpware chain, see httpware exceptions, and compose with `AsyncRetry` and `AsyncBulkhead`. `event_hooks` run a layer below, on every transport attempt. That is also why `httpware` refuses `event_hooks=` as a client option: an `httpx2.HTTPStatusError` raised in a hook (say, by `raise_for_status()`) reaches the chain as a plain `TransportError` instead of a `StatusError`, so `AsyncRetry` never retries it and the circuit breaker never counts it.
14
14
- URL or header validation: `httpx2` already does it.
15
15
- Creating HTTP spans for tracing: install `opentelemetry-instrumentation-httpx`, which already traces every transport call. See [Observability](observability.md).
16
-
- Redaction: httpware redacts URLs before they reach logs, telemetry, and error messages. It strips `user:pass@` userinfo and masks the values of sensitive query and fragment parameters ([Errors](errors.md#excresponse-access-pattern)lists them). It does not touch headers or bodies, so if your middleware logs those, redact them yourself, for example with a `logging.Filter`.
16
+
- Redaction: httpware redacts URLs before they reach logs, telemetry, and error messages. It strips `user:pass@` userinfo and replaces every query and fragment parameter value with `REDACTED`, keeping the keys ([Errors](errors.md#excresponse-access-pattern)has an example). It does not touch headers or bodies, so if your middleware logs those, redact them yourself, for example with a `logging.Filter`.
Copy file name to clipboardExpand all lines: docs/observability.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,8 @@ The logger and event names below are a stable public contract:
13
13
14
14
Each log record has an `event` field holding the event name, such as `event="circuit.opened"`, which you can filter on in your log aggregator. Events from `AsyncKeyedCircuitBreaker` and `KeyedCircuitBreaker` also carry `circuit_key`, the circuit they belong to. [Resilience](resilience.md) describes when each event fires.
15
15
16
+
Events that carry a `url` attribute get it redacted first: httpware strips `user:pass@` userinfo and replaces every query-parameter value with `REDACTED`, keeping the keys. A URL logged as `https://api.test/items?page=REDACTED&sort=REDACTED` came from `https://api.test/items?page=2&sort=name`.
0 commit comments