Merge pull request #118 from codl/subclass-api-errors
Subclass api errors
This commit is contained in:
commit
42b1d8fa58
S'han modificat 4 arxius amb 137 adicions i 33 eliminacions
|
@ -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"""
|
||||||
|
|
82
tests/cassettes/test_unauthed_home_tl_throws.yaml
Normal file
82
tests/cassettes/test_unauthed_home_tl_throws.yaml
Normal file
|
@ -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
|
|
@ -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 :/")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Referencia en una nova incidència