From c23698e64f3244636290ffa4065f3b70ba5151e9 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 29 Oct 2011 16:11:27 +0200 Subject: Wip on handlers --- github3/exceptions.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 github3/exceptions.py (limited to 'github3/exceptions.py') diff --git a/github3/exceptions.py b/github3/exceptions.py new file mode 100644 index 0000000..72da776 --- /dev/null +++ b/github3/exceptions.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina + +class AnomUser(Exception): + """ Exception for AnomUser handler """ + pass -- cgit v1.2.3-59-g8ed1b From bb540725ce15dcafd5ac5e5bb93e5a232ff4f33a Mon Sep 17 00:00:00 2001 From: David Medina Date: Fri, 4 Nov 2011 01:21:50 +0100 Subject: Refactor base to handler design --- github3/__init__.py | 2 +- github3/api.py | 255 +++++++++++------------------------------------ github3/config.py | 4 +- github3/errors.py | 34 +++++++ github3/exceptions.py | 6 ++ github3/handlers/base.py | 8 ++ github3/handlers/user.py | 7 ++ 7 files changed, 116 insertions(+), 200 deletions(-) create mode 100644 github3/errors.py (limited to 'github3/exceptions.py') diff --git a/github3/__init__.py b/github3/__init__.py index c2fdbad..a99ea00 100644 --- a/github3/__init__.py +++ b/github3/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -from core import * \ No newline at end of file +#from core import * diff --git a/github3/api.py b/github3/api.py index 8b2e6e8..3376bea 100644 --- a/github3/api.py +++ b/github3/api.py @@ -1,207 +1,68 @@ -# -*- coding: utf-8 -*- - -""" -github3.api -~~~~~~~~~~~ - -This module provies the core GitHub3 API interface. -""" - -from urlparse import urlparse, parse_qs +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina import requests -from decorator import decorator - -from .packages import omnijson as json -from .packages.link_header import parse_link_value - -from .helpers import is_collection, to_python, to_api, get_scope -from .config import settings -import handlers +import json +from errors import GithubError +import github3.exceptions as ghexceptions - -PAGING_SIZE = 100 +RESOURCES_PER_PAGE = 100 class GithubCore(object): + """ Wrapper for requests """ - _rate_limit = None - _rate_limit_remaining = None + requests_remaining = None def __init__(self): self.session = requests.session() - self.session.params = {'per_page': PAGING_SIZE} - - - @staticmethod - def _resource_serialize(o): - """Returns JSON serialization of given object.""" - return json.dumps(o) - - - @staticmethod - def _resource_deserialize(s): - """Returns dict deserialization of a given JSON string.""" - + self.session.params = {'per_page': RESOURCES_PER_PAGE} + self._parser = json + + #@paginate to slice a generator after + def get(self, request, **kwargs): + response = self._request('GET', request, **kwargs) + return self._parser.loads(response.content) + + def head(self, request, **kwargs): + return self._request('HEAD', request, **kwargs).headers + + def post(self, request, data=None, **kwargs): + kwargs['data'] = self._parser.dumps(data) + response = self._request('POST', request, **kwargs) + assert response.status_code == 201 + return self._parser.loads(response.content) + + def patch(self, request, data=None, **kwargs): + kwargs['data'] = self._parser.dumps(data) + response = self._request('PATCH', request, **kwargs) + assert response.status_code == 200 + return self._parser.loads(response.content) + + def put(self, request, **kwargs): + response = self._request('PUT', request, **kwargs) + assert response.status_code == 204 + + def bool(self, request, **kwargs): try: - return json.loads(s) - except ValueError: - raise ResponseError('The API Response was not valid.') - - - @staticmethod - def _generate_url(endpoint): - """Generates proper endpoint URL.""" - - if is_collection(endpoint): - resource = map(str, endpoint) - resource = '/'.join(resource) - else: - resource = endpoint - - return (settings.base_url + resource) - - - def _requests_post_hook(self, r): - """Post-processing for HTTP response objects.""" - - self._rate_limit = int(r.headers.get('x-ratelimit-limit', -1)) - self._rate_limit_remaining = int(r.headers.get('x-ratelimit-remaining', -1)) - - return r - - - def _http_resource(self, verb, endpoint, params=None, check_status=True, **etc): - - url = self._generate_url(endpoint) - args = (verb, url) - - if params: - kwargs = {'params': params} - kwargs.update(etc) - else: - kwargs = etc - - r = self.session.request(*args, **kwargs) - r = self._requests_post_hook(r) - - if check_status: - r.raise_for_status() - - return r - - - def _get_resource(self, resource, obj, **kwargs): - - r = self._http_resource('GET', resource, params=kwargs) - item = self._resource_deserialize(r.content) - - return obj.new_from_dict(item, gh=self) - - def _patch_resource(self, resource, data, **kwargs): - r = self._http_resource('PATCH', resource, data=data, params=kwargs) - msg = self._resource_deserialize(r.content) - - return msg - - - @staticmethod - def _total_pages_from_header(link_header): - - if link_header is None: - return link_header - - page_info = {} - - for link in link_header.split(','): - - uri, meta = map(str.strip, link.split(';')) - - # Strip <>'s - uri = uri[1:-1] - - # Get query params from header. - q = parse_qs(urlparse(uri).query) - meta = meta[5:-1] - - page_info[meta] = q - - try: - return int(page_info['last']['page'].pop()) - except KeyError: - return True - - def _get_resources(self, resource, obj, limit=None, **kwargs): - - if limit is not None: - assert limit > 0 - - moar = True - is_truncated = (limit > PAGING_SIZE) or (limit is None) - r_count = 0 - page = 1 - - while moar: - - if not is_truncated: - kwargs['per_page'] = limit - moar = False - else: - kwargs['page'] = page - if limit: - if (limit - r_count) < PAGING_SIZE: - kwargs['per_page'] = (limit - r_count) - moar = False - - r = self._http_resource('GET', resource, params=kwargs) - max_page = self._total_pages_from_header(r.headers['link']) - - if (max_page is True) or (max_page is None): - moar = False - - d_items = self._resource_deserialize(r.content) - - for item in d_items: - if (r_count < limit) or (limit is None): - r_count += 1 - yield obj.new_from_dict(item, gh=self) - else: - moar = False - - page += 1 - - def _get_bool(self, resource): - resp = self._http_resource('GET', resource, check_status=False) - return True if resp.status_code == 204 else False - - def _get_raw(self, resource): - resp = self._http_resource('GET', resource) - return self._resource_deserialize(resp.content) - - def _to_map(self, obj, iterable): - """Maps given dict iterable to a given Resource object.""" - - a = list() - - for it in iterable: - a.append(obj.new_from_dict(it, rdd=self)) - - return a - -class Github(GithubCore): - """docstring for Github""" - - def __init__(self): - super(Github, self).__init__() - self.is_authenticated = False - - def user_handler(self, username=None, **kwargs): - if kwargs.get('force') or not getattr(self, '_user_handler', False): - if kwargs.get('private'): - self._user_handler = handlers.AuthUser(self) - else: - self._user_handler = handlers.User(self, username) - return self._user_handler - -class ResponseError(Exception): - """The API Response was unexpected.""" - + response = self._request('GET', request, **kwargs) + except ghexceptions.NotFound: + return False + assert response.status_code == 204 + return True + + def delete(self, request, **kwargs): + response = self._request('DELETE', request, **kwargs) + assert response.status_code == 204 + + def _request(self, verb, request, **kwargs): + + request = settings.base_url + request + response = self.session.request(verb, request, **kwargs) + self.requests_remaining = response.headers.get( + 'x-ratelimit-remaining',-1) + error = GithubError(response) + error.process() + + return response diff --git a/github3/config.py b/github3/config.py index 9fbf305..6fec55f 100644 --- a/github3/config.py +++ b/github3/config.py @@ -54,5 +54,5 @@ class Settings(object): return object.__getattribute__(self, key) settings = Settings() -settings.verbose = False -settings.base_url = 'https://api.github.com/' \ No newline at end of file +settings.verbose = True +settings.base_url = 'https://api.github.com/' diff --git a/github3/errors.py b/github3/errors.py new file mode 100644 index 0000000..6932360 --- /dev/null +++ b/github3/errors.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina + +import json +import github3.exceptions as exceptions + +class GithubError(object): + """ Handler for API errors """ + + def __init__(self, response): + self._parser = json + self.status_code = response.status_code + self.debug = self._parser.loads(response.content) + + def error_400(self): + return exceptions.BadRequest("400 - %s" % self.debug.get('message')) + + def error_404(self) + return exceptions.NotFound("404 - %s" % self.debug.get('message')) + + def error_422(self): + errors = self.debug.get('errors') + if errors: + errors = ['{resource}: {code} => {field}'.format(**error) + for error in errors] + return exceptions.UnprocessableEntity( + '422 - %s %s' % (self.debug.get('message'), errors)) + + def process(self): + raise_error = getattr(self, 'error_%s' % self.status_code, False) + if raise_error: + raise raise_error() diff --git a/github3/exceptions.py b/github3/exceptions.py index 72da776..b0894a9 100644 --- a/github3/exceptions.py +++ b/github3/exceptions.py @@ -3,6 +3,12 @@ # # author: David Medina +class BadRequest(Exception): + pass +class UnprocessableEntity(Exception): + pass +class NotFound(Exception): + pass class AnomUser(Exception): """ Exception for AnomUser handler """ pass diff --git a/github3/handlers/base.py b/github3/handlers/base.py index 92cd73a..8f79cdd 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -31,3 +31,11 @@ class Handler(object): map_model = kwargs.get('model', self._model) return self._gh._get_resources(url, map_model, **kwargs) + def _post_raw(self, *args, **kwargs): + url = self._extend_url(*args) + return self._gh._post_raw(url, **kwargs) + + def _delete_raw(self, *args, **kwargs): + url = self._extend_url(*args) + return self._gh._delete_raw(url, **kwargs) + diff --git a/github3/handlers/user.py b/github3/handlers/user.py index 5f5d130..5018aa1 100644 --- a/github3/handlers/user.py +++ b/github3/handlers/user.py @@ -72,3 +72,10 @@ class AuthUser(User): def get_key(self, key_id): return self._get_resource('keys', key_id, model=models.Key) + def post_emails(self, *emails): + emails_parsed = map(str, emails) + return self._post_raw('emails', data=emails_parsed) + + def delete_emails(self, *emails): + emails_parsed = map(str, emails) + return self._delete_raw('emails', data=emails_parsed) -- cgit v1.2.3-59-g8ed1b