Merge pull request #235 from arittner/stream-unknown-handler
Support of processing unknown events and event names with dots.
This commit is contained in:
commit
c7fdcf3fae
S'han modificat 3 arxius amb 89 adicions i 15 eliminacions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"}',
|
||||||
'',
|
'',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
Loading…
Referencia en una nova incidència