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 11*20f38712SPatrick Williams 12a464a7ceSSivas SRRimport requests 13a464a7ceSSivas SRR 14a464a7ceSSivas SRRauth = None 15*20f38712SPatrick 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(): 26*20f38712SPatrick Williams if "pull_request" not in issue: 27*20f38712SPatrick Williams labels = ", ".join([lable["name"] for lable in issue["labels"]]) 28b4eb9ac1SSivas SRR 299fe1b12eSSivas SRR # Below lines to overcome "TypeError: 'NoneType' object has 309fe1b12eSSivas SRR # no attribute '__getitem__'" 319fe1b12eSSivas SRR 32*20f38712SPatrick Williams close_date = issue.get("closed_at") 33b4eb9ac1SSivas SRR if close_date: 34*20f38712SPatrick Williams close_date = issue.get("closed_at").split("T")[0] 35b4eb9ac1SSivas SRR 36*20f38712SPatrick Williams assignee_resp = issue.get("assignees", "Not Assigned") 379fe1b12eSSivas SRR if assignee_resp: 38*20f38712SPatrick Williams owners = ",".join( 39*20f38712SPatrick Williams [ 40*20f38712SPatrick Williams assignee_login["login"] 41*20f38712SPatrick Williams for assignee_login in assignee_resp 42*20f38712SPatrick Williams ] 43*20f38712SPatrick Williams ) 449fe1b12eSSivas SRR else: 45b4eb9ac1SSivas SRR owners = "Not Assigned" 46b4eb9ac1SSivas SRR 47*20f38712SPatrick Williams milestone_resp = issue.get("milestone", "Not Assigned") 48b4eb9ac1SSivas SRR if milestone_resp: 49*20f38712SPatrick Williams milestone_resp = milestone_resp["title"].encode("utf-8") 509fe1b12eSSivas SRR 51a464a7ceSSivas SRR # Change the following line to write out additional fields 52*20f38712SPatrick Williams csv_out.writerow( 53*20f38712SPatrick Williams [ 54*20f38712SPatrick Williams labels.encode("utf-8"), 55*20f38712SPatrick Williams issue.get("title").encode("utf-8"), 56*20f38712SPatrick Williams issue.get("state").encode("utf-8"), 57*20f38712SPatrick Williams issue.get("created_at").split("T")[0], 58b4eb9ac1SSivas SRR close_date, 59*20f38712SPatrick Williams issue.get("html_url").encode("utf-8"), 60*20f38712SPatrick Williams issue.get("user").get("login").encode("utf-8"), 61*20f38712SPatrick Williams owners, 62*20f38712SPatrick Williams milestone_resp, 63*20f38712SPatrick Williams ] 64*20f38712SPatrick 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 78*20f38712SPatrick Williams if "link" in response.headers: 79*20f38712SPatrick Williams pages = { 80*20f38712SPatrick Williams rel[6:-1]: url[url.index("<") + 1 : -1] 81*20f38712SPatrick Williams for url, rel in ( 82*20f38712SPatrick Williams link.split(";") for link in response.headers["link"].split(",") 83*20f38712SPatrick Williams ) 84*20f38712SPatrick Williams } 85*20f38712SPatrick Williams while "last" in pages and "next" in pages: 86*20f38712SPatrick Williams pages = { 87*20f38712SPatrick Williams rel[6:-1]: url[url.index("<") + 1 : -1] 88*20f38712SPatrick Williams for url, rel in ( 89*20f38712SPatrick Williams link.split(";") 90*20f38712SPatrick Williams for link in response.headers["link"].split(",") 91*20f38712SPatrick Williams ) 92*20f38712SPatrick Williams } 93*20f38712SPatrick Williams response = requests.get(pages["next"], auth=auth) 94a464a7ceSSivas SRR write_issues(response, csv_out) 95*20f38712SPatrick Williams if pages["next"] == pages["last"]: 96a464a7ceSSivas SRR break 97a464a7ceSSivas SRR 98a464a7ceSSivas SRR 99*20f38712SPatrick Williamsparser = argparse.ArgumentParser( 100*20f38712SPatrick Williams description="Write GitHub repository issues to CSV file." 101*20f38712SPatrick Williams) 102a464a7ceSSivas SRR 103*20f38712SPatrick Williamsparser.add_argument( 104*20f38712SPatrick Williams "username", nargs="?", help="GitHub user name, formatted as 'username'" 105*20f38712SPatrick Williams) 106a464a7ceSSivas SRR 107*20f38712SPatrick Williamsparser.add_argument( 108*20f38712SPatrick Williams "repositories", 109*20f38712SPatrick Williams nargs="+", 110*20f38712SPatrick Williams help="Repository names, formatted as 'basereponame/repo'", 111*20f38712SPatrick Williams) 112a464a7ceSSivas SRR 113*20f38712SPatrick Williamsparser.add_argument( 114*20f38712SPatrick Williams "--all", action="store_true", help="Returns both open and closed issues." 115*20f38712SPatrick Williams) 1160419fb0aSSivas SRR 117a464a7ceSSivas SRRargs = parser.parse_args() 118a464a7ceSSivas SRR 119a464a7ceSSivas SRRif args.all: 120*20f38712SPatrick 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: 131*20f38712SPatrick Williams csvfilename_temp = "{}".format(repository.replace("/", "-")) 1320419fb0aSSivas SRR csvfilename = csvfilename + csvfilename_temp 133*20f38712SPatrick Williamscsvfilename = csvfilename + "-issues.csv" 134*20f38712SPatrick Williamswith open(csvfilename, "w") as csvfileout: 1350419fb0aSSivas SRR csv_out = csv.writer(csvfileout) 136*20f38712SPatrick Williams csv_out.writerow( 137*20f38712SPatrick Williams [ 138*20f38712SPatrick Williams "Labels", 139*20f38712SPatrick Williams "Title", 140*20f38712SPatrick Williams "State", 141*20f38712SPatrick Williams "Open Date", 142*20f38712SPatrick Williams "Close Date", 143*20f38712SPatrick Williams "URL", 144*20f38712SPatrick Williams "Author", 145*20f38712SPatrick Williams "Assignees", 146*20f38712SPatrick Williams "Milestone", 147*20f38712SPatrick Williams ] 148*20f38712SPatrick Williams ) 1490419fb0aSSivas SRR for repository in args.repositories: 150*20f38712SPatrick 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