From af59a460687864341b544159a714e4de3891ff08 Mon Sep 17 00:00:00 2001 From: Aljoscha Rittner Date: Mon, 20 Jun 2022 12:23:37 +0200 Subject: [PATCH] Support of processing unknown events and event names with dots. #Fixes 234 --- mastodon/streaming.py | 50 +++++++++++++++++++++++++++++------------ tests/test_streaming.py | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 15 deletions(-) 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): contains the resulting conversation dict.""" pass + def on_unknown_event(self, name, unknown_event = None): + """An unknown mastodon API event has been received. The name contains the event-name and unknown_event + contains the content of the unknown event. + + This function must be implemented, if unknown events should be handled without an error. + """ + exception = MastodonMalformedEventError('Bad event type', name) + self.on_abort(exception) + raise exception + def handle_heartbeat(self): """The server has sent us a keep-alive message. This callback may be useful to carry out periodic housekeeping tasks, or just to confirm @@ -56,6 +66,11 @@ class StreamListener(object): Handles a stream of events from the Mastodon server. When each event is received, the corresponding .on_[name]() method is called. + When the Mastodon API changes, the on_unknown_event(name, content) + function is called. + The default behavior is to throw an error. Define a callback handler + to intercept unknown events if needed (and avoid errors) + response; a requests response object with the open stream for reading. """ event = {} @@ -137,33 +152,32 @@ class StreamListener(object): exception, err ) - - handler_name = 'on_' + name - try: - handler = getattr(self, handler_name) - except AttributeError as err: - exception = MastodonMalformedEventError('Bad event type', name) - self.on_abort(exception) - six.raise_from( - exception, - err - ) - else: + # New mastodon API also supports event names with dots: + handler_name = 'on_' + name.replace('.', '_') + # A generic way to handle unknown events to make legacy code more stable for future changes + handler = getattr(self, handler_name, self.on_unknown_event) + if handler != self.on_unknown_event: handler(payload) + else: + handler(name, payload) + class CallbackStreamListener(StreamListener): """ Simple callback stream handler class. Can optionally additionally send local update events to a separate handler. + Define an unknown_event_handler for new Mastodon API events. If not, the + listener will raise an error on new, not handled, events from the API. """ - def __init__(self, update_handler = None, local_update_handler = None, delete_handler = None, notification_handler = None, conversation_handler = None): + def __init__(self, update_handler = None, local_update_handler = None, delete_handler = None, notification_handler = None, conversation_handler = None, unknown_event_handler = None): super(CallbackStreamListener, self).__init__() self.update_handler = update_handler self.local_update_handler = local_update_handler self.delete_handler = delete_handler self.notification_handler = notification_handler self.conversation_handler = conversation_handler - + self.unknown_event_handler = unknown_event_handler + def on_update(self, status): if self.update_handler != None: self.update_handler(status) @@ -188,3 +202,11 @@ class CallbackStreamListener(StreamListener): def on_conversation(self, conversation): if self.conversation_handler != None: self.conversation_handler(conversation) + + def on_unknown_event(self, name, unknown_event = None): + if self.unknown_event_handler != None: + self.unknown_event_handler(name, unknown_event) + else: + exception = MastodonMalformedEventError('Bad event type', name) + self.on_abort(exception) + 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): self.notifications = [] self.deletes = [] self.heartbeats = 0 + self.bla_called = False + self.do_something_called = False def on_update(self, status): self.updates.append(status) @@ -72,6 +74,11 @@ class Listener(StreamListener): self.deletes.append(status_id) def on_blahblah(self, data): + self.bla_called = True + pass + + def on_do_something(self, data): + self.do_something_called = True pass def handle_heartbeat(self): @@ -158,6 +165,37 @@ def test_unknown_event(): 'data: {}', '', ]) + assert listener.bla_called == True + assert listener.updates == [] + assert listener.notifications == [] + assert listener.deletes == [] + assert listener.heartbeats == 0 + +def test_unknown_handled_event(): + """Be tolerant of new unknown event types, if on_unknown_event is available""" + listener = Listener() + listener.on_unknown_event = lambda name, payload: None + + listener.handle_stream_([ + 'event: complete.new.event', + 'data: {"k": "v"}', + '', + ]) + + assert listener.updates == [] + assert listener.notifications == [] + assert listener.deletes == [] + assert listener.heartbeats == 0 + +def test_dotted_unknown_event(): + """Be tolerant of new event types with dots in the event-name""" + listener = Listener() + listener.handle_stream_([ + 'event: do.something', + 'data: {}', + '', + ]) + assert listener.do_something_called == True assert listener.updates == [] assert listener.notifications == [] assert listener.deletes == [] @@ -169,7 +207,7 @@ def test_invalid_event(): with pytest.raises(MastodonMalformedEventError): listener.handle_stream_([ 'event: whatup', - 'data: {}', + 'data: {"k": "v"}', '', ])