| George Keishing | e7e9171 | 2021-09-03 11:28:44 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python3 | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 2 |  | 
 | 3 | r""" | 
 | 4 | Exports issues from a list of repositories to individual CSV files. | 
 | 5 | Uses basic authentication (GitHub username + password) to retrieve issues | 
 | 6 | from a repository that username has access to. Supports GitHub API v3. | 
 | 7 | """ | 
 | 8 | import argparse | 
 | 9 | import csv | 
| Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 10 | import getpass | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 11 |  | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 12 | import requests | 
 | 13 |  | 
 | 14 | auth = None | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 15 | states = "all" | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 16 |  | 
 | 17 |  | 
 | 18 | def write_issues(response, csv_out): | 
 | 19 |     r""" | 
 | 20 |     Parses JSON response and writes to CSV. | 
 | 21 |     """ | 
| vinaybs6 | 65dbeaa | 2019-09-19 01:46:49 -0500 | [diff] [blame] | 22 |     print(response) | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 23 |     if response.status_code != 200: | 
 | 24 |         raise Exception(response.status_code) | 
 | 25 |     for issue in response.json(): | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 26 |         if "pull_request" not in issue: | 
 | 27 |             labels = ", ".join([lable["name"] for lable in issue["labels"]]) | 
| Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 28 |  | 
| Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 29 |             # Below lines to overcome "TypeError: 'NoneType' object has | 
 | 30 |             # no attribute '__getitem__'" | 
 | 31 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 32 |             close_date = issue.get("closed_at") | 
| Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 33 |             if close_date: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 34 |                 close_date = issue.get("closed_at").split("T")[0] | 
| Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 35 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 36 |             assignee_resp = issue.get("assignees", "Not Assigned") | 
| Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 37 |             if assignee_resp: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 38 |                 owners = ",".join( | 
 | 39 |                     [ | 
 | 40 |                         assignee_login["login"] | 
 | 41 |                         for assignee_login in assignee_resp | 
 | 42 |                     ] | 
 | 43 |                 ) | 
| Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 44 |             else: | 
| Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 45 |                 owners = "Not Assigned" | 
 | 46 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 47 |             milestone_resp = issue.get("milestone", "Not Assigned") | 
| Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 48 |             if milestone_resp: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 49 |                 milestone_resp = milestone_resp["title"].encode("utf-8") | 
| Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 50 |  | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 51 |             # Change the following line to write out additional fields | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 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 |             ) | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 65 |  | 
 | 66 |  | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 67 | def get_issues_from_github_to_csv(name, response): | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 68 |     r""" | 
 | 69 |     Requests issues from GitHub API and writes to CSV file. | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 70 |     Description of argument(s): | 
 | 71 |     name  Name of the GitHub repository | 
 | 72 |     response  GitHub repository response | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 73 |     """ | 
| vinaybs6 | 65dbeaa | 2019-09-19 01:46:49 -0500 | [diff] [blame] | 74 |     print(name) | 
 | 75 |     print(states) | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 76 |  | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 77 |     # Multiple requests are required if response is paged | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 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) | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 94 |             write_issues(response, csv_out) | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 95 |             if pages["next"] == pages["last"]: | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 96 |                 break | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 97 |  | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 98 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 99 | parser = argparse.ArgumentParser( | 
 | 100 |     description="Write GitHub repository issues to CSV file." | 
 | 101 | ) | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 102 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 103 | parser.add_argument( | 
 | 104 |     "username", nargs="?", help="GitHub user name, formatted as 'username'" | 
 | 105 | ) | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 106 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 107 | parser.add_argument( | 
 | 108 |     "repositories", | 
 | 109 |     nargs="+", | 
 | 110 |     help="Repository names, formatted as 'basereponame/repo'", | 
 | 111 | ) | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 112 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 113 | parser.add_argument( | 
 | 114 |     "--all", action="store_true", help="Returns both open and closed issues." | 
 | 115 | ) | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 116 |  | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 117 | args = parser.parse_args() | 
 | 118 |  | 
 | 119 | if args.all: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 120 |     state = "all" | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 121 |  | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 122 | username = args.username | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 123 |  | 
| Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 124 | password = getpass.getpass("Enter your GitHub Password:") | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 125 |  | 
 | 126 | auth = (username, password) | 
 | 127 |  | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 128 | # To set the csv filename | 
 | 129 | csvfilename = "" | 
| Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 130 | for repository in args.repositories: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 131 |     csvfilename_temp = "{}".format(repository.replace("/", "-")) | 
| Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 132 |     csvfilename = csvfilename + csvfilename_temp | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 133 | csvfilename = csvfilename + "-issues.csv" | 
 | 134 | with open(csvfilename, "w") as csvfileout: | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 135 |     csv_out = csv.writer(csvfileout) | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 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 |     ) | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 149 |     for repository in args.repositories: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 150 |         l_url = "https://api.github.com/repos/{}/issues?state={}" | 
| Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 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) | 
 | 155 | csvfileout.close() |