refactor: extract focused clients from GiteaClient (Slices 1-6)

- Create gitea/issues_client.py with IssuesClient class (9 methods)
- Create gitea/prs_client.py with PullRequestsClient class (17 methods)
- Create gitea/files_client.py with FilesClient class (4 methods)
- Create gitea/notifications_client.py with NotificationsClient class (2 methods)
- Create gitea/repos_client.py with ReposClient class (2 methods)
- Create gitea/__init__.py to export all client classes
- Remove delegation methods from GiteaClient (now ~70 lines)
- Update all callers to use sub-clients (client.issues, client.prs, etc.)
- Update test files to mock sub-client attributes

GiteaClient is now a facade that provides access to focused sub-clients:
- repos: Repository operations (ReposClient)
- issues: Issue operations (IssuesClient)
- prs: Pull request operations (PullRequestsClient)
- files: File and git ref operations (FilesClient)
- notifications: Notification operations (NotificationsClient)

Refs: #godclass-refactor
This commit is contained in:
meeks
2026-07-17 07:31:05 +02:00
parent 3c94c3cfac
commit 25473ed684
21 changed files with 1480 additions and 728 deletions
+29 -25
View File
@@ -43,7 +43,7 @@ def _find_pr_for_issue_helper(
"""Find an open pull request that addresses the given issue number."""
owner, repo_name = repo_full_name.split("/")
try:
prs = client.list_repo_pull_requests(owner, repo_name)
prs = client.prs.list_repo_pull_requests(owner, repo_name)
for pr in prs:
ref = pr.head.get("ref", "") if pr.head else ""
if re.search(rf"(?<!\d){issue_number}(?!\d)", ref):
@@ -195,7 +195,7 @@ class PRTaskProcessor(TaskProcessor):
pr_details = pr_info.model_dump_json(indent=2)
pr_diff = ""
try:
pr_diff = self.client.get_pull_request_diff(
pr_diff = self.client.prs.get_pull_request_diff(
self.owner, self.repo_name, pr_number
)
except Exception as e:
@@ -204,7 +204,7 @@ class PRTaskProcessor(TaskProcessor):
pr_files: list[PullRequestFileModel] = []
try:
pr_files = self.client.get_pull_request_files(
pr_files = self.client.prs.get_pull_request_files(
self.owner, self.repo_name, pr_number
)
except Exception as e:
@@ -220,7 +220,7 @@ class PRTaskProcessor(TaskProcessor):
comments: list[CommentModel] = []
try:
comments = self.client.get_pull_request_comments(
comments = self.client.prs.get_pull_request_comments(
self.owner, self.repo_name, pr_number
)
if not isinstance(comments, list):
@@ -232,7 +232,9 @@ class PRTaskProcessor(TaskProcessor):
reviews: list[dict[str, Any]] = []
try:
reviews = self.client.get_pr_reviews(self.owner, self.repo_name, pr_number)
reviews = self.client.prs.get_pr_reviews(
self.owner, self.repo_name, pr_number
)
if not isinstance(reviews, list):
reviews = []
except Exception as e:
@@ -302,8 +304,10 @@ class PRTaskProcessor(TaskProcessor):
issues_details = []
for issue_num in linked_issues:
try:
issue = self.client.get_issue(self.owner, self.repo_name, issue_num)
issue_comments = self.client.get_issue_comments(
issue = self.client.issues.get_issue(
self.owner, self.repo_name, issue_num
)
issue_comments = self.client.issues.get_issue_comments(
self.owner, self.repo_name, issue_num
)
comments_list = (
@@ -392,14 +396,14 @@ class PRTaskProcessor(TaskProcessor):
async def process(self, attempt_limit: int) -> str:
try:
pr_detail = self.client.get_pull_request(
pr_detail = self.client.prs.get_pull_request(
self.owner, self.repo_name, self.item.task_number
)
except Exception as e:
logger.warning(f"Error fetching PR #{self.item.task_number} detail: {e}")
return f"FAILED: Could not fetch details for PR #{self.item.task_number}."
is_own_pr = pr_detail.user and pr_detail.user.login == self.ai_username
is_own_pr = bool(pr_detail.user and pr_detail.user.login == self.ai_username)
is_requested_reviewer = any(
r.login == self.ai_username for r in pr_detail.requested_reviewers
)
@@ -412,7 +416,7 @@ class PRTaskProcessor(TaskProcessor):
pr_comments = []
try:
pr_comments = self.client.get_pull_request_comments(
pr_comments = self.client.prs.get_pull_request_comments(
self.owner, self.repo_name, self.item.task_number
)
except Exception as e:
@@ -490,7 +494,7 @@ class IssueTaskProcessor(TaskProcessor):
comments: list[CommentModel] = []
try:
comments = self.client.get_issue_comments(
comments = self.client.issues.get_issue_comments(
self.owner, self.repo_name, issue_number
)
except Exception as e:
@@ -558,7 +562,7 @@ class IssueTaskProcessor(TaskProcessor):
is_wip = title_upper.startswith("WIP:") or "[WIP]" in title_upper
try:
reviews = self.client.get_pr_reviews(
reviews = self.client.prs.get_pr_reviews(
self.owner, self.repo_name, existing_pr.number
)
has_request_changes = any(
@@ -578,7 +582,7 @@ class IssueTaskProcessor(TaskProcessor):
# Check comments on issue and PR
issue_comments = []
try:
issue_comments = self.client.get_issue_comments(
issue_comments = self.client.issues.get_issue_comments(
self.owner, self.repo_name, self.item.task_number
)
except Exception as e:
@@ -590,7 +594,7 @@ class IssueTaskProcessor(TaskProcessor):
pr_comments = []
if existing_pr:
try:
pr_comments = self.client.get_pull_request_comments(
pr_comments = self.client.prs.get_pull_request_comments(
self.owner, self.repo_name, existing_pr.number
)
except Exception as e:
@@ -639,7 +643,7 @@ class IssueTaskProcessor(TaskProcessor):
else "No PR comments yet."
)
try:
reviews = self.client.get_pr_reviews(
reviews = self.client.prs.get_pr_reviews(
self.owner, self.repo_name, existing_pr.number
)
reviews_str = (
@@ -703,7 +707,7 @@ class IssueTaskProcessor(TaskProcessor):
f"<!-- agent:plan-proposal -->\n"
f"<!-- agent:awaiting-reply -->"
)
self.client.add_comment(
self.client.issues.add_comment(
self.owner, self.repo_name, self.item.task_number, comment_body
)
return f"POSTED_COMMENT: PROPOSE_PLAN comment posted to issue #{self.item.task_number}."
@@ -718,7 +722,7 @@ class IssueTaskProcessor(TaskProcessor):
f"<!-- agent:question-response -->\n"
f"<!-- agent:awaiting-reply -->"
)
self.client.add_comment(
self.client.issues.add_comment(
self.owner, self.repo_name, self.item.task_number, comment_body
)
return f"POSTED_COMMENT: ANSWER_QUESTION comment posted to issue #{self.item.task_number}."
@@ -727,10 +731,10 @@ class IssueTaskProcessor(TaskProcessor):
comment = coord_tools.arguments.get(
"comment", "Closing the issue as resolved."
)
self.client.add_comment(
self.client.issues.add_comment(
self.owner, self.repo_name, self.item.task_number, comment
)
self.client.close_issue(
self.client.issues.close_issue(
self.owner, self.repo_name, self.item.task_number
)
return f"CLOSED_ISSUE: Issue #{self.item.task_number} closed."
@@ -805,7 +809,7 @@ class IssueTaskProcessor(TaskProcessor):
pr_description = (
f"Work in progress for issue #{self.item.task_number}."
)
pr_to_use = self.client.create_pull_request(
pr_to_use = self.client.prs.create_pull_request(
self.owner,
self.repo_name,
head=branch_name,
@@ -821,7 +825,7 @@ class IssueTaskProcessor(TaskProcessor):
start_comment = (
f"Started work on PR #{pr_to_use.number} ({pr_link})."
)
self.client.add_comment(
self.client.issues.add_comment(
self.owner,
self.repo_name,
self.item.task_number,
@@ -919,7 +923,7 @@ class AgentDispatcher:
results: list[str] = []
# Get authenticated username for reviewer filter
try:
user = self._client.get_authenticated_user()
user = self._client.repos.get_authenticated_user()
except Exception as e:
raise RuntimeError("No authenticated user found.") from e
@@ -973,7 +977,7 @@ class AgentDispatcher:
return _find_pr_for_issue_helper(self._client, repo_full_name, issue_number)
def _is_awaiting_reply(self, comments: list[CommentModel]) -> bool:
user = self._client.get_authenticated_user()
user = self._client.repos.get_authenticated_user()
if not user or not user.login:
raise RuntimeError("No authenticated user found.")
return _is_awaiting_reply_helper(comments, user.login)
@@ -982,7 +986,7 @@ class AgentDispatcher:
pr_info = item.task_info
if not isinstance(pr_info, PullRequestModel):
raise TypeError("Expected task_info to be a PullRequestModel")
user = self._client.get_authenticated_user()
user = self._client.repos.get_authenticated_user()
if not user or not user.login:
raise RuntimeError("No authenticated user found.")
processor = PRTaskProcessor(
@@ -1002,7 +1006,7 @@ class AgentDispatcher:
issue_info = item.task_info
if not isinstance(issue_info, IssueModel):
raise TypeError("Expected task_info to be an IssueModel")
user = self._client.get_authenticated_user()
user = self._client.repos.get_authenticated_user()
if not user or not user.login:
raise RuntimeError("No authenticated user found.")
processor = IssueTaskProcessor(
+13 -5
View File
@@ -90,7 +90,9 @@ class AgentOrchestrator:
f"Polling unread notifications since: {last_checked or 'beginning'}"
)
notifications = self._client.list_unread_notifications(since=last_checked)
notifications = self._client.notifications.list_unread_notifications(
since=last_checked
)
if not notifications:
logger.info("No new notifications found.")
@@ -165,7 +167,9 @@ class AgentOrchestrator:
f"Skipping notification {notification_id} for {repo_full_name}#{task_number}. Reason: {reason}"
)
if notification_id is not None:
self._client.mark_notification_as_read(notification_id)
self._client.notifications.mark_notification_as_read(
notification_id
)
logger.info(
f"Marked skipped Gitea notification thread {notification_id} as read."
)
@@ -174,7 +178,7 @@ class AgentOrchestrator:
# Route based on decided action
if notification_tools.action == "PROCESS_ISSUE":
try:
issue = self._client.get_issue(owner, repo_name, task_number)
issue = self._client.issues.get_issue(owner, repo_name, task_number)
if issue.repository is None:
issue = issue.model_copy(
update={"repository": RepositoryModel(**repo_info)}
@@ -195,7 +199,9 @@ class AgentOrchestrator:
)
elif notification_tools.action == "PROCESS_PR":
try:
pr = self._client.get_pull_request(owner, repo_name, task_number)
pr = self._client.prs.get_pull_request(
owner, repo_name, task_number
)
if pr.repository is None:
pr = pr.model_copy(
update={"repository": RepositoryModel(**repo_info)}
@@ -252,7 +258,9 @@ class AgentOrchestrator:
f"Completed {item.task_type} #{item.task_number}: {result[:200]}"
)
if item.notification_id is not None:
self._client.mark_notification_as_read(item.notification_id)
self._client.notifications.mark_notification_as_read(
item.notification_id
)
logger.info(
f"Marked Gitea notification thread {item.notification_id} as read."
)