From a76eafbd46bc51f5306d7baab123597fca1de66d Mon Sep 17 00:00:00 2001 From: Elizabeth Myers Date: Sat, 19 Aug 2017 05:49:08 -0500 Subject: Remove trailing slashes in base URL The streaming API can't handle multiple slashes in the lead of a path request. This is probably a bug in Mastodon, but should be worked around here for now. --- mastodon/Mastodon.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 8aa741a..4012945 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -1083,6 +1083,9 @@ class Mastodon: """Internal add-protocol-to-url helper""" if not base_url.startswith("http://") and not base_url.startswith("https://"): base_url = "https://" + base_url + + # Some API endpoints can't handle extra /'s in path requests + base_url = base_url.rstrip("/") return base_url ## -- cgit v1.2.3 From badc8aff20aba45060847afc44d8632f4f4e5221 Mon Sep 17 00:00:00 2001 From: Lorenz Diener Date: Mon, 21 Aug 2017 16:26:02 +0200 Subject: Fixing setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 81dafe6..20c1bf1 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup(name='Mastodon.py', packages=['mastodon'], setup_requires=['pytest-runner'], tests_require=['pytest'], - install_requires=['requests', 'dateutil', 'six'], + install_requires=['requests', 'python-dateutil', 'six'], url='https://github.com/halcy/Mastodon.py', author='Lorenz Diener', author_email='lorenzd+mastodonpypypi@gmail.com', -- cgit v1.2.3 From e9591099ba7a3e6c1ecb8349014ba98f93198d23 Mon Sep 17 00:00:00 2001 From: codl Date: Wed, 23 Aug 2017 09:51:01 +0200 Subject: setup.py: fix dateutil name --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 81dafe6..20c1bf1 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup(name='Mastodon.py', packages=['mastodon'], setup_requires=['pytest-runner'], tests_require=['pytest'], - install_requires=['requests', 'dateutil', 'six'], + install_requires=['requests', 'python-dateutil', 'six'], url='https://github.com/halcy/Mastodon.py', author='Lorenz Diener', author_email='lorenzd+mastodonpypypi@gmail.com', -- cgit v1.2.3 From 02d9f5196c073c9247efb98cef1082cf71ed3700 Mon Sep 17 00:00:00 2001 From: Lorenz Diener Date: Tue, 5 Sep 2017 16:19:27 +0200 Subject: Fix fetch_* methods modifying their parameters --- mastodon/Mastodon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 4012945..13373d1 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -758,11 +758,11 @@ class Mastodon: """ if isinstance(previous_page, list): if '_pagination_next' in previous_page[-1]: - params = previous_page[-1]['_pagination_next'] + params = copy.deepcopy(previous_page[-1]['_pagination_next']) else: return None else: - params = previous_page + params = copy.deepcopy(previous_page) method = params['_pagination_method'] del params['_pagination_method'] @@ -782,11 +782,11 @@ class Mastodon: """ if isinstance(next_page, list): if '_pagination_prev' in next_page[-1]: - params = next_page[-1]['_pagination_prev'] + params = copy.deepcopy(next_page[-1]['_pagination_prev']) else: return None else: - params = next_page + params = copy.deepcopy(next_page) method = params['_pagination_method'] del params['_pagination_method'] -- cgit v1.2.3 From 613ad895c84b652dd82b7296d73cb7cd70716a04 Mon Sep 17 00:00:00 2001 From: Lorenz Diener Date: Tue, 5 Sep 2017 16:33:13 +0200 Subject: Fix copy-paste errors in fetch_* methods (fixes #57) --- mastodon/Mastodon.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 13373d1..1d71e94 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -781,8 +781,8 @@ class Mastodon: Returns the previous page or None if no further data is available. """ if isinstance(next_page, list): - if '_pagination_prev' in next_page[-1]: - params = copy.deepcopy(next_page[-1]['_pagination_prev']) + if '_pagination_prev' in next_page[0]: + params = copy.deepcopy(next_page[0]['_pagination_prev']) else: return None else: @@ -970,6 +970,8 @@ class Mastodon: next_params['_pagination_method'] = method next_params['_pagination_endpoint'] = endpoint next_params['max_id'] = int(matchgroups.group(1)) + if "since_id" in next_params: + del next_params['since_id'] response[-1]['_pagination_next'] = next_params if url['rel'] == 'prev': @@ -981,7 +983,9 @@ class Mastodon: prev_params = copy.deepcopy(params) prev_params['_pagination_method'] = method prev_params['_pagination_endpoint'] = endpoint - prev_params['max_id'] = int(matchgroups.group(1)) + prev_params['since_id'] = int(matchgroups.group(1)) + if "max_id" in prev_params: + del prev_params['max_id'] response[0]['_pagination_prev'] = prev_params # Handle rate limiting -- cgit v1.2.3 From 87ee1df1c0e045243a486ff43514028e49f44386 Mon Sep 17 00:00:00 2001 From: Lorenz Diener Date: Tue, 5 Sep 2017 16:36:32 +0200 Subject: Fix #59 --- mastodon/Mastodon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 1d71e94..6513c33 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -809,7 +809,7 @@ class Mastodon: all_pages = [] current_page = first_page - while current_page != None: + while current_page != None and len(current_page) > 0: all_pages.extend(current_page) current_page = self.fetch_next(current_page) -- cgit v1.2.3 From 1c93e350f74626b67bb50503d6ee6fcb9656bc2b Mon Sep 17 00:00:00 2001 From: Lorenz Diener Date: Tue, 5 Sep 2017 16:39:17 +0200 Subject: Fix crash on URLs with no 'rel' attribute (Fixes #79) --- mastodon/Mastodon.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 6513c33..d5c6225 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -960,6 +960,9 @@ class Mastodon: if isinstance(response, list) and 'Link' in response_object.headers and response_object.headers['Link'] != "": tmp_urls = requests.utils.parse_header_links(response_object.headers['Link'].rstrip('>').replace('>,<', ',<')) for url in tmp_urls: + if not 'rel' in url: + continue + if url['rel'] == 'next': # Be paranoid and extract max_id specifically next_url = url['url'] -- cgit v1.2.3 From c8490be2a717467c61381245e0fa2793a48c57ae Mon Sep 17 00:00:00 2001 From: Lorenz Diener Date: Tue, 5 Sep 2017 16:45:24 +0200 Subject: Streams are requested via GET (Fixes #50) --- mastodon/Mastodon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index d5c6225..c835bac 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -857,7 +857,7 @@ class Mastodon: This method blocks forever, calling callbacks on 'listener' for incoming events. """ - return self.__stream('/api/v1/streaming/hashtag', listener, params={'tag': tag}) + return self.__stream("/api/v1/streaming/hashtag?tag={}".format(tag), listener) ### # Internal helpers, dragons probably -- cgit v1.2.3 From e0e68ccd6a8007fa9fb50aa7e8432505dc93f7b8 Mon Sep 17 00:00:00 2001 From: FoxMaSk Date: Tue, 5 Sep 2017 22:59:32 +0200 Subject: not pep8 compliant #71 --- docs/conf.py | 4 +- mastodon/Mastodon.py | 335 +++++++++++++++++++++++++++--------------------- mastodon/streaming.py | 29 ++--- setup.py | 7 +- tests/__init__.py | 0 tests/test_streaming.py | 17 +-- 6 files changed, 218 insertions(+), 174 deletions(-) create mode 100644 tests/__init__.py diff --git a/docs/conf.py b/docs/conf.py index 9c4a292..4c5352a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,7 @@ import os import sys sys.path.insert(0, os.path.abspath('../')) autodoc_member_order = 'by_source' -#print(sys.path) +# print(sys.path) # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -154,7 +154,7 @@ todo_include_todos = False # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index c835bac..c8f90ee 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -6,7 +6,6 @@ import mimetypes import time import random import string -import pytz import datetime from contextlib import closing import pytz @@ -17,6 +16,7 @@ import dateutil.parser import re import copy + class Mastodon: """ Super basic but thorough and easy to use Mastodon @@ -28,12 +28,12 @@ class Mastodon: __DEFAULT_BASE_URL = 'https://mastodon.social' __DEFAULT_TIMEOUT = 300 - ### # Registering apps ### @staticmethod - def create_app(client_name, scopes = ['read', 'write', 'follow'], redirect_uris = None, website = None, to_file = None, api_base_url = __DEFAULT_BASE_URL, request_timeout = __DEFAULT_TIMEOUT): + def create_app(client_name, scopes=['read', 'write', 'follow'], redirect_uris=None, website=None, to_file=None, + api_base_url=__DEFAULT_BASE_URL, request_timeout=__DEFAULT_TIMEOUT): """ Create a new app with given client_name and scopes (read, write, follow) @@ -47,7 +47,7 @@ class Mastodon: Returns client_id and client_secret. """ api_base_url = Mastodon.__protocolize(api_base_url) - + request_data = { 'client_name': client_name, 'scopes': " ".join(scopes) @@ -55,18 +55,18 @@ class Mastodon: try: if redirect_uris is not None: - request_data['redirect_uris'] = redirect_uris; + request_data['redirect_uris'] = redirect_uris else: - request_data['redirect_uris'] = 'urn:ietf:wg:oauth:2.0:oob'; + request_data['redirect_uris'] = 'urn:ietf:wg:oauth:2.0:oob' if website is not None: request_data['website'] = website - - response = requests.post(api_base_url + '/api/v1/apps', data = request_data, timeout = request_timeout) + + response = requests.post(api_base_url + '/api/v1/apps', data=request_data, timeout=request_timeout) response = response.json() except Exception as e: raise MastodonNetworkError("Could not complete request: %s" % e) - if to_file != None: + if to_file is not None: with open(to_file, 'w') as secret_file: secret_file.write(response['client_id'] + '\n') secret_file.write(response['client_secret'] + '\n') @@ -76,7 +76,9 @@ class Mastodon: ### # Authentication, including constructor ### - def __init__(self, client_id, client_secret = None, access_token = None, api_base_url = __DEFAULT_BASE_URL, debug_requests = False, ratelimit_method = "wait", ratelimit_pacefactor = 1.1, request_timeout = __DEFAULT_TIMEOUT): + def __init__(self, client_id, client_secret=None, access_token=None, api_base_url=__DEFAULT_BASE_URL, + debug_requests=False, ratelimit_method="wait", ratelimit_pacefactor=1.1, + request_timeout=__DEFAULT_TIMEOUT): """ Create a new API wrapper instance based on the given client_secret and client_id. If you give a client_id and it is not a file, you must also give a secret. @@ -115,7 +117,7 @@ class Mastodon: self.request_timeout = request_timeout - if not ratelimit_method in ["throw", "wait", "pace"]: + if ratelimit_method not in ["throw", "wait", "pace"]: raise MastodonIllegalArgumentError("Invalid ratelimit method.") if os.path.isfile(self.client_id): @@ -123,15 +125,15 @@ class Mastodon: self.client_id = secret_file.readline().rstrip() self.client_secret = secret_file.readline().rstrip() else: - if self.client_secret == None: + if self.client_secret is None: raise MastodonIllegalArgumentError('Specified client id directly, but did not supply secret') - if self.access_token != None and os.path.isfile(self.access_token): + if self.access_token is not None and os.path.isfile(self.access_token): with open(self.access_token, 'r') as token_file: self.access_token = token_file.readline().rstrip() - - def auth_request_url(self, client_id = None, redirect_uris = "urn:ietf:wg:oauth:2.0:oob", scopes = ['read', 'write', 'follow']): + def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob", + scopes=['read', 'write', 'follow']): """Returns the url that a client needs to request the grant from the server. """ if client_id is None: @@ -140,8 +142,8 @@ class Mastodon: if os.path.isfile(client_id): with open(client_id, 'r') as secret_file: client_id = secret_file.readline().rstrip() - - params = {} + + params = dict() params['client_id'] = client_id params['response_type'] = "code" params['redirect_uri'] = redirect_uris @@ -149,18 +151,18 @@ class Mastodon: formatted_params = urlencode(params) return "".join([self.api_base_url, "/oauth/authorize?", formatted_params]) - def log_in(self, username = None, password = None,\ - code = None, redirect_uri = "urn:ietf:wg:oauth:2.0:oob", refresh_token = None,\ - scopes = ['read', 'write', 'follow'], to_file = None): + def log_in(self, username=None, password=None, + code=None, redirect_uri="urn:ietf:wg:oauth:2.0:oob", refresh_token=None, + scopes=['read', 'write', 'follow'], to_file=None): """ Your username is the e-mail you use to log in into mastodon. - + Can persist access token to file, to be used in the constructor. - + Supports refresh_token but Mastodon.social doesn't implement it at the moment. Handles password, authorization_code, and refresh_token authentication. - + Will throw a MastodonIllegalArgumentError if username / password are wrong, scopes are not valid or granted scopes differ from requested. @@ -179,13 +181,13 @@ class Mastodon: params['grant_type'] = 'refresh_token' else: raise MastodonIllegalArgumentError('Invalid arguments given. username and password or code are required.') - + params['client_id'] = self.client_id params['client_secret'] = self.client_secret params['scope'] = " ".join(scopes) - + try: - response = self.__api_request('POST', '/oauth/token', params, do_ratelimiting = False) + response = self.__api_request('POST', '/oauth/token', params, do_ratelimiting=False) self.access_token = response['access_token'] self.__set_refresh_token(response.get('refresh_token')) self.__set_token_expired(int(response.get('expires_in', 0))) @@ -201,9 +203,10 @@ class Mastodon: received_scopes = " ".join(sorted(response["scope"].split(" "))) if requested_scopes != received_scopes: - raise MastodonAPIError('Granted scopes "' + received_scopes + '" differ from requested scopes "' + requested_scopes + '".') + raise MastodonAPIError( + 'Granted scopes "' + received_scopes + '" differ from requested scopes "' + requested_scopes + '".') - if to_file != None: + if to_file is not None: with open(to_file, 'w') as token_file: token_file.write(response['access_token'] + '\n') @@ -223,7 +226,7 @@ class Mastodon: ### # Reading data: Timelines ## - def timeline(self, timeline = "home", max_id = None, since_id = None, limit = None): + def timeline(self, timeline="home", max_id=None, since_id=None, limit=None): """ Fetch statuses, most recent ones first. Timeline can be home, local, public, or tag/hashtag. See the following functions documentation for what those do. @@ -239,39 +242,44 @@ class Mastodon: params_initial['local'] = True params = self.__generate_params(params_initial, ['timeline']) - return self.__api_request('GET', '/api/v1/timelines/' + timeline, params) + url = '/api/v1/timelines/{0}'.format(timeline) + return self.__api_request('GET', url, params) - def timeline_home(self, max_id = None, since_id = None, limit = None): + def timeline_home(self, max_id=None, since_id=None, limit=None): """ Fetch the authenticated users home timeline (i.e. followed users and self). Returns a list of toot dicts. """ - return self.timeline('home', max_id = max_id, since_id = since_id, limit = limit) + return self.timeline('home', max_id=max_id, since_id=since_id, + limit=limit) - def timeline_local(self, max_id = None, since_id = None, limit = None): + def timeline_local(self, max_id=None, since_id=None, limit=None): """ Fetches the local / instance-wide timeline, not including replies. Returns a list of toot dicts. """ - return self.timeline('local', max_id = max_id, since_id = since_id, limit = limit) + return self.timeline('local', max_id=max_id, since_id=since_id, + limit=limit) - def timeline_public(self, max_id = None, since_id = None, limit = None): + def timeline_public(self, max_id=None, since_id=None, limit=None): """ Fetches the public / visible-network timeline, not including replies. Returns a list of toot dicts. """ - return self.timeline('public', max_id = max_id, since_id = since_id, limit = limit) + return self.timeline('public', max_id=max_id, since_id=since_id, + limit=limit) - def timeline_hashtag(self, hashtag, max_id = None, since_id = None, limit = None): + def timeline_hashtag(self, hashtag, max_id=None, since_id=None, limit=None): """ Fetch a timeline of toots with a given hashtag. Returns a list of toot dicts. """ - return self.timeline('tag/' + str(hashtag), max_id = max_id, since_id = since_id, limit = limit) + url = 'tag/{0}'.format(str(hashtag)) + return self.timeline(url, max_id=max_id, since_id=since_id, limit=limit) ### # Reading data: Statuses @@ -282,7 +290,8 @@ class Mastodon: Returns a toot dict. """ - return self.__api_request('GET', '/api/v1/statuses/' + str(id)) + url = '/api/v1/statuses/{0}'.format(str(id)) + return self.__api_request('GET', url) def status_card(self, id): """ @@ -291,7 +300,8 @@ class Mastodon: Returns a card dict. """ - return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/card') + url = '/api/v1/statuses/{0}/card'.format(str(id)) + return self.__api_request('GET', url) def status_context(self, id): """ @@ -299,7 +309,8 @@ class Mastodon: Returns a context dict. """ - return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/context') + url = '/api/v1/statuses/{0}/context'.format(str(id)) + return self.__api_request('GET', url) def status_reblogged_by(self, id): """ @@ -307,7 +318,8 @@ class Mastodon: Returns a list of user dicts. """ - return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/reblogged_by') + url = '/api/v1/statuses/{0}/reblogged_by'.format(str(id)) + return self.__api_request('GET', url) def status_favourited_by(self, id): """ @@ -315,12 +327,13 @@ class Mastodon: Returns a list of user dicts. """ - return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/favourited_by') + url = '/api/v1/statuses/{0}/favourited_by'.format(str(id)) + return self.__api_request('GET', url) ### # Reading data: Notifications ### - def notifications(self, id = None, max_id = None, since_id = None, limit = None): + def notifications(self, id=None, max_id=None, since_id=None, limit=None): """ Fetch notifications (mentions, favourites, reblogs, follows) for the authenticated user. @@ -329,11 +342,12 @@ class Mastodon: Returns a list of notification dicts. """ - if id == None: + if id is None: params = self.__generate_params(locals(), ['id']) return self.__api_request('GET', '/api/v1/notifications', params) else: - return self.__api_request('GET', '/api/v1/notifications/' + str(id)) + url = '/api/v1/notifications/{0}'.format(str(id)) + return self.__api_request('GET', url) ### # Reading data: Accounts @@ -344,7 +358,8 @@ class Mastodon: Returns a user dict. """ - return self.__api_request('GET', '/api/v1/accounts/' + str(id)) + url = '/api/v1/accounts/{0}'.format(str(id)) + return self.__api_request('GET', url) def account_verify_credentials(self): """ @@ -354,32 +369,35 @@ class Mastodon: """ return self.__api_request('GET', '/api/v1/accounts/verify_credentials') - def account_statuses(self, id, max_id = None, since_id = None, limit = None): + def account_statuses(self, id, max_id=None, since_id=None, limit=None): """ Fetch statuses by user id. Same options as timeline are permitted. Returns a list of toot dicts. """ params = self.__generate_params(locals(), ['id']) - return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/statuses', params) + url = '/api/v1/accounts/{0}/statuses'.format(str(id)) + return self.__api_request('GET', url, params) - def account_following(self, id, max_id = None, since_id = None, limit = None): + def account_following(self, id, max_id=None, since_id=None, limit=None): """ Fetch users the given user is following. Returns a list of user dicts. """ params = self.__generate_params(locals(), ['id']) - return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/following', params) + url = '/api/v1/accounts/{0}/following'.format(str(id)) + return self.__api_request('GET', url, params) - def account_followers(self, id, max_id = None, since_id = None, limit = None): + def account_followers(self, id, max_id=None, since_id=None, limit=None): """ Fetch users the given user is followed by. Returns a list of user dicts. """ params = self.__generate_params(locals(), ['id']) - return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/followers', params) + url = '/api/v1/accounts/{0}/followers'.format(str(id)) + return self.__api_request('GET', url, params) def account_relationships(self, id): """ @@ -389,9 +407,10 @@ class Mastodon: Returns a list of relationship dicts. """ params = self.__generate_params(locals()) - return self.__api_request('GET', '/api/v1/accounts/relationships', params) + return self.__api_request('GET', '/api/v1/accounts/relationships', + params) - def account_search(self, q, limit = None): + def account_search(self, q, limit=None): """ Fetch matching accounts. Will lookup an account remotely if the search term is in the username@domain format and not yet in the database. @@ -404,7 +423,7 @@ class Mastodon: ### # Reading data: Searching ### - def search(self, q, resolve = False): + def search(self, q, resolve=False): """ Fetch matching hashtags, accounts and statuses. Will search federated instances if resolve is True. @@ -417,7 +436,7 @@ class Mastodon: ### # Reading data: Mutes and Blocks ### - def mutes(self, max_id = None, since_id = None, limit = None): + def mutes(self, max_id=None, since_id=None, limit=None): """ Fetch a list of users muted by the authenticated user. @@ -426,7 +445,7 @@ class Mastodon: params = self.__generate_params(locals()) return self.__api_request('GET', '/api/v1/mutes', params) - def blocks(self, max_id = None, since_id = None, limit = None): + def blocks(self, max_id=None, since_id=None, limit=None): """ Fetch a list of users blocked by the authenticated user. @@ -449,7 +468,7 @@ class Mastodon: ### # Reading data: Favourites ### - def favourites(self, max_id = None, since_id = None, limit = None): + def favourites(self, max_id=None, since_id=None, limit=None): """ Fetch the authenticated user's favourited statuses. @@ -461,7 +480,7 @@ class Mastodon: ### # Reading data: Follow requests ### - def follow_requests(self, max_id = None, since_id = None, limit = None): + def follow_requests(self, max_id=None, since_id=None, limit=None): """ Fetch the authenticated user's incoming follow requests. @@ -473,7 +492,7 @@ class Mastodon: ### # Reading data: Domain blocks ### - def domain_blocks(self, max_id = None, since_id = None, limit = None): + def domain_blocks(self, max_id=None, since_id=None, limit=None): """ Fetch the authenticated user's blocked domains. @@ -481,11 +500,12 @@ class Mastodon: """ params = self.__generate_params(locals()) return self.__api_request('GET', '/api/v1/domain_blocks', params) - + ### # Writing data: Statuses ### - def status_post(self, status, in_reply_to_id = None, media_ids = None, sensitive = False, visibility = '', spoiler_text = None): + def status_post(self, status, in_reply_to_id=None, media_ids=None, + sensitive=False, visibility='', spoiler_text=None): """ Post a status. Can optionally be in reply to another status and contain up to four pieces of media (Uploaded via media_post()). media_ids can @@ -519,10 +539,10 @@ class Mastodon: if params_initial['visibility'].lower() not in valid_visibilities: raise ValueError('Invalid visibility value! Acceptable values are %s' % valid_visibilities) - if params_initial['sensitive'] == False: - del[params_initial['sensitive']] + if params_initial['sensitive'] is False: + del [params_initial['sensitive']] - if media_ids != None: + if media_ids is not None: try: media_ids_proper = [] for media_id in media_ids: @@ -552,14 +572,16 @@ class Mastodon: Returns an empty dict for good measure. """ - return self.__api_request('DELETE', '/api/v1/statuses/' + str(id)) + url = '/api/v1/statuses/{0}'.format(str(id)) + return self.__api_request('DELETE', url) def status_reblog(self, id): """Reblog a status. Returns a toot with with a new status that wraps around the reblogged one. """ - return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/reblog") + url = '/api/v1/statuses/{0}/reblog'.format(str(id)) + return self.__api_request('POST', url) def status_unreblog(self, id): """ @@ -567,7 +589,8 @@ class Mastodon: Returns a toot dict with the status that used to be reblogged. """ - return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unreblog") + url = '/api/v1/statuses/{0}/unreblog'.format(str(id)) + return self.__api_request('POST', url) def status_favourite(self, id): """ @@ -575,7 +598,8 @@ class Mastodon: Returns a toot dict with the favourited status. """ - return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/favourite") + url = '/api/v1/statuses/{0}/favourite'.format(str(id)) + return self.__api_request('POST', url) def status_unfavourite(self, id): """ @@ -583,7 +607,8 @@ class Mastodon: Returns a toot dict with the un-favourited status. """ - return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unfavourite") + url = '/api/v1/statuses/{0}/unfavourite'.format(str(id)) + return self.__api_request('POST', url) ### # Writing data: Notifications @@ -603,7 +628,8 @@ class Mastodon: Returns a relationship dict containing the updated relationship to the user. """ - return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/follow") + url = '/api/v1/accounts/{0}/follow'.format(str(id)) + return self.__api_request('POST', url) def follows(self, uri): """ @@ -620,7 +646,8 @@ class Mastodon: Returns a relationship dict containing the updated relationship to the user. """ - return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unfollow") + url = '/api/v1/accounts/{0}/unfollow'.format(str(id)) + return self.__api_request('POST', url) def account_block(self, id): """ @@ -628,7 +655,8 @@ class Mastodon: Returns a relationship dict containing the updated relationship to the user. """ - return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/block") + url = '/api/v1/accounts/{0}/block'.format(str(id)) + return self.__api_request('POST', url) def account_unblock(self, id): """ @@ -636,7 +664,8 @@ class Mastodon: Returns a relationship dict containing the updated relationship to the user. """ - return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unblock") + url = '/api/v1/accounts/{0}/unblock'.format(str(id)) + return self.__api_request('POST', url) def account_mute(self, id): """ @@ -644,7 +673,8 @@ class Mastodon: Returns a relationship dict containing the updated relationship to the user. """ - return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/mute") + url = '/api/v1/accounts/{0}/mute'.format(str(id)) + return self.__api_request('POST', url) def account_unmute(self, id): """ @@ -652,9 +682,11 @@ class Mastodon: Returns a relationship dict containing the updated relationship to the user. """ - return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unmute") + url = '/api/v1/accounts/{0}/unmute'.format(str(id)) + return self.__api_request('POST', url) - def account_update_credentials(self, display_name = None, note = None, avatar = None, header = None): + def account_update_credentials(self, display_name=None, note=None, + avatar=None, header=None): """ Update the profile for the currently authenticated user. @@ -689,7 +721,8 @@ class Mastodon: Returns an empty dict. """ - return self.__api_request('POST', '/api/v1/follow_requests/' + str(id) + "/authorize") + url = '/api/v1/follow_requests/{0}/authorize'.format(str(id)) + return self.__api_request('POST', url) def follow_request_reject(self, id): """ @@ -697,12 +730,13 @@ class Mastodon: Returns an empty dict. """ - return self.__api_request('POST', '/api/v1/follow_requests/' + str(id) + "/reject") + url = '/api/v1/follow_requests/{0}/reject'.format(str(id)) + return self.__api_request('POST', url) ### # Writing data: Media ### - def media_post(self, media_file, mime_type = None): + def media_post(self, media_file, mime_type=None): """ Post an image. media_file can either be image data or a file name. If image data is passed directly, the mime @@ -715,45 +749,48 @@ class Mastodon: Returns a media dict. This contains the id that can be used in status_post to attach the media file to a toot. """ - if mime_type == None and os.path.isfile(media_file): + if mime_type is None and os.path.isfile(media_file): mime_type = mimetypes.guess_type(media_file)[0] media_file = open(media_file, 'rb') - if mime_type == None: - raise MastodonIllegalArgumentError('Could not determine mime type or data passed directly without mime type.') + if mime_type is None: + raise MastodonIllegalArgumentError( + 'Could not determine mime type or data passed directly without mime type.') random_suffix = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) - file_name = "mastodonpyupload_" + str(time.time()) + "_" + str(random_suffix) + mimetypes.guess_extension(mime_type) + file_name = "mastodonpyupload_" + str(time.time()) + "_" + str(random_suffix) + mimetypes.guess_extension( + mime_type) media_file_description = (file_name, media_file, mime_type) - return self.__api_request('POST', '/api/v1/media', files = {'file': media_file_description}) + return self.__api_request('POST', '/api/v1/media', + files={'file': media_file_description}) ### # Writing data: Domain blocks ### - def domain_block(self, domain = None): + def domain_block(self, domain=None): """ Add a block for all statuses originating from the specified domain for the logged-in user. """ params = self.__generate_params(locals()) return self.__api_request('POST', '/api/v1/domain_blocks', params) - - def domain_unblock(self, domain = None): + + def domain_unblock(self, domain=None): """ Remove a domain block for the logged-in user. """ params = self.__generate_params(locals()) return self.__api_request('DELETE', '/api/v1/domain_blocks', params) - + ### # Pagination ### def fetch_next(self, previous_page): """ Fetches the next page of results of a paginated request. Pass in the - previous page in its entirety, or the pagination information dict + previous page in its entirety, or the pagination information dict returned as a part of that pages last status ('_pagination_next'). - + Returns the next page or None if no further data is available. """ if isinstance(previous_page, list): @@ -763,21 +800,21 @@ class Mastodon: return None else: params = copy.deepcopy(previous_page) - + method = params['_pagination_method'] del params['_pagination_method'] - + endpoint = params['_pagination_endpoint'] del params['_pagination_endpoint'] - + return self.__api_request(method, endpoint, params) - + def fetch_previous(self, next_page): """ Fetches the previous page of results of a paginated request. Pass in the - previous page in its entirety, or the pagination information dict + previous page in its entirety, or the pagination information dict returned as a part of that pages first status ('_pagination_prev'). - + Returns the previous page or None if no further data is available. """ if isinstance(next_page, list): @@ -787,34 +824,34 @@ class Mastodon: return None else: params = copy.deepcopy(next_page) - + method = params['_pagination_method'] del params['_pagination_method'] - + endpoint = params['_pagination_endpoint'] del params['_pagination_endpoint'] - + return self.__api_request(method, endpoint, params) - + def fetch_remaining(self, first_page): """ - Fetches all the remaining pages of a paginated request starting from a + Fetches all the remaining pages of a paginated request starting from a first page and returns the entire set of results (including the first page that was passed in) as a big list. - + Be careful, as this might generate a lot of requests, depending on what you are fetching, and might cause you to run into rate limits very quickly. """ first_page = copy.deepcopy(first_page) - + all_pages = [] current_page = first_page - while current_page != None and len(current_page) > 0: + while current_page is not None and len(current_page) > 0: all_pages.extend(current_page) current_page = self.fetch_next(current_page) - + return all_pages - + ### # Streaming ### @@ -858,7 +895,7 @@ class Mastodon: incoming events. """ return self.__stream("/api/v1/streaming/hashtag?tag={}".format(tag), listener) - + ### # Internal helpers, dragons probably ### @@ -870,22 +907,22 @@ class Mastodon: Assumes UTC if timezone is not given. """ date_time_utc = None - if date_time.tzinfo == None: - date_time_utc = date_time.replace(tzinfo = pytz.utc) + if date_time.tzinfo is None: + date_time_utc = date_time.replace(tzinfo=pytz.utc) else: date_time_utc = date_time.astimezone(pytz.utc) - epoch_utc = datetime.datetime.utcfromtimestamp(0).replace(tzinfo = pytz.utc) + epoch_utc = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) return (date_time_utc - epoch_utc).total_seconds() - def __api_request(self, method, endpoint, params = {}, files = {}, do_ratelimiting = True): + def __api_request(self, method, endpoint, params={}, files={}, do_ratelimiting=True): """ Internal API request helper. """ response = None headers = None - + remaining_wait = 0 # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it # would take to not hit the rate limit at that request rate. if do_ratelimiting and self.ratelimit_method == "pace": @@ -906,10 +943,10 @@ class Mastodon: time.sleep(to_next) # Generate request headers - if self.access_token != None: + if self.access_token is not None: headers = {'Authorization': 'Bearer ' + self.access_token} - if self.debug_requests == True: + if self.debug_requests: print('Mastodon: Request to endpoint "' + endpoint + '" using method "' + method + '".') print('Parameters: ' + str(params)) print('Headers: ' + str(headers)) @@ -923,24 +960,28 @@ class Mastodon: response_object = None try: if method == 'GET': - response_object = requests.get(self.api_base_url + endpoint, data = params, headers = headers, files = files, timeout = self.request_timeout) + response_object = requests.get(self.api_base_url + endpoint, data=params, headers=headers, + files=files, timeout=self.request_timeout) if method == 'POST': - response_object = requests.post(self.api_base_url + endpoint, data = params, headers = headers, files = files, timeout = self.request_timeout) + response_object = requests.post(self.api_base_url + endpoint, data=params, headers=headers, + files=files, timeout=self.request_timeout) if method == 'PATCH': - response_object = requests.patch(self.api_base_url + endpoint, data = params, headers = headers, files = files, timeout = self.request_timeout) + response_object = requests.patch(self.api_base_url + endpoint, data=params, headers=headers, + files=files, timeout=self.request_timeout) if method == 'DELETE': - response_object = requests.delete(self.api_base_url + endpoint, data = params, headers = headers, files = files, timeout = self.request_timeout) + response_object = requests.delete(self.api_base_url + endpoint, data=params, headers=headers, + files=files, timeout=self.request_timeout) except Exception as e: raise MastodonNetworkError("Could not complete request: %s" % e) - if response_object == None: + if response_object is None: raise MastodonIllegalArgumentError("Illegal request.") # Handle response - if self.debug_requests == True: + if self.debug_requests: print('Mastodon: Response received with code ' + str(response_object.status_code) + '.') print('response headers: ' + str(response_object.headers)) print('Response text content: ' + str(response_object.text)) @@ -954,20 +995,25 @@ class Mastodon: try: response = response_object.json() except: - raise MastodonAPIError("Could not parse response as JSON, response code was %s, bad json content was '%s'" % (response_object.status_code, response_object.content)) + raise MastodonAPIError( + "Could not parse response as JSON, response code was %s, bad json content was '%s'" % ( + response_object.status_code, response_object.content)) # Parse link headers - if isinstance(response, list) and 'Link' in response_object.headers and response_object.headers['Link'] != "": - tmp_urls = requests.utils.parse_header_links(response_object.headers['Link'].rstrip('>').replace('>,<', ',<')) + if isinstance(response, list) and \ + 'Link' in response_object.headers and \ + response_object.headers['Link'] != "": + tmp_urls = requests.utils.parse_header_links( + response_object.headers['Link'].rstrip('>').replace('>,<', ',<')) for url in tmp_urls: if not 'rel' in url: continue - + if url['rel'] == 'next': # Be paranoid and extract max_id specifically next_url = url['url'] matchgroups = re.search(r"max_id=([0-9]*)", next_url) - + if matchgroups: next_params = copy.deepcopy(params) next_params['_pagination_method'] = method @@ -976,12 +1022,12 @@ class Mastodon: if "since_id" in next_params: del next_params['since_id'] response[-1]['_pagination_next'] = next_params - + if url['rel'] == 'prev': # Be paranoid and extract since_id specifically prev_url = url['url'] matchgroups = re.search(r"since_id=([0-9]*)", prev_url) - + if matchgroups: prev_params = copy.deepcopy(params) prev_params['_pagination_method'] = method @@ -990,7 +1036,7 @@ class Mastodon: if "max_id" in prev_params: del prev_params['max_id'] response[0]['_pagination_prev'] = prev_params - + # Handle rate limiting if 'X-RateLimit-Remaining' in response_object.headers and do_ratelimiting: self.ratelimit_remaining = int(response_object.headers['X-RateLimit-Remaining']) @@ -1024,21 +1070,20 @@ class Mastodon: return response - def __stream(self, endpoint, listener, params = {}): + def __stream(self, endpoint, listener, params={}): """ Internal streaming API helper. """ headers = {} - if self.access_token != None: + if self.access_token is not None: headers = {'Authorization': 'Bearer ' + self.access_token} url = self.api_base_url + endpoint - with closing(requests.get(url, headers = headers, data = params, stream = True)) as r: + with closing(requests.get(url, headers=headers, data=params, stream=True)) as r: listener.handle_stream(r.iter_lines()) - - def __generate_params(self, params, exclude = []): + def __generate_params(self, params, exclude=[]): """ Internal named-parameters-to-dict helper. @@ -1052,7 +1097,7 @@ class Mastodon: del params['self'] param_keys = list(params.keys()) for key in param_keys: - if params[key] == None or key in exclude: + if params[key] is None or key in exclude: del params[key] param_keys = list(params.keys()) @@ -1063,28 +1108,24 @@ class Mastodon: return params - def __get_token_expired(self): """Internal helper for oauth code""" - if self._token_expired < datetime.datetime.now(): - return True - else: - return False + return self._token_expired < datetime.datetime.now() def __set_token_expired(self, value): """Internal helper for oauth code""" self._token_expired = datetime.datetime.now() + datetime.timedelta(seconds=value) return - + def __get_refresh_token(self): """Internal helper for oauth code""" return self._refresh_token - + def __set_refresh_token(self, value): """Internal helper for oauth code""" self._refresh_token = value return - + @staticmethod def __protocolize(base_url): """Internal add-protocol-to-url helper""" @@ -1095,21 +1136,25 @@ class Mastodon: base_url = base_url.rstrip("/") return base_url + ## # Exceptions ## class MastodonIllegalArgumentError(ValueError): pass + class MastodonFileNotFoundError(IOError): pass + class MastodonNetworkError(IOError): pass + class MastodonAPIError(Exception): pass + class MastodonRatelimitError(Exception): pass - diff --git a/mastodon/streaming.py b/mastodon/streaming.py index 3212848..290ed44 100644 --- a/mastodon/streaming.py +++ b/mastodon/streaming.py @@ -1,7 +1,7 @@ -''' +""" Handlers for the Streaming API: https://github.com/tootsuite/mastodon/blob/master/docs/Using-the-API/Streaming-API.md -''' +""" import json import logging @@ -12,43 +12,43 @@ log = logging.getLogger(__name__) class MalformedEventError(Exception): - '''Raised when the server-sent event stream is malformed.''' + """Raised when the server-sent event stream is malformed.""" pass class StreamListener(object): - '''Callbacks for the streaming API. Create a subclass, override the on_xxx + """Callbacks for the streaming API. Create a subclass, override the on_xxx methods for the kinds of events you're interested in, then pass an instance of your subclass to Mastodon.user_stream(), Mastodon.public_stream(), or - Mastodon.hashtag_stream().''' + Mastodon.hashtag_stream().""" def on_update(self, status): - '''A new status has appeared! 'status' is the parsed JSON dictionary - describing the status.''' + """A new status has appeared! 'status' is the parsed JSON dictionary +describing the status.""" pass def on_notification(self, notification): - '''A new notification. 'notification' is the parsed JSON dictionary - describing the notification.''' + """A new notification. 'notification' is the parsed JSON dictionary + describing the notification.""" pass def on_delete(self, status_id): - '''A status has been deleted. status_id is the status' integer ID.''' + """A status has been deleted. status_id is the status' integer ID.""" pass def handle_heartbeat(self): - '''The server has sent us a keep-alive message. This callback may be + """The server has sent us a keep-alive message. This callback may be useful to carry out periodic housekeeping tasks, or just to confirm - that the connection is still open.''' + that the connection is still open.""" def handle_stream(self, lines): - ''' + """ Handles a stream of events from the Mastodon server. When each event is received, the corresponding .on_[name]() method is called. lines: an iterable of lines of bytes sent by the Mastodon server, as returned by requests.Response.iter_lines(). - ''' + """ event = {} for raw_line in lines: try: @@ -104,4 +104,3 @@ class StreamListener(object): else: # TODO: allow handlers to return/raise to stop streaming cleanly handler(payload) - diff --git a/setup.py b/setup.py index 20c1bf1..0ebf236 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import setup setup(name='Mastodon.py', version='1.0.8', @@ -6,7 +6,7 @@ setup(name='Mastodon.py', packages=['mastodon'], setup_requires=['pytest-runner'], tests_require=['pytest'], - install_requires=['requests', 'python-dateutil', 'six'], + install_requires=['requests', 'python-dateutil', 'six', 'pytz'], url='https://github.com/halcy/Mastodon.py', author='Lorenz Diener', author_email='lorenzd+mastodonpypypi@gmail.com', @@ -19,5 +19,4 @@ setup(name='Mastodon.py', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', - ] -) + ]) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_streaming.py b/tests/test_streaming.py index c79a8e9..1f8d75f 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -24,9 +24,10 @@ class Listener(StreamListener): self.heartbeats += 1 def handle_stream_(self, lines): - '''Test helper to avoid littering all tests with six.b().''' + """Test helper to avoid littering all tests with six.b().""" return self.handle_stream(map(six.b, lines)) + def test_heartbeat(): listener = Listener() listener.handle_stream_([':one', ':two']) @@ -85,7 +86,7 @@ def test_many(events): def test_unknown_event(): - '''Be tolerant of new event types''' + """Be tolerant of new event types""" listener = Listener() listener.handle_stream_([ 'event: blahblah', @@ -137,11 +138,11 @@ def test_sse_order_doesnt_matter(): def test_extra_keys_ignored(): - ''' + """ https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format defines 'id' and 'retry' keys which the Mastodon streaming API doesn't use, and alleges that "All other field names are ignored". - ''' + """ listener = Listener() listener.handle_stream_([ 'event: update', @@ -155,7 +156,7 @@ def test_extra_keys_ignored(): def test_valid_utf8(): - '''Snowman Cat Face With Tears Of Joy''' + """Snowman Cat Face With Tears Of Joy""" listener = Listener() listener.handle_stream_([ 'event: update', @@ -166,7 +167,7 @@ def test_valid_utf8(): def test_invalid_utf8(): - '''Cat Face With Tears O''' + """Cat Face With Tears O""" listener = Listener() with pytest.raises(MalformedEventError): listener.handle_stream_([ @@ -177,13 +178,13 @@ def test_invalid_utf8(): def test_multiline_payload(): - ''' + """ https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Data-only_messages says that newlines in the 'data' field can be encoded by sending the field twice! This would be really pathological for Mastodon because the payload is JSON, but technically literal newlines are permissible (outside strings) so let's handle this case. - ''' + """ listener = Listener() listener.handle_stream_([ 'event: update', -- cgit v1.2.3 From 6ef2724f66fbc9358c13e342a98f7385d25df4a1 Mon Sep 17 00:00:00 2001 From: FoxMaSk Date: Tue, 5 Sep 2017 23:07:24 +0200 Subject: not pep8 compliant #71 --- mastodon/Mastodon.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index c8f90ee..715da12 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -76,8 +76,9 @@ class Mastodon: ### # Authentication, including constructor ### - def __init__(self, client_id, client_secret=None, access_token=None, api_base_url=__DEFAULT_BASE_URL, - debug_requests=False, ratelimit_method="wait", ratelimit_pacefactor=1.1, + def __init__(self, client_id, client_secret=None, access_token=None, + api_base_url=__DEFAULT_BASE_URL, debug_requests=False, + ratelimit_method="wait", ratelimit_pacefactor=1.1, request_timeout=__DEFAULT_TIMEOUT): """ Create a new API wrapper instance based on the given client_secret and client_id. If you @@ -166,7 +167,8 @@ class Mastodon: Will throw a MastodonIllegalArgumentError if username / password are wrong, scopes are not valid or granted scopes differ from requested. - For OAuth2 documentation, compare https://github.com/doorkeeper-gem/doorkeeper/wiki/Interacting-as-an-OAuth-client-with-Doorkeeper + For OAuth2 documentation, compare + https://github.com/doorkeeper-gem/doorkeeper/wiki/Interacting-as-an-OAuth-client-with-Doorkeeper Returns the access token. """ @@ -537,7 +539,8 @@ class Mastodon: # Validate visibility parameter valid_visibilities = ['private', 'public', 'unlisted', 'direct', ''] if params_initial['visibility'].lower() not in valid_visibilities: - raise ValueError('Invalid visibility value! Acceptable values are %s' % valid_visibilities) + raise ValueError('Invalid visibility value! Acceptable ' + 'values are %s' % valid_visibilities) if params_initial['sensitive'] is False: del [params_initial['sensitive']] @@ -551,7 +554,8 @@ class Mastodon: else: media_ids_proper.append(media_id) except Exception as e: - raise MastodonIllegalArgumentError("Invalid media dict: %s" % e) + raise MastodonIllegalArgumentError("Invalid media " + "dict: %s" % e) params_initial["media_ids"] = media_ids_proper @@ -754,8 +758,9 @@ class Mastodon: media_file = open(media_file, 'rb') if mime_type is None: - raise MastodonIllegalArgumentError( - 'Could not determine mime type or data passed directly without mime type.') + raise MastodonIllegalArgumentError('Could not determine mime type' + ' or data passed directly ' + 'without mime type.') random_suffix = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) file_name = "mastodonpyupload_" + str(time.time()) + "_" + str(random_suffix) + mimetypes.guess_extension( @@ -996,8 +1001,9 @@ class Mastodon: response = response_object.json() except: raise MastodonAPIError( - "Could not parse response as JSON, response code was %s, bad json content was '%s'" % ( - response_object.status_code, response_object.content)) + "Could not parse response as JSON, response code was %s, " + "bad json content was '%s'" % (response_object.status_code, + response_object.content)) # Parse link headers if isinstance(response, list) and \ @@ -1006,7 +1012,7 @@ class Mastodon: tmp_urls = requests.utils.parse_header_links( response_object.headers['Link'].rstrip('>').replace('>,<', ',<')) for url in tmp_urls: - if not 'rel' in url: + if 'rel' not in url: continue if url['rel'] == 'next': -- cgit v1.2.3 From 71d1038b323a5c949c4480a552a77855b5a1f5c3 Mon Sep 17 00:00:00 2001 From: lambadalambda Date: Fri, 8 Sep 2017 07:45:23 +0200 Subject: Send params as part of the query in GET requests Using the data argument will send them form-encoded like for the other requests, which isn't parsed by many servers for GET requests. --- mastodon/Mastodon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index c835bac..cd641e5 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -923,7 +923,7 @@ class Mastodon: response_object = None try: if method == 'GET': - response_object = requests.get(self.api_base_url + endpoint, data = params, headers = headers, files = files, timeout = self.request_timeout) + response_object = requests.get(self.api_base_url + endpoint, params = params, headers = headers, files = files, timeout = self.request_timeout) if method == 'POST': response_object = requests.post(self.api_base_url + endpoint, data = params, headers = headers, files = files, timeout = self.request_timeout) -- cgit v1.2.3 From c628ff4f0595deba742d8649f6d1cc35e3f22cb9 Mon Sep 17 00:00:00 2001 From: Lorenz Diener Date: Fri, 8 Sep 2017 14:49:57 +0200 Subject: Small documentation fix for toot() --- mastodon/Mastodon.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 68f4914..9e165ca 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -565,6 +565,8 @@ class Mastodon: def toot(self, status): """ Synonym for status_post that only takes the status text as input. + + Usage in production code is not recommended. Returns a toot dict with the new status. """ -- cgit v1.2.3 From 0edc424b11a14ce2908ac16739b910febdd1e140 Mon Sep 17 00:00:00 2001 From: Lorenz Diener Date: Fri, 8 Sep 2017 14:51:07 +0200 Subject: Clarify visibility documentation --- mastodon/Mastodon.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 9e165ca..f073a66 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -526,7 +526,9 @@ class Mastodon: 'public' - post will be public If not passed in, visibility defaults to match the current account's - privacy setting (private if the account is locked, public otherwise). + locked setting (private if the account is locked, public otherwise). + Note that the "privacy" setting is not currently used in determining + visibility when not specified. The spoiler_text parameter is a string to be shown as a warning before the text of the status. If no text is passed in, no warning will be -- cgit v1.2.3