1#!/usr/bin/env python3 2 3r""" 4Exports issues from a list of repositories to individual CSV files. 5Uses basic authentication (GitHub username + password) to retrieve issues 6from a repository that username has access to. Supports GitHub API v3. 7""" 8 9import argparse 10import csv 11import getpass 12 13import requests 14 15auth = None 16states = "all" 17 18 19def write_issues(response, csv_out): 20 r""" 21 Parses JSON response and writes to CSV. 22 """ 23 print(response) 24 if response.status_code != 200: 25 raise Exception(response.status_code) 26 for issue in response.json(): 27 if "pull_request" not in issue: 28 labels = ", ".join([label["name"] for label in issue["labels"]]) 29 30 # Below lines to overcome "TypeError: 'NoneType' object has 31 # no attribute '__getitem__'" 32 33 close_date = issue.get("closed_at") 34 if close_date: 35 close_date = issue.get("closed_at").split("T")[0] 36 37 assignee_resp = issue.get("assignees", "Not Assigned") 38 if assignee_resp: 39 owners = ",".join( 40 [ 41 assignee_login["login"] 42 for assignee_login in assignee_resp 43 ] 44 ) 45 else: 46 owners = "Not Assigned" 47 48 milestone_resp = issue.get("milestone", "Not Assigned") 49 if milestone_resp: 50 milestone_resp = milestone_resp["title"].encode("utf-8") 51 52 # Change the following line to write out additional fields 53 csv_out.writerow( 54 [ 55 labels.encode("utf-8"), 56 issue.get("title").encode("utf-8"), 57 issue.get("state").encode("utf-8"), 58 issue.get("created_at").split("T")[0], 59 close_date, 60 issue.get("html_url").encode("utf-8"), 61 issue.get("user").get("login").encode("utf-8"), 62 owners, 63 milestone_resp, 64 ] 65 ) 66 67 68def get_issues_from_github_to_csv(name, response): 69 r""" 70 Requests issues from GitHub API and writes to CSV file. 71 Description of argument(s): 72 name Name of the GitHub repository 73 response GitHub repository response 74 """ 75 print(name) 76 print(states) 77 78 # Multiple requests are required if response is paged 79 if "link" in response.headers: 80 pages = { 81 rel[6:-1]: url[url.index("<") + 1 : -1] 82 for url, rel in ( 83 link.split(";") for link in response.headers["link"].split(",") 84 ) 85 } 86 while "last" in pages and "next" in pages: 87 pages = { 88 rel[6:-1]: url[url.index("<") + 1 : -1] 89 for url, rel in ( 90 link.split(";") 91 for link in response.headers["link"].split(",") 92 ) 93 } 94 response = requests.get(pages["next"], auth=auth) 95 write_issues(response, csv_out) 96 if pages["next"] == pages["last"]: 97 break 98 99 100parser = argparse.ArgumentParser( 101 description="Write GitHub repository issues to CSV file." 102) 103 104parser.add_argument( 105 "username", nargs="?", help="GitHub user name, formatted as 'username'" 106) 107 108parser.add_argument( 109 "repositories", 110 nargs="+", 111 help="Repository names, formatted as 'basereponame/repo'", 112) 113 114parser.add_argument( 115 "--all", action="store_true", help="Returns both open and closed issues." 116) 117 118args = parser.parse_args() 119 120if args.all: 121 state = "all" 122 123username = args.username 124 125password = getpass.getpass("Enter your GitHub Password:") 126 127auth = (username, password) 128 129# To set the csv filename 130csvfilename = "" 131for repository in args.repositories: 132 csvfilename_temp = "{}".format(repository.replace("/", "-")) 133 csvfilename = csvfilename + csvfilename_temp 134csvfilename = csvfilename + "-issues.csv" 135with open(csvfilename, "w") as csvfileout: 136 csv_out = csv.writer(csvfileout) 137 csv_out.writerow( 138 [ 139 "Labels", 140 "Title", 141 "State", 142 "Open Date", 143 "Close Date", 144 "URL", 145 "Author", 146 "Assignees", 147 "Milestone", 148 ] 149 ) 150 for repository in args.repositories: 151 l_url = "https://api.github.com/repos/{}/issues?state={}" 152 l_url = l_url.format(repository, states) 153 response = requests.get(l_url, auth=auth) 154 write_issues(response, csv_out) 155 get_issues_from_github_to_csv(repository, response) 156csvfileout.close() 157