Skip to content

Commit

Permalink
Merge pull request #666 from tjphopkins/new_api_codes
Browse files Browse the repository at this point in the history
Explicitly return api code when parsing error
  • Loading branch information
joshthecoder committed Nov 10, 2015
2 parents 89d8197 + 7f322d7 commit 7f23b99
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 10 deletions.
287 changes: 287 additions & 0 deletions cassettes/testfailure.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
{
"version": 1,
"interactions": [
{
"request": {
"method": "GET",
"uri": "https://api.twitter.com:443/1.1/direct_messages.json",
"headers": {
"Host": [
"api.twitter.com"
]
},
"body": null
},
"response": {
"status": {
"message": "Bad Request",
"code": 400
},
"headers": {
"x-response-time": [
"6"
],
"content-type": [
"application/json; charset=utf-8"
],
"server": [
"tsa_b"
],
"strict-transport-security": [
"max-age=631138519"
],
"set-cookie": [
"guest_id=v1%3A144655290597218733; Domain=.twitter.com; Path=/; Expires=Thu, 02-Nov-2017 12:15:05 UTC"
],
"date": [
"Tue, 03 Nov 2015 12:15:05 GMT"
],
"x-connection-hash": [
"bbad9c628533f023920a78c282b82a2e"
],
"content-length": [
"62"
]
},
"body": {
"string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
}
}
},
{
"request": {
"method": "GET",
"uri": "https://api.twitter.com:443/1.1/direct_messages.json",
"headers": {
"Cookie": [
"guest_id=v1%3A144655290597218733"
],
"Host": [
"api.twitter.com"
]
},
"body": null
},
"response": {
"status": {
"message": "Bad Request",
"code": 400
},
"headers": {
"x-response-time": [
"5"
],
"content-type": [
"application/json; charset=utf-8"
],
"server": [
"tsa_b"
],
"strict-transport-security": [
"max-age=631138519"
],
"date": [
"Tue, 03 Nov 2015 12:15:11 GMT"
],
"x-connection-hash": [
"bbad9c628533f023920a78c282b82a2e"
],
"content-length": [
"62"
]
},
"body": {
"string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
}
}
},
{
"request": {
"method": "GET",
"uri": "https://api.twitter.com:443/1.1/direct_messages.json",
"headers": {
"Cookie": [
"guest_id=v1%3A144655290597218733"
],
"Host": [
"api.twitter.com"
]
},
"body": null
},
"response": {
"status": {
"message": "Bad Request",
"code": 400
},
"headers": {
"x-response-time": [
"3"
],
"content-type": [
"application/json; charset=utf-8"
],
"server": [
"tsa_b"
],
"strict-transport-security": [
"max-age=631138519"
],
"date": [
"Tue, 03 Nov 2015 12:15:16 GMT"
],
"x-connection-hash": [
"bbad9c628533f023920a78c282b82a2e"
],
"content-length": [
"62"
]
},
"body": {
"string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
}
}
},
{
"request": {
"method": "GET",
"uri": "https://api.twitter.com:443/1.1/direct_messages.json",
"headers": {
"Host": [
"api.twitter.com"
]
},
"body": null
},
"response": {
"status": {
"message": "Bad Request",
"code": 400
},
"headers": {
"x-response-time": [
"4"
],
"content-type": [
"application/json; charset=utf-8"
],
"server": [
"tsa_b"
],
"strict-transport-security": [
"max-age=631138519"
],
"set-cookie": [
"guest_id=v1%3A144655293331152269; Domain=.twitter.com; Path=/; Expires=Thu, 02-Nov-2017 12:15:33 UTC"
],
"date": [
"Tue, 03 Nov 2015 12:15:33 GMT"
],
"x-connection-hash": [
"f3384743aac99980194e77031a6b9d66"
],
"content-length": [
"62"
]
},
"body": {
"string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
}
}
},
{
"request": {
"method": "GET",
"uri": "https://api.twitter.com:443/1.1/direct_messages.json",
"headers": {
"Cookie": [
"guest_id=v1%3A144655293331152269"
],
"Host": [
"api.twitter.com"
]
},
"body": null
},
"response": {
"status": {
"message": "Bad Request",
"code": 400
},
"headers": {
"x-response-time": [
"4"
],
"content-type": [
"application/json; charset=utf-8"
],
"server": [
"tsa_b"
],
"strict-transport-security": [
"max-age=631138519"
],
"date": [
"Tue, 03 Nov 2015 12:15:38 GMT"
],
"x-connection-hash": [
"f3384743aac99980194e77031a6b9d66"
],
"content-length": [
"62"
]
},
"body": {
"string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
}
}
},
{
"request": {
"method": "GET",
"uri": "https://api.twitter.com:443/1.1/direct_messages.json",
"headers": {
"Cookie": [
"guest_id=v1%3A144655293331152269"
],
"Host": [
"api.twitter.com"
]
},
"body": null
},
"response": {
"status": {
"message": "Bad Request",
"code": 400
},
"headers": {
"x-response-time": [
"6"
],
"content-type": [
"application/json; charset=utf-8"
],
"server": [
"tsa_b"
],
"strict-transport-security": [
"max-age=631138519"
],
"date": [
"Tue, 03 Nov 2015 12:15:43 GMT"
],
"x-connection-hash": [
"f3384743aac99980194e77031a6b9d66"
],
"content-length": [
"62"
]
},
"body": {
"string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
}
}
}
]
}
13 changes: 13 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import shutil
from time import sleep
import os
from ast import literal_eval

from nose import SkipTest

Expand Down Expand Up @@ -32,6 +33,18 @@ def testpickle(self):

class TweepyAPITests(TweepyTestCase):

@tape.use_cassette('testfailure.json')
def testapierror(self):
from tweepy.error import TweepError

with self.assertRaises(TweepError) as cm:
self.api.direct_messages()

reason, = literal_eval(cm.exception.reason)
self.assertEqual(reason['message'], 'Bad Authentication data.')
self.assertEqual(reason['code'], 215)
self.assertEqual(cm.exception.api_code, 215)

# TODO: Actually have some sort of better assertion
@tape.use_cassette('testgetoembed.json')
def testgetoembed(self):
Expand Down
6 changes: 4 additions & 2 deletions tweepy/binder.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,16 @@ def execute(self):
self.api.last_response = resp
if resp.status_code and not 200 <= resp.status_code < 300:
try:
error_msg = self.parser.parse_error(resp.text)
error_msg, api_error_code = \
self.parser.parse_error(resp.text)
except Exception:
error_msg = "Twitter error response: status code = %s" % resp.status_code
api_error_code = None

if is_rate_limit_error_message(error_msg):
raise RateLimitError(error_msg, resp)
else:
raise TweepError(error_msg, resp)
raise TweepError(error_msg, resp, api_code=api_error_code)

# Parse the response payload
result = self.parser.parse(self, resp.text)
Expand Down
5 changes: 4 additions & 1 deletion tweepy/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@
class TweepError(Exception):
"""Tweepy exception"""

def __init__(self, reason, response=None):
def __init__(self, reason, response=None, api_code=None):
self.reason = six.text_type(reason)
self.response = response
self.api_code = api_code
Exception.__init__(self, reason)

def __str__(self):
return self.reason


def is_rate_limit_error_message(message):
"""Check if the supplied error message belongs to a rate limit error."""
return isinstance(message, list) \
and len(message) > 0 \
and 'code' in message[0] \
and message[0]['code'] == 88


class RateLimitError(TweepError):
"""Exception for Tweepy hitting the rate limit."""
# RateLimitError has the exact same properties and inner workings
Expand Down
21 changes: 14 additions & 7 deletions tweepy/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def parse(self, method, payload):

def parse_error(self, payload):
"""
Parse the error message from payload.
If unable to parse the message, throw an exception
and default error message will be used.
Parse the error message and api error code from payload.
Return them as an (error_msg, error_code) tuple. If unable to parse the
message, throw an exception and default error message will be used.
"""
raise NotImplementedError

Expand Down Expand Up @@ -63,11 +63,18 @@ def parse(self, method, payload):
return json

def parse_error(self, payload):
error = self.json_lib.loads(payload)
if 'error' in error:
return error['error']
error_object = self.json_lib.loads(payload)

if 'error' in error_object:
reason = error_object['error']
api_code = error_object.get('code')
else:
return error['errors']
reason = error_object['errors']
api_code = [error.get('code') for error in
reason if error.get('code')]
api_code = api_code[0] if len(api_code) == 1 else api_code

return reason, api_code


class ModelParser(JSONParser):
Expand Down

0 comments on commit 7f23b99

Please sign in to comment.