Repository navigation
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
chipkent
force-pushed
the
fix/rest-bad-response-status
branch
from
October 24, 2026 03:26
e4c82fb to
8a83934
Compare
chipkent
force-pushed
the
fix/rest-bad-response-status
branch
from
October 24, 2026 04:15
8a83934 to
108e01e
Compare
BaseClient._get raised BadResponse(resp.data.decode("utf-8")) for any
non-200 response, so the HTTP status code was lost. Callers had to
guess from the body whether a request failed because the key is not
entitled (403), because it was rate limited (429), or because of a
server error (5xx).
BadResponse now accepts a keyword-only status argument and exposes it
as an attribute; _get passes resp.status. Positional arguments still go
to Exception unchanged, so str(exc) and exc.args are the same as before,
and BadResponse() and BadResponse("a", "b") still work (status is None).
Statuses in the retry list (413, 429, 499, 500, 502, 503, 504) never
reach _get by default: once the retries run out, urllib3 raises
MaxRetryError instead. That stays the default. A new raise_on_status
argument on RESTClient and BaseClient, default True, is passed to the
Retry; with raise_on_status=False the last response is returned and
_get raises BadResponse with its status and body.
exceptions.py gets an __all__, so "from .exceptions import *" in the
package does not re-export typing.Optional as massive.Optional.
Tests drive 403, 429 and 500 responses through pook, check that
construction without status is unchanged, and run 429 and 503 against
a local HTTP server: with the defaults they still raise MaxRetryError,
and with raise_on_status=False they raise BadResponse with the status
after exactly retries + 1 requests.
chipkent
force-pushed
the
fix/rest-bad-response-status
branch
from
October 24, 2026 04:26
108e01e to
b74a664
Compare
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #<issue: F5>
Summary
When a request fails,
BadResponsecarries the response body but not the HTTP status.BaseClient._getraisesBadResponse(resp.data.decode("utf-8"))for any non-200 response, so callers have to guess from the body whether the key is not entitled (403), the request was rate limited (429), or the server failed (5xx). And with the defaultretries=3, a 429 or 5xx that is still failing after the last retry surfaces as urllib3'sMaxRetryError, notBadResponse.This PR adds the status to
BadResponse, and adds an opt-in so that those retried statuses can also end inBadResponse.Existing behavior is unchanged. With the default settings every request raises the same exception type as before,
str(exc)andexc.argsare the same, and the retry policy is the same. The only caller-visible additions are theBadResponse.statusattribute and the newraise_on_statusargument.Changes
BadResponse.status.BadResponsetakes a keyword-onlystatusargument and stores it as an attribute;_getpassesresp.status. Positional arguments go toExceptionunchanged, sostr(exc)andexc.argsare still the decoded body, andBadResponse(),BadResponse("msg")andBadResponse("a", "b")still work, withstatusset toNone.raise_on_statusonRESTClient(andBaseClient), defaultTrue. It is passed to the client's urllib3Retryand reaches both the main client andclient.vx.__all__inmassive/exceptions.py. The package doesfrom .exceptions import *, so without it the newtyping.Optionalimport would appear asmassive.Optional. The exported names are unchanged:AuthErrorandBadResponse.The
raise_on_statusopt-inThe client retries 413, 429, 499, 500, 502, 503 and 504 up to
retriestimes (3 by default). If the last attempt still gets one of those statuses:raise_on_statusTrue(default, same as today)urllib3.exceptions.MaxRetryError;BadResponseis not raisedFalseBadResponse, withstatusset (for example 429) and the response body as the messageStatuses that are not retried, such as 403 or 404, raise
BadResponseeither way. Connection errors that use up the retries raiseMaxRetryErroreither way. The number of attempts and the backoff are the same either way.Why a boolean and not a
Retryobject. The constructor already exposes connection settings as plain values (retries,maxsize,proxy), each passed to urllib3. The name and meaning match urllib3'sRetry(raise_on_status=...). Lettingretriesalso accept aRetrywould widen an existing argument's type, and callers would have to restate the client's status list and backoff to change this one setting. A customRetryis already possible throughconnection_pool_kw={"retries": ...}.Test plan
test_rest/test_bad_response.py:status,str()andargs;BadResponse(),BadResponse("boom")andBadResponse("boom", 1)keep theirargsandstr(), withstatusset toNone;urlopen, which is where retries happen, so a real server is needed). With the default settings,MaxRetryErroris still raised after 4 requests (the first attempt plus 3 retries), which pins today's behavior; withraise_on_status=Falseandretriesset to 0 or 3,BadResponseis raised with the rightstatusand body after exactlyretries + 1requests;raise_on_statusreaches both the main client andclient.vx.make test,make styleandmake staticpass on Python 3.10 and 3.14.Independent of the other PRs.