Merge pull request #235 from arittner/stream-unknown-handler

Support of processing unknown events and event names with dots.
This commit is contained in:
Aljoscha Rittner 2022-06-24 18:11:23 +02:00 cometido por GitHub
commit c7fdcf3fae
No se encontró ninguna clave conocida en la base de datos para esta firma
ID de clave GPG: 4AEE18F83AFDEB23
S'han modificat 3 arxius amb 89 adicions i 15 eliminacions

Veure arxiu

@ -1275,6 +1275,19 @@ The streaming functions take instances of `StreamListener` as the `listener` par
A `CallbackStreamListener` class that allows you to specify function callbacks A `CallbackStreamListener` class that allows you to specify function callbacks
directly is included for convenience. directly is included for convenience.
For new well-known events implement the streaming function in `StreamListener` or `CallbackStreamListener`.
The function name is `on_` + the event name. If the event-name contains dots, use an underscore instead.
E.g. for `'status.update'` the listener function should be named as `on_status_update`.
It may be that future Mastodon versions will come with completely new (unknown) event names. In this
case a (deprecated) Mastodon.py would throw an error. If you want to avoid this in general, you can
override the listener function `on_unknown_event`. This has an additional parameter `name` which informs
about the name of the event. `unknown_event` contains the content of the event.
Alternatively, a callback function can be passed in the `unknown_event_handler` parameter in the
`CallbackStreamListener` constructor.
When in not-async mode or async mode without async_reconnect, the stream functions may raise When in not-async mode or async mode without async_reconnect, the stream functions may raise
various exceptions: `MastodonMalformedEventError` if a received event cannot be parsed and various exceptions: `MastodonMalformedEventError` if a received event cannot be parsed and
`MastodonNetworkError` if any connection problems occur. `MastodonNetworkError` if any connection problems occur.
@ -1294,6 +1307,7 @@ StreamListener
.. automethod:: StreamListener.on_notification .. automethod:: StreamListener.on_notification
.. automethod:: StreamListener.on_delete .. automethod:: StreamListener.on_delete
.. automethod:: StreamListener.on_conversation .. automethod:: StreamListener.on_conversation
.. automethod:: StreamListener.on_unknown_event
.. automethod:: StreamListener.on_abort .. automethod:: StreamListener.on_abort
.. automethod:: StreamListener.handle_heartbeat .. automethod:: StreamListener.handle_heartbeat

Veure arxiu

@ -45,6 +45,16 @@ class StreamListener(object):
contains the resulting conversation dict.""" contains the resulting conversation dict."""
pass 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): def handle_heartbeat(self):
"""The server has sent us a keep-alive message. This callback may be """The server has sent us a keep-alive message. This callback may be
useful to carry out periodic housekeeping tasks, or just to confirm 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 Handles a stream of events from the Mastodon server. When each event
is received, the corresponding .on_[name]() method is called. 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. response; a requests response object with the open stream for reading.
""" """
event = {} event = {}
@ -137,32 +152,31 @@ class StreamListener(object):
exception, exception,
err err
) )
# New mastodon API also supports event names with dots:
handler_name = 'on_' + name handler_name = 'on_' + name.replace('.', '_')
try: # A generic way to handle unknown events to make legacy code more stable for future changes
handler = getattr(self, handler_name) handler = getattr(self, handler_name, self.on_unknown_event)
except AttributeError as err: if handler != self.on_unknown_event:
exception = MastodonMalformedEventError('Bad event type', name)
self.on_abort(exception)
six.raise_from(
exception,
err
)
else:
handler(payload) handler(payload)
else:
handler(name, payload)
class CallbackStreamListener(StreamListener): class CallbackStreamListener(StreamListener):
""" """
Simple callback stream handler class. Simple callback stream handler class.
Can optionally additionally send local update events to a separate handler. 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__() super(CallbackStreamListener, self).__init__()
self.update_handler = update_handler self.update_handler = update_handler
self.local_update_handler = local_update_handler self.local_update_handler = local_update_handler
self.delete_handler = delete_handler self.delete_handler = delete_handler
self.notification_handler = notification_handler self.notification_handler = notification_handler
self.conversation_handler = conversation_handler self.conversation_handler = conversation_handler
self.unknown_event_handler = unknown_event_handler
def on_update(self, status): def on_update(self, status):
if self.update_handler != None: if self.update_handler != None:
@ -188,3 +202,11 @@ class CallbackStreamListener(StreamListener):
def on_conversation(self, conversation): def on_conversation(self, conversation):
if self.conversation_handler != None: if self.conversation_handler != None:
self.conversation_handler(conversation) 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

Veure arxiu

@ -61,6 +61,8 @@ class Listener(StreamListener):
self.notifications = [] self.notifications = []
self.deletes = [] self.deletes = []
self.heartbeats = 0 self.heartbeats = 0
self.bla_called = False
self.do_something_called = False
def on_update(self, status): def on_update(self, status):
self.updates.append(status) self.updates.append(status)
@ -72,6 +74,11 @@ class Listener(StreamListener):
self.deletes.append(status_id) self.deletes.append(status_id)
def on_blahblah(self, data): def on_blahblah(self, data):
self.bla_called = True
pass
def on_do_something(self, data):
self.do_something_called = True
pass pass
def handle_heartbeat(self): def handle_heartbeat(self):
@ -158,6 +165,37 @@ def test_unknown_event():
'data: {}', '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.updates == []
assert listener.notifications == [] assert listener.notifications == []
assert listener.deletes == [] assert listener.deletes == []
@ -169,7 +207,7 @@ def test_invalid_event():
with pytest.raises(MastodonMalformedEventError): with pytest.raises(MastodonMalformedEventError):
listener.handle_stream_([ listener.handle_stream_([
'event: whatup', 'event: whatup',
'data: {}', 'data: {"k": "v"}',
'', '',
]) ])