request() built outgoing headers from per-request kwargs only and never read self._default_headers, so update_headers()/clear_headers() mutated a field that never reached the wire — while preview() DID read it, so preview diverged from the real send. request() now merges _default_headers (defaults -> per-request -> overwrites), making the session-default header API functional and preview consistent with request.
Signed-off-by: disqualifier <dev@disqualifier.me>
header/body/url/proxy values were wrapped in raw single quotes, so a value containing a quote or shell metacharacter produced a broken or injectable command. every interpolated value is now shell-quoted.
Signed-off-by: disqualifier <dev@disqualifier.me>
- #7: request_with_retries routed only dicts to json=, so a valid JSON list body
was form-encoded via data=. add _route_body so dict OR list -> json=.
- #6: when every attempt returned a retryable status, the loop discarded the real
response and returned a synthetic FailureResponse (status 0). now the real last
4xx/5xx Response is returned on exhaustion (only a pure-exception failure yields
FailureResponse).
- migrate the retry/backoff loop onto commons.aretry (>=0.2.0); backoff schedule
unchanged (1,2,... = backoff_base**n), jitter off to match prior behavior.
verified by execution: list->json routing, exhausted 503 returns real 503 + body
with correct backoff, success/404 immediate, exception->falsy FailureResponse.
Signed-off-by: disqualifier <dev@disqualifier.me>