| #!/usr/bin/env python3 |
| |
| r""" |
| Exports issues from a list of repositories to individual CSV files. |
| Uses basic authentication (GitHub username + password) to retrieve issues |
| from a repository that username has access to. Supports GitHub API v3. |
| """ |
| import argparse |
| import csv |
| import getpass |
| |
| import requests |
| |
| auth = None |
| states = "all" |
| |
| |
| def write_issues(response, csv_out): |
| r""" |
| Parses JSON response and writes to CSV. |
| """ |
| print(response) |
| if response.status_code != 200: |
| raise Exception(response.status_code) |
| for issue in response.json(): |
| if "pull_request" not in issue: |
| labels = ", ".join([lable["name"] for lable in issue["labels"]]) |
| |
| # Below lines to overcome "TypeError: 'NoneType' object has |
| # no attribute '__getitem__'" |
| |
| close_date = issue.get("closed_at") |
| if close_date: |
| close_date = issue.get("closed_at").split("T")[0] |
| |
| assignee_resp = issue.get("assignees", "Not Assigned") |
| if assignee_resp: |
| owners = ",".join( |
| [ |
| assignee_login["login"] |
| for assignee_login in assignee_resp |
| ] |
| ) |
| else: |
| owners = "Not Assigned" |
| |
| milestone_resp = issue.get("milestone", "Not Assigned") |
| if milestone_resp: |
| milestone_resp = milestone_resp["title"].encode("utf-8") |
| |
| # Change the following line to write out additional fields |
| csv_out.writerow( |
| [ |
| labels.encode("utf-8"), |
| issue.get("title").encode("utf-8"), |
| issue.get("state").encode("utf-8"), |
| issue.get("created_at").split("T")[0], |
| close_date, |
| issue.get("html_url").encode("utf-8"), |
| issue.get("user").get("login").encode("utf-8"), |
| owners, |
| milestone_resp, |
| ] |
| ) |
| |
| |
| def get_issues_from_github_to_csv(name, response): |
| r""" |
| Requests issues from GitHub API and writes to CSV file. |
| Description of argument(s): |
| name Name of the GitHub repository |
| response GitHub repository response |
| """ |
| print(name) |
| print(states) |
| |
| # Multiple requests are required if response is paged |
| if "link" in response.headers: |
| pages = { |
| rel[6:-1]: url[url.index("<") + 1 : -1] |
| for url, rel in ( |
| link.split(";") for link in response.headers["link"].split(",") |
| ) |
| } |
| while "last" in pages and "next" in pages: |
| pages = { |
| rel[6:-1]: url[url.index("<") + 1 : -1] |
| for url, rel in ( |
| link.split(";") |
| for link in response.headers["link"].split(",") |
| ) |
| } |
| response = requests.get(pages["next"], auth=auth) |
| write_issues(response, csv_out) |
| if pages["next"] == pages["last"]: |
| break |
| |
| |
| parser = argparse.ArgumentParser( |
| description="Write GitHub repository issues to CSV file." |
| ) |
| |
| parser.add_argument( |
| "username", nargs="?", help="GitHub user name, formatted as 'username'" |
| ) |
| |
| parser.add_argument( |
| "repositories", |
| nargs="+", |
| help="Repository names, formatted as 'basereponame/repo'", |
| ) |
| |
| parser.add_argument( |
| "--all", action="store_true", help="Returns both open and closed issues." |
| ) |
| |
| args = parser.parse_args() |
| |
| if args.all: |
| state = "all" |
| |
| username = args.username |
| |
| password = getpass.getpass("Enter your GitHub Password:") |
| |
| auth = (username, password) |
| |
| # To set the csv filename |
| csvfilename = "" |
| for repository in args.repositories: |
| csvfilename_temp = "{}".format(repository.replace("/", "-")) |
| csvfilename = csvfilename + csvfilename_temp |
| csvfilename = csvfilename + "-issues.csv" |
| with open(csvfilename, "w") as csvfileout: |
| csv_out = csv.writer(csvfileout) |
| csv_out.writerow( |
| [ |
| "Labels", |
| "Title", |
| "State", |
| "Open Date", |
| "Close Date", |
| "URL", |
| "Author", |
| "Assignees", |
| "Milestone", |
| ] |
| ) |
| for repository in args.repositories: |
| l_url = "https://api.github.com/repos/{}/issues?state={}" |
| l_url = l_url.format(repository, states) |
| response = requests.get(l_url, auth=auth) |
| write_issues(response, csv_out) |
| get_issues_from_github_to_csv(repository, response) |
| csvfileout.close() |