This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git
The following commit(s) were added to refs/heads/main by this push:
new 76dcb97 fix(gmail-oauth-draft): wrap api_get with HTTPError handler
for clean failure surface (#240)
76dcb97 is described below
commit 76dcb97778a9597c70b63a3562fd41e5c287d1aa
Author: André Ahlert <[email protected]>
AuthorDate: Wed May 20 05:17:43 2026 -0300
fix(gmail-oauth-draft): wrap api_get with HTTPError handler for clean
failure surface (#240)
`api_get` was the only `urllib.request.urlopen` call in the package
without a `try/except urllib.error.HTTPError`. `api_post`,
`mark_threads_read` (both calls), and the token refresh in
`credentials.py` all wrap the call and raise `SystemExit` with a
one-line `"Gmail API <path> failed (<code>): <body>"` message.
`api_get` is the path that `latest_reply_headers` uses, so any
401 / 403 / 404 from `GET /threads/{id}?format=full` surfaced as a
raw Python traceback instead of the clean error the rest of the
tool produces. That path is exercised on every
`oauth-draft-create --thread-id <X>` invocation that does not pass
`--no-reply-headers`.
Wraps `api_get` with the same handler as `api_post` and adds a
regression test mirroring `test_api_post_raises_on_http_error`.
---
.../oauth-draft/src/oauth_draft/create_draft.py | 7 +++++--
tools/gmail/oauth-draft/tests/test_create_draft.py | 23 ++++++++++++++++++++++
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/tools/gmail/oauth-draft/src/oauth_draft/create_draft.py
b/tools/gmail/oauth-draft/src/oauth_draft/create_draft.py
index e24fcc6..bfc07e9 100644
--- a/tools/gmail/oauth-draft/src/oauth_draft/create_draft.py
+++ b/tools/gmail/oauth-draft/src/oauth_draft/create_draft.py
@@ -61,8 +61,11 @@ def api_get(access_token: str, path: str) -> dict:
f"{GMAIL_API}{path}",
headers={"Authorization": f"Bearer {access_token}"},
)
- with urllib.request.urlopen(req, timeout=15) as r:
- return json.loads(r.read())
+ try:
+ with urllib.request.urlopen(req, timeout=15) as r:
+ return json.loads(r.read())
+ except urllib.error.HTTPError as e:
+ raise SystemExit(f"Gmail API {path} failed ({e.code}):
{e.read().decode(errors='replace')}") from e
def api_post(access_token: str, path: str, payload: dict) -> dict:
diff --git a/tools/gmail/oauth-draft/tests/test_create_draft.py
b/tools/gmail/oauth-draft/tests/test_create_draft.py
index d643962..a50056e 100644
--- a/tools/gmail/oauth-draft/tests/test_create_draft.py
+++ b/tools/gmail/oauth-draft/tests/test_create_draft.py
@@ -250,6 +250,29 @@ def test_api_post_parses_json_response():
assert json.loads(request.data) == {"message": {"raw": "X"}}
+def test_api_get_raises_on_http_error():
+ """``api_get`` must surface HTTP errors as a clean ``SystemExit``.
+
+ Regression: ``api_get`` was the only ``urlopen`` call in the
+ package without a ``try/except HTTPError``, so a 401/403/404 from
+ ``threads.get`` (the path exercised on every ``oauth-draft-create
+ --thread-id <X>`` invocation) produced a raw Python traceback
+ instead of a one-line error matching the rest of the tool.
+ """
+ err = urllib.error.HTTPError(
+ url="https://x",
+ code=404,
+ msg="Not Found",
+ hdrs=None, # type: ignore[arg-type]
+ fp=io.BytesIO(b'{"error": "thread missing"}'),
+ )
+ with patch("oauth_draft.create_draft.urllib.request.urlopen",
side_effect=err):
+ with pytest.raises(SystemExit) as excinfo:
+ api_get("token", "/threads/missing-thread")
+ assert "failed (404)" in str(excinfo.value)
+ assert "thread missing" in str(excinfo.value)
+
+
def test_api_post_raises_on_http_error():
err = urllib.error.HTTPError(
url="https://x",