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