Implement, test and document featured and suggested tags APIs (fixes #191)

This commit is contained in:
Lorenz Diener 2019-10-12 21:47:58 +02:00
pare 3194b1295e
commit 63bf5afc61
S'han modificat 5 arxius amb 312 adicions i 1 eliminacions

Veure arxiu

@ -756,6 +756,21 @@ Preference dicts
# content warnings by default
}
Featured tag dicts
~~~~~~~~~~~~~~~~~~
.. _featured tag dict:
.. code-block:: python
mastodon.featured_tags()[0]
# Returns the following dictionary:
{
'id': # The featured tags id
'name': # The featured tags name (without leading #)
'statuses_count': # Number of publicly visible statuses posted with this hashtag that this instance knows about
'last_status_at': # The last time a public status containing this hashtag was added to this instances database
# (can be None if there are none)
}
Admin account dicts
~~~~~~~~~~~~~~~~~~~
@ -907,6 +922,13 @@ their relationships.
.. automethod:: Mastodon.account_relationships
.. automethod:: Mastodon.account_search
Reading data: Featured tags
~~~~~~~~~~~~~~~~~~~~~~~~~~~
These functions allow retrieving info about a users featured and suggested tags.
.. automethod:: Mastodon.featured_tags
.. automethod:: Mastodon.featured_tag_suggestions
Reading data: Keyword filters
-----------------------------
These functions allow you to get information about keyword filters.
@ -1058,6 +1080,13 @@ These functions allow you to interact with other accounts: To (un)follow and
.. automethod:: Mastodon.account_unpin
.. automethod:: Mastodon.account_update_credentials
Writing data: Featured tags
~~~~~~~~~~~~~~~~~~~~~~~~~~~
These functions allow setting which tags are featured on a users profile.
.. automethod:: Mastodon.featured_tag_create
.. automethod:: Mastodon.featured_tag_delete
Writing data: Keyword filters
-----------------------------
These functions allow you to manipulate keyword filters.

Veure arxiu

@ -212,6 +212,7 @@ class Mastodon:
__DICT_VERSION_SCHEDULED_STATUS = bigger_version("2.7.0", __DICT_VERSION_STATUS)
__DICT_VERSION_PREFERENCES = "2.8.0"
__DICT_VERSION_ADMIN_ACCOUNT = "2.9.1"
__DICT_VERSION_FEATURED_TAG = "3.0.0"
###
# Registering apps
@ -1144,6 +1145,28 @@ class Mastodon:
url = '/api/v1/accounts/{0}/lists'.format(str(id))
return self.__api_request('GET', url, params)
###
# Reading data: Featured hashtags
###
@api_version("3.0.0", "3.0.0", __DICT_VERSION_FEATURED_TAG)
def featured_tags(self):
"""
Return the hashtags the logged-in user has set to be featured on
their profile as a list of `featured tag dicts`_.
Returns a list of `featured tag dicts`_.
"""
return self.__api_request('GET', '/api/v1/featured_tags')
@api_version("3.0.0", "3.0.0", __DICT_VERSION_HASHTAG)
def featured_tag_suggestions(self):
"""
Returns the logged-in users 10 most commonly hashtags.
Returns a list of `hashtag dicts`_.
"""
return self.__api_request('GET', '/api/v1/featured_tags/suggestions')
###
# Reading data: Keyword filters
###
@ -2137,6 +2160,28 @@ class Mastodon:
url = '/api/v1/accounts/{0}/unpin'.format(str(id))
return self.__api_request('POST', url)
###
# Writing data: Featured hashtags
###
@api_version("3.0.0", "3.0.0", __DICT_VERSION_FEATURED_TAG)
def featured_tag_create(self, name):
"""
Creates a new featured hashtag displayed on the logged-in users profile.
Returns a `featured tag dict`_ with the newly featured tag.
"""
params = self.__generate_params(locals())
return self.__api_request('POST', '/api/v1/featured_tags', params)
@api_version("3.0.0", "3.0.0", __DICT_VERSION_FEATURED_TAG)
def featured_tag_delete(self, id):
"""
Deletes one of the logged-in users featured hashtags.
"""
id = self.__unpack_id(id)
url = '/api/v1/featured_tags/{0}'.format(str(id))
self.__api_request('DELETE', url)
###
# Writing data: Keyword filters
###
@ -2982,7 +3027,7 @@ class Mastodon:
"""
Parse dates in certain known json fields, if possible.
"""
known_date_fields = ["created_at", "week", "day", "expires_at", "scheduled_at", "updated_at"]
known_date_fields = ["created_at", "week", "day", "expires_at", "scheduled_at", "updated_at", "last_status_at"]
for k, v in json_object.items():
if k in known_date_fields:
if v != None:

Veure arxiu

@ -0,0 +1,113 @@
interactions:
- request:
body: name=mastopytesttag
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
Connection: [keep-alive]
Content-Length: ['19']
Content-Type: [application/x-www-form-urlencoded]
User-Agent: [python-requests/2.18.4]
method: POST
uri: http://localhost:3000/api/v1/featured_tags
response:
body: {string: '{"id":"1","name":"mastopytesttag","statuses_count":0,"last_status_at":null}'}
headers:
Cache-Control: ['no-cache, no-store']
Content-Type: [application/json; charset=utf-8]
Referrer-Policy: [strict-origin-when-cross-origin]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Download-Options: [noopen]
X-Frame-Options: [SAMEORIGIN]
X-Permitted-Cross-Domain-Policies: [none]
X-Request-Id: [319b60a5-1408-4515-9b41-91a9eb90b81c]
X-Runtime: ['0.122775']
X-XSS-Protection: [1; mode=block]
content-length: ['75']
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
Connection: [keep-alive]
User-Agent: [python-requests/2.18.4]
method: GET
uri: http://localhost:3000/api/v1/featured_tags
response:
body: {string: '[{"id":"1","name":"mastopytesttag","statuses_count":0,"last_status_at":null}]'}
headers:
Cache-Control: ['no-cache, no-store']
Content-Type: [application/json; charset=utf-8]
Referrer-Policy: [strict-origin-when-cross-origin]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Download-Options: [noopen]
X-Frame-Options: [SAMEORIGIN]
X-Permitted-Cross-Domain-Policies: [none]
X-Request-Id: [6c35b860-5264-46a1-9219-388f2d65f038]
X-Runtime: ['0.030432']
X-XSS-Protection: [1; mode=block]
content-length: ['77']
status: {code: 200, message: OK}
- 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/featured_tags/1
response:
body: {string: '{}'}
headers:
Cache-Control: ['no-cache, no-store']
Content-Type: [application/json; charset=utf-8]
Referrer-Policy: [strict-origin-when-cross-origin]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Download-Options: [noopen]
X-Frame-Options: [SAMEORIGIN]
X-Permitted-Cross-Domain-Policies: [none]
X-Request-Id: [7c062d77-90bd-4400-9476-42495d98d77b]
X-Runtime: ['0.031225']
X-XSS-Protection: [1; mode=block]
content-length: ['2']
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
Connection: [keep-alive]
User-Agent: [python-requests/2.18.4]
method: GET
uri: http://localhost:3000/api/v1/featured_tags
response:
body: {string: '[]'}
headers:
Cache-Control: ['no-cache, no-store']
Content-Type: [application/json; charset=utf-8]
Referrer-Policy: [strict-origin-when-cross-origin]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Download-Options: [noopen]
X-Frame-Options: [SAMEORIGIN]
X-Permitted-Cross-Domain-Policies: [none]
X-Request-Id: [8e5eaf25-5e6a-4998-bb44-188b123b726c]
X-Runtime: ['0.020343']
X-XSS-Protection: [1; mode=block]
content-length: ['2']
status: {code: 200, message: OK}
version: 1

Veure arxiu

@ -0,0 +1,90 @@
interactions:
- request:
body: status=cool+free+%23ringtones
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
Connection: [keep-alive]
Content-Length: ['29']
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":"102951123020457171","created_at":"2019-10-12T19:44:29.956Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost/users/mastodonpy_test/statuses/102951123020457171","url":"http://localhost/@mastodonpy_test/102951123020457171","replies_count":0,"reblogs_count":0,"favourites_count":0,"favourited":false,"reblogged":false,"muted":false,"pinned":false,"content":"\u003cp\u003ecool
free \u003ca href=\"http://localhost/tags/ringtones\" class=\"mention hashtag\"
rel=\"tag\"\u003e#\u003cspan\u003eringtones\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","reblog":null,"application":{"name":"Mastodon.py
test suite","website":null},"account":{"id":"1234567890123456","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":false,"bot":false,"created_at":"2019-06-22T23:11:52.441Z","note":"\u003cp\u003e\u003c/p\u003e","url":"http://localhost/@mastodonpy_test","avatar":"http://localhost/avatars/original/missing.png","avatar_static":"http://localhost/avatars/original/missing.png","header":"http://localhost/headers/original/missing.png","header_static":"http://localhost/headers/original/missing.png","followers_count":0,"following_count":1,"statuses_count":3,"last_status_at":"2019-10-12T19:44:29.976Z","emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"ringtones","url":"http://localhost/tags/ringtones"}],"emojis":[],"card":null,"poll":null}'}
headers:
Cache-Control: ['no-cache, no-store']
Content-Type: [application/json; charset=utf-8]
Referrer-Policy: [strict-origin-when-cross-origin]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Download-Options: [noopen]
X-Frame-Options: [SAMEORIGIN]
X-Permitted-Cross-Domain-Policies: [none]
X-Request-Id: [7c348bde-1d33-451a-8ae0-87cb09d8a1a2]
X-Runtime: ['0.222922']
X-XSS-Protection: [1; mode=block]
content-length: ['1494']
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
Connection: [keep-alive]
User-Agent: [python-requests/2.18.4]
method: GET
uri: http://localhost:3000/api/v1/featured_tags/suggestions
response:
body: {string: '[{"name":"ringtones","url":"http://localhost/tags/ringtones","history":[{"day":"1570838400","uses":"1","accounts":"1"},{"day":"1570752000","uses":"0","accounts":"0"},{"day":"1570665600","uses":"0","accounts":"0"},{"day":"1570579200","uses":"0","accounts":"0"},{"day":"1570492800","uses":"0","accounts":"0"},{"day":"1570406400","uses":"0","accounts":"0"},{"day":"1570320000","uses":"0","accounts":"0"}]}]'}
headers:
Cache-Control: ['no-cache, no-store']
Content-Type: [application/json; charset=utf-8]
Referrer-Policy: [strict-origin-when-cross-origin]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Download-Options: [noopen]
X-Frame-Options: [SAMEORIGIN]
X-Permitted-Cross-Domain-Policies: [none]
X-Request-Id: [fc6ec1a8-9c2e-4c08-925d-f80522d5bf65]
X-Runtime: ['0.061457']
X-XSS-Protection: [1; mode=block]
content-length: ['403']
status: {code: 200, message: OK}
- 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/102951123020457171
response:
body: {string: '{"id":"102951123020457171","created_at":"2019-10-12T19:44:29.956Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost/users/mastodonpy_test/statuses/102951123020457171","url":"http://localhost/@mastodonpy_test/102951123020457171","replies_count":0,"reblogs_count":0,"favourites_count":0,"favourited":false,"reblogged":false,"muted":false,"pinned":false,"text":"cool
free #ringtones","reblog":null,"application":{"name":"Mastodon.py test suite","website":null},"account":{"id":"1234567890123456","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":false,"bot":false,"created_at":"2019-06-22T23:11:52.441Z","note":"\u003cp\u003e\u003c/p\u003e","url":"http://localhost/@mastodonpy_test","avatar":"http://localhost/avatars/original/missing.png","avatar_static":"http://localhost/avatars/original/missing.png","header":"http://localhost/headers/original/missing.png","header_static":"http://localhost/headers/original/missing.png","followers_count":0,"following_count":1,"statuses_count":3,"last_status_at":"2019-10-12T19:44:29.976Z","emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"ringtones","url":"http://localhost/tags/ringtones"}],"emojis":[],"card":null,"poll":null}'}
headers:
Cache-Control: ['no-cache, no-store']
Content-Type: [application/json; charset=utf-8]
Referrer-Policy: [strict-origin-when-cross-origin]
Transfer-Encoding: [chunked]
Vary: ['Accept-Encoding, Origin']
X-Content-Type-Options: [nosniff]
X-Download-Options: [noopen]
X-Frame-Options: [SAMEORIGIN]
X-Permitted-Cross-Domain-Policies: [none]
X-Request-Id: [68301b80-982f-4397-8c6b-d9e7d8cd308b]
X-Runtime: ['0.118422']
X-XSS-Protection: [1; mode=block]
content-length: ['1325']
status: {code: 200, message: OK}
version: 1

Veure arxiu

@ -1,6 +1,7 @@
import pytest
from mastodon.Mastodon import MastodonAPIError, MastodonIllegalArgumentError
import re
import time
@pytest.mark.vcr()
def test_account(api):
@ -215,3 +216,36 @@ def test_account_pin_unpin(api, api2):
def test_preferences(api):
prefs = api.preferences()
assert prefs
@pytest.mark.vcr()
def test_suggested_tags(api):
try:
status = api.status_post("cool free #ringtones")
time.sleep(2)
suggests = api.featured_tag_suggestions()
assert suggests
assert len(suggests) > 0
finally:
api.status_delete(status)
@pytest.mark.vcr()
def test_featured_tags(api):
featured_tag = api.featured_tag_create("mastopytesttag")
assert featured_tag
tag_list = api.featured_tags()
assert featured_tag.name in list(map(lambda x: x.name, tag_list))
api.featured_tag_delete(featured_tag)
tag_list = api.featured_tags()
assert not featured_tag.name in list(map(lambda x: x.name, tag_list))