1e7e9171eSGeorge Keishing#!/usr/bin/env python3 2a464a7ceSSivas SRR 3a464a7ceSSivas SRRr""" 4a464a7ceSSivas SRRExports issues from a list of repositories to individual CSV files. 5a464a7ceSSivas SRRUses basic authentication (GitHub username + password) to retrieve issues 6a464a7ceSSivas SRRfrom a repository that username has access to. Supports GitHub API v3. 7a464a7ceSSivas SRR""" 8a464a7ceSSivas SRRimport argparse 9a464a7ceSSivas SRRimport csv 109fe1b12eSSivas SRRimport getpass 1120f38712SPatrick Williams 12a464a7ceSSivas SRRimport requests 13a464a7ceSSivas SRR 14a464a7ceSSivas SRRauth = None 1520f38712SPatrick Williamsstates = "all" 16a464a7ceSSivas SRR 17a464a7ceSSivas SRR 18a464a7ceSSivas SRRdef write_issues(response, csv_out): 19a464a7ceSSivas SRR r""" 20a464a7ceSSivas SRR Parses JSON response and writes to CSV. 21a464a7ceSSivas SRR """ 2265dbeaadSvinaybs6 print(response) 23a464a7ceSSivas SRR if response.status_code != 200: 24a464a7ceSSivas SRR raise Exception(response.status_code) 25a464a7ceSSivas SRR for issue in response.json(): 2620f38712SPatrick Williams if "pull_request" not in issue: 27*4ebb3281SGeorge Keishing labels = ", ".join([label["name"] for label in issue["labels"]]) 28b4eb9ac1SSivas SRR 299fe1b12eSSivas SRR # Below lines to overcome "TypeError: 'NoneType' object has 309fe1b12eSSivas SRR # no attribute '__getitem__'" 319fe1b12eSSivas SRR 3220f38712SPatrick Williams close_date = issue.get("closed_at") 33b4eb9ac1SSivas SRR if close_date: 3420f38712SPatrick Williams close_date = issue.get("closed_at").split("T")[0] 35b4eb9ac1SSivas SRR 3620f38712SPatrick Williams assignee_resp = issue.get("assignees", "Not Assigned") 379fe1b12eSSivas SRR if assignee_resp: 3820f38712SPatrick Williams owners = ",".join( 3920f38712SPatrick Williams [ 4020f38712SPatrick Williams assignee_login["login"] 4120f38712SPatrick Williams for assignee_login in assignee_resp 4220f38712SPatrick Williams ] 4320f38712SPatrick Williams ) 449fe1b12eSSivas SRR else: 45b4eb9ac1SSivas SRR owners = "Not Assigned" 46b4eb9ac1SSivas SRR 4720f38712SPatrick Williams milestone_resp = issue.get("milestone", "Not Assigned") 48b4eb9ac1SSivas SRR if milestone_resp: 4920f38712SPatrick Williams milestone_resp = milestone_resp["title"].encode("utf-8") 509fe1b12eSSivas SRR 51a464a7ceSSivas SRR # Change the following line to write out additional fields 5220f38712SPatrick Williams csv_out.writerow( 5320f38712SPatrick Williams [ 5420f38712SPatrick Williams labels.encode("utf-8"), 5520f38712SPatrick Williams issue.get("title").encode("utf-8"), 5620f38712SPatrick Williams issue.get("state").encode("utf-8"), 5720f38712SPatrick Williams issue.get("created_at").split("T")[0], 58b4eb9ac1SSivas SRR close_date, 5920f38712SPatrick Williams issue.get("html_url").encode("utf-8"), 6020f38712SPatrick Williams issue.get("user").get("login").encode("utf-8"), 6120f38712SPatrick Williams owners, 6220f38712SPatrick Williams milestone_resp, 6320f38712SPatrick Williams ] 6420f38712SPatrick Williams ) 65a464a7ceSSivas SRR 66a464a7ceSSivas SRR 670419fb0aSSivas SRRdef get_issues_from_github_to_csv(name, response): 68a464a7ceSSivas SRR r""" 69a464a7ceSSivas SRR Requests issues from GitHub API and writes to CSV file. 700419fb0aSSivas SRR Description of argument(s): 710419fb0aSSivas SRR name Name of the GitHub repository 720419fb0aSSivas SRR response GitHub repository response 73a464a7ceSSivas SRR """ 7465dbeaadSvinaybs6 print(name) 7565dbeaadSvinaybs6 print(states) 76a464a7ceSSivas SRR 77a464a7ceSSivas SRR # Multiple requests are required if response is paged 7820f38712SPatrick Williams if "link" in response.headers: 7920f38712SPatrick Williams pages = { 8020f38712SPatrick Williams rel[6:-1]: url[url.index("<") + 1 : -1] 8120f38712SPatrick Williams for url, rel in ( 8220f38712SPatrick Williams link.split(";") for link in response.headers["link"].split(",") 8320f38712SPatrick Williams ) 8420f38712SPatrick Williams } 8520f38712SPatrick Williams while "last" in pages and "next" in pages: 8620f38712SPatrick Williams pages = { 8720f38712SPatrick Williams rel[6:-1]: url[url.index("<") + 1 : -1] 8820f38712SPatrick Williams for url, rel in ( 8920f38712SPatrick Williams link.split(";") 9020f38712SPatrick Williams for link in response.headers["link"].split(",") 9120f38712SPatrick Williams ) 9220f38712SPatrick Williams } 9320f38712SPatrick Williams response = requests.get(pages["next"], auth=auth) 94a464a7ceSSivas SRR write_issues(response, csv_out) 9520f38712SPatrick Williams if pages["next"] == pages["last"]: 96a464a7ceSSivas SRR break 97a464a7ceSSivas SRR 98a464a7ceSSivas SRR 9920f38712SPatrick Williamsparser = argparse.ArgumentParser( 10020f38712SPatrick Williams description="Write GitHub repository issues to CSV file." 10120f38712SPatrick Williams) 102a464a7ceSSivas SRR 10320f38712SPatrick Williamsparser.add_argument( 10420f38712SPatrick Williams "username", nargs="?", help="GitHub user name, formatted as 'username'" 10520f38712SPatrick Williams) 106a464a7ceSSivas SRR 10720f38712SPatrick Williamsparser.add_argument( 10820f38712SPatrick Williams "repositories", 10920f38712SPatrick Williams nargs="+", 11020f38712SPatrick Williams help="Repository names, formatted as 'basereponame/repo'", 11120f38712SPatrick Williams) 112a464a7ceSSivas SRR 11320f38712SPatrick Williamsparser.add_argument( 11420f38712SPatrick Williams "--all", action="store_true", help="Returns both open and closed issues." 11520f38712SPatrick Williams) 1160419fb0aSSivas SRR 117a464a7ceSSivas SRRargs = parser.parse_args() 118a464a7ceSSivas SRR 119a464a7ceSSivas SRRif args.all: 12020f38712SPatrick Williams state = "all" 121a464a7ceSSivas SRR 1220419fb0aSSivas SRRusername = args.username 123a464a7ceSSivas SRR 1249fe1b12eSSivas SRRpassword = getpass.getpass("Enter your GitHub Password:") 125a464a7ceSSivas SRR 126a464a7ceSSivas SRRauth = (username, password) 127a464a7ceSSivas SRR 1280419fb0aSSivas SRR# To set the csv filename 1290419fb0aSSivas SRRcsvfilename = "" 130a464a7ceSSivas SRRfor repository in args.repositories: 13120f38712SPatrick Williams csvfilename_temp = "{}".format(repository.replace("/", "-")) 1320419fb0aSSivas SRR csvfilename = csvfilename + csvfilename_temp 13320f38712SPatrick Williamscsvfilename = csvfilename + "-issues.csv" 13420f38712SPatrick Williamswith open(csvfilename, "w") as csvfileout: 1350419fb0aSSivas SRR csv_out = csv.writer(csvfileout) 13620f38712SPatrick Williams csv_out.writerow( 13720f38712SPatrick Williams [ 13820f38712SPatrick Williams "Labels", 13920f38712SPatrick Williams "Title", 14020f38712SPatrick Williams "State", 14120f38712SPatrick Williams "Open Date", 14220f38712SPatrick Williams "Close Date", 14320f38712SPatrick Williams "URL", 14420f38712SPatrick Williams "Author", 14520f38712SPatrick Williams "Assignees", 14620f38712SPatrick Williams "Milestone", 14720f38712SPatrick Williams ] 14820f38712SPatrick Williams ) 1490419fb0aSSivas SRR for repository in args.repositories: 15020f38712SPatrick Williams l_url = "https://api.github.com/repos/{}/issues?state={}" 1510419fb0aSSivas SRR l_url = l_url.format(repository, states) 1520419fb0aSSivas SRR response = requests.get(l_url, auth=auth) 1530419fb0aSSivas SRR write_issues(response, csv_out) 1540419fb0aSSivas SRR get_issues_from_github_to_csv(repository, response) 1550419fb0aSSivas SRRcsvfileout.close() 156