aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenz Diener <[email protected]>2018-01-29 11:18:37 +0100
committerGitHub <[email protected]>2018-01-29 11:18:37 +0100
commit42b1d8fa5895f416853311db220cf9412ad8fd13 (patch)
treecc8615955cba00aa7ba224733e3371379bbfb855
parent1d54c35101a6c349e457e7b708e29db15242a139 (diff)
parent56e6bac9cb19101346cb42b73628290740889ecf (diff)
downloadmastodon.py-42b1d8fa5895f416853311db220cf9412ad8fd13.tar.gz
Merge pull request #118 from codl/subclass-api-errors
Subclass api errors
-rw-r--r--mastodon/Mastodon.py78
-rw-r--r--tests/cassettes/test_unauthed_home_tl_throws.yaml82
-rw-r--r--tests/test_status.py5
-rw-r--r--tests/test_timeline.py9
4 files changed, 139 insertions, 35 deletions
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py
index 28ad9d8..5aca185 100644
--- a/mastodon/Mastodon.py
+++ b/mastodon/Mastodon.py
@@ -1550,33 +1550,45 @@ class Mastodon:
1550 print('response headers: ' + str(response_object.headers)) 1550 print('response headers: ' + str(response_object.headers))
1551 print('Response text content: ' + str(response_object.text)) 1551 print('Response text content: ' + str(response_object.text))
1552 1552
1553 if response_object.status_code == 404: 1553 if not response_object.ok:
1554 try: 1554 try:
1555 response = response_object.json() 1555 response = response_object.json(object_hook=self.__json_hooks)
1556 except: 1556 if not isinstance(response, dict) or 'error' not in response:
1557 raise MastodonAPIError('Endpoint not found.') 1557 error_msg = None
1558 1558 error_msg = response['error']
1559 if isinstance(response, dict) and 'error' in response: 1559 except ValueError:
1560 raise MastodonAPIError("Mastodon API returned error: " + str(response['error'])) 1560 error_msg = None
1561
1562 # Handle rate limiting
1563 if response_object.status_code == 429:
1564 if self.ratelimit_method == 'throw' or not do_ratelimiting:
1565 raise MastodonRatelimitError('Hit rate limit.')
1566 elif self.ratelimit_method in ('wait', 'pace'):
1567 to_next = self.ratelimit_reset - time.time()
1568 if to_next > 0:
1569 # As a precaution, never sleep longer than 5 minutes
1570 to_next = min(to_next, 5 * 60)
1571 time.sleep(to_next)
1572 request_complete = False
1573 continue
1574
1575 if response_object.status_code == 404:
1576 ex_type = MastodonNotFoundError
1577 if not error_msg:
1578 error_msg = 'Endpoint not found.'
1579 # this is for compatibility with older versions
1580 # which raised MastodonAPIError('Endpoint not found.')
1581 # on any 404
1582 elif response_object.status_code == 401:
1583 ex_type = MastodonUnauthorizedError
1561 else: 1584 else:
1562 raise MastodonAPIError('Endpoint not found.') 1585 ex_type = MastodonAPIError
1563 1586
1564 1587 raise ex_type(
1565 if response_object.status_code == 500: 1588 'Mastodon API returned error',
1566 raise MastodonAPIError('General API problem.') 1589 response_object.status_code,
1567 1590 response_object.reason,
1568 # Handle rate limiting 1591 error_msg)
1569 if response_object.status_code == 429:
1570 if self.ratelimit_method == 'throw' or not do_ratelimiting:
1571 raise MastodonRatelimitError('Hit rate limit.')
1572 elif self.ratelimit_method in ('wait', 'pace'):
1573 to_next = self.ratelimit_reset - time.time()
1574 if to_next > 0:
1575 # As a precaution, never sleep longer than 5 minutes
1576 to_next = min(to_next, 5 * 60)
1577 time.sleep(to_next)
1578 request_complete = False
1579 continue
1580 1592
1581 try: 1593 try:
1582 response = response_object.json(object_hook=self.__json_hooks) 1594 response = response_object.json(object_hook=self.__json_hooks)
@@ -1586,12 +1598,6 @@ class Mastodon:
1586 "bad json content was '%s'" % (response_object.status_code, 1598 "bad json content was '%s'" % (response_object.status_code,
1587 response_object.content)) 1599 response_object.content))
1588 1600
1589 # See if the returned dict is an error dict even though status is 200
1590 if isinstance(response, dict) and 'error' in response:
1591 if not isinstance(response['error'], six.string_types):
1592 response['error'] = six.text_type(response['error'])
1593 raise MastodonAPIError("Mastodon API returned error: " + response['error'])
1594
1595 # Parse link headers 1601 # Parse link headers
1596 if isinstance(response, list) and \ 1602 if isinstance(response, list) and \
1597 'Link' in response_object.headers and \ 1603 'Link' in response_object.headers and \
@@ -1801,6 +1807,16 @@ class MastodonAPIError(MastodonError):
1801 """Raised when the mastodon API generates a response that cannot be handled""" 1807 """Raised when the mastodon API generates a response that cannot be handled"""
1802 pass 1808 pass
1803 1809
1810class MastodonNotFoundError(MastodonAPIError):
1811 """Raised when the mastodon API returns a 404 Not Found error"""
1812 pass
1813
1814class MastodonUnauthorizedError(MastodonAPIError):
1815 """Raised when the mastodon API returns a 401 Unauthorized error
1816
1817 This happens when an OAuth token is invalid or has been revoked."""
1818 pass
1819
1804 1820
1805class MastodonRatelimitError(MastodonError): 1821class MastodonRatelimitError(MastodonError):
1806 """Raised when rate limiting is set to manual mode and the rate limit is exceeded""" 1822 """Raised when rate limiting is set to manual mode and the rate limit is exceeded"""
diff --git a/tests/cassettes/test_unauthed_home_tl_throws.yaml b/tests/cassettes/test_unauthed_home_tl_throws.yaml
new file mode 100644
index 0000000..b63d840
--- /dev/null
+++ b/tests/cassettes/test_unauthed_home_tl_throws.yaml
@@ -0,0 +1,82 @@
1interactions:
2- request:
3 body: visibility=&status=Toot%21
4 headers:
5 Accept: ['*/*']
6 Accept-Encoding: ['gzip, deflate']
7 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
8 Connection: [keep-alive]
9 Content-Length: ['26']
10 Content-Type: [application/x-www-form-urlencoded]
11 User-Agent: [python-requests/2.18.4]
12 method: POST
13 uri: http://localhost:3000/api/v1/statuses
14 response:
15 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
16 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":[]}'}
17 headers:
18 Cache-Control: ['max-age=0, private, must-revalidate']
19 Content-Type: [application/json; charset=utf-8]
20 ETag: [W/"d9b57bb0592371b00e98fbc0f44a8fc9"]
21 Transfer-Encoding: [chunked]
22 Vary: ['Accept-Encoding, Origin']
23 X-Content-Type-Options: [nosniff]
24 X-Frame-Options: [SAMEORIGIN]
25 X-Request-Id: [d7a9df07-1a3c-4784-adc5-b67bd6347614]
26 X-Runtime: ['0.301984']
27 X-XSS-Protection: [1; mode=block]
28 content-length: ['1175']
29 status: {code: 200, message: OK}
30- request:
31 body: null
32 headers:
33 Accept: ['*/*']
34 Accept-Encoding: ['gzip, deflate']
35 Connection: [keep-alive]
36 User-Agent: [python-requests/2.18.4]
37 method: GET
38 uri: http://localhost:3000/api/v1/timelines/home
39 response:
40 body: {string: '{"error":"The access token is invalid"}'}
41 headers:
42 Cache-Control: [no-store]
43 Content-Type: [application/json; charset=utf-8]
44 Pragma: [no-cache]
45 Transfer-Encoding: [chunked]
46 Vary: ['Accept-Encoding, Origin']
47 WWW-Authenticate: ['Bearer realm="Doorkeeper", error="invalid_token", error_description="The
48 access token is invalid"']
49 X-Content-Type-Options: [nosniff]
50 X-Frame-Options: [SAMEORIGIN]
51 X-Request-Id: [dc45d4f4-c203-4b28-ad27-f0db32912a16]
52 X-Runtime: ['0.010224']
53 X-XSS-Protection: [1; mode=block]
54 content-length: ['39']
55 status: {code: 401, message: Unauthorized}
56- request:
57 body: null
58 headers:
59 Accept: ['*/*']
60 Accept-Encoding: ['gzip, deflate']
61 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
62 Connection: [keep-alive]
63 Content-Length: ['0']
64 User-Agent: [python-requests/2.18.4]
65 method: DELETE
66 uri: http://localhost:3000/api/v1/statuses/99285482671609362
67 response:
68 body: {string: '{}'}
69 headers:
70 Cache-Control: ['max-age=0, private, must-revalidate']
71 Content-Type: [application/json; charset=utf-8]
72 ETag: [W/"8ca371aea536ee2c56c8d13b43824703"]
73 Transfer-Encoding: [chunked]
74 Vary: ['Accept-Encoding, Origin']
75 X-Content-Type-Options: [nosniff]
76 X-Frame-Options: [SAMEORIGIN]
77 X-Request-Id: [ddbd4335-1aeb-42af-8dea-fa78a787609f]
78 X-Runtime: ['0.017701']
79 X-XSS-Protection: [1; mode=block]
80 content-length: ['2']
81 status: {code: 200, message: OK}
82version: 1
diff --git a/tests/test_status.py b/tests/test_status.py
index b177517..2e129ac 100644
--- a/tests/test_status.py
+++ b/tests/test_status.py
@@ -1,6 +1,5 @@
1import pytest 1import pytest
2from mastodon.Mastodon import MastodonAPIError 2from mastodon.Mastodon import MastodonAPIError, MastodonNotFoundError
3from time import sleep
4 3
5@pytest.mark.vcr() 4@pytest.mark.vcr()
6def test_status(status, api): 5def test_status(status, api):
@@ -14,7 +13,7 @@ def test_status_empty(api):
14 13
15@pytest.mark.vcr() 14@pytest.mark.vcr()
16def test_status_missing(api): 15def test_status_missing(api):
17 with pytest.raises(MastodonAPIError): 16 with pytest.raises(MastodonNotFoundError):
18 api.status(0) 17 api.status(0)
19 18
20@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 :/") 19@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 :/")
diff --git a/tests/test_timeline.py b/tests/test_timeline.py
index 6a27be3..5108b63 100644
--- a/tests/test_timeline.py
+++ b/tests/test_timeline.py
@@ -1,5 +1,7 @@
1import pytest 1import pytest
2from mastodon.Mastodon import MastodonAPIError, MastodonIllegalArgumentError 2from mastodon.Mastodon import MastodonAPIError,\
3 MastodonIllegalArgumentError,\
4 MastodonUnauthorizedError
3 5
4@pytest.mark.vcr() 6@pytest.mark.vcr()
5def test_public_tl_anonymous(api_anonymous, status): 7def test_public_tl_anonymous(api_anonymous, status):
@@ -18,6 +20,11 @@ def test_public_tl(api, status):
18 assert status['id'] in map(lambda st: st['id'], local) 20 assert status['id'] in map(lambda st: st['id'], local)
19 21
20@pytest.mark.vcr() 22@pytest.mark.vcr()
23def test_unauthed_home_tl_throws(api_anonymous, status):
24 with pytest.raises(MastodonUnauthorizedError):
25 api_anonymous.timeline_home()
26
27@pytest.mark.vcr()
21def test_home_tl(api, status): 28def test_home_tl(api, status):
22 tl = api.timeline_home() 29 tl = api.timeline_home()
23 assert status['id'] in map(lambda st: st['id'], tl) 30 assert status['id'] in map(lambda st: st['id'], tl)
Powered by cgit v1.2.3 (git 2.41.0)