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