diff --git a/ghapi/__init__.py b/ghapi/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/ghapi/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/ghapi/api.py b/ghapi/api.py new file mode 100644 index 0000000..ec43902 --- /dev/null +++ b/ghapi/api.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +import urlparse +from django.http import QueryDict +from ghapi.linkheader import parse_link_value +import requests + +def get_next_page(response): + link = response.headers.get('link', None) + if not link: + return None + output = parse_link_value(link) + for url, info in output.items(): + if info.get('rel', None) == 'next': + return QueryDict(urlparse.urlparse(url).query)['page'] + return None + +class GitHub(object): + def __init__(self, token=None): + headers = {} + if token: + headers['Authorization'] = 'token %s' % token + self.session = requests.session(headers=headers) + + def get(self, path, params=None): + """ + Gets a resource, eg 'users/ojii'. + + Returns tuple (jsondata, response) + """ + if params is None: + params = {} + params['per_page'] = 100 + response = self.session.get('https://api.github.com/%s' % path, params=params) + response.raise_for_status() + return response.json, response + + def get_iter(self, path, params=None): + """ + Returns an iterator over a resource, eg 'repos/divio/django-cms/watchers' that automatically handles + pagination. + """ + data, response = self.get(path, params) + for thing in data: + yield thing + next_page = get_next_page(response) + if next_page: + params['page'] = next_page + for thing in self.get(path, params): + yield thing diff --git a/ghapi/linkheader.py b/ghapi/linkheader.py new file mode 100644 index 0000000..ec049d7 --- /dev/null +++ b/ghapi/linkheader.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +import re + +TOKEN = r'(?:[^\(\)<>@,;:\\"/\[\]\?={} \t]+?)' +QUOTED_STRING = r'(?:"(?:\\"|[^"])*")' +PARAMETER = r'(?:%(TOKEN)s(?:=(?:%(TOKEN)s|%(QUOTED_STRING)s))?)' % locals() +LINK = r'<[^>]*>\s*(?:;\s*%(PARAMETER)s?\s*)*' % locals() +COMMA = r'(?:\s*(?:,\s*)+)' +LINK_SPLIT = r'%s(?=%s|\s*$)' % (LINK, COMMA) + +def _unquotestring(instr): + if instr[0] == instr[-1] == '"': + instr = instr[1:-1] + instr = re.sub(r'\\(.)', r'\1', instr) + return instr +def _splitstring(instr, item, split): + if not instr: + return [] + return [ h.strip() for h in re.findall(r'%s(?=%s|\s*$)' % (item, split), instr)] + +link_splitter = re.compile(LINK_SPLIT) + +def parse_link_value(instr): + """ + Given a link-value (i.e., after separating the header-value on commas), + return a dictionary whose keys are link URLs and values are dictionaries + of the parameters for their associated links. + + Note that internationalised parameters (e.g., title*) are + NOT percent-decoded. + + Also, only the last instance of a given parameter will be included. + + For example, + + >>> parse_link_value('; rel="self"; title*=utf-8\'de\'letztes%20Kapitel') + {'/foo': {'title*': "utf-8'de'letztes%20Kapitel", 'rel': 'self'}} + + """ + out = {} + if not instr: + return out + for link in [h.strip() for h in link_splitter.findall(instr)]: + url, params = link.split(">", 1) + url = url[1:] + param_dict = {} + for param in _splitstring(params, PARAMETER, "\s*;\s*"): + try: + a, v = param.split("=", 1) + param_dict[a.lower()] = _unquotestring(v) + except ValueError: + param_dict[param.lower()] = None + out[url] = param_dict + return out diff --git a/ghapi/middleware.py b/ghapi/middleware.py new file mode 100644 index 0000000..06f89d1 --- /dev/null +++ b/ghapi/middleware.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from django.utils.functional import SimpleLazyObject +from ghapi.api import GitHub +from social_auth.db.django_models import UserSocialAuth + + +def get_github(request): + if not request.user.is_authenticated(): + return GitHub() + try: + social = UserSocialAuth.objects.get(provider='github', user_id=request.user.pk) + except UserSocialAuth.DoesNotExist: + return GitHub() + token = social.tokens.get('access_token', None) + return GitHub(token) + + +class GithubAPIMiddleware(object): + def process_request(self, request): + request.github = SimpleLazyObject(lambda : get_github(request)) + diff --git a/settings.py b/settings.py index 0613f02..23a7373 100644 --- a/settings.py +++ b/settings.py @@ -41,6 +41,7 @@ MIDDLEWARE_CLASSES = [ 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', + 'ghapi.middleware.GithubAPIMiddleware', ] TEMPLATE_CONTEXT_PROCESSORS = [