Skip to content

Commit ed34830

Browse files
committed
New design. Merge develop branch
1 parent 27fc6ac commit ed34830

File tree

28 files changed

+868
-2215
lines changed

28 files changed

+868
-2215
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.py?
2+
*.swp

README.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
Fork
2+
======================================
3+
Refactor and complete api wrapper. Intensive work in progress
4+
15
Github3: Python wrapper for the (new) GitHub API v3
26
===================================================
37

@@ -75,4 +79,4 @@ Roadmap
7579
- Sphinx Documetnation
7680
- Examples
7781
- Unittests
78-
- OAuth Last (how?)
82+
- OAuth Last (how?)

github3/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
# -*- coding: utf-8 -*-
2-
3-
from core import *

github3/api.py

Lines changed: 106 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1,223 +1,123 @@
1-
# -*- coding: utf-8 -*-
2-
3-
"""
4-
github3.api
5-
~~~~~~~~~~~
6-
7-
This module provies the core GitHub3 API interface.
8-
"""
9-
10-
from urlparse import urlparse, parse_qs
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
#
4+
# author: David Medina
115

126
import requests
13-
from decorator import decorator
14-
15-
from .packages import omnijson as json
16-
from .packages.link_header import parse_link_value
17-
18-
from .models import *
19-
from .helpers import is_collection, to_python, to_api, get_scope
20-
from .config import settings
7+
import json
8+
from errors import GithubError
219

22-
23-
24-
25-
PAGING_SIZE = 100
10+
RESOURCES_PER_PAGE = 100
2611

2712
class GithubCore(object):
13+
"""
14+
Wrapper to github api requests
15+
16+
Methods: get, head, post, patch, put, delete
17+
"""
2818

29-
_rate_limit = None
30-
_rate_limit_remaining = None
19+
requests_remaining = None
20+
base_url = 'https://api.github.com/'
3121

3222
def __init__(self):
23+
"""
24+
Init `requests.session`
25+
Init JSON parser
26+
"""
3327
self.session = requests.session()
34-
self.session.params = {'per_page': PAGING_SIZE}
35-
36-
37-
@staticmethod
38-
def _resource_serialize(o):
39-
"""Returns JSON serialization of given object."""
40-
return json.dumps(o)
41-
42-
43-
@staticmethod
44-
def _resource_deserialize(s):
45-
"""Returns dict deserialization of a given JSON string."""
46-
47-
try:
48-
return json.loads(s)
49-
except ValueError:
50-
raise ResponseError('The API Response was not valid.')
51-
52-
53-
@staticmethod
54-
def _generate_url(endpoint):
55-
"""Generates proper endpoint URL."""
56-
57-
if is_collection(endpoint):
58-
resource = map(str, endpoint)
59-
resource = '/'.join(endpoint)
28+
self.session.params = {'per_page': RESOURCES_PER_PAGE}
29+
self._parser = json
30+
31+
def get(self, request, paginate=False, **kwargs):
32+
"""
33+
GET request
34+
35+
:param paginate: Boolean to return link header to paginate
36+
"""
37+
response = self._request('GET', request, **kwargs)
38+
content = self._parser.loads(response.content)
39+
if paginate:
40+
return response.headers.get('link'), content
6041
else:
61-
resource = endpoint
62-
63-
return (settings.base_url + resource)
64-
65-
66-
def _requests_post_hook(self, r):
67-
"""Post-processing for HTTP response objects."""
68-
69-
self._ratelimit = int(r.headers.get('x-ratelimit-limit', -1))
70-
self._ratelimit_remaining = int(r.headers.get('x-ratelimit-remaining', -1))
71-
72-
return r
73-
74-
75-
def _http_resource(self, verb, endpoint, params=None, check_status=True, **etc):
76-
77-
url = self._generate_url(endpoint)
78-
args = (verb, url)
79-
80-
if params:
81-
kwargs = {'params': params}
82-
kwargs.update(etc)
42+
return content
43+
44+
def head(self, request, **kwargs):
45+
""" HEAD request """
46+
return self._request('HEAD', request, **kwargs).headers
47+
48+
def post(self, request, data=None, **kwargs):
49+
"""
50+
POST request
51+
52+
:param data: raw python object to send
53+
"""
54+
kwargs['data'] = self._parser.dumps(data)
55+
response = self._request('POST', request, **kwargs)
56+
assert response.status_code == 201
57+
return self._parser.loads(response.content)
58+
59+
def patch(self, request, data=None, **kwargs):
60+
"""
61+
PATCH request
62+
63+
:param data: raw python object to send
64+
"""
65+
kwargs['data'] = self._parser.dumps(data)
66+
response = self._request('PATCH', request, **kwargs)
67+
assert response.status_code == 200
68+
return self._parser.loads(response.content)
69+
70+
def put(self, request, **kwargs):
71+
""" PUT request """
72+
response = self._request('PUT', request, **kwargs)
73+
assert response.status_code == 204
74+
75+
def delete(self, request, **kwargs):
76+
""" DELETE request """
77+
response = self._request('DELETE', request, **kwargs)
78+
assert response.status_code == 204
79+
80+
def _parse_args(self, request_args):
81+
"""
82+
Arg's parser to `_request` method
83+
84+
It check keyword args to parse extra request args to params
85+
Sample:
86+
_parse_args(arg1=1, arg2=2) => params = {'arg1': 1, 'arg2': 2}
87+
"""
88+
request_core = (
89+
'params','data','headers','cookies','files','auth','tiemout',
90+
'allow_redirects','proxies','return_response','config')
91+
request_params = request_args.get('params')
92+
extra_params = {}
93+
for k, v in request_args.items():
94+
if k in request_core: continue
95+
extra_params.update({k: v})
96+
del request_args[k]
97+
if request_params:
98+
request_args['params'].update(extra_params)
8399
else:
84-
kwargs = etc
85-
86-
r = self.session.request(*args, **kwargs)
87-
r = self._requests_post_hook(r)
88-
89-
if check_status:
90-
r.raise_for_status()
91-
92-
return r
93-
94-
95-
def _get_resource(self, resource, obj, **kwargs):
96-
97-
r = self._http_resource('GET', resource, params=kwargs)
98-
item = self._resource_deserialize(r.content)
99-
100-
return obj.new_from_dict(item, gh=self)
101-
102-
def _patch_resource(self, resource, data, **kwargs):
103-
r = self._http_resource('PATCH', resource, data=data, params=kwargs)
104-
msg = self._resource_deserialize(r.content)
105-
106-
return msg
107-
100+
request_args['params'] = extra_params
108101

109-
@staticmethod
110-
def _total_pages_from_header(link_header):
102+
return request_args
111103

112-
if link_header is None:
113-
return link_header
114-
115-
page_info = {}
116-
117-
for link in link_header.split(','):
118-
119-
uri, meta = map(str.strip, link.split(';'))
120-
121-
# Strip <>'s
122-
uri = uri[1:-1]
123-
124-
# Get query params from header.
125-
q = parse_qs(urlparse(uri).query)
126-
meta = meta[5:-1]
127-
128-
page_info[meta] = q
129-
130-
try:
131-
return int(page_info['last']['page'].pop())
132-
except KeyError:
133-
return True
134-
135-
def _get_resources(self, resource, obj, limit=None, **kwargs):
136-
137-
if limit is not None:
138-
assert limit > 0
139-
140-
moar = True
141-
is_truncated = (limit > PAGING_SIZE) or (limit is None)
142-
r_count = 0
143-
page = 1
144-
145-
while moar:
146-
147-
if not is_truncated:
148-
kwargs['per_page'] = limit
149-
moar = False
150-
else:
151-
kwargs['page'] = page
152-
if limit:
153-
if (limit - r_count) < PAGING_SIZE:
154-
kwargs['per_page'] = (limit - r_count)
155-
moar = False
156-
157-
r = self._http_resource('GET', resource, params=kwargs)
158-
max_page = self._total_pages_from_header(r.headers['link'])
159-
160-
if (max_page is True) or (max_page is None):
161-
moar = False
162-
163-
d_items = self._resource_deserialize(r.content)
164-
165-
for item in d_items:
166-
if (r_count < limit) or (limit is None):
167-
r_count += 1
168-
yield obj.new_from_dict(item, gh=self)
169-
else:
170-
moar = False
171-
172-
page += 1
173-
174-
175-
def _to_map(self, obj, iterable):
176-
"""Maps given dict iterable to a given Resource object."""
177-
178-
a = list()
179-
180-
for it in iterable:
181-
a.append(obj.new_from_dict(it, rdd=self))
182-
183-
return a
184-
185-
def _get_url(self, resource):
186-
187-
if is_collection(resource):
188-
resource = map(str, resource)
189-
resource = '/'.join(resource)
190-
191-
return resource
104+
def _request(self, verb, request, **kwargs):
105+
"""
106+
Http request wrapper
192107
108+
:param verb: Http method
109+
:param request : Url query request
110+
:param kwargs: Keyword args to request
111+
"""
112+
request = self.base_url + request
113+
parsed_args = self._parse_args(kwargs)
114+
response = self.session.request(verb, request, **parsed_args)
115+
self.requests_remaining = response.headers.get(
116+
'x-ratelimit-remaining',-1)
117+
error = GithubError(response)
118+
error.process()
193119

120+
return response
194121

195122
class Github(GithubCore):
196-
"""docstring for Github"""
197-
198-
def __init__(self):
199-
super(Github, self).__init__()
200-
self.is_authenticated = False
201-
202-
203-
def get_user(self, username):
204-
"""Get a single user."""
205-
return self._get_resource(('users', username), User)
206-
207-
208-
def get_me(self):
209-
"""Get the authenticated user."""
210-
return self._get_resource(('user'), CurrentUser)
211-
212-
def get_repo(self, username, reponame):
213-
"""Get the given repo."""
214-
return self._get_resource(('repos', username, reponame), Repo)
215-
216-
def get_org(self, login):
217-
"""Get organization."""
218-
return self._get_resource(('orgs', login), Org)
219-
220-
221-
class ResponseError(Exception):
222-
"""The API Response was unexpected."""
223-
123+
pass

0 commit comments

Comments
 (0)