Skip to content

Commit bf6e7e0

Browse files
committed
Merge branch 'pr/459' into develop
2 parents dfbab2e + ea94c9b commit bf6e7e0

File tree

7 files changed

+78
-31
lines changed

7 files changed

+78
-31
lines changed

AUTHORS.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,5 @@ Contributors
106106
- Sourav Singh(@souravsingh)
107107

108108
- Matt Chung (@itsmemattchung)
109+
110+
- Martin Ek (@ekmartin)

docs/testing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ environment variables when running `py.test`, e.g.,
257257
.. code::
258258
259259
GH_AUTH="abc123" py.test
260-
GH_USER="sigmavirus24" GH_PASS="super-secure-password-plz-kthxbai" py.test
260+
GH_USER="sigmavirus24" GH_PASSWORD="super-secure-password-plz-kthxbai" py.test
261261
262262
If you are concerned that your credentials will be saved, you need not worry.
263263
Betamax sanitizes information like that before saving the cassette. It never

github3/github.py

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -363,25 +363,53 @@ def feeds(self):
363363
364364
:returns: dictionary parsed to include URITemplates
365365
"""
366-
url = self._build_url('feeds')
367-
json = self._json(self._get(url), 200)
368-
del json['ETag']
369-
del json['Last-Modified']
370-
371-
urls = [
372-
'timeline_url', 'user_url', 'current_user_public_url',
373-
'current_user_url', 'current_user_actor_url',
374-
'current_user_organization_url',
375-
]
376-
377-
for url in urls:
378-
json[url] = URITemplate(json[url])
366+
def replace_href(feed_dict):
367+
if not feed_dict:
368+
return feed_dict
369+
ret_dict = {}
370+
# Let's pluck out what we're most interested in, the href value
371+
href = feed_dict.pop('href', None)
372+
# Then we update the return dictionary with the rest of the values
373+
ret_dict.update(feed_dict)
374+
if href is not None:
375+
# So long as there is something to template, let's template it
376+
ret_dict['href'] = URITemplate(href)
377+
return ret_dict
379378

380-
links = json.get('_links', {})
381-
for d in links.values():
382-
d['href'] = URITemplate(d['href'])
383-
384-
return json
379+
url = self._build_url('feeds')
380+
json = self._json(self._get(url), 200, include_cache_info=False)
381+
if json is None: # If something went wrong, get out early
382+
return None
383+
384+
# We have a response body to parse
385+
feeds = {}
386+
387+
# Let's pop out the old links so we don't have to skip them below
388+
old_links = json.pop('_links', {})
389+
_links = {}
390+
# If _links is in the response JSON, iterate over that and recreate it
391+
# so that any templates contained inside can be turned into
392+
# URITemplates
393+
for key, value in old_links.items():
394+
if isinstance(value, list):
395+
# If it's an array/list of links, let's replace that with a
396+
# new list of links
397+
_links[key] = [replace_href(d) for d in value]
398+
else:
399+
# Otherwise, just use the new value
400+
_links[key] = replace_href(value)
401+
402+
# Start building up our return dictionary
403+
feeds['_links'] = _links
404+
405+
for key, value in json.items():
406+
# This should roughly be the same logic as above.
407+
if isinstance(value, list):
408+
feeds[key] = [URITemplate(v) for v in value]
409+
else:
410+
feeds[key] = URITemplate(value)
411+
412+
return feeds
385413

386414
@requires_auth
387415
def follow(self, username):

github3/models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,16 @@ def _instance_or_null(self, instance_class, json):
152152
except TypeError: # instance_class is not a subclass of GitHubCore
153153
return instance_class(json)
154154

155-
def _json(self, response, status_code):
155+
def _json(self, response, status_code, include_cache_info=True):
156156
ret = None
157157
if self._boolean(response, status_code, 404) and response.content:
158158
__logs__.info('Attempting to get JSON information from a Response '
159159
'with status code %d expecting %d',
160160
response.status_code, status_code)
161161
ret = response.json()
162162
headers = response.headers
163-
if ((headers.get('Last-Modified') or headers.get('ETag')) and
163+
if (include_cache_info and
164+
(headers.get('Last-Modified') or headers.get('ETag')) and
164165
isinstance(ret, dict)):
165166
ret['Last-Modified'] = response.headers.get(
166167
'Last-Modified', ''

tests/cassettes/GitHub_feeds.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"http_interactions": [{"request": {"body": "", "headers": {"Accept-Encoding": "gzip, deflate, compress", "Accept": "application/vnd.github.v3.full+json", "User-Agent": "github3.py/0.8.0", "Accept-Charset": "utf-8", "Content-Type": "application/json", "Authorization": "Basic <BASIC_AUTH>"}, "method": "GET", "uri": "https://api.github.com/feeds"}, "response": {"body": {"base64_string": "H4sIAAAAAAAAA7WS3W6EIBCF34XbNmJ/zFqTTR/FIDsoWRUCw6at4d2rNt0Yo2zZpHcQzpw5zHwDQdlBK3sonWlJQRpEbQtKa4mNqxKuOvqrII/EWTD7wmF69qOMO2Ogx3KWa1e1ku9XWVl37CKNs8+v69rdTMuiRBt5YQjvqM7QH08VHPI8fTrkbxlLU8iAifQlEyeRVYJzse7BOKrAp7Y6JXNNwlB1dzZVpma9/GIoVb8/mqXK0mG8erqdJypJOe77bEkxXJc/nRsD4iYA+KlhFDGtx53O6ek0hYePriX+h4+w15WRG04bCIWNVxhF2P/dN560iBjlDFV8mLtgjMm1pDAc7594DYb1/hvCfOv/xAQAAA==", "encoding": "utf-8"}, "headers": {"status": "200 OK", "x-ratelimit-remaining": "4995", "x-github-media-type": "github.v3; param=full; format=json", "x-content-type-options": "nosniff", "access-control-expose-headers": "ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes", "transfer-encoding": "chunked", "x-github-request-id": "451DE2C3:1D7B:505B77D:52883781", "content-encoding": "gzip", "vary": "Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding", "server": "GitHub.com", "cache-control": "private, max-age=60, s-maxage=60", "x-ratelimit-limit": "5000", "etag": "\"3bbafa6adae7e4ed162e5a704331adc8\"", "access-control-allow-credentials": "true", "date": "Sun, 17 Nov 2013 03:26:57 GMT", "access-control-allow-origin": "*", "content-type": "application/json; charset=utf-8", "x-ratelimit-reset": "1384660420"}, "url": "https://api.github.com/feeds", "status_code": 200}, "recorded_at": "2013-11-17T03:26:48"}], "recorded_with": "betamax"}
1+
{"recorded_with": "betamax/0.5.1", "http_interactions": [{"recorded_at": "2015-11-03T23:30:23", "response": {"url": "https://api.github.com/feeds", "headers": {"X-XSS-Protection": "1; mode=block", "X-RateLimit-Remaining": "4981", "X-GitHub-Request-Id": "54CA42F2:1AD5:EED2670:5639438F", "X-Frame-Options": "deny", "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", "Access-Control-Expose-Headers": "ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval", "Status": "200 OK", "Content-Security-Policy": "default-src 'none'", "Transfer-Encoding": "chunked", "X-RateLimit-Limit": "5000", "X-Served-By": "d594a23ec74671eba905bf91ef329026", "X-RateLimit-Reset": "1446595535", "X-GitHub-Media-Type": "github.v3; param=full; format=json", "X-Content-Type-Options": "nosniff", "Cache-Control": "private, max-age=60, s-maxage=60", "ETag": "W/\"9052b85649ffe4245d566403987a31e5\"", "Access-Control-Allow-Origin": "*", "Content-Type": "application/json; charset=utf-8", "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP", "Server": "GitHub.com", "Date": "Tue, 03 Nov 2015 23:30:23 GMT", "Access-Control-Allow-Credentials": "true", "Content-Encoding": "gzip"}, "status": {"code": 200, "message": "OK"}, "body": {"string": "", "encoding": "utf-8", "base64_string": "H4sIAAAAAAAAA72TYU/CMBCG/0u/yphCREJCDEJEBHEifEBDljIKa7Zuo+1gQPbfbQEXGGw4Tfy2dO/dvXf33AZwTJCNHaT71AYVYHLusYqqzjA3/XHecIn6rQA54DNEk4Ub+TsUMsOnFDlc38o9f2xjIzlqV6nIEeNSHw9PtBWLy3sULyBHechdcs9dCznV2muJkoZhl5dttuorbmfSXQ8HA2IFil9CXcVipeueM9SWtWo1Xhca3E3pNbG6DDvxUFtoynzZanYezVnw9tz7eLrtBi+QFNt+mgeXzqCD15Bj19kPMG4zLmGg8nluiYc6psphi5ebQnHKJvtdRxtInGTroV7o3ZUn3AyaWBs0+q/1eb3f0t4b1lEXoxzQBVKW8LKJ+JLfJkXTi4zxlYeECHqewGbbuipXehUQG4Q7BNNzRRheyHSG0vTEp6RmqJApdeIKfg5zBmv6lvVfGvwj71lsHjJ84FacxD6L5ONoq0fUi8tIQ/A/LyRyfJ7yURh+AZ4k23SZBQAA"}}, "request": {"headers": {"Authorization": "Basic <BASIC_AUTH>", "Accept": "application/vnd.github.v3.full+json", "Connection": "keep-alive", "Accept-Charset": "utf-8", "Accept-Encoding": "gzip, deflate", "User-Agent": "github3.py/1.0.0a2", "Content-Type": "application/json"}, "uri": "https://api.github.com/feeds", "body": {"string": "", "encoding": "utf-8"}, "method": "GET"}}]}

tests/integration/test_github.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,21 @@ def test_feeds(self):
102102
with self.recorder.use_cassette(cassette_name):
103103
feeds = self.gh.feeds()
104104

105-
for v in feeds['_links'].values():
106-
assert isinstance(v['href'], uritemplate.URITemplate)
107-
108-
# The processing on _links has been tested. Get rid of it.
109-
del feeds['_links']
110-
111-
# Test the rest of the response
112-
for v in feeds.values():
113-
assert isinstance(v, uritemplate.URITemplate)
105+
_links = feeds.pop('_links')
106+
107+
for urls in feeds.values():
108+
if not isinstance(urls, list):
109+
urls = [urls]
110+
for url in urls:
111+
assert isinstance(url, uritemplate.URITemplate)
112+
113+
for links in _links.values():
114+
if not isinstance(links, list):
115+
links = [links]
116+
for link in links:
117+
href = link.get('href')
118+
assert (href is None or
119+
isinstance(href, uritemplate.URITemplate))
114120

115121
def test_gist(self):
116122
"""Test the ability to retrieve a single gist."""

tests/unit/test_github.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ def test_emojis(self):
114114

115115
self.session.get.assert_called_once_with(url_for('emojis'))
116116

117+
def test_feeds(self):
118+
self.instance.feeds()
119+
120+
self.session.get.assert_called_once_with(url_for('feeds'))
121+
117122
def test_follow(self):
118123
"""Test the request to follow a user."""
119124
self.instance.follow('username')
@@ -867,6 +872,11 @@ def test_emails(self):
867872
with pytest.raises(AuthenticationFailed):
868873
self.instance.emails()
869874

875+
def test_feeds(self):
876+
"""Show that one needs to authenticate to use #feeds."""
877+
with pytest.raises(AuthenticationFailed):
878+
self.instance.feeds()
879+
870880
def test_follow(self):
871881
"""Show that one needs to authenticate to use #follow."""
872882
with pytest.raises(AuthenticationFailed):

0 commit comments

Comments
 (0)