Merge pull request #118 from codl/subclass-api-errors

Subclass api errors
This commit is contained in:
Lorenz Diener 2018-01-29 11:18:37 +01:00 cometido por GitHub
commit 42b1d8fa58
No se encontró ninguna clave conocida en la base de datos para esta firma
ID de clave GPG: 4AEE18F83AFDEB23
S'han modificat 4 arxius amb 137 adicions i 33 eliminacions

Veure arxiu

@ -1550,33 +1550,45 @@ class Mastodon:
print('response headers: ' + str(response_object.headers)) print('response headers: ' + str(response_object.headers))
print('Response text content: ' + str(response_object.text)) print('Response text content: ' + str(response_object.text))
if response_object.status_code == 404: if not response_object.ok:
try: try:
response = response_object.json() response = response_object.json(object_hook=self.__json_hooks)
except: if not isinstance(response, dict) or 'error' not in response:
raise MastodonAPIError('Endpoint not found.') error_msg = None
error_msg = response['error']
except ValueError:
error_msg = None
if isinstance(response, dict) and 'error' in response: # Handle rate limiting
raise MastodonAPIError("Mastodon API returned error: " + str(response['error'])) if response_object.status_code == 429:
if self.ratelimit_method == 'throw' or not do_ratelimiting:
raise MastodonRatelimitError('Hit rate limit.')
elif self.ratelimit_method in ('wait', 'pace'):
to_next = self.ratelimit_reset - time.time()
if to_next > 0:
# As a precaution, never sleep longer than 5 minutes
to_next = min(to_next, 5 * 60)
time.sleep(to_next)
request_complete = False
continue
if response_object.status_code == 404:
ex_type = MastodonNotFoundError
if not error_msg:
error_msg = 'Endpoint not found.'
# this is for compatibility with older versions
# which raised MastodonAPIError('Endpoint not found.')
# on any 404
elif response_object.status_code == 401:
ex_type = MastodonUnauthorizedError
else: else:
raise MastodonAPIError('Endpoint not found.') ex_type = MastodonAPIError
raise ex_type(
if response_object.status_code == 500: 'Mastodon API returned error',
raise MastodonAPIError('General API problem.') response_object.status_code,
response_object.reason,
# Handle rate limiting error_msg)
if response_object.status_code == 429:
if self.ratelimit_method == 'throw' or not do_ratelimiting:
raise MastodonRatelimitError('Hit rate limit.')
elif self.ratelimit_method in ('wait', 'pace'):
to_next = self.ratelimit_reset - time.time()
if to_next > 0:
# As a precaution, never sleep longer than 5 minutes
to_next = min(to_next, 5 * 60)
time.sleep(to_next)
request_complete = False
continue
try: try:
response = response_object.json(object_hook=self.__json_hooks) response = response_object.json(object_hook=self.__json_hooks)
@ -1586,12 +1598,6 @@ class Mastodon:
"bad json content was '%s'" % (response_object.status_code, "bad json content was '%s'" % (response_object.status_code,
response_object.content)) response_object.content))
# See if the returned dict is an error dict even though status is 200
if isinstance(response, dict) and 'error' in response:
if not isinstance(response['error'], six.string_types):
response['error'] = six.text_type(response['error'])
raise MastodonAPIError("Mastodon API returned error: " + response['error'])
# Parse link headers # Parse link headers
if isinstance(response, list) and \ if isinstance(response, list) and \
'Link' in response_object.headers and \ 'Link' in response_object.headers and \
@ -1801,6 +1807,16 @@ class MastodonAPIError(MastodonError):
"""Raised when the mastodon API generates a response that cannot be handled""" """Raised when the mastodon API generates a response that cannot be handled"""
pass pass
class MastodonNotFoundError(MastodonAPIError):
"""Raised when the mastodon API returns a 404 Not Found error"""
pass
class MastodonUnauthorizedError(MastodonAPIError):
"""Raised when the mastodon API returns a 401 Unauthorized error
This happens when an OAuth token is invalid or has been revoked."""
pass
class MastodonRatelimitError(MastodonError): class MastodonRatelimitError(MastodonError):
"""Raised when rate limiting is set to manual mode and the rate limit is exceeded""" """Raised when rate limiting is set to manual mode and the rate limit is exceeded"""

Veure arxiu

@ -0,0 +1,82 @@
interactions:
- request:
body: visibility=&status=Toot%21
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
Connection: [keep-alive]
Content-Length: ['26']
Content-Type: [application/x-www-form-urlencoded]
User-Agent: [python-requests/2.18.4]
method: POST
uri: http://localhost:3000/api/v1/statuses
response:
body: {string: '{"id":"99285482671609362","created_at":"2018-01-03T10:43:57.160Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"private","language":"ja","uri":"http://localhost:3000/users/mastodonpy_test/statuses/99285482671609362","content":"\u003cp\u003eToot!\u003c/p\u003e","url":"http://localhost:3000/@mastodonpy_test/99285482671609362","reblogs_count":0,"favourites_count":0,"favourited":false,"reblogged":false,"muted":false,"reblog":null,"application":{"name":"Mastodon.py
test suite","website":null},"account":{"id":"1234567890123456","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":true,"created_at":"2018-01-03T11:24:32.957Z","note":"\u003cp\u003e\u003c/p\u003e","url":"http://localhost:3000/@mastodonpy_test","avatar":"http://localhost:3000/avatars/original/missing.png","avatar_static":"http://localhost:3000/avatars/original/missing.png","header":"http://localhost:3000/headers/original/missing.png","header_static":"http://localhost:3000/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":1},"media_attachments":[],"mentions":[],"tags":[],"emojis":[]}'}
headers:
Cache-Control: ['max-age=0, private, must-revalidate']
Content-Type: [application/json; charset=utf-8]
ETag: [W/"d9b57bb0592371b00e98fbc0f44a8fc9"]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Frame-Options: [SAMEORIGIN]
X-Request-Id: [d7a9df07-1a3c-4784-adc5-b67bd6347614]
X-Runtime: ['0.301984']
X-XSS-Protection: [1; mode=block]
content-length: ['1175']
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
User-Agent: [python-requests/2.18.4]
method: GET
uri: http://localhost:3000/api/v1/timelines/home
response:
body: {string: '{"error":"The access token is invalid"}'}
headers:
Cache-Control: [no-store]
Content-Type: [application/json; charset=utf-8]
Pragma: [no-cache]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
WWW-Authenticate: ['Bearer realm="Doorkeeper", error="invalid_token", error_description="The
access token is invalid"']
X-Content-Type-Options: [nosniff]
X-Frame-Options: [SAMEORIGIN]
X-Request-Id: [dc45d4f4-c203-4b28-ad27-f0db32912a16]
X-Runtime: ['0.010224']
X-XSS-Protection: [1; mode=block]
content-length: ['39']
status: {code: 401, message: Unauthorized}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
Connection: [keep-alive]
Content-Length: ['0']
User-Agent: [python-requests/2.18.4]
method: DELETE
uri: http://localhost:3000/api/v1/statuses/99285482671609362
response:
body: {string: '{}'}
headers:
Cache-Control: ['max-age=0, private, must-revalidate']
Content-Type: [application/json; charset=utf-8]
ETag: [W/"8ca371aea536ee2c56c8d13b43824703"]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Frame-Options: [SAMEORIGIN]
X-Request-Id: [ddbd4335-1aeb-42af-8dea-fa78a787609f]
X-Runtime: ['0.017701']
X-XSS-Protection: [1; mode=block]
content-length: ['2']
status: {code: 200, message: OK}
version: 1

Veure arxiu

@ -1,6 +1,5 @@
import pytest import pytest
from mastodon.Mastodon import MastodonAPIError from mastodon.Mastodon import MastodonAPIError, MastodonNotFoundError
from time import sleep
@pytest.mark.vcr() @pytest.mark.vcr()
def test_status(status, api): def test_status(status, api):
@ -14,7 +13,7 @@ def test_status_empty(api):
@pytest.mark.vcr() @pytest.mark.vcr()
def test_status_missing(api): def test_status_missing(api):
with pytest.raises(MastodonAPIError): with pytest.raises(MastodonNotFoundError):
api.status(0) api.status(0)
@pytest.mark.skip(reason="Doesn't look like mastodon will make a card for an url that doesn't have a TLD, and relying on some external website being reachable to make a card of is messy :/") @pytest.mark.skip(reason="Doesn't look like mastodon will make a card for an url that doesn't have a TLD, and relying on some external website being reachable to make a card of is messy :/")

Veure arxiu

@ -1,5 +1,7 @@
import pytest import pytest
from mastodon.Mastodon import MastodonAPIError, MastodonIllegalArgumentError from mastodon.Mastodon import MastodonAPIError,\
MastodonIllegalArgumentError,\
MastodonUnauthorizedError
@pytest.mark.vcr() @pytest.mark.vcr()
def test_public_tl_anonymous(api_anonymous, status): def test_public_tl_anonymous(api_anonymous, status):
@ -17,6 +19,11 @@ def test_public_tl(api, status):
assert status['id'] in map(lambda st: st['id'], public) assert status['id'] in map(lambda st: st['id'], public)
assert status['id'] in map(lambda st: st['id'], local) assert status['id'] in map(lambda st: st['id'], local)
@pytest.mark.vcr()
def test_unauthed_home_tl_throws(api_anonymous, status):
with pytest.raises(MastodonUnauthorizedError):
api_anonymous.timeline_home()
@pytest.mark.vcr() @pytest.mark.vcr()
def test_home_tl(api, status): def test_home_tl(api, status):
tl = api.timeline_home() tl = api.timeline_home()