Coverage for scripts / utils / delete-previous-lintro-comments.py: 44%
70 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1#!/usr/bin/env python3
2"""Delete previous lintro bot comments from a GitHub pull request.
4This script deletes all comments containing the marker '<!-- lintro-report -->'
5from a specified pull request.
7Uses httpx for HTTP requests.
9Environment Variables:
10 GITHUB_TOKEN (str): GitHub API token with repo permissions.
11 GITHUB_REPOSITORY (str): Repository in 'owner/repo' format.
12 PR_NUMBER (str or int): Pull request number.
14Usage:
15 uv run python scripts/delete-previous-lintro-comments.py
17Intended for use in CI workflows to keep PRs clean of duplicate bot comments.
18"""
20import os
21import sys
22from time import sleep
24import httpx
27def get_marker() -> str:
28 """Get the marker/tag from command-line arguments or use default.
30 Returns:
31 str: The marker/tag to use.
32 """
33 if len(sys.argv) > 1:
34 return sys.argv[1]
35 return "<!-- lintro-report -->"
38def get_env_var(name: str) -> str:
39 """Get an environment variable or exit with error.
41 Args:
42 name: Name of the environment variable.
44 Returns:
45 str: Value of the environment variable.
46 """
47 value: str | None = os.environ.get(name)
48 if not value:
49 print(f"Error: Environment variable {name} is required.", file=sys.stderr)
50 sys.exit(1)
51 return value
54def get_pr_comments(
55 repo: str,
56 pr_number: str,
57 token: str,
58) -> list[dict[str, object]]:
59 """Fetch all comments for a pull request with pagination and retries.
61 Args:
62 repo: Repository in 'owner/repo' format.
63 pr_number: Pull request number.
64 token: GitHub API token.
66 Returns:
67 list[dict[str, str | int]]: List of comment objects.
69 Raises:
70 Exception: If the GitHub API request fails after retries or returns
71 a non-successful response status.
72 """
73 base_url: str = os.environ.get("GITHUB_API_URL", "https://api.github.com").rstrip(
74 "/",
75 )
76 headers: dict[str, str] = {
77 "Authorization": f"Bearer {token}",
78 "Accept": "application/vnd.github+json",
79 "X-GitHub-Api-Version": "2022-11-28",
80 }
81 all_comments: list[dict[str, object]] = []
82 page: int = 1
83 per_page: int = 100
84 with httpx.Client(timeout=15) as client:
85 while True:
86 url: str = (
87 f"{base_url}/repos/{repo}/issues/{pr_number}/comments"
88 f"?per_page={per_page}&page={page}"
89 )
90 # Simple retry/backoff loop
91 for attempt in range(3):
92 try:
93 response: httpx.Response = client.get(url=url, headers=headers)
94 response.raise_for_status()
95 break
96 except Exception:
97 if attempt == 2:
98 raise
99 sleep(0.5 * (2**attempt))
100 data = response.json()
101 if not isinstance(data, list) or not data:
102 break
103 all_comments.extend(data)
104 if len(data) < per_page:
105 break
106 page += 1
107 return all_comments
110def delete_comment(repo: str, comment_id: int, token: str) -> None:
111 """Delete a comment by ID.
113 Args:
114 repo: Repository in 'owner/repo' format.
115 comment_id: Comment ID.
116 token: GitHub API token.
117 """
118 base_url: str = os.environ.get("GITHUB_API_URL", "https://api.github.com").rstrip(
119 "/",
120 )
121 headers: dict[str, str] = {
122 "Authorization": f"Bearer {token}",
123 "Accept": "application/vnd.github+json",
124 "X-GitHub-Api-Version": "2022-11-28",
125 }
126 url: str = f"{base_url}/repos/{repo}/issues/comments/{comment_id}"
127 with httpx.Client(timeout=15) as client:
128 response: httpx.Response = client.delete(url=url, headers=headers)
129 if response.status_code == 204:
130 print(f"Deleted comment {comment_id}")
131 else:
132 print(
133 f"Failed to delete comment {comment_id}: "
134 f"{response.status_code} {response.text}",
135 file=sys.stderr,
136 )
139def main() -> None:
140 """Main entry point for the script."""
141 repo: str = get_env_var(name="GITHUB_REPOSITORY")
142 pr_number: str = get_env_var(name="PR_NUMBER")
143 token: str = get_env_var(name="GITHUB_TOKEN")
144 marker: str = get_marker()
146 try:
147 comments: list[dict[str, object]] = get_pr_comments(
148 repo=repo,
149 pr_number=pr_number,
150 token=token,
151 )
152 except Exception as e:
153 print(f"Error fetching comments: {e}", file=sys.stderr)
154 sys.exit(1)
156 deleted_any: bool = False
157 for comment in comments:
158 body = comment.get("body")
159 comment_id = comment.get("id")
160 if isinstance(body, str) and marker in body and isinstance(comment_id, int):
161 delete_comment(repo=repo, comment_id=comment_id, token=token)
162 deleted_any = True
164 if not deleted_any:
165 print(f"No previous comments found to delete for marker: {marker}")
168if __name__ == "__main__":
169 main()