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