Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Breaking Changes
~~~~~~~~~~~~~~~~

- The default for ``GlobusAPIError.code`` is now ``None``, when no ``code`` is
supplied in the error body. It previously was ``"Error"``. (:pr:`NUMBER`)
4 changes: 2 additions & 2 deletions src/globus_sdk/exc/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class GlobusAPIError(GlobusError):
Wraps errors returned by a REST API.

:ivar int http_status: HTTP status code
:ivar str code: Error code from the API or "Error" for unclassified errors
:ivar str code: Error code from the API or ``None`` for unclassified errors
:ivar str request_id: The 'request_id' included in the error data, if any.
:ivar list[str] messages: A list of error messages, extracted from the response
data. If the data cannot be parsed or does not contain any clear message fields,
Expand All @@ -46,7 +46,7 @@ def __init__(self, r: requests.Response, *args: t.Any, **kwargs: t.Any) -> None:

self.http_status = r.status_code
# defaults, may be rewritten during parsing
self.code: str | None = "Error"
self.code: str | None = None
self.request_id: str | None = None
self.messages: list[str] = []
self.errors: list[ErrorSubdocument] = []
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/services/auth/test_auth_client_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def test_oauth2_exchange_code_for_tokens_native(native_client):
with pytest.raises(globus_sdk.AuthAPIError) as excinfo:
native_client.oauth2_exchange_code_for_tokens("invalid_code")
assert excinfo.value.http_status == 401
assert excinfo.value.code == "Error"
assert excinfo.value.code is None


def test_oauth2_exchange_code_for_tokens_confidential(confidential_client):
Expand All @@ -319,4 +319,4 @@ def test_oauth2_exchange_code_for_tokens_confidential(confidential_client):
with pytest.raises(globus_sdk.AuthAPIError) as excinfo:
confidential_client.oauth2_exchange_code_for_tokens("invalid_code")
assert excinfo.value.http_status == 401
assert excinfo.value.code == "Error"
assert excinfo.value.code is None
4 changes: 2 additions & 2 deletions tests/unit/errors/test_auth_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_auth_error_get_args_simple():
req.url,
None,
404,
"Error",
None,
"simple auth error message",
]

Expand All @@ -38,7 +38,7 @@ def test_nested_auth_error_message_and_code():
)

assert err.message == "nested auth error message; some secondary error"
assert err.code == "Error"
assert err.code is None


@pytest.mark.parametrize(
Expand Down
32 changes: 12 additions & 20 deletions tests/unit/errors/test_common_functionality.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ def test_imperative_message_setting_warns():
@pytest.mark.parametrize(
"body, response_headers, http_status, expect_code, expect_message",
(
("text_data", {}, 401, "Error", "Unauthorized"), # text
("text_data", {}, 401, None, "Unauthorized"), # text
# JSON with unrecognized contents
(
{"foo": "bar"},
{"Content-Type": "application/json"},
403,
"Error",
None,
"Forbidden",
),
# JSON with well-known contents
Expand All @@ -108,15 +108,15 @@ def test_imperative_message_setting_warns():
"[]",
{"Content-Type": "application/json"},
403,
"Error",
None,
"Forbidden",
),
# invalid JSON
(
"{",
{"Content-Type": "application/json"},
400,
"Error",
None,
"Bad Request",
),
),
Expand Down Expand Up @@ -544,7 +544,8 @@ def test_error_repr_has_expected_info(
if error_code is not None:
assert error_code in stringified
else:
assert "'Error'" in stringified
# several things could be 'None', but at least one of them is 'code'
assert "None" in stringified
if error_message is None:
assert "otherdata" in stringified
else:
Expand Down Expand Up @@ -584,16 +585,10 @@ def test_loads_jsonapi_error_subdocuments(content_type):
)

# code is not taken from any of the subdocuments (inherently too ambiguous)
# behavior will depend on which parsing path was taken
if content_type.endswith("vnd.api+json"):
# code becomes None because we saw "true" JSON:API and can opt-in to
# better behavior
assert err.code is None
else:
# code remains 'Error' for backwards compatibility in the non-JSON:API case
assert err.code == "Error"
# this holds regardless of which parsing path was taken
assert err.code is None

# but messages can be extracted, and they prefer detail to title
# messages can be extracted, and they prefer detail to title
assert err.messages == [
"password was only 3 chars long, must be at least 8",
"password must have non-alphanumeric characters",
Expand Down Expand Up @@ -649,22 +644,19 @@ def test_loads_jsonapi_error_messages_from_various_fields(content_type):
body=body, http_status=422, response_headers={"Content-Type": content_type}
)

# no code was found
assert err.code is None

# messages are extracted, and they use whichever field is appropriate for
# each sub-error
# note that 'message' will *not* be extracted if the Content-Type indicated JSON:API
# because JSON:API does not define such a field
if content_type.endswith("vnd.api+json"):
# code becomes None because we saw "true" JSON:API and can opt-in to
# better behavior
assert err.code is None
assert err.messages == [
"Must contain capital letter",
"password must have non-alphanumeric characters",
]
else:
# code remains 'Error' for backwards compatibility in the non-JSON:API case
assert err.code == "Error"

assert err.messages == [
"invalid password value",
"Must contain capital letter",
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/errors/test_timers_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ def test_timer_error_load_nested():

def test_timer_error_load_unrecognized_format():
err = construct_error(error_class=TimersAPIError, body={}, http_status=400)
assert err.code == "Error"
assert err.code is None
assert err.message is None