Add ability to persist base urls with clientid/secret/token (fixes #200)

This commit is contained in:
Lorenz Diener 2019-10-12 18:58:46 +02:00
pare 5c4916bd81
commit ca45cd65aa
S'han modificat 3 arxius amb 65 adicions i 19 eliminacions

Veure arxiu

@ -267,16 +267,17 @@ class Mastodon:
if to_file is not None: if to_file is not None:
with open(to_file, 'w') as secret_file: with open(to_file, 'w') as secret_file:
secret_file.write(response['client_id'] + '\n') secret_file.write(response['client_id'] + "\n")
secret_file.write(response['client_secret'] + '\n') secret_file.write(response['client_secret'] + "\n")
secret_file.write(api_base_url + "\n")
return (response['client_id'], response['client_secret']) return (response['client_id'], response['client_secret'])
### ###
# Authentication, including constructor # Authentication, including constructor
### ###
def __init__(self, client_id=None, client_secret=None, access_token=None, def __init__(self, client_id=None, client_secret=None, access_token=None,
api_base_url=__DEFAULT_BASE_URL, debug_requests=False, api_base_url=None, debug_requests=False,
ratelimit_method="wait", ratelimit_pacefactor=1.1, ratelimit_method="wait", ratelimit_pacefactor=1.1,
request_timeout=__DEFAULT_TIMEOUT, mastodon_version=None, request_timeout=__DEFAULT_TIMEOUT, mastodon_version=None,
version_check_mode = "created", session=None): version_check_mode = "created", session=None):
@ -285,9 +286,12 @@ class Mastodon:
give a `client_id` and it is not a file, you must also give a secret. If you specify an give a `client_id` and it is not a file, you must also give a secret. If you specify an
`access_token` then you don't need to specify a `client_id`. It is allowed to specify `access_token` then you don't need to specify a `client_id`. It is allowed to specify
neither - in this case, you will be restricted to only using endpoints that do not neither - in this case, you will be restricted to only using endpoints that do not
require authentication. require authentication. If a file is given as `client_id`, client ID, secret and
base url are read from that file.
You can also specify an `access_token`, directly or as a file (as written by `log_in()`_). You can also specify an `access_token`, directly or as a file (as written by `log_in()`_). If
a file is given, Mastodon.py also tries to load the base URL from this file, if present. A
client id and secret are not required in this case.
Mastodon.py can try to respect rate limits in several ways, controlled by `ratelimit_method`. Mastodon.py can try to respect rate limits in several ways, controlled by `ratelimit_method`.
"throw" makes functions throw a `MastodonRatelimitError` when the rate "throw" makes functions throw a `MastodonRatelimitError` when the rate
@ -298,8 +302,9 @@ class Mastodon:
even in "wait" and "pace" mode, requests can still fail due to network or other problems! Also even in "wait" and "pace" mode, requests can still fail due to network or other problems! Also
note that "pace" and "wait" are NOT thread safe. note that "pace" and "wait" are NOT thread safe.
Specify `api_base_url` if you wish to talk to an instance other than the flagship one. Specify `api_base_url` if you wish to talk to an instance other than the flagship one. When
If a file is given as `client_id`, client ID and secret are read from that file. reading from client id or access token files as written by Mastodon.py 1.5.0 or larger,
this can be omitted.
By default, a timeout of 300 seconds is used for all requests. If you wish to change this, By default, a timeout of 300 seconds is used for all requests. If you wish to change this,
pass the desired timeout (in seconds) as `request_timeout`. pass the desired timeout (in seconds) as `request_timeout`.
@ -317,7 +322,10 @@ class Mastodon:
changed after the version of Mastodon that is connected has been released. If it is set to "none", changed after the version of Mastodon that is connected has been released. If it is set to "none",
version checking is disabled. version checking is disabled.
""" """
self.api_base_url = Mastodon.__protocolize(api_base_url) self.api_base_url = None
if not api_base_url is None:
self.api_base_url = Mastodon.__protocolize(api_base_url)
self.client_id = client_id self.client_id = client_id
self.client_secret = client_secret self.client_secret = client_secret
self.access_token = access_token self.access_token = access_token
@ -364,6 +372,13 @@ class Mastodon:
with open(self.client_id, 'r') as secret_file: with open(self.client_id, 'r') as secret_file:
self.client_id = secret_file.readline().rstrip() self.client_id = secret_file.readline().rstrip()
self.client_secret = secret_file.readline().rstrip() self.client_secret = secret_file.readline().rstrip()
try_base_url = secret_file.readline().rstrip()
if (not try_base_url is None) and len(try_base_url) != 0:
try_base_url = Mastodon.__protocolize(try_base_url)
if not (self.api_base_url is None or try_base_url == self.api_base_url):
raise MastodonIllegalArgumentError('Mismatch in base URLs between files and/or specified')
self.api_base_url = try_base_url
else: else:
if self.client_secret is None: if self.client_secret is None:
raise MastodonIllegalArgumentError('Specified client id directly, but did not supply secret') raise MastodonIllegalArgumentError('Specified client id directly, but did not supply secret')
@ -371,7 +386,14 @@ class Mastodon:
if self.access_token is not 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: with open(self.access_token, 'r') as token_file:
self.access_token = token_file.readline().rstrip() self.access_token = token_file.readline().rstrip()
try_base_url = token_file.readline().rstrip()
if (not try_base_url is None) and len(try_base_url) != 0:
try_base_url = Mastodon.__protocolize(try_base_url)
if not (self.api_base_url is None or try_base_url == self.api_base_url):
raise MastodonIllegalArgumentError('Mismatch in base URLs between files and/or specified')
self.api_base_url = try_base_url
def retrieve_mastodon_version(self): def retrieve_mastodon_version(self):
""" """
Determine installed mastodon version and set major, minor and patch (not including RC info) accordingly. Determine installed mastodon version and set major, minor and patch (not including RC info) accordingly.
@ -508,8 +530,9 @@ class Mastodon:
if to_file is not None: if to_file is not None:
with open(to_file, 'w') as token_file: with open(to_file, 'w') as token_file:
token_file.write(response['access_token'] + '\n') token_file.write(response['access_token'] + "\n")
token_file.write(self.api_base_url + "\n")
self.__logged_in_id = None self.__logged_in_id = None
return response['access_token'] return response['access_token']
@ -572,8 +595,9 @@ class Mastodon:
if to_file is not None: if to_file is not None:
with open(to_file, 'w') as token_file: with open(to_file, 'w') as token_file:
token_file.write(response['access_token'] + '\n') token_file.write(response['access_token'] + "\n")
token_file.write(self.api_base_url + "\n")
self.__logged_in_id = None self.__logged_in_id = None
return response['access_token'] return response['access_token']

Veure arxiu

@ -30,7 +30,6 @@ def test_log_in_password(api_anonymous):
password='mastodonadmin') password='mastodonadmin')
assert token assert token
@pytest.mark.vcr() @pytest.mark.vcr()
def test_log_in_password_incorrect(api_anonymous): def test_log_in_password_incorrect(api_anonymous):
with pytest.raises(MastodonIllegalArgumentError): with pytest.raises(MastodonIllegalArgumentError):
@ -38,7 +37,6 @@ def test_log_in_password_incorrect(api_anonymous):
username='admin@localhost', username='admin@localhost',
password='hunter2') password='hunter2')
@pytest.mark.vcr() @pytest.mark.vcr()
def test_log_in_password_to_file(api_anonymous, tmpdir): def test_log_in_password_to_file(api_anonymous, tmpdir):
filepath = tmpdir.join('token') filepath = tmpdir.join('token')
@ -46,18 +44,42 @@ def test_log_in_password_to_file(api_anonymous, tmpdir):
username='admin@localhost', username='admin@localhost',
password='mastodonadmin', password='mastodonadmin',
to_file=str(filepath)) to_file=str(filepath))
token = filepath.read_text('UTF-8').rstrip() token = filepath.read_text('UTF-8').rstrip().split("\n")[0]
assert token assert token
api = api_anonymous api = api_anonymous
api.access_token = token api.access_token = token
assert api.account_verify_credentials() assert api.account_verify_credentials()
@pytest.mark.vcr()
def test_url_errors(tmpdir):
clientid_good = tmpdir.join("clientid")
token_good = tmpdir.join("token")
clientid_bad = tmpdir.join("clientid_bad")
token_bad = tmpdir.join("token_bad")
clientid_good.write_text("foo\nbar\nhttps://zombo.com\n", "UTF-8")
token_good.write_text("foo\nhttps://zombo.com\n", "UTF-8")
clientid_bad.write_text("foo\nbar\nhttps://evil.org\n", "UTF-8")
token_bad.write_text("foo\nhttps://evil.org\n", "UTF-8")
api = Mastodon(client_id = clientid_good, access_token = token_good)
assert api
assert api.api_base_url == "https://zombo.com"
assert Mastodon(client_id = clientid_good, access_token = token_good, api_base_url = "zombo.com")
with pytest.raises(MastodonIllegalArgumentError):
Mastodon(client_id = clientid_good, access_token = token_bad, api_base_url = "zombo.com")
with pytest.raises(MastodonIllegalArgumentError):
Mastodon(client_id = clientid_bad, access_token = token_good, api_base_url = "zombo.com")
with pytest.raises(MastodonIllegalArgumentError):
Mastodon(client_id = clientid_bad, access_token = token_bad, api_base_url = "zombo.com")
@pytest.mark.skip(reason="Not sure how to test this without setting up selenium or a similar browser automation suite to click on the allow button") @pytest.mark.skip(reason="Not sure how to test this without setting up selenium or a similar browser automation suite to click on the allow button")
def test_log_in_code(api_anonymous): def test_log_in_code(api_anonymous):
pass pass
@pytest.mark.skip(reason="Not supported by Mastodon >:@ (yet?)") @pytest.mark.skip(reason="Not supported by Mastodon >:@ (yet?)")
def test_log_in_refresh(api_anonymous): def test_log_in_refresh(api_anonymous):
pass pass

Veure arxiu

@ -31,7 +31,7 @@ def test_create_app(mocker, to_file=None, redirect_uris=None, website=None):
def test_create_app_to_file(mocker, tmpdir): def test_create_app_to_file(mocker, tmpdir):
filepath = tmpdir.join('credentials') filepath = tmpdir.join('credentials')
test_create_app(mocker, to_file=str(filepath)) test_create_app(mocker, to_file=str(filepath))
assert filepath.read_text('UTF-8') == "foo\nbar\n" assert filepath.read_text('UTF-8') == "foo\nbar\nhttps://example.com\n"
def test_create_app_redirect_uris(mocker): def test_create_app_redirect_uris(mocker):
test_create_app(mocker, redirect_uris='http://example.net') test_create_app(mocker, redirect_uris='http://example.net')