aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAljoscha Rittner <[email protected]>2022-06-24 18:11:23 +0200
committerGitHub <[email protected]>2022-06-24 18:11:23 +0200
commitc7fdcf3faec29040d32b5a9afa81859cc2195d99 (patch)
tree5f7c2a66e063b80e77b7be1fe2c24be986e70e1d
parentc9008a1cdcd31ba47c10d7b6f35cc7aceffe81f5 (diff)
parenta987af8a0d20db59f1531492e2313ac8cce81f48 (diff)
downloadmastodon.py-c7fdcf3faec29040d32b5a9afa81859cc2195d99.tar.gz
Merge pull request #235 from arittner/stream-unknown-handler
Support of processing unknown events and event names with dots.
-rw-r--r--docs/index.rst14
-rw-r--r--mastodon/streaming.py50
-rw-r--r--tests/test_streaming.py40
3 files changed, 89 insertions, 15 deletions
diff --git a/docs/index.rst b/docs/index.rst
index 473777a..e7b1ed9 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1275,6 +1275,19 @@ The streaming functions take instances of `StreamListener` as the `listener` par
1275A `CallbackStreamListener` class that allows you to specify function callbacks 1275A `CallbackStreamListener` class that allows you to specify function callbacks
1276directly is included for convenience. 1276directly is included for convenience.
1277 1277
1278For new well-known events implement the streaming function in `StreamListener` or `CallbackStreamListener`.
1279The function name is `on_` + the event name. If the event-name contains dots, use an underscore instead.
1280
1281E.g. for `'status.update'` the listener function should be named as `on_status_update`.
1282
1283It may be that future Mastodon versions will come with completely new (unknown) event names. In this
1284case a (deprecated) Mastodon.py would throw an error. If you want to avoid this in general, you can
1285override the listener function `on_unknown_event`. This has an additional parameter `name` which informs
1286about the name of the event. `unknown_event` contains the content of the event.
1287
1288Alternatively, a callback function can be passed in the `unknown_event_handler` parameter in the
1289`CallbackStreamListener` constructor.
1290
1278When in not-async mode or async mode without async_reconnect, the stream functions may raise 1291When in not-async mode or async mode without async_reconnect, the stream functions may raise
1279various exceptions: `MastodonMalformedEventError` if a received event cannot be parsed and 1292various exceptions: `MastodonMalformedEventError` if a received event cannot be parsed and
1280`MastodonNetworkError` if any connection problems occur. 1293`MastodonNetworkError` if any connection problems occur.
@@ -1294,6 +1307,7 @@ StreamListener
1294.. automethod:: StreamListener.on_notification 1307.. automethod:: StreamListener.on_notification
1295.. automethod:: StreamListener.on_delete 1308.. automethod:: StreamListener.on_delete
1296.. automethod:: StreamListener.on_conversation 1309.. automethod:: StreamListener.on_conversation
1310.. automethod:: StreamListener.on_unknown_event
1297.. automethod:: StreamListener.on_abort 1311.. automethod:: StreamListener.on_abort
1298.. automethod:: StreamListener.handle_heartbeat 1312.. automethod:: StreamListener.handle_heartbeat
1299 1313
diff --git a/mastodon/streaming.py b/mastodon/streaming.py
index 214ed1c..ceb61ea 100644
--- a/mastodon/streaming.py
+++ b/mastodon/streaming.py
@@ -45,6 +45,16 @@ class StreamListener(object):
45 contains the resulting conversation dict.""" 45 contains the resulting conversation dict."""
46 pass 46 pass
47 47
48 def on_unknown_event(self, name, unknown_event = None):
49 """An unknown mastodon API event has been received. The name contains the event-name and unknown_event
50 contains the content of the unknown event.
51
52 This function must be implemented, if unknown events should be handled without an error.
53 """
54 exception = MastodonMalformedEventError('Bad event type', name)
55 self.on_abort(exception)
56 raise exception
57
48 def handle_heartbeat(self): 58 def handle_heartbeat(self):
49 """The server has sent us a keep-alive message. This callback may be 59 """The server has sent us a keep-alive message. This callback may be
50 useful to carry out periodic housekeeping tasks, or just to confirm 60 useful to carry out periodic housekeeping tasks, or just to confirm
@@ -56,6 +66,11 @@ class StreamListener(object):
56 Handles a stream of events from the Mastodon server. When each event 66 Handles a stream of events from the Mastodon server. When each event
57 is received, the corresponding .on_[name]() method is called. 67 is received, the corresponding .on_[name]() method is called.
58 68
69 When the Mastodon API changes, the on_unknown_event(name, content)
70 function is called.
71 The default behavior is to throw an error. Define a callback handler
72 to intercept unknown events if needed (and avoid errors)
73
59 response; a requests response object with the open stream for reading. 74 response; a requests response object with the open stream for reading.
60 """ 75 """
61 event = {} 76 event = {}
@@ -137,33 +152,32 @@ class StreamListener(object):
137 exception, 152 exception,
138 err 153 err
139 ) 154 )
140 155 # New mastodon API also supports event names with dots:
141 handler_name = 'on_' + name 156 handler_name = 'on_' + name.replace('.', '_')
142 try: 157 # A generic way to handle unknown events to make legacy code more stable for future changes
143 handler = getattr(self, handler_name) 158 handler = getattr(self, handler_name, self.on_unknown_event)
144 except AttributeError as err: 159 if handler != self.on_unknown_event:
145 exception = MastodonMalformedEventError('Bad event type', name)
146 self.on_abort(exception)
147 six.raise_from(
148 exception,
149 err
150 )
151 else:
152 handler(payload) 160 handler(payload)
161 else:
162 handler(name, payload)
163
153 164
154class CallbackStreamListener(StreamListener): 165class CallbackStreamListener(StreamListener):
155 """ 166 """
156 Simple callback stream handler class. 167 Simple callback stream handler class.
157 Can optionally additionally send local update events to a separate handler. 168 Can optionally additionally send local update events to a separate handler.
169 Define an unknown_event_handler for new Mastodon API events. If not, the
170 listener will raise an error on new, not handled, events from the API.
158 """ 171 """
159 def __init__(self, update_handler = None, local_update_handler = None, delete_handler = None, notification_handler = None, conversation_handler = None): 172 def __init__(self, update_handler = None, local_update_handler = None, delete_handler = None, notification_handler = None, conversation_handler = None, unknown_event_handler = None):
160 super(CallbackStreamListener, self).__init__() 173 super(CallbackStreamListener, self).__init__()
161 self.update_handler = update_handler 174 self.update_handler = update_handler
162 self.local_update_handler = local_update_handler 175 self.local_update_handler = local_update_handler
163 self.delete_handler = delete_handler 176 self.delete_handler = delete_handler
164 self.notification_handler = notification_handler 177 self.notification_handler = notification_handler
165 self.conversation_handler = conversation_handler 178 self.conversation_handler = conversation_handler
166 179 self.unknown_event_handler = unknown_event_handler
180
167 def on_update(self, status): 181 def on_update(self, status):
168 if self.update_handler != None: 182 if self.update_handler != None:
169 self.update_handler(status) 183 self.update_handler(status)
@@ -188,3 +202,11 @@ class CallbackStreamListener(StreamListener):
188 def on_conversation(self, conversation): 202 def on_conversation(self, conversation):
189 if self.conversation_handler != None: 203 if self.conversation_handler != None:
190 self.conversation_handler(conversation) 204 self.conversation_handler(conversation)
205
206 def on_unknown_event(self, name, unknown_event = None):
207 if self.unknown_event_handler != None:
208 self.unknown_event_handler(name, unknown_event)
209 else:
210 exception = MastodonMalformedEventError('Bad event type', name)
211 self.on_abort(exception)
212 raise exception
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
index cddb79a..8912b9c 100644
--- a/tests/test_streaming.py
+++ b/tests/test_streaming.py
@@ -61,6 +61,8 @@ class Listener(StreamListener):
61 self.notifications = [] 61 self.notifications = []
62 self.deletes = [] 62 self.deletes = []
63 self.heartbeats = 0 63 self.heartbeats = 0
64 self.bla_called = False
65 self.do_something_called = False
64 66
65 def on_update(self, status): 67 def on_update(self, status):
66 self.updates.append(status) 68 self.updates.append(status)
@@ -72,6 +74,11 @@ class Listener(StreamListener):
72 self.deletes.append(status_id) 74 self.deletes.append(status_id)
73 75
74 def on_blahblah(self, data): 76 def on_blahblah(self, data):
77 self.bla_called = True
78 pass
79
80 def on_do_something(self, data):
81 self.do_something_called = True
75 pass 82 pass
76 83
77 def handle_heartbeat(self): 84 def handle_heartbeat(self):
@@ -158,6 +165,37 @@ def test_unknown_event():
158 'data: {}', 165 'data: {}',
159 '', 166 '',
160 ]) 167 ])
168 assert listener.bla_called == True
169 assert listener.updates == []
170 assert listener.notifications == []
171 assert listener.deletes == []
172 assert listener.heartbeats == 0
173
174def test_unknown_handled_event():
175 """Be tolerant of new unknown event types, if on_unknown_event is available"""
176 listener = Listener()
177 listener.on_unknown_event = lambda name, payload: None
178
179 listener.handle_stream_([
180 'event: complete.new.event',
181 'data: {"k": "v"}',
182 '',
183 ])
184
185 assert listener.updates == []
186 assert listener.notifications == []
187 assert listener.deletes == []
188 assert listener.heartbeats == 0
189
190def test_dotted_unknown_event():
191 """Be tolerant of new event types with dots in the event-name"""
192 listener = Listener()
193 listener.handle_stream_([
194 'event: do.something',
195 'data: {}',
196 '',
197 ])
198 assert listener.do_something_called == True
161 assert listener.updates == [] 199 assert listener.updates == []
162 assert listener.notifications == [] 200 assert listener.notifications == []
163 assert listener.deletes == [] 201 assert listener.deletes == []
@@ -169,7 +207,7 @@ def test_invalid_event():
169 with pytest.raises(MastodonMalformedEventError): 207 with pytest.raises(MastodonMalformedEventError):
170 listener.handle_stream_([ 208 listener.handle_stream_([
171 'event: whatup', 209 'event: whatup',
172 'data: {}', 210 'data: {"k": "v"}',
173 '', 211 '',
174 ]) 212 ])
175 213
Powered by cgit v1.2.3 (git 2.41.0)