Report Qobuz API failures cleanly instead of crashing; keep credentials out of errors - #1037
Open
sjbrownrigg wants to merge 1 commit into
Open
sjbrownrigg wants to merge 1 commit into
sjbrownrigg wants to merge 1 commit into
Conversation
…of errors rip search crashed with a bare "AssertionError: 400". The 400 was Qobuz's own search backend failing -- "Impossible to connect, please check your Algolia Application Id." -- and it cleared on its own: the same search succeeded 20 and 40 seconds later. _paginate asserted on the status and threw the message away. Requests that must succeed now go through _request_ok(), which retries once after a short pause when the failure looks transient (a 5xx, or that Algolia message) and otherwise raises APIError carrying Qobuz's own message. The search commands print it instead of a traceback. The 3-second pause is a guess: recovery was only observed at 20 seconds. Credentials also reached error output. Qobuz takes user_id and user_auth_token as URL query parameters, and an HTML error page -- a 502 from Qobuz's edge, seen during the same session -- made aiohttp raise a ContentTypeError quoting the full URL, token included. A failed login interpolated the whole params dict into the exception, and debug logging printed params and the login response. Non-JSON responses are now reported by status, a login answering anything but 200/400/401 raises APIError instead of failing on a missing key, and params are masked wherever they reach a log line or an error.
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.
Problem
rip search qobuz artist <name>can crash with a bare traceback ending in:The 400 comes from Qobuz's own search backend, and its message explains it:
It is transient. Probing the same search three times, 20 seconds apart, it failed once and succeeded twice, with the same credentials and app id throughout.
_paginate()asserts on the status and discards the message, so the user gets neither an explanation nor a second chance.Change
A graceful exit instead of a traceback. Requests that must succeed go through a new
_request_ok(). When a failure looks transient — a 5xx, or that Algolia message — it retries once after a short pause. Otherwise it raises a newAPIErrorcarrying Qobuz's own message, and the search commands print it and return:The 3-second pause is a guess; I only observed recovery at the 20-second mark. The retry is a mitigation, and the clear message is the main fix.
A login that returns something other than 200/400/401 now raises
APIErrortoo, instead of failing later on a missingresp["user"].Credentials in error output
Qobuz takes
user_idanduser_auth_tokenas URL query parameters, and they currently reach error output in three ways:response.json()raises aiohttp'sContentTypeError, whose message quotes the full request URL, token included._api_request()now returns the status and content type for non-JSON responses instead.Invalid credentials from params {params}puts the whole params dict, token included, into the exception. See [BUG] <title>Invalid Credentials from params #796, where it ended up in a public issue. Params are now masked in those messages.paramsand the full login response. Both are now masked.Relationship to #955
#955 already redacts the login payload (
_redact_auth_payload) and rewrites these authentication messages as part of its new login flow — credit to @mikelandzelo173. The two overlap on the login block. If #955 lands first, I'm happy to rebase so that its login block wins and this PR keeps only the non-200 check there; the_api_requestmasking, the non-JSON handling and the search changes are independent of it.Merge order
I trial-merged this branch against every open PR touching
qobuz.py,main.pyorexceptions.py.Merge cleanly: #847, #944, #982, #1007, #1013, #1024, #1031, #1036.
Conflict, with the resolution:
qobuz.pylogin blockelif status != 200: raise APIError(...)branch from this PR.main.pyimportsfrom ..exceptions import APIError, AuthenticationErrorqobuz.pylogin blocktryto close the session. Keep its structure and apply this PR's_redacted(params)calls and the non-200 branch inside it.qobuz.py_api_request_api_requestwith its own non-JSON handling, which raises without quoting the URL. Take #967's version and keep_redacted(params)in its debug log line. The retry then won't cover HTML error pages, which is acceptable.exceptions.pymain.pyimportsWhichever of these lands after this one, I'm happy to rebase.
Testing
tests/test_qobuz_errors.py(6 tests):APIErrorwith Qobuz's messagerip searchprintsSearch failed: …and returns instead of raisingThe same mocked failing search, before and after:
A failed login with a known token: the token appears in the exception message on
dev, and does not with this change.Full suite: 65 passed, 7 skipped;
tests/test_meta.py::test_album_metadata_qobuzfails identically on unmodifieddev. A liverip search --output-fileagainst Qobuz succeeded twice, with no credentials in the output.