Fix ClientError arguments and crashes on unusable 4xx bodies - #318
Open
pucedoteth wants to merge 1 commit into
Open
Fix ClientError arguments and crashes on unusable 4xx bodies#318pucedoteth wants to merge 1 commit into
pucedoteth wants to merge 1 commit into
Conversation
`API._handle_exception` has two problems, both in the 4xx branch.
The two fallback `raise`s pass their arguments in the wrong positions.
`ClientError.__init__` is `(status_code, error_code, error_message, header,
error_data)`, but they pass `(status_code, None, response.text, None,
response.headers)`, so `header` ends up `None` and the HTTP headers land in
`error_data`. The success path gets this right, so the same exception carries
headers in different attributes depending on the response body.
This is the ordinary path, not an edge case: the API answers a bad request with
a plain-text body ("Failed to deserialize the JSON body into the target type"),
which is not valid JSON, so every real 4xx takes the `JSONDecodeError` fallback
and loses its headers. Anything reading `err.header` to back off on a rate limit
sees `None`.
Second, the body is only checked for `None` before being used as a mapping.
`json.loads` returns `None` only for the literal `null`; a JSON string, array or
number is equally not a dict, and `err.get("data")` raises `AttributeError` on
all three. An object that lacks `code` or `msg` -- as an intermediary with its
own error schema would return -- raises `KeyError`. Either way the caller gets
an exception the SDK does not define, in place of the `ClientError` it catches.
Accept a body only when it is a dict carrying both keys, and otherwise raise a
`ClientError` holding the raw text as the message, with the headers in `header`.
Adds tests/api_test.py: the well-formed body is unpacked as before, 5xx still
raises `ServerError`, and eight unusable bodies -- including the plain-text one
the live API returns -- all produce a well-formed `ClientError`. Eight of the
eleven fail before this change.
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.
Two problems in
API._handle_exception, both in the 4xx branch.1. The fallback
raises pass arguments in the wrong positionsClientError.__init__is(status_code, error_code, error_message, header, error_data), but both fallbacks pass:so
headerisNoneand the HTTP headers land inerror_data. The success path passes them correctly, so the same exception type carries headers in a different attribute depending on what the body looked like.This is the ordinary path, not an edge case. The API answers a bad request with a plain-text body, which is not valid JSON, so every real 4xx takes the
JSONDecodeErrorfallback:Anything reading
err.headerto back off on a rate limit seesNonetoday.2. Non-object JSON bodies raise the wrong exception type
The body is only checked for
Nonebefore being used as a mapping.json.loadsreturnsNoneonly for the literalnull— a JSON string, array or number is equally not a dict:"Unauthorized"AttributeError: 'str' object has no attribute 'get'["a","b"]AttributeError: 'list' object has no attribute 'get'429AttributeError: 'int' object has no attribute 'get'{"error": "rate limited"}KeyError: 'code'The last row is what an intermediary with its own error schema returns — a gateway, proxy, or the local node at
LOCAL_API_URL. In every case the caller gets an exception the SDK does not define, instead of theClientErrorthey wrapped the call to catch.Fix
Accept a body only when it is a dict carrying both
codeandmsg; otherwise raise aClientErrorholding the raw text as the message, with the headers inheader. A well-formed body is unpacked exactly as before.Testing
Adds
tests/api_test.py— the well-formed body is unpacked as before,5xxstill raisesServerError, and eight unusable bodies (including the plain-text one the live API actually returns) all produce a well-formedClientError.Reverting only
hyperliquid/api.pyand keeping the tests: 8 of 11 fail, showing both bugs —Full suite: 47 passed.
black --check,isort --check-onlyandmypyclean.Doesn't overlap #300, which adds messages to the exception classes in
utils/error.pyand leaves this function alone.