aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml27
-rw-r--r--mastodon/Mastodon.py219
-rw-r--r--mastodon/statuses.py108
-rw-r--r--mastodon/timeline.py121
-rw-r--r--tests/test_status.py10
5 files changed, 256 insertions, 229 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e6accb3..3c677fe 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,5 +1,18 @@
1version: 2.1 1version: 2.1
2jobs: 2jobs:
3 run-tests-36:
4 docker:
5 - image: cimg/python:3.6
6 steps:
7 - checkout
8 - run:
9 name: "Install test deps"
10 command: "pip install .[test]"
11 - run:
12 name: "Run tests"
13 command: "python setup.py pytest --addopts '--junitxml=tests/result.xml'"
14 - store_test_results:
15 path: tests
3 run-tests-37: 16 run-tests-37:
4 docker: 17 docker:
5 - image: cimg/python:3.7 18 - image: cimg/python:3.7
@@ -9,9 +22,6 @@ jobs:
9 name: "Install test deps" 22 name: "Install test deps"
10 command: "pip install .[test]" 23 command: "pip install .[test]"
11 - run: 24 - run:
12 name: "Install codecov"
13 command: "pip install codecov"
14 - run:
15 name: "Run tests" 25 name: "Run tests"
16 command: "python setup.py pytest --addopts '--junitxml=tests/result.xml'" 26 command: "python setup.py pytest --addopts '--junitxml=tests/result.xml'"
17 - store_test_results: 27 - store_test_results:
@@ -42,10 +52,7 @@ jobs:
42 - checkout 52 - checkout
43 - run: 53 - run:
44 name: "Install test deps" 54 name: "Install test deps"
45 command: "pip install .[test]" 55 command: "pip install .[test]"
46 - run:
47 name: "Install codecov"
48 command: "pip install codecov"
49 - run: 56 - run:
50 name: "Run tests" 57 name: "Run tests"
51 command: "python setup.py pytest --addopts '--junitxml=tests/result.xml'" 58 command: "python setup.py pytest --addopts '--junitxml=tests/result.xml'"
@@ -58,10 +65,7 @@ jobs:
58 - checkout 65 - checkout
59 - run: 66 - run:
60 name: "Install test deps" 67 name: "Install test deps"
61 command: "pip install .[test]" 68 command: "pip install .[test]"
62 - run:
63 name: "Install codecov"
64 command: "pip install codecov"
65 - run: 69 - run:
66 name: "Run tests" 70 name: "Run tests"
67 command: "python setup.py pytest --addopts '--junitxml=tests/result.xml'" 71 command: "python setup.py pytest --addopts '--junitxml=tests/result.xml'"
@@ -70,6 +74,7 @@ jobs:
70workflows: 74workflows:
71 run-tests-workflow: 75 run-tests-workflow:
72 jobs: 76 jobs:
77 - run-tests-36
73 - run-tests-37 78 - run-tests-37
74 - run-tests-38-cov 79 - run-tests-38-cov
75 - run-tests-39 80 - run-tests-39
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py
index 35b8444..0ded1cf 100644
--- a/mastodon/Mastodon.py
+++ b/mastodon/Mastodon.py
@@ -43,11 +43,13 @@ from .internals import Mastodon as Internals
43from .authentication import Mastodon as Authentication 43from .authentication import Mastodon as Authentication
44from .accounts import Mastodon as Accounts 44from .accounts import Mastodon as Accounts
45from .instance import Mastodon as Instance 45from .instance import Mastodon as Instance
46from .timeline import Mastodon as Timeline
47from .statuses import Mastodon as Statuses
46 48
47## 49##
48# The actual Mastodon class 50# The actual Mastodon class
49### 51###
50class Mastodon(Utility, Authentication, Accounts, Instance): 52class Mastodon(Utility, Authentication, Accounts, Instance, Timeline, Statuses):
51 """ 53 """
52 Thorough and easy to use Mastodon 54 Thorough and easy to use Mastodon
53 API wrapper in Python. 55 API wrapper in Python.
@@ -65,221 +67,6 @@ class Mastodon(Utility, Authentication, Accounts, Instance):
65 return Mastodon.__SUPPORTED_MASTODON_VERSION 67 return Mastodon.__SUPPORTED_MASTODON_VERSION
66 68
67 ### 69 ###
68 # Reading data: Timelines
69 ##
70 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
71 def timeline(self, timeline="home", max_id=None, min_id=None, since_id=None, limit=None, only_media=False, local=False, remote=False):
72 """
73 Fetch statuses, most recent ones first. `timeline` can be 'home', 'local', 'public',
74 'tag/hashtag' or 'list/id'. See the following functions documentation for what those do.
75
76 The default timeline is the "home" timeline.
77
78 Specify `only_media` to only get posts with attached media. Specify `local` to only get local statuses,
79 and `remote` to only get remote statuses. Some options are mutually incompatible as dictated by logic.
80
81 May or may not require authentication depending on server settings and what is specifically requested.
82
83 Returns a list of :ref:`status dicts <status dicts>`.
84 """
85 if max_id is not None:
86 max_id = self.__unpack_id(max_id, dateconv=True)
87
88 if min_id is not None:
89 min_id = self.__unpack_id(min_id, dateconv=True)
90
91 if since_id is not None:
92 since_id = self.__unpack_id(since_id, dateconv=True)
93
94 params_initial = locals()
95
96 if not local:
97 del params_initial['local']
98
99 if not remote:
100 del params_initial['remote']
101
102 if not only_media:
103 del params_initial['only_media']
104
105 if timeline == "local":
106 timeline = "public"
107 params_initial['local'] = True
108
109 params = self.__generate_params(params_initial, ['timeline'])
110 url = '/api/v1/timelines/{0}'.format(timeline)
111 return self.__api_request('GET', url, params)
112
113 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
114 def timeline_home(self, max_id=None, min_id=None, since_id=None, limit=None, only_media=False, local=False, remote=False):
115 """
116 Convenience method: Fetches the logged-in user's home timeline (i.e. followed users and self). Params as in `timeline()`.
117
118 Returns a list of :ref:`status dicts <status dicts>`.
119 """
120 return self.timeline('home', max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media, local=local, remote=remote)
121
122 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
123 def timeline_local(self, max_id=None, min_id=None, since_id=None, limit=None, only_media=False):
124 """
125 Convenience method: Fetches the local / instance-wide timeline, not including replies. Params as in `timeline()`.
126
127 Returns a list of :ref:`status dicts <status dicts>`.
128 """
129 return self.timeline('local', max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media)
130
131 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
132 def timeline_public(self, max_id=None, min_id=None, since_id=None, limit=None, only_media=False, local=False, remote=False):
133 """
134 Convenience method: Fetches the public / visible-network / federated timeline, not including replies. Params as in `timeline()`.
135
136 Returns a list of :ref:`status dicts <status dicts>`.
137 """
138 return self.timeline('public', max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media, local=local, remote=remote)
139
140 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
141 def timeline_hashtag(self, hashtag, local=False, max_id=None, min_id=None, since_id=None, limit=None, only_media=False, remote=False):
142 """
143 Convenience method: Fetch a timeline of toots with a given hashtag. The hashtag parameter
144 should not contain the leading #. Params as in `timeline()`.
145
146 Returns a list of :ref:`status dicts <status dicts>`.
147 """
148 if hashtag.startswith("#"):
149 raise MastodonIllegalArgumentError(
150 "Hashtag parameter should omit leading #")
151 return self.timeline('tag/{0}'.format(hashtag), max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media, local=local, remote=remote)
152
153 @api_version("2.1.0", "3.1.4", _DICT_VERSION_STATUS)
154 def timeline_list(self, id, max_id=None, min_id=None, since_id=None, limit=None, only_media=False, local=False, remote=False):
155 """
156 Convenience method: Fetches a timeline containing all the toots by users in a given list. Params as in `timeline()`.
157
158 Returns a list of :ref:`status dicts <status dicts>`.
159 """
160 id = self.__unpack_id(id)
161 return self.timeline('list/{0}'.format(id), max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media, local=local, remote=remote)
162
163 @api_version("2.6.0", "2.6.0", _DICT_VERSION_CONVERSATION)
164 def conversations(self, max_id=None, min_id=None, since_id=None, limit=None):
165 """
166 Fetches a user's conversations.
167
168 Returns a list of :ref:`conversation dicts <conversation dicts>`.
169 """
170 if max_id is not None:
171 max_id = self.__unpack_id(max_id, dateconv=True)
172
173 if min_id is not None:
174 min_id = self.__unpack_id(min_id, dateconv=True)
175
176 if since_id is not None:
177 since_id = self.__unpack_id(since_id, dateconv=True)
178
179 params = self.__generate_params(locals())
180 return self.__api_request('GET', "/api/v1/conversations/", params)
181
182 ###
183 # Reading data: Statuses
184 ###
185 @api_version("1.0.0", "2.0.0", _DICT_VERSION_STATUS)
186 def status(self, id):
187 """
188 Fetch information about a single toot.
189
190 Does not require authentication for publicly visible statuses.
191
192 Returns a :ref:`status dict <status dict>`.
193 """
194 id = self.__unpack_id(id)
195 url = '/api/v1/statuses/{0}'.format(str(id))
196 return self.__api_request('GET', url)
197
198 @api_version("1.0.0", "3.0.0", _DICT_VERSION_CARD)
199 def status_card(self, id):
200 """
201 Fetch a card associated with a status. A card describes an object (such as an
202 external video or link) embedded into a status.
203
204 Does not require authentication for publicly visible statuses.
205
206 This function is deprecated as of 3.0.0 and the endpoint does not
207 exist anymore - you should just use the "card" field of the status dicts
208 instead. Mastodon.py will try to mimic the old behaviour, but this
209 is somewhat inefficient and not guaranteed to be the case forever.
210
211 Returns a :ref:`card dict <card dict>`.
212 """
213 if self.verify_minimum_version("3.0.0", cached=True):
214 return self.status(id).card
215 else:
216 id = self.__unpack_id(id)
217 url = '/api/v1/statuses/{0}/card'.format(str(id))
218 return self.__api_request('GET', url)
219
220 @api_version("1.0.0", "1.0.0", _DICT_VERSION_CONTEXT)
221 def status_context(self, id):
222 """
223 Fetch information about ancestors and descendants of a toot.
224
225 Does not require authentication for publicly visible statuses.
226
227 Returns a :ref:`context dict <context dict>`.
228 """
229 id = self.__unpack_id(id)
230 url = '/api/v1/statuses/{0}/context'.format(str(id))
231 return self.__api_request('GET', url)
232
233 @api_version("1.0.0", "2.1.0", _DICT_VERSION_ACCOUNT)
234 def status_reblogged_by(self, id):
235 """
236 Fetch a list of users that have reblogged a status.
237
238 Does not require authentication for publicly visible statuses.
239
240 Returns a list of :ref:`account dicts <account dicts>`.
241 """
242 id = self.__unpack_id(id)
243 url = '/api/v1/statuses/{0}/reblogged_by'.format(str(id))
244 return self.__api_request('GET', url)
245
246 @api_version("1.0.0", "2.1.0", _DICT_VERSION_ACCOUNT)
247 def status_favourited_by(self, id):
248 """
249 Fetch a list of users that have favourited a status.
250
251 Does not require authentication for publicly visible statuses.
252
253 Returns a list of :ref:`account dicts <account dicts>`.
254 """
255 id = self.__unpack_id(id)
256 url = '/api/v1/statuses/{0}/favourited_by'.format(str(id))
257 return self.__api_request('GET', url)
258
259 ###
260 # Reading data: Scheduled statuses
261 ###
262 @api_version("2.7.0", "2.7.0", _DICT_VERSION_SCHEDULED_STATUS)
263 def scheduled_statuses(self):
264 """
265 Fetch a list of scheduled statuses
266
267 Returns a list of :ref:`scheduled status dicts <scheduled status dicts>`.
268 """
269 return self.__api_request('GET', '/api/v1/scheduled_statuses')
270
271 @api_version("2.7.0", "2.7.0", _DICT_VERSION_SCHEDULED_STATUS)
272 def scheduled_status(self, id):
273 """
274 Fetch information about the scheduled status with the given id.
275
276 Returns a :ref:`scheduled status dict <scheduled status dict>`.
277 """
278 id = self.__unpack_id(id)
279 url = '/api/v1/scheduled_statuses/{0}'.format(str(id))
280 return self.__api_request('GET', url)
281
282 ###
283 # Reading data: Polls 70 # Reading data: Polls
284 ### 71 ###
285 @api_version("2.8.0", "2.8.0", _DICT_VERSION_POLL) 72 @api_version("2.8.0", "2.8.0", _DICT_VERSION_POLL)
diff --git a/mastodon/statuses.py b/mastodon/statuses.py
new file mode 100644
index 0000000..ae891d5
--- /dev/null
+++ b/mastodon/statuses.py
@@ -0,0 +1,108 @@
1
2from .versions import _DICT_VERSION_STATUS, _DICT_VERSION_CARD, _DICT_VERSION_CONTEXT, _DICT_VERSION_ACCOUNT, _DICT_VERSION_SCHEDULED_STATUS
3from .utility import api_version
4
5from .internals import Mastodon as Internals
6
7
8class Mastodon(Internals):
9 ###
10 # Reading data: Statuses
11 ###
12 @api_version("1.0.0", "2.0.0", _DICT_VERSION_STATUS)
13 def status(self, id):
14 """
15 Fetch information about a single toot.
16
17 Does not require authentication for publicly visible statuses.
18
19 Returns a :ref:`status dict <status dict>`.
20 """
21 id = self.__unpack_id(id)
22 url = '/api/v1/statuses/{0}'.format(str(id))
23 return self.__api_request('GET', url)
24
25 @api_version("1.0.0", "3.0.0", _DICT_VERSION_CARD)
26 def status_card(self, id):
27 """
28 Fetch a card associated with a status. A card describes an object (such as an
29 external video or link) embedded into a status.
30
31 Does not require authentication for publicly visible statuses.
32
33 This function is deprecated as of 3.0.0 and the endpoint does not
34 exist anymore - you should just use the "card" field of the status dicts
35 instead. Mastodon.py will try to mimic the old behaviour, but this
36 is somewhat inefficient and not guaranteed to be the case forever.
37
38 Returns a :ref:`card dict <card dict>`.
39 """
40 if self.verify_minimum_version("3.0.0", cached=True):
41 return self.status(id).card
42 else:
43 id = self.__unpack_id(id)
44 url = '/api/v1/statuses/{0}/card'.format(str(id))
45 return self.__api_request('GET', url)
46
47 @api_version("1.0.0", "1.0.0", _DICT_VERSION_CONTEXT)
48 def status_context(self, id):
49 """
50 Fetch information about ancestors and descendants of a toot.
51
52 Does not require authentication for publicly visible statuses.
53
54 Returns a :ref:`context dict <context dict>`.
55 """
56 id = self.__unpack_id(id)
57 url = '/api/v1/statuses/{0}/context'.format(str(id))
58 return self.__api_request('GET', url)
59
60 @api_version("1.0.0", "2.1.0", _DICT_VERSION_ACCOUNT)
61 def status_reblogged_by(self, id):
62 """
63 Fetch a list of users that have reblogged a status.
64
65 Does not require authentication for publicly visible statuses.
66
67 Returns a list of :ref:`account dicts <account dicts>`.
68 """
69 id = self.__unpack_id(id)
70 url = '/api/v1/statuses/{0}/reblogged_by'.format(str(id))
71 return self.__api_request('GET', url)
72
73 @api_version("1.0.0", "2.1.0", _DICT_VERSION_ACCOUNT)
74 def status_favourited_by(self, id):
75 """
76 Fetch a list of users that have favourited a status.
77
78 Does not require authentication for publicly visible statuses.
79
80 Returns a list of :ref:`account dicts <account dicts>`.
81 """
82 id = self.__unpack_id(id)
83 url = '/api/v1/statuses/{0}/favourited_by'.format(str(id))
84 return self.__api_request('GET', url)
85
86 ###
87 # Reading data: Scheduled statuses
88 ###
89 @api_version("2.7.0", "2.7.0", _DICT_VERSION_SCHEDULED_STATUS)
90 def scheduled_statuses(self):
91 """
92 Fetch a list of scheduled statuses
93
94 Returns a list of :ref:`scheduled status dicts <scheduled status dicts>`.
95 """
96 return self.__api_request('GET', '/api/v1/scheduled_statuses')
97
98 @api_version("2.7.0", "2.7.0", _DICT_VERSION_SCHEDULED_STATUS)
99 def scheduled_status(self, id):
100 """
101 Fetch information about the scheduled status with the given id.
102
103 Returns a :ref:`scheduled status dict <scheduled status dict>`.
104 """
105 id = self.__unpack_id(id)
106 url = '/api/v1/scheduled_statuses/{0}'.format(str(id))
107 return self.__api_request('GET', url)
108 \ No newline at end of file
diff --git a/mastodon/timeline.py b/mastodon/timeline.py
new file mode 100644
index 0000000..b5a4068
--- /dev/null
+++ b/mastodon/timeline.py
@@ -0,0 +1,121 @@
1from .versions import _DICT_VERSION_STATUS, _DICT_VERSION_CONVERSATION
2from .error import MastodonIllegalArgumentError, MastodonNotFoundError
3from .utility import api_version
4
5from .internals import Mastodon as Internals
6
7class Mastodon(Internals):
8 ###
9 # Reading data: Timelines
10 ##
11 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
12 def timeline(self, timeline="home", max_id=None, min_id=None, since_id=None, limit=None, only_media=False, local=False, remote=False):
13 """
14 Fetch statuses, most recent ones first. `timeline` can be 'home', 'local', 'public',
15 'tag/hashtag' or 'list/id'. See the following functions documentation for what those do.
16
17 The default timeline is the "home" timeline.
18
19 Specify `only_media` to only get posts with attached media. Specify `local` to only get local statuses,
20 and `remote` to only get remote statuses. Some options are mutually incompatible as dictated by logic.
21
22 May or may not require authentication depending on server settings and what is specifically requested.
23
24 Returns a list of :ref:`status dicts <status dicts>`.
25 """
26 if max_id is not None:
27 max_id = self.__unpack_id(max_id, dateconv=True)
28
29 if min_id is not None:
30 min_id = self.__unpack_id(min_id, dateconv=True)
31
32 if since_id is not None:
33 since_id = self.__unpack_id(since_id, dateconv=True)
34
35 params_initial = locals()
36
37 if not local:
38 del params_initial['local']
39
40 if not remote:
41 del params_initial['remote']
42
43 if not only_media:
44 del params_initial['only_media']
45
46 if timeline == "local":
47 timeline = "public"
48 params_initial['local'] = True
49
50 params = self.__generate_params(params_initial, ['timeline'])
51 url = '/api/v1/timelines/{0}'.format(timeline)
52 return self.__api_request('GET', url, params)
53
54 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
55 def timeline_home(self, max_id=None, min_id=None, since_id=None, limit=None, only_media=False, local=False, remote=False):
56 """
57 Convenience method: Fetches the logged-in user's home timeline (i.e. followed users and self). Params as in `timeline()`.
58
59 Returns a list of :ref:`status dicts <status dicts>`.
60 """
61 return self.timeline('home', max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media, local=local, remote=remote)
62
63 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
64 def timeline_local(self, max_id=None, min_id=None, since_id=None, limit=None, only_media=False):
65 """
66 Convenience method: Fetches the local / instance-wide timeline, not including replies. Params as in `timeline()`.
67
68 Returns a list of :ref:`status dicts <status dicts>`.
69 """
70 return self.timeline('local', max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media)
71
72 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
73 def timeline_public(self, max_id=None, min_id=None, since_id=None, limit=None, only_media=False, local=False, remote=False):
74 """
75 Convenience method: Fetches the public / visible-network / federated timeline, not including replies. Params as in `timeline()`.
76
77 Returns a list of :ref:`status dicts <status dicts>`.
78 """
79 return self.timeline('public', max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media, local=local, remote=remote)
80
81 @api_version("1.0.0", "3.1.4", _DICT_VERSION_STATUS)
82 def timeline_hashtag(self, hashtag, local=False, max_id=None, min_id=None, since_id=None, limit=None, only_media=False, remote=False):
83 """
84 Convenience method: Fetch a timeline of toots with a given hashtag. The hashtag parameter
85 should not contain the leading #. Params as in `timeline()`.
86
87 Returns a list of :ref:`status dicts <status dicts>`.
88 """
89 if hashtag.startswith("#"):
90 raise MastodonIllegalArgumentError(
91 "Hashtag parameter should omit leading #")
92 return self.timeline('tag/{0}'.format(hashtag), max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media, local=local, remote=remote)
93
94 @api_version("2.1.0", "3.1.4", _DICT_VERSION_STATUS)
95 def timeline_list(self, id, max_id=None, min_id=None, since_id=None, limit=None, only_media=False, local=False, remote=False):
96 """
97 Convenience method: Fetches a timeline containing all the toots by users in a given list. Params as in `timeline()`.
98
99 Returns a list of :ref:`status dicts <status dicts>`.
100 """
101 id = self.__unpack_id(id)
102 return self.timeline('list/{0}'.format(id), max_id=max_id, min_id=min_id, since_id=since_id, limit=limit, only_media=only_media, local=local, remote=remote)
103
104 @api_version("2.6.0", "2.6.0", _DICT_VERSION_CONVERSATION)
105 def conversations(self, max_id=None, min_id=None, since_id=None, limit=None):
106 """
107 Fetches a user's conversations.
108
109 Returns a list of :ref:`conversation dicts <conversation dicts>`.
110 """
111 if max_id is not None:
112 max_id = self.__unpack_id(max_id, dateconv=True)
113
114 if min_id is not None:
115 min_id = self.__unpack_id(min_id, dateconv=True)
116
117 if since_id is not None:
118 since_id = self.__unpack_id(since_id, dateconv=True)
119
120 params = self.__generate_params(locals())
121 return self.__api_request('GET', "/api/v1/conversations/", params)
diff --git a/tests/test_status.py b/tests/test_status.py
index 1fa7fd5..8461bc3 100644
--- a/tests/test_status.py
+++ b/tests/test_status.py
@@ -1,7 +1,13 @@
1import pytest 1import pytest
2from mastodon.Mastodon import MastodonAPIError, MastodonNotFoundError 2from mastodon.Mastodon import MastodonAPIError, MastodonNotFoundError
3import datetime 3import datetime
4import zoneinfo 4try:
5 import zoneinfo
6 timezone = zoneinfo.ZoneInfo
7except:
8 import pytz
9 timezone = pytz.timezone
10
5import vcr 11import vcr
6import time 12import time
7import pickle 13import pickle
@@ -154,7 +160,7 @@ def test_status_pin_unpin(status, api):
154 160
155@pytest.mark.vcr(match_on=['path']) 161@pytest.mark.vcr(match_on=['path'])
156def test_scheduled_status(api): 162def test_scheduled_status(api):
157 base_time = datetime.datetime(4000, 1, 1, 12, 13, 14, 0, zoneinfo.ZoneInfo("Etc/GMT+2")) 163 base_time = datetime.datetime(4000, 1, 1, 12, 13, 14, 0, timezone("Etc/GMT+2"))
158 the_future = base_time + datetime.timedelta(minutes=20) 164 the_future = base_time + datetime.timedelta(minutes=20)
159 scheduled_toot = api.status_post("please ensure adequate headroom", scheduled_at=the_future) 165 scheduled_toot = api.status_post("please ensure adequate headroom", scheduled_at=the_future)
160 assert scheduled_toot 166 assert scheduled_toot
Powered by cgit v1.2.3 (git 2.41.0)