Skip to content

Commit

Permalink
Refactor githubutils into a ServiceClient.
Browse files Browse the repository at this point in the history
Part of me hates replacing all these pure functions with a class, but I
find it much more readable, less repetitive, and more congruent with the
other services.
  • Loading branch information
ryneeverett committed May 12, 2016
1 parent 0ab5f31 commit bd7d866
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 103 deletions.
23 changes: 12 additions & 11 deletions bugwarrior/services/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from bugwarrior.config import asbool, die
from bugwarrior.services import IssueService, Issue

from . import githubutils
from .githubutils import GithubClient


class GithubIssue(Issue):
Expand Down Expand Up @@ -130,16 +130,17 @@ class GithubService(IssueService):
def __init__(self, *args, **kw):
super(GithubService, self).__init__(*args, **kw)

self.auth = {}

auth = {}
login = self.config_get('login')
token = self.config_get_default('token')
if self.config_has('token'):
token = self.config_get_password('token', login)
self.auth['token'] = token
auth['token'] = token
else:
password = self.config_get_password('password', login)
self.auth['basic'] = (login, password)
auth['basic'] = (login, password)

self.client = GithubClient(auth)

self.exclude_repos = []
if self.config_get_default('exclude_repos', None):
Expand Down Expand Up @@ -183,14 +184,14 @@ def get_service_metadata(self):
def get_owned_repo_issues(self, tag):
""" Grab all the issues """
issues = {}
for issue in githubutils.get_issues(*tag.split('/'), auth=self.auth):
for issue in self.client.get_issues(*tag.split('/')):
issues[issue['url']] = (tag, issue)
return issues

def get_involved_issues(self, user):
""" Grab all 'interesting' issues """
issues = {}
for issue in githubutils.get_involved_issues(user, auth=self.auth):
for issue in self.client.get_involved_issues(user):
url = issue['html_url']
tag = re.match('.*github\\.com/(.*)/(issues|pull)/[^/]*$', url)
if tag is None:
Expand All @@ -204,7 +205,7 @@ def get_directly_assigned_issues(self):
r'.*/repos/(?P<owner>[^/]+)/(?P<project>[^/]+)/.*'
)
issues = {}
for issue in githubutils.get_directly_assigned_issues(auth=self.auth):
for issue in self.client.get_directly_assigned_issues():
match_dict = project_matcher.match(issue['url']).groupdict()
issues[issue['url']] = (
'{owner}/{project}'.format(
Expand All @@ -216,7 +217,7 @@ def get_directly_assigned_issues(self):

def _comments(self, tag, number):
user, repo = tag.split('/')
return githubutils.get_comments(user, repo, number, auth=self.auth)
return self.client.get_comments(user, repo, number)

def annotations(self, tag, issue, issue_obj):
url = issue['html_url']
Expand All @@ -237,7 +238,7 @@ def _reqs(self, tag):
""" Grab all the pull requests """
return [
(tag, i) for i in
githubutils.get_pulls(*tag.split('/'), auth=self.auth)
self.client.get_pulls(*tag.split('/'))
]

def get_owner(self, issue):
Expand Down Expand Up @@ -265,7 +266,7 @@ def include(self, issue):
def issues(self):
user = self.config.get(self.target, 'github.username')

all_repos = githubutils.get_repos(username=user, auth=self.auth)
all_repos = self.client.get_repos(user)
assert(type(all_repos) == list)
repos = filter(self.filter_repos, all_repos)

Expand Down
145 changes: 53 additions & 92 deletions bugwarrior/services/githubutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import requests

from bugwarrior.services import ServiceClient


def _link_field_to_dict(field):
""" Utility for ripping apart github's Link header field.
Expand All @@ -22,111 +24,70 @@ def _link_field_to_dict(field):
])


def get_repos(username, auth):
""" username should be a string
auth should be a tuple of username and password.
item can be one of "repos" or "orgs"
"""

tmpl = "https://api.github.com/users/{username}/repos?per_page=100"
url = tmpl.format(username=username)
return _getter(url, auth)


def get_involved_issues(username, auth):
""" username should be a string
auth should be a tuple of username and password.
"""

tmpl = "https://api.github.com/search/issues?q=involves%3A{username}&per_page=100"
url = tmpl.format(username=username)
return _getter(url, auth, subkey='items')


def get_issues(username, repo, auth):
""" username and repo should be strings
auth should be a tuple of username and password.
"""

tmpl = "https://api.github.com/repos/{username}/{repo}/issues?per_page=100"
url = tmpl.format(username=username, repo=repo)
return _getter(url, auth)


def get_directly_assigned_issues(auth):
""" Returns all issues assigned to authenticated user.
This will return all issues assigned to the authenticated user
regardless of whether the user owns the repositories in which the
issues exist.
class GithubClient(ServiceClient):
def __init__(self, auth):
self.auth = auth

"""
url = "https://api.github.com/user/issues?per_page=100"
return _getter(url, auth)


def get_comments(username, repo, number, auth):
tmpl = "https://api.github.com/repos/{username}/{repo}/issues/" + \
"{number}/comments?per_page=100"
url = tmpl.format(username=username, repo=repo, number=number)
return _getter(url, auth)
def get_repos(self, username):
tmpl = "https://api.github.com/users/{username}/repos?per_page=100"
url = tmpl.format(username=username)
return self._getter(url)

def get_involved_issues(self, username):
tmpl = "https://api.github.com/search/issues?q=involves%3A{username}&per_page=100"
url = tmpl.format(username=username)
return self._getter(url, subkey='items')

def get_pulls(username, repo, auth):
""" username and repo should be strings
auth should be a tuple of username and password.
"""

tmpl = "https://api.github.com/repos/{username}/{repo}/pulls?per_page=100"
url = tmpl.format(username=username, repo=repo)
return _getter(url, auth)
def get_issues(self, username, repo):
tmpl = "https://api.github.com/repos/{username}/{repo}/issues?per_page=100"
url = tmpl.format(username=username, repo=repo)
return self._getter(url)

def get_directly_assigned_issues(self):
""" Returns all issues assigned to authenticated user.
def _getter(url, auth, subkey=None):
""" Pagination utility. Obnoxious. """
This will return all issues assigned to the authenticated user
regardless of whether the user owns the repositories in which the
issues exist.
"""
url = "https://api.github.com/user/issues?per_page=100"
return self._getter(url)

kwargs = {}
def get_comments(self, username, repo, number):
tmpl = "https://api.github.com/repos/{username}/{repo}/issues/" + \
"{number}/comments?per_page=100"
url = tmpl.format(username=username, repo=repo, number=number)
return self._getter(url)

if 'token' in auth:
kwargs['headers'] = {
'Authorization': 'token ' + auth['token']
}
elif 'basic' in auth:
kwargs['auth'] = auth['basic']
def get_pulls(self, username, repo):
tmpl = "https://api.github.com/repos/{username}/{repo}/pulls?per_page=100"
url = tmpl.format(username=username, repo=repo)
return self._getter(url)

results = []
link = dict(next=url)
while 'next' in link:
response = requests.get(link['next'], **kwargs)
def _getter(self, url, subkey=None):
""" Pagination utility. Obnoxious. """

if callable(response.json):
# Newer python-requests
json_res = response.json()
else:
# Older python-requests
json_res = response.json
kwargs = {}

# And.. if we didn't get good results, just bail.
if response.status_code != 200:
raise IOError(
"Non-200 status code %r; %r; %r" % (
response.status_code, url, json_res))
if 'token' in self.auth:
kwargs['headers'] = {
'Authorization': 'token ' + self.auth['token']
}
elif 'basic' in self.auth:
kwargs['auth'] = self.auth['basic']

if subkey is not None:
json_res = json_res[subkey]
results = []
link = dict(next=url)

results += json_res
while 'next' in link:
response = requests.get(link['next'], **kwargs)
json_res = self.json_response(response)

link = _link_field_to_dict(response.headers.get('link', None))
if subkey is not None:
json_res = json_res[subkey]

return results
results += json_res

if __name__ == '__main__':
# Little test.
import getpass
username = raw_input("GitHub Username: ")
password = getpass.getpass()
link = _link_field_to_dict(response.headers.get('link', None))

results = get_all(username, (username, password))
print len(results), "repos found."
return results

0 comments on commit bd7d866

Please sign in to comment.