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

1#!/usr/bin/env python3 

2"""Delete previous lintro bot comments from a GitHub pull request. 

3 

4This script deletes all comments containing the marker '<!-- lintro-report -->' 

5from a specified pull request. 

6 

7Uses httpx for HTTP requests. 

8 

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. 

13 

14Usage: 

15 uv run python scripts/delete-previous-lintro-comments.py 

16 

17Intended for use in CI workflows to keep PRs clean of duplicate bot comments. 

18""" 

19 

20import os 

21import sys 

22from time import sleep 

23 

24import httpx 

25 

26 

27def get_marker() -> str: 

28 """Get the marker/tag from command-line arguments or use default. 

29 

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 -->" 

36 

37 

38def get_env_var(name: str) -> str: 

39 """Get an environment variable or exit with error. 

40 

41 Args: 

42 name: Name of the environment variable. 

43 

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 

52 

53 

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. 

60 

61 Args: 

62 repo: Repository in 'owner/repo' format. 

63 pr_number: Pull request number. 

64 token: GitHub API token. 

65 

66 Returns: 

67 list[dict[str, str | int]]: List of comment objects. 

68 

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 

108 

109 

110def delete_comment(repo: str, comment_id: int, token: str) -> None: 

111 """Delete a comment by ID. 

112 

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 ) 

137 

138 

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() 

145 

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) 

155 

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 

163 

164 if not deleted_any: 

165 print(f"No previous comments found to delete for marker: {marker}") 

166 

167 

168if __name__ == "__main__": 

169 main()