Recently Written · git

subplz-web

git clone https://github.com/equwal/subplz-web

Log | Files | Refs


commit 66110b8a55aafa0248d5facbf46fad9e65b8c242
equwal <truex@equwal.com>
2026-09-26 19:14:38 -0700

Answer 502 when the Google token server fails for a short time

google-auth raises a RefreshError that it marks retryable when the Google
token server answers 408, 429, 500, 503 or 504 after its own retries.
play._call changed each RefreshError to the answer 503 "Google Play
purchases are not set up on this server." It also logged "Google refused
the Play key" at the ERROR level. So during a short Google failure, a buyer
who paid saw that the server was not set up, and the log blamed the key.

A retryable RefreshError now gives 502 "Google Play did not answer. Try
again later." and no error in the log. A RefreshError that is not retryable
still gives 503.

The test of the 502 answers now includes a retryable RefreshError. It also
checks the text of each answer, and that the log has no error.

 backend/play.py    | 4 ++++
 tests/test_play.py | 9 +++++++--
 2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/backend/play.py b/backend/play.py
index e8ef6ca..b32bf8b 100644
--- a/backend/play.py
+++ b/backend/play.py
@@ -142,6 +142,10 @@ def _call(method: str, url: str):
         with _session() as http:
             response = http.request(method, url, timeout=15)
     except exceptions.RefreshError as exc:
+        # A retryable error is a temporary failure of the Google token server,
+        # for example 503 after the retries of google-auth. The key can be good.
+        if exc.retryable:
+            raise PlayError(502, NO_ANSWER) from exc
         log.error("Google refused the Play key: %s", type(exc).__name__)
         raise PlayError(503, NOT_SET_UP) from exc
     except (requests.RequestException, exceptions.TransportError) as exc:
diff --git a/tests/test_play.py b/tests/test_play.py
index ebdace5..230fa2e 100644
--- a/tests/test_play.py
+++ b/tests/test_play.py
@@ -472,12 +472,17 @@ def test_google_errors_become_clear_answers(http, google_status, status):
     requests.ConnectionError("no route to host"),
     requests.Timeout("read timed out"),
     google_errors.TransportError("no route to the token server"),
+    # google-auth raises this after its own retries, when the Google token
+    # server answers 408, 429, 500, 503 or 504.
+    google_errors.RefreshError("temporarily_unavailable", retryable=True),
 ])
-def test_no_connection_to_google_is_502(http, error):
+def test_no_connection_to_google_is_502(http, error, caplog):
     http.answers = [error]
     with pytest.raises(play.PlayError) as caught:
         play.get("captions20", "abc")
-    assert caught.value.status == 502
+    assert (caught.value.status, caught.value.detail) == (502, play.NO_ANSWER)
+    # The key can be good. So the log must not tell the operator that Google refused it.
+    assert [r for r in caplog.records if r.levelno >= logging.ERROR] == []
 
 
 def test_a_key_that_google_refuses_is_503(http):