From 7f322d76cf0761a591b3aa7e9db27fcc707a0236 Mon Sep 17 00:00:00 2001 From: tjphopkins Date: Tue, 3 Nov 2015 11:40:18 +0000 Subject: [PATCH] Explicitly return api code when parsing error --- cassettes/testfailure.json | 287 +++++++++++++++++++++++++++++++++++++ tests/test_api.py | 13 ++ tweepy/binder.py | 6 +- tweepy/error.py | 5 +- tweepy/parsers.py | 21 ++- 5 files changed, 322 insertions(+), 10 deletions(-) create mode 100644 cassettes/testfailure.json diff --git a/cassettes/testfailure.json b/cassettes/testfailure.json new file mode 100644 index 0000000..96e29e5 --- /dev/null +++ b/cassettes/testfailure.json @@ -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.\"}]}" + } + } + } + ] +} diff --git a/tests/test_api.py b/tests/test_api.py index 6216022..e38c4a0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3,6 +3,7 @@ import random import shutil from time import sleep import os +from ast import literal_eval from nose import SkipTest @@ -32,6 +33,18 @@ class TweepyErrorTests(unittest.TestCase): 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): diff --git a/tweepy/binder.py b/tweepy/binder.py index ba55570..b96e4d3 100644 --- a/tweepy/binder.py +++ b/tweepy/binder.py @@ -217,14 +217,16 @@ def bind_api(**config): 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) diff --git a/tweepy/error.py b/tweepy/error.py index 7827029..f7d5894 100644 --- a/tweepy/error.py +++ b/tweepy/error.py @@ -9,14 +9,16 @@ import six 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) \ @@ -24,6 +26,7 @@ def is_rate_limit_error_message(message): 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 diff --git a/tweepy/parsers.py b/tweepy/parsers.py index 869fc62..6381912 100644 --- a/tweepy/parsers.py +++ b/tweepy/parsers.py @@ -21,9 +21,9 @@ class Parser(object): 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 @@ -63,11 +63,18 @@ class JSONParser(Parser): 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): -- 2.25.1