|
| 1 | +import os |
| 2 | +import boto3 |
| 3 | +import json |
| 4 | +import time |
| 5 | +import requests |
| 6 | +from github import Github |
| 7 | +from github import Auth |
| 8 | +from datetime import datetime, timezone |
| 9 | +from dateutil import tz |
| 10 | +from zoneinfo import ZoneInfo |
| 11 | +from pathlib import Path |
| 12 | + |
| 13 | +owner = "TheAlgorithms" |
| 14 | +repo = "Python" |
| 15 | +owner = "public-apis" |
| 16 | +repo = "public-apis" |
| 17 | +pull_number = 25653 # Replace with the desired PR number |
| 18 | +url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}" |
| 19 | +pulls_url = f"https://api.github.com/repos/{owner}/{repo}/pulls" |
| 20 | +# vinta_pulls = "https://api.github.com/vinta/awesome-python/pulls" # Example for a different repo |
| 21 | +SUPPORTED_EXTENSIONS = {'.py', '.js', '.ts', '.java', '.cs', '.cpp', '.c', '.go', '.rb'} |
| 22 | + |
| 23 | +# Create an SSM client |
| 24 | +ssm = boto3.client('ssm') |
| 25 | +def get_parameter(name): |
| 26 | + """Fetch parameter value from Parameter Store""" |
| 27 | + response = ssm.get_parameter( |
| 28 | + Name=name, |
| 29 | + WithDecryption=True |
| 30 | + ) |
| 31 | + return response['Parameter']['Value'] |
| 32 | + |
| 33 | +# Load secrets from AWS at cold start |
| 34 | +GIT_API_KEY = get_parameter("/prreview/GIT_API_KEY") |
| 35 | +if GIT_API_KEY is None: |
| 36 | + raise ValueError("GIT_API_KEY was not found in the parameter store.") |
| 37 | + |
| 38 | +headers = { |
| 39 | + "Accept": "application/vnd.github.v3+json", |
| 40 | + # Optional: Add token for higher rate limits |
| 41 | + # "Authorization": "Bearer YOUR_TOKEN" |
| 42 | +} |
| 43 | + |
| 44 | +def get_pr_details(): |
| 45 | + response = requests.get(url, headers=headers) |
| 46 | + if response.status_code == 200: |
| 47 | + pr_data = response.json() |
| 48 | + print("PR Title:", pr_data["title"]) |
| 49 | + print("Source Branch:", pr_data["head"]["ref"]) |
| 50 | + print("Target Branch:", pr_data["base"]["ref"]) |
| 51 | + print("Diff URL:", pr_data["diff_url"]) |
| 52 | + else: |
| 53 | + print(f"Error: {response.status_code}, {response.json().get('message')}") |
| 54 | + |
| 55 | +def get_pr_diff(repo,pr_number): |
| 56 | + # Headers for diff request |
| 57 | + diff_headers = { |
| 58 | + "Accept": "application/vnd.github.v3.diff", |
| 59 | + } |
| 60 | + token = GIT_API_KEY |
| 61 | + # print(f"Using GitHub API Key: {token}") |
| 62 | + if token: |
| 63 | + headers["Authorization"] = f"token {token}" |
| 64 | + |
| 65 | + # Construct the diff URL |
| 66 | + url = f"https://github.com/{repo}/pull/{pr_number}.diff" |
| 67 | + # Get the diff for this PR |
| 68 | + # print(f"Fetching diff for PR #{pr_number} in {repo}...") |
| 69 | + response = requests.get(url, headers=diff_headers) |
| 70 | + if response.status_code == 200: |
| 71 | + diff = response.text |
| 72 | + #print(f'Diff: {diff}') |
| 73 | + return diff |
| 74 | + else: |
| 75 | + print(f"Error: {response.status_code}") |
| 76 | + |
| 77 | +def get_supported_diffs(repo, pr_number): |
| 78 | + url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}/files" |
| 79 | + headers = { |
| 80 | + "Authorization": f"token {GIT_API_KEY}", |
| 81 | + "Accept": "application/vnd.github.v3+json" |
| 82 | + } |
| 83 | + response = requests.get(url, headers=headers) |
| 84 | + response.raise_for_status() |
| 85 | + all_files = response.json() |
| 86 | + #print(f"File: {all_files[0]}") |
| 87 | + |
| 88 | + |
| 89 | + # Check if the response contains a list of files |
| 90 | + |
| 91 | + # Keep only files with a supported extension |
| 92 | + supported_diffs = [ |
| 93 | + file for file in all_files |
| 94 | + if os.path.splitext(file['filename'])[1] in SUPPORTED_EXTENSIONS and 'patch' in file |
| 95 | + ] |
| 96 | + if len(all_files) != len(supported_diffs): |
| 97 | + print(f"Found {len(all_files) - len(supported_diffs)} out of {len(all_files)} filetypes in diffs which are not supported for PR #{pr_number} in {repo}.") |
| 98 | + |
| 99 | + return supported_diffs |
| 100 | + |
| 101 | +def get_pr_files(repo, pr_number): |
| 102 | + url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}/files" |
| 103 | + # print(f"Using GitHub API Key: {token}") |
| 104 | + headers = { |
| 105 | + "Authorization": f"Bearer {GIT_API_KEY}", |
| 106 | + "Accept": "application/vnd.github.v3+json", |
| 107 | + } |
| 108 | + response = requests.get(url, headers=headers) |
| 109 | + response.raise_for_status() # Raise an error for bad responses |
| 110 | + |
| 111 | + files = response.json() |
| 112 | + for file in files: |
| 113 | + filename = file.get("filename") |
| 114 | + status = file.get("status") # e.g. 'added', 'modified', 'removed' |
| 115 | + print(f"{status.upper()}: {filename}") |
| 116 | + |
| 117 | + return files |
| 118 | + |
| 119 | + |
| 120 | +def get_pull_requests(state='open'): |
| 121 | + """ |
| 122 | + Fetch pull requests from a GitHub repository. |
| 123 | + |
| 124 | + Args: |
| 125 | + owner (str): Repository owner (e.g., 'octocat') |
| 126 | + repo (str): Repository name (e.g., 'hello-world') |
| 127 | + token (str, optional): GitHub Personal Access Token for authentication |
| 128 | + state (str): State of PRs to fetch ('open', 'closed', or 'all') |
| 129 | + |
| 130 | + Returns: |
| 131 | + list: List of pull requests |
| 132 | + """ |
| 133 | + params = { |
| 134 | + "state": state, |
| 135 | + "per_page": 5 # Maximum number of PRs per page |
| 136 | + } |
| 137 | + |
| 138 | + pull_requests = [] |
| 139 | + page = 1 |
| 140 | + print(f"Fetching {state} pull requests from {pulls_url}...") |
| 141 | + #while True: |
| 142 | + params["page"] = page |
| 143 | + response = requests.get(pulls_url, headers=headers, params=params) |
| 144 | + |
| 145 | + if response.status_code != 200: |
| 146 | + print(f"Error: {response.status_code} - {response.json().get('message', 'Unknown error')}") |
| 147 | + return |
| 148 | + |
| 149 | + prs = response.json() |
| 150 | + # if not prs: # No more PRs to fetch |
| 151 | + # break |
| 152 | + |
| 153 | + pull_requests.extend(prs) |
| 154 | + #page += 1 |
| 155 | + |
| 156 | + return pull_requests |
| 157 | + |
| 158 | +def print_pull_requests(prs): |
| 159 | + """ |
| 160 | + Print basic information about pull requests. |
| 161 | + |
| 162 | + Args: |
| 163 | + prs (list): List of pull requests |
| 164 | + """ |
| 165 | + for pr in prs: |
| 166 | + print(f"State: {pr['state'].capitalize()}") |
| 167 | + print(f"{pr['title']}") |
| 168 | + user_name = pr['user']['login'] if 'user' in pr else 'Unknown User' |
| 169 | + created_at = pr['created_at'] |
| 170 | + if created_at: |
| 171 | + local_timezone = tz.tzlocal() |
| 172 | + date_object = datetime.strptime(created_at, "%Y-%m-%dT%H:%M:%SZ") |
| 173 | + local_time = date_object.astimezone(local_timezone) |
| 174 | + |
| 175 | + created_at = local_time.strftime("%Y-%m-%d at %H:%M:%S") |
| 176 | + else: |
| 177 | + created_at = "Unknown Date" |
| 178 | + print(f"PR #{pr['number']} opened by {user_name} on {created_at}") |
| 179 | + |
| 180 | + print(f"URL: {pr['html_url']}") |
| 181 | + print("-" * 50) |
| 182 | + |
| 183 | +def post_review(repo, pr_number, decision, review): |
| 184 | + headers = { |
| 185 | + "Accept": "application/vnd.github.v3.diff", |
| 186 | + "X-GitHub-Api-Version" : "2022-11-28" |
| 187 | + } |
| 188 | + if GIT_API_KEY: |
| 189 | + headers["Authorization"] = f"token {GIT_API_KEY}" |
| 190 | + |
| 191 | + payload = { |
| 192 | + "body": f"{review}", |
| 193 | + "event": f"{decision}", |
| 194 | + "comments": [ |
| 195 | + { |
| 196 | + "path": "path/to/file.py", |
| 197 | + "position": 1, |
| 198 | + "body": "Please change this line to improve readability." |
| 199 | + } |
| 200 | + ] |
| 201 | + } |
| 202 | + url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}/reviews" |
| 203 | + response = requests.post(url, headers=headers) |
| 204 | + if response.status_code == 200: |
| 205 | + review_data = response.json() |
| 206 | + print("Review submitted successfully.") |
| 207 | + print(f"Review ID: {review_data['id']}") |
| 208 | + print(f"State: {review_data['state']}") |
| 209 | + print(f"Submitted by: {review_data['user']['login']}") |
| 210 | + print(f"HTML URL: {review_data['html_url']}") |
| 211 | + else: |
| 212 | + print(f"Failed to submit review: {response.status_code} - {response.text}") |
| 213 | + |
| 214 | + |
| 215 | +def request_review(repo, pr_number, reviewer): |
| 216 | + url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}/requested_reviewers" |
| 217 | + headers = { |
| 218 | + "Authorization": f"token {GIT_API_KEY}", |
| 219 | + "Accept": "application/vnd.github+json" |
| 220 | + } |
| 221 | + payload = { |
| 222 | + "reviewers": [reviewer] |
| 223 | + # Optional: "team_reviewers": ["team-slug"] |
| 224 | + } |
| 225 | + |
| 226 | + response = requests.post(url, headers=headers, json=payload) |
| 227 | + |
| 228 | + if response.status_code == 201: |
| 229 | + print("Reviewers requested successfully.") |
| 230 | + else: |
| 231 | + print(f"Failed to request reviewers: {response.status_code} - {response.text}") |
| 232 | + |
| 233 | + |
| 234 | +if __name__ == "__main__": |
| 235 | + # repo = "ississippi/pull-request-test-repo" |
| 236 | + repo = "ississippi/pull-request-automated-review" |
| 237 | + pr_number = 13 |
| 238 | + # get_pr_details() |
| 239 | + # get_pr_files() |
| 240 | + # get_pr_diff("ississippi/pull-request-test-repo", 16) |
| 241 | + # Fetch pull requests |
| 242 | + # prs = get_pull_requests() |
| 243 | + # # Print results |
| 244 | + # print(f"Found {len(prs)} pull requests:") |
| 245 | + # print_pull_requests(prs) |
| 246 | + # git_pr_list() : needs work |
| 247 | + # get_pr_files(owner=owner,repo=repo,pr_number=pr_number) |
| 248 | + # request_review(repo, 10, "ississippi") |
| 249 | + # decision = "REQUEST_CHANGES" |
| 250 | + # review = "This is close to perfect! Please address the suggested inline change." |
| 251 | + # post_review(repo, 10, decision, review) |
| 252 | + get_supported_diffs(repo, pr_number) |
0 commit comments