Source code for falcon.asgi.structures
from falcon.constants import MEDIA_JSON
from falcon.media.json import _DEFAULT_JSON_HANDLER
__all__ = ['SSEvent']
[docs]
class SSEvent:
"""Represents a Server-Sent Event (SSE).
Instances of this class can be yielded by an async generator in order to
send a series of `Server-Sent Events`_ to the user agent.
(See also: :attr:`falcon.asgi.Response.sse`)
Keyword Args:
data (bytes): Raw byte string to use as the ``data`` field for the
event message. Takes precedence over both `text` and `json`.
text (str): String to use for the ``data`` field in the message. Will
be encoded as UTF-8 in the event. Takes precedence over `json`.
json (object): JSON-serializable object to be converted to JSON and
used as the ``data`` field in the event message.
event (str): A string identifying the event type (AKA event name).
event_id (str): The event ID that the User Agent should use for
the `EventSource` object's last event ID value.
retry (int): The reconnection time to use when attempting to send the
event. This must be an integer, specifying the reconnection time
in milliseconds.
comment (str): Comment to include in the event message; this is
normally ignored by the user agent, but is useful when composing
a periodic "ping" message to keep the connection alive. Since this
is a common use case, a default "ping" comment will be included
in any event that would otherwise be blank (i.e., one that does
not specify any fields when initializing the `SSEvent` instance.)
Attributes:
data (bytes): Raw byte string to use as the ``data`` field for the
event message. Takes precedence over both `text` and `json`.
text (str): String to use for the ``data`` field in the message. Will
be encoded as UTF-8 in the event. Takes precedence over `json`.
json (object): JSON-serializable object to be converted to JSON and
used as the ``data`` field in the event message.
event (str): A string identifying the event type (AKA event name).
event_id (str): The event ID that the User Agent should use for
the `EventSource` object's last event ID value.
retry (int): The reconnection time to use when attempting to send the
event. This must be an integer, specifying the reconnection time
in milliseconds.
comment (str): Comment to include in the event message; this is
normally ignored by the user agent, but is useful when composing
a periodic "ping" message to keep the connection alive. Since this
is a common use case, a default "ping" comment will be included
in any event that would otherwise be blank (i.e., one that does
not specify any of these fields when initializing the
`SSEvent` instance.)
.. _Server-Sent Events:
https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
"""
__slots__ = [
'data',
'text',
'json',
'event',
'event_id',
'retry',
'comment',
]
def __init__(
self,
data=None,
text=None,
json=None,
event=None,
event_id=None,
retry=None,
comment=None,
):
# NOTE(kgriffs): Check up front since this makes it a lot easier
# to debug the source of the problem in the app vs. waiting for
# an error to be raised from the framework when it calls serialize()
# after the fact.
if data is not None and not isinstance(data, bytes):
raise TypeError('data must be a byte string')
if text is not None and not isinstance(text, str):
raise TypeError('text must be a string')
if event is not None and not isinstance(event, str):
raise TypeError('event name must be a string')
if event_id is not None and not isinstance(event_id, str):
raise TypeError('event_id must be a string')
if comment is not None and not isinstance(comment, str):
raise TypeError('comment must be a string')
if retry is not None and not isinstance(retry, int):
raise TypeError('retry must be an int')
self.data = data
self.text = text
self.json = json
self.event = event
self.event_id = event_id
self.retry = retry
self.comment = comment
[docs]
def serialize(self, handler=None):
"""Serialize this event to string.
Args:
handler: Handler object that will be used to serialize the ``json``
attribute to string. When not provided, a default handler using
the builtin JSON library will be used (default ``None``).
Returns:
bytes: string representation of this event.
"""
if self.comment is not None:
block = f': {self.comment}\n'
else:
block = ''
if self.event is not None:
block += f'event: {self.event}\n'
if self.event_id is not None:
# NOTE(kgriffs): f-strings are a tiny bit faster than str().
block += f'id: {self.event_id}\n'
if self.retry is not None:
block += f'retry: {self.retry}\n'
if self.data is not None:
# NOTE(kgriffs): While this decode() may seem unnecessary, it
# does provide a check to ensure it is valid UTF-8. I'm also
# assuming for the moment that most people will not use this
# attribute, but rather the text and json ones instead. If that
# is true, it makes sense to construct the entire string
# first, then encode it all in one go at the end.
block += f'data: {self.data.decode()}\n'
elif self.text is not None:
block += f'data: {self.text}\n'
elif self.json is not None:
if handler is None:
handler = _DEFAULT_JSON_HANDLER
serialized = handler.serialize(self.json, MEDIA_JSON)
block += 'data: '
return block.encode() + serialized + b'\n\n'
if not block:
return b': ping\n\n'
return (block + '\n').encode()