Skip to content

Commit 8336b69

Browse files
committed
fix(github): add exception handling to get_repos and configurable pagination
get_repos: - Wrap HTTP request and JSON parsing in try/except - Return empty list on network errors instead of raising - Use .get() for all fields to handle missing keys gracefully - Log errors with context (page number, error details) get_all_repos: - Add configurable max_pages parameter (default 10, None for no limit) - Log warning when pagination is stopped due to limit - Include total repos fetched and stopped page in warning
1 parent 2658107 commit 8336b69

1 file changed

Lines changed: 71 additions & 43 deletions

File tree

backend/services/github.py

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -96,49 +96,71 @@ async def get_repos(
9696
Uses /user/repos which returns repos the user has explicit access to,
9797
including personal repos and org repos where user is a member
9898
"""
99-
async with httpx.AsyncClient() as client:
100-
params = {
101-
"visibility": "all" if include_private else "public",
102-
"affiliation": "owner,organization_member",
103-
"sort": "updated",
104-
"direction": "desc",
105-
"per_page": per_page,
106-
"page": page
107-
}
108-
response = await client.get(
109-
f"{GITHUB_API_BASE}/user/repos",
110-
headers=self.headers,
111-
params=params,
112-
timeout=30.0
113-
)
114-
if response.status_code != 200:
115-
raise Exception(f"GitHub API error: {response.status_code}")
99+
try:
100+
async with httpx.AsyncClient() as client:
101+
params = {
102+
"visibility": "all" if include_private else "public",
103+
"affiliation": "owner,organization_member",
104+
"sort": "updated",
105+
"direction": "desc",
106+
"per_page": per_page,
107+
"page": page
108+
}
109+
response = await client.get(
110+
f"{GITHUB_API_BASE}/user/repos",
111+
headers=self.headers,
112+
params=params,
113+
timeout=30.0
114+
)
115+
if response.status_code != 200:
116+
logger.error(
117+
"GitHub API error fetching repos",
118+
status_code=response.status_code,
119+
page=page
120+
)
121+
return []
116122

117-
repos = []
118-
for repo in response.json():
119-
if not include_forks and repo.get("fork", False):
120-
continue
121-
repos.append(GitHubRepo(
122-
id=repo["id"],
123-
name=repo["name"],
124-
full_name=repo["full_name"],
125-
description=repo.get("description"),
126-
html_url=repo["html_url"],
127-
clone_url=repo["clone_url"],
128-
ssh_url=repo["ssh_url"],
129-
default_branch=repo.get("default_branch", "main"),
130-
private=repo["private"],
131-
fork=repo.get("fork", False),
132-
stargazers_count=repo.get("stargazers_count", 0),
133-
language=repo.get("language"),
134-
size=repo.get("size", 0),
135-
owner_login=repo["owner"]["login"],
136-
owner_avatar=repo["owner"]["avatar_url"]
137-
))
138-
return repos
123+
repos = []
124+
for repo in response.json():
125+
if not include_forks and repo.get("fork", False):
126+
continue
127+
repos.append(GitHubRepo(
128+
id=repo.get("id", 0),
129+
name=repo.get("name", ""),
130+
full_name=repo.get("full_name", ""),
131+
description=repo.get("description"),
132+
html_url=repo.get("html_url", ""),
133+
clone_url=repo.get("clone_url", ""),
134+
ssh_url=repo.get("ssh_url", ""),
135+
default_branch=repo.get("default_branch", "main"),
136+
private=repo.get("private", False),
137+
fork=repo.get("fork", False),
138+
stargazers_count=repo.get("stargazers_count", 0),
139+
language=repo.get("language"),
140+
size=repo.get("size", 0),
141+
owner_login=repo.get("owner", {}).get("login", ""),
142+
owner_avatar=repo.get("owner", {}).get("avatar_url", "")
143+
))
144+
return repos
145+
except (httpx.RequestError, httpx.TimeoutException) as e:
146+
logger.error("Network error fetching GitHub repos", error=str(e), page=page)
147+
return []
148+
except (KeyError, ValueError, TypeError) as e:
149+
logger.error("Failed to parse GitHub repos response", error=str(e), page=page)
150+
return []
139151

140-
async def get_all_repos(self, include_forks: bool = False) -> list[GitHubRepo]:
141-
"""Fetch all repos with pagination"""
152+
async def get_all_repos(
153+
self,
154+
include_forks: bool = False,
155+
max_pages: Optional[int] = 10
156+
) -> list[GitHubRepo]:
157+
"""
158+
Fetch all repos with pagination
159+
160+
Args:
161+
include_forks: Whether to include forked repos
162+
max_pages: Maximum pages to fetch (None for no limit, default 10)
163+
"""
142164
all_repos = []
143165
page = 1
144166
while True:
@@ -153,7 +175,13 @@ async def get_all_repos(self, include_forks: bool = False) -> list[GitHubRepo]:
153175
if len(repos) < 100:
154176
break
155177
page += 1
156-
# Safety limit to prevent infinite loops
157-
if page > 10:
178+
179+
if max_pages is not None and page > max_pages:
180+
logger.warning(
181+
"GitHub repo pagination stopped at limit",
182+
max_pages=max_pages,
183+
total_repos_fetched=len(all_repos),
184+
stopped_at_page=page
185+
)
158186
break
159187
return all_repos

0 commit comments

Comments
 (0)