8000 Merge pull request #353 from sigmavirus24/bug/310 · staticdev/github4.py@1271a48 · GitHub
[go: up one dir, main page]

Skip to content
This repository was archived by the owner on May 22, 2021. It is now read-only.

Commit 1271a48

Browse files
committed
Merge pull request sigmavirus24#353 from sigmavirus24/bug/310
Raise an exception if json is not the expected type
2 parents e49183b + 504e9fc commit 1271a48

File tree

6 files changed

+58
-32
lines changed

6 files changed

+58
-32
lines changed

github3/exceptions.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44

55
class GitHubError(Exception):
6-
76
"""The base exception class."""
87

98
def __init__(self, resp):
@@ -35,76 +34,74 @@ def message(self):
3534
return self.msg
3635

3736

38-
class BadRequest(GitHubError):
37+
class UnprocessableResponseBody(GitHubError):
38+
"""Exception class for response objects that cannot be handled."""
39+
def __init__(self, message, body):
40+
Exception.__init__(self, message)
41+
self.body = body
42+
self.msg = message
43+
44+
def __repr__(self):
45+
return '<{0} [{1}]>'.format('UnprocessableResponseBody', self.body)
46+
47+
def __str__(self):
48+
return self.message
3949

40-
"""Exception class for 400 responses."""
4150

51+
class BadRequest(GitHubError):
52+
"""Exception class for 400 responses."""
4253
pass
4354

4455

4556
class AuthenticationFailed(GitHubError):
46-
4757
"""Exception class for 401 responses.
4858
4959
Possible reasons:
5060
5161
- Need one time password (for two-factor authentication)
5262
- You are not authorized to access the resource
5363
"""
54-
5564
pass
5665

5766

5867
class ForbiddenError(GitHubError):
59-
6068
"""Exception class for 403 responses.
6169
6270
Possible reasons:
6371
6472
- Too many requests (you've exceeded the ratelimit)
6573
- Too many login failures
6674
"""
67-
6875
pass
6976

7077

7178
class NotFoundError(GitHubError):
72-
7379
"""Exception class for 404 responses."""
74-
7580
pass
7681

7782

7883
class MethodNotAllowed(GitHubError):
79-
8084
"""Exception class for 405 responses."""
81-
8285
pass
8386

8487

8588
class NotAcceptable(GitHubError):
86-
8789
"""Exception class for 406 responses."""
88-
8990
pass
9091

9192

9293
class UnprocessableEntity(GitHubError):
93-
9494
"""Exception class for 422 responses."""
95-
9695
pass
9796

9897

9998
class ClientError(GitHubError):
100-
10199
"""Catch-all for 400 responses that aren't specific errors."""
100+
pass
102101

103102

104103
class ServerError(GitHubError):
105-
106104
"""Exception class for 5xx responses."""
107-
108105
pass
109106

110107

github3/models.py

Lines changed: 6 additions & 2 deletions
< 10000 td data-grid-cell-id="diff-aed85c0fffe76e433f22d3037b326ed27e0a949100a43a9dc5773bc56256ac44-17-17-2" data-line-anchor="diff-aed85c0fffe76e433f22d3037b326ed27e0a949100a43a9dc5773bc56256ac44L17" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-deletionLine-bgColor, var(--diffBlob-deletion-bgColor-line));padding-right:24px" tabindex="-1" valign="top" class="focusable-grid-cell diff-text-cell left-side-diff-cell border-right left-side">-
from .exceptions import error_for
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
from datetime import datetime
1414
from logging import getLogger
1515

16+
from . import exceptions
1617
from .decorators import requires_auth
17
1818
from .null import NullObject
1919
from .session import GitHubSession
2020
from .utils import UTC
@@ -143,6 +143,10 @@ def _remove_none(data):
143143
def _instance_or_null(self, instance_class, json):
144144
if json is None:
145145
return NullObject(instance_class.__name__)
146+
if not isinstance(json, dict):
147+
return exceptions.UnprocessableResponseBody(
148+
"GitHub's API returned a body that could not be handled", json
149+
)
146150
try:
147151
return instance_class(json, self)
148152
except TypeError: # instance_class is not a subclass of GitHubCore
@@ -171,7 +175,7 @@ def _boolean(self, response, true_code, false_code):
171175
if status_code == true_code:
172176
return True
173177
if status_code != false_code and status_code >= 400:
174-
raise error_for(response)
178+
raise exceptions.error_for(response)
175179
return False
176180

177181
def _delete(self, url, **kwargs):

github3/structs.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# -*- coding: utf-8 -*-
2-
from collections import Iterator
3-
from .models import GitHubCore
2+
import collections
3+
import functools
4+
45
from requests.compat import urlparse, urlencode
56

7+
from . import exceptions
8+
from . import models
9+
610

7-
class GitHubIterator(GitHubCore, Iterator):
11+
class GitHubIterator(models.GitHubCore, collections.Iterator):
812
"""The :class:`GitHubIterator` class powers all of the iter_* methods."""
913
def __init__(self, count, url, cls, session, params=None, etag=None,
1014
headers=None):
11-
GitHubCore.__init__(self, {}, session)
15+
models.GitHubCore.__init__(self, {}, session)
1216
#: Original number of items requested
1317
self.original = count
1418
#: Number of items left in the iterator
@@ -45,7 +49,7 @@ def _repr(self):
4549
return '<GitHubIterator [{0}, {1}]>'.format(self.count, self.path)
4650

4751
def __iter__(self):
48-
self.last_url, params, cls = self.url, self.params, self.cls
52+
self.last_url, params = self.url, self.params
4953
headers = self.headers
5054

5155
if 0 < self.count <= 100 and self.count != -1:
@@ -54,6 +58,10 @@ def __iter__(self):
5458
if 'per_page' not in params and self.count == -1:
5559
params['per_page'] = 100
5660

61+
cls = self.cls
62+
if issubclass(self.cls, models.GitHubCore):
63+
cls = functools.partial(self.cls, session=self)
64+
5765
while (self.count == -1 or self.count > 0) and self.last_url:
5866
response = self._get(self.last_url, params=params,
5967
headers=headers)
@@ -72,14 +80,19 @@ def __iter__(self):
7280

7381
# languages returns a single dict. We want the items.
7482
if isinstance(json, dict):
83+
if issubclass(self.cls, models.GitHubObject):
84+
raise exceptions.UnprocessableResponseBody(
85+
"GitHub's API returned a body that could not be"
86+
" handled", json
87+
)
7588
if json.get('ETag'):
7689
del json['ETag']
7790
if json.get('Last-Modified'):
7891
del json['Last-Modified']
7992
json = json.items()
8093

8194
for i in json:
82-
yield cls(i, self) if issubclass(cls, GitHubCore) else cls(i)
95+
yield cls(i)
8396
self.count -= 1 if self.count > 0 else 0
8497
if self.count == 0:
8598
break
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"recorded_with": "betamax/0.4.1", "http_interactions": [{"recorded_at": "2015-02-22T04:23:56", "request": {"headers": {"Content-Type": "application/json", "Accept-Charset": "utf-8", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive", "User-Agent": "github3.py/1.0.0a1", "Accept": "application/vnd.github.v3.full+json"}, "body": {"encoding": "utf-8", "string": ""}, "method": "GET", "uri": "https://api.github.com/repos/sigmavirus24/github3.py"}, "response": {"headers": {"Access-Control-Allow-Origin": "*", "X-Content-Type-Options": "nosniff", "Access-Control-Allow-Credentials": "true", "Content-Type": "application/json; charset=utf-8", "Transfer-Encoding": "chunked", "Date": "Sun, 22 Feb 2015 04:23:56 GMT", "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", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Media-Type": "github.v3; param=full; format=json", "Status": "200 OK", "ETag": "W/\"07acb8446729c2cedb1aa44279995ef3\"", "Cache-Control": "public, max-age=60, s-maxage=60", "X-XSS-Protection": "1; mode=block", "X-Frame-Options": "deny", "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", "X-RateLimit-Reset": "1424580854", "Content-Encoding": "gzip", "Server": "GitHub.com", "X-RateLimit-Limit": "60", "X-GitHub-Request-Id": "451DE374:0FF4:16646F2B:54E959DC", "X-RateLimit-Remaining": "43", "Vary": "Accept, Accept-Encoding", "X-Served-By": "474556b853193c38f1b14328ce2d1b7d", "Last-Modified": "Sun, 22 Feb 2015 02:56:55 GMT"}, "body": {"base64_string": "H4sIAAAAAAAAA62YTZPiNhCG/wrlaxgEZgizrkrt7inJbQ+bSy6UbAtbNbbkkmQoxjX/Pa8sf5IKDKNcKDDqR69a3XK3moCnQbTdb9b7zWYZCFqyIAoybvI63q6qS7AMjnVRHLo/NM9KeuKq1uEzmY2SZ8FUEDVBITMuwJgOBcVOEz6vX7brZUBP1FB1qFWBcbkxlY4IcQ/1ylFrzVQihWHCrBJZkpo446+n37agZapjWGyAB1esinccZwyYJleCclMWVxLc1K3J1eCjLAp5BuVa9L2JyGBpPdlSuMg+SYFlQ6TJGXyHJb1bR3BtHhfVWjXYQG0OPLUcjQ1RLH1YWGcHWXb/3xuiWCVbYB3rRPHKcCkeFzizBk2qjAr+Rj9Hg7UGxEp7XEprBWt2Qiw+bu7MGlIpfqLJxbpGsYTxE5z9SeSVPYjmUtm0/QtBYV3PDTvQtLRpeKSFZu/LoJ3eYFD7YIms+2j0z9M8ZcOuYsIfF5NLsSh4rKi6LI5SLThyVh1pglhdnHGMLBCui9+5+aOOF99//Hmy2Ytxr4OSm5nbOn+WjHM5lnRnT24ikJ4AQNIru3hxrH1D8NnlU4JUp7FU1Mh7h8ZtgTNQQ6Y/bSwZRksv4S0AoFxKP0+2AIC41jX7UGjfXnjL0aTPH1GXsTvyPpI1t9GOAK1U45wXjHl5cIA0pD+VkQ4iyf2wPaMh7lu72zTzkmrtgYkLGXtx8KIkLaQhOqfuPWQOvuos1TJmUMWO3lItY4Aa5bnfrUwLGZB4CRpsvZfOnkGazqMFFVlNMz/qAMGu21d1Rt/uFjG3c2ekAGkrNMXj2v+QGzlWqasdkO9+Lh0xI7QtSG6XOXccMClsWheUJb9XF9wmdohZ2P8PWBun12j7+34Zc1+uZTRkPJPdod/Rfbzbnfq9zukcXTvgFRI9gzS/VNTk9uTCVBVVzEd0hyBNTFFsrVarJme0LatLpjwz2BGAoirJUTX66Gx6Bqqekpq2Wj9amSmq90LS1Mu3AwRAt40+Wh1huv8V+lAvgS1gSix5wbSRwu+MHSlTtpCGH3nykY7ldrrNQM1XzUXClrQolohawxOOOEatbXcRBSfz85AjYBm4BnCdSsEQ0l5eV8wxGuI6zUQxNCLpgRo0EOF6Ez6tt0+b7c/Nl2j3Eu22f2MldZXOxuye1uFTGP5ch9Hu12i3s2OqWucTzHTIPlqHdghOwC4E8Q1XDPjEtca/+vtJS2FvDWCodT4afhvNov+4/+jMkgKxdBX0H5/zdP1aum8KqbksWYUyobtJGVa5rS4reDpF+5XKRK/QAxO7Mv6GoS/7zX5WECSyFtiPcP+8DM7UoHbFq3f6sC8khqbPTk31waVpEBlV264ST8ZjYPLwzF/52HtilJWsezPXxXXTfQlxbHKlZHdBJJC1uAComOgmG3Thvsq1b5G1mYzAQvBfv45uWSk70rowB1dNYx0p2oBCVliIYOaMPrAHW9q0BOn9sHv/B529Yv8uEwAA", "encoding": "utf-8", "string": ""}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/sigmavirus24/github3.py"}}, {"recorded_at": "2015-02-22T04:23:56", "request": {"headers": {"Content-Type": "application/json", "Accept-Charset": "utf-8", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive", "User-Agent": "github3.py/1.0.0a1", "Accept": "application/vnd.github.v3.full+json"}, "body": {"encoding": "utf-8", "string": ""}, "method": "GET", "uri": "https://api.github.com/repos/sigmavirus24/github3.py/git/refs/heads/develop?per_page=100"}, "response": {"headers": {"Access-Control-Allow-Origin": "*", "Content-Security-Policy": "default-src 'none'", "X-Content-Type-Options": "nosniff", "Access-Control-Allow-Credentials": "true", "Content-Type": "application/json; charset=utf-8", "Transfer-Encoding": "chunked", "Date": "Sun, 22 Feb 2015 04:23:56 GMT", "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", "X-Frame-Options": "deny", "X-GitHub-Media-Type": "github.v3; param=full; format=json", "Status": "200 OK", "ETag": "W/\"f148066c5c6c3ba6ca6116fed0a94fb0\"", "Cache-Control": "public, max-age=60, s-maxage=60", "X-XSS-Protection": "1; mode=block", "X-Poll-Interval": "300", "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", "X-RateLimit-Reset": "1424580854", "Content-Encoding": "gzip", "Server": "GitHub.com", "X-RateLimit-Limit": "60", "X-GitHub-Request-Id": "451DE374:0FF4:16646F65:54E959DC", "X-RateLimit-Remaining": "42", "Vary": "Accept, Accept-Encoding", "X-Served-By": "d594a23ec74671eba905bf91ef329026", "Last-Modified": "Sun, 22 Feb 2015 02:56:55 GMT"}, "body": {"base64_string": "H4sIAAAAAAAAA6WOyw6DIBBF/4V146BgffwNyFRoNBAGTIzx34tx20WTbiaT3DtnzsEivth4TQKLyhAY3HDxgT1YjkuJbEqBRgAVXDW7ZLOuJr9CxOAJyM2r2lzM1Ei4U1GF/VpL4wvT6zdOiY0HI6sKHeVQ90J3WjWGSyOQy7ZW7TRgU3MueK+wH9pnV3TSHrBclOerS//r3RyCnw3O8wNTw7SJLgEAAA==", "encoding": "utf-8", "string": ""}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/sigmavirus24/github3.py/git/refs/heads/develop?per_page=100"}}]}

tests/integration/test_repos_repo.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
"""Integration tests for Repositories."""
22
import github3
3+
import github3.exceptions as exc
34

4-
from .helper import IntegrationHelper
5+
import pytest
56

7+
from . import helper
68

7-
class TestRepository(IntegrationHelper):
9+
10+
class TestRepository(helper.IntegrationHelper):
811

912
"""Integration tests for the Repository object."""
1013

@@ -421,6 +424,15 @@ def test_refs(self):
421424
for ref in references:
422425
assert isinstance(ref, github3.git.Reference)
423426

427+
def test_refs_raises_unprocessable_exception(self):
428+
"""Verify github3.exceptions.UnprocessableResponseBody is raised."""
429+
cassette_name = self.cassette_name('invalid_refs')
430+
with self.recorder.use_cassette(cassette_name):
431+
repository = self.gh.repository('sigmavirus24', 'github3.py')
432+
assert repository is not None
433+
with pytest.raises(exc.UnprocessableResponseBody):
434+
list(repository.refs('heads/develop'))
435+
424436
def test_stargazers(self):
425437
"""Test the ability to retrieve the stargazers on a repository."""
426438
cassette_name = self.cassette_name('stargazers')

tests/unit/test_structs.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
class TestGitHubIterator(UnitHelper):
66
described_class = GitHubIterator
77

8-
def setUp(self):
9-
super(TestGitHubIterator, self).setUp()
10-
self.count = -1
11-
self.cls = object
8+
def after_setup(self):
9+
self.count = self.instance.count = -1
10+
self.cls = self.instance.cls = object
1211

1312
def create_instance_of_described_class(self):
1413
self.url = 'https://api.github.com/users'

0 commit comments

Comments
 (0)
0