diff --git a/bugwarrior/services/github.py b/bugwarrior/services/github.py index b239e0a1c..1595df871 100644 --- a/bugwarrior/services/github.py +++ b/bugwarrior/services/github.py @@ -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): @@ -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): @@ -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: @@ -204,7 +205,7 @@ def get_directly_assigned_issues(self): r'.*/repos/(?P[^/]+)/(?P[^/]+)/.*' ) 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( @@ -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'] @@ -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): @@ -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) diff --git a/bugwarrior/services/githubutils.py b/bugwarrior/services/githubutils.py index c67a074e4..15feed7ad 100644 --- a/bugwarrior/services/githubutils.py +++ b/bugwarrior/services/githubutils.py @@ -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. @@ -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