blob: ce4e44f3f031c5efa6260fa3cce87eb6d364476c [file] [log] [blame]
from dataclasses import dataclass, field
from typing import Any, Optional
import time
from logging import Logger
from urllib.parse import quote_plus
import requests
GITHUB_API_BASE = "https://api.github.com"
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting
INTERVAL_IN_SECONDS = 1.0
# https://docs.github.com/en/rest/search#rate-limit
SEARCH_INTERVAL_IN_SECONDS = 5
@dataclass
class GHIssueComment:
id: int
body: str
def check_authentication(token: str):
check_url = GITHUB_API_BASE + "/user"
headers = {"Authorization": f"token {token}"}
res = requests.get(check_url, headers=headers)
assert res.status_code == 200, f"Authentication failed. Please check your GitHub token. status_code={res.status_code}, message={res.text}"
def get_issue_body(token: str, repo: str, issue_number: int, logger: Logger) -> Optional[str]:
url = GITHUB_API_BASE + f"/repos/{repo}/issues/{issue_number}"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
res = requests.get(url, headers=headers)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to get issue {issue_number}; status_code={res.status_code}, message={res.text}")
return None
return res.json().get("body")
def create_issue(token: str, repo: str, title: str, body: str, logger: Logger) -> Optional[tuple[str, int]]:
url = GITHUB_API_BASE + f"/repos/{repo}/issues"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
data = {"title": title, "body": body}
res = requests.post(url, headers=headers, json=data)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 201:
logger.error(f"Failed to create issue; status_code={res.status_code}, message={res.text}")
return None
html_url = res.json()["html_url"]
number = res.json()["number"]
return (html_url, number)
def update_issue_body(token: str, repo: str, issue_number: int, body: str, logger: Logger) -> bool:
url = GITHUB_API_BASE + f"/repos/{repo}/issues/{issue_number}"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
data = {"body": body}
res = requests.patch(url, headers=headers, json=data)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to update issue {issue_number}; status_code={res.status_code}, message={res.text}")
return False
return True
def get_issue_comments(token: str, repo: str, issue_number: int, logger: Logger) -> list[GHIssueComment]:
url = GITHUB_API_BASE + f"/repos/{repo}/issues/{issue_number}/comments?per_page=100"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
li = []
stop = False
page = 1
while not stop:
url_with_paging = url + f"&page={page}"
res = requests.get(url_with_paging, headers=headers)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to get issue comments for {issue_number}; status_code={res.status_code}, message={res.text}")
break
if not res.json():
stop = True
for comment in res.json():
li.append(GHIssueComment(id=comment.get("id"), body=comment.get("body")))
page += 1
return li
def create_comment(token: str, repo: str, issue_number: int, body: str, logger: Logger) -> Optional[int]:
url = GITHUB_API_BASE + f"/repos/{repo}/issues/{issue_number}/comments"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
data = {"body": body}
res = requests.post(url, headers=headers, json=data)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 201:
logger.error(f"Failed to create comment; status_code={res.status_code}, message={res.text}")
return None
id = res.json()["id"]
return id
def update_comment_body(token: str, repo: str, comment_id: int, body: str, logger: Logger) -> bool:
url = GITHUB_API_BASE + f"/repos/{repo}/issues/comments/{comment_id}"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
data = {"body": body}
res = requests.patch(url, headers=headers, json=data)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to update comment {comment_id}; status_code={res.status_code}, message={res.text}")
return False
return True
def import_issue(token: str, repo: str, issue_data: dict, logger: Logger) -> str:
url = GITHUB_API_BASE + f"/repos/{repo}/import/issues"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.golden-comet-preview+json"}
res = requests.post(url, headers=headers, json=issue_data)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 202:
logger.error(f"Failed to import issue {issue_data['issue']['title']}; status_code={res.status_code}, message={res.text}")
return res.json().get("url")
def get_import_status(token: str, url: str, logger: Logger) -> Optional[tuple[str, str, list[Any]]]:
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.golden-comet-preview+json"}
res = requests.get(url, headers=headers)
if res.status_code != 200:
logger.error(f"Failed to get import status for {url}; status code={res.status_code}, message={res.text}")
return None
return (res.json().get("status"), res.json().get("issue_url", ""), res.json().get("errors", []))
def check_if_can_be_assigned(token: str, repo: str, assignee: str, logger: Logger) -> bool:
url = GITHUB_API_BASE + f"/repos/{repo}/assignees/{assignee}"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
res = requests.get(url, headers=headers)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code == 204:
return True
else:
logger.warning(f"Assignee {assignee} cannot be assigned; status code={res.status_code}, message={res.text}")
return False
def search_users(token: str, q: str, logger: Logger) -> list[str]:
url = GITHUB_API_BASE + f"/search/users?q={quote_plus(q)}"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
res = requests.get(url, headers=headers)
time.sleep(SEARCH_INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to search users with query {q}; status_code={res.status_code}, message={res.text}")
return []
return [item["login"] for item in res.json()["items"]]
def get_user(token: str, username: str, logger: Logger) -> Optional[dict[str, Any]]:
url = GITHUB_API_BASE + f"/users/{username}"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
res = requests.get(url, headers=headers)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to get user {username}; status_code={res.status_code}, message={res.text}")
return None
return res.json()
def list_organization_members(token: str, org: str, logger: Logger) -> list[str]:
url = GITHUB_API_BASE + f"/orgs/{org}/members?per_page=100"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
page = 1
users = []
while True:
res = requests.get(f"{url}&page={page}", headers=headers)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to get organization members for {org}; status_code={res.status_code}, message={res.text}")
return users
if len(res.json()) == 0:
break
users.extend(x["login"] for x in res.json())
logger.debug(f"{len(users)} members found.")
page += 1
return users
def list_commit_authors(token: str, repo: str, logger: Logger) -> list[str]:
url = GITHUB_API_BASE + f"/repos/{repo}/commits?per_page=100"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
page = 1
authors = set([])
while True:
res = requests.get(f"{url}&page={page}", headers=headers)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to get commits for {repo}; status_code={res.status_code}, message={res.text}")
return authors
if len(res.json()) == 0:
break
for commit in res.json():
author = commit.get("author")
if author:
authors.add(author["login"])
logger.debug(f"{len(authors)} authors found.")
page += 1
return authors
def list_labels(token: str, repo: str, logger: Logger) -> list[str]:
url = GITHUB_API_BASE + f"/repos/{repo}/labels?per_page=100"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
page = 1
labels = []
while True:
res = requests.get(f"{url}&page={page}", headers=headers)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to get labels for {repo}; status_code={res.status_code}, message={res.text}")
return labels
if len(res.json()) == 0:
break
for l in res.json():
name = l["name"]
labels.append(name)
page += 1
return labels
def update_label(token: str, repo: str, name: str, color: str, description: str, logger: Logger) -> bool:
url = GITHUB_API_BASE + f"/repos/{repo}/labels/{name}"
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
data = {"color": color, "description": description}
res = requests.patch(url, headers=headers, json=data)
time.sleep(INTERVAL_IN_SECONDS)
if res.status_code != 200:
logger.error(f"Failed to update label {name}; status_code={res.status_code}, message={res.text}")
return False
logger.debug(f"Label {name} updated.")
return True