fix: add shared API pagination and failure handling (#67)

Harden gitea_auth.api_request: add a per-request timeout (env
GITEA_HTTP_TIMEOUT), convert timeouts and DNS/network failures
(URLError/TimeoutError) into clear RuntimeErrors, give 502/503/504 an
explicit 'upstream unavailable' message, convert malformed success JSON
into a clean error, and redact credential-like substrings from all error
text. Preserves the success path and existing 429 retry/backoff.

Add shared gitea_auth.api_get_all: page-based pagination that tolerates
missing/malformed metadata (relies on page length, not Link/X-Total-Count
headers), honors an optional overall limit, and caps pages. Wire it into
the read-only list tools gitea_list_issues, gitea_list_prs, and
gitea_list_labels (return shape unchanged).

Add tests/test_api_reliability.py (18 cases) and update the three list-tool
tests to the new call path. No auth/profile/merge/review/tracker behavior
changed. No modular #65 refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 13:27:06 -04:00
parent 093945254d
commit cfe3ff6755
4 changed files with 337 additions and 18 deletions
+6 -5
View File
@@ -38,6 +38,7 @@ from gitea_auth import ( # noqa: E402
get_credentials,
get_auth_header,
api_request,
api_get_all,
repo_api_url,
get_profile,
)
@@ -384,7 +385,7 @@ def gitea_list_prs(
h, o, r = _resolve(remote, host, org, repo)
auth = _auth(h)
url = f"{repo_api_url(h, o, r)}/pulls?state={state}"
prs = api_request("GET", url, auth) or []
prs = api_get_all(url, auth)
return [
{
"number": pr["number"],
@@ -1266,7 +1267,7 @@ def gitea_list_issues(
Args:
state: Filter by state — 'open', 'closed', or 'all'.
label: Filter by label name (e.g. 'important').
limit: Max number of issues to return (default: 50).
limit: Max number of issues to return across all pages (default: 50).
remote: Known instance — 'dadeschools' or 'prgs'.
host: Override the Gitea host.
org: Override the owner/organization.
@@ -1277,11 +1278,11 @@ def gitea_list_issues(
"""
h, o, r = _resolve(remote, host, org, repo)
auth = _auth(h)
params = f"state={state}&limit={limit}&type=issues"
params = f"state={state}&type=issues"
if label:
params += f"&labels={label}"
url = f"{repo_api_url(h, o, r)}/issues?{params}"
issues = api_request("GET", url, auth)
issues = api_get_all(url, auth, limit=limit)
return [
{
"number": i["number"],
@@ -1554,7 +1555,7 @@ def gitea_list_labels(
h, o, r = _resolve(remote, host, org, repo)
auth = _auth(h)
base = repo_api_url(h, o, r)
return api_request("GET", f"{base}/labels?limit=100", auth)
return api_get_all(f"{base}/labels", auth)
@mcp.tool()