Skip to content

Commit

Permalink
New @event decorator for handler registration
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed Jun 1, 2019
1 parent aaa87a8 commit 70ebfdb
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 87 deletions.
83 changes: 42 additions & 41 deletions docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,45 +40,51 @@ appropriate client class::
Defining Event Handlers
-----------------------

To responds to events triggered by the connection or the server, event Handler
functions must be defined using the ``on`` decorator::
The Socket.IO protocol is event based. When a server wants to communicate with
a client it *emits* an event. Each event has a name, and a list of
arguments. The client registers event handler functions with the
:func:`socketio.Client.event` or :func:`socketio.Client.on` decorators::

@sio.on('connect')
def on_connect():
print('I\'m connected!')

@sio.on('message')
def on_message(data):
@sio.event
def message(data):
print('I received a message!')

@sio.on('my message')
def on_message(data):
print('I received a custom message!')
print('I received a message!')

@sio.on('disconnect')
def on_disconnect():
print('I\'m disconnected!')
In the first example the event name is obtained from the name of the
handler function. The second example is slightly more verbose, but it
allows the event name to be different than the function name or to include
characters that are illegal in function names, such as spaces.

For the ``asyncio`` server, event handlers can be regular functions as above,
For the ``asyncio`` client, event handlers can be regular functions as above,
or can also be coroutines::

@sio.on('message')
async def on_message(data):
@sio.event
async def message(data):
print('I received a message!')

The argument given to the ``on`` decorator is the event name. The predefined
events that are supported are ``connect``, ``message`` and ``disconnect``. The
application can define any other desired event names.
The ``connect`` and ``disconnect`` events are special; they are invoked
automatically when a client connects or disconnects from the server::

@sio.event
def connect():
print("I'm connected!")

@sio.event
def disconnect():
print("I'm disconnected!")

Note that the ``disconnect`` handler is invoked for application initiated
disconnects, server initiated disconnects, or accidental disconnects, for
example due to networking failures. In the case of an accidental disconnection,
the client is going to attempt to reconnect immediately after invoking the
disconnect handler. As soon as the connection is re-established the connect
handler will be invoked once again.
example due to networking failures. In the case of an accidental
disconnection, the client is going to attempt to reconnect immediately after
invoking the disconnect handler. As soon as the connection is re-established
the connect handler will be invoked once again.

The ``data`` argument passed to the ``'message'`` and custom event Handlers
contains application-specific data provided by the server.
If the server includes arguments with an event, those are passed to the
handler function as arguments.

Connecting to a Server
----------------------
Expand Down Expand Up @@ -109,24 +115,15 @@ Or in the case of ``asyncio``, as a coroutine::
await sio.emit('my message', {'foo': 'bar'})

The single argument provided to the method is the data that is passed on
to the server. The data can be of type ``str``, ``bytes``, ``dict`` or
``list``. The data included inside dictionaries and lists is also
constrained to these types.
to the server. The data can be of type ``str``, ``bytes``, ``dict``,
``list`` or ``tuple``. When sending a ``tuple``, the elements in it need to
be of any of the other four allowed types. The elements of the tuple will be
passed as multiple arguments to the server-side event handler function.

The ``emit()`` method can be invoked inside an event handler as a response
to a server event, or in any other part of the application, including in
background tasks.

For convenience, a ``send()`` method is also provided. This method accepts
a data element as its only argument, and emits the standard ``message``
event with it::

sio.send('some data')

In the case of ``asyncio``, ``send()`` is a coroutine::

await sio.send('some data')

Event Callbacks
---------------

Expand All @@ -137,8 +134,8 @@ client can provide a list of return values that are to be passed on to the
callback function set up by the server. This is achieves simply by returning
the desired values from the handler function::

@sio.on('my event', namespace='/chat')
def my_event_handler(sid, data):
@sio.event
def my_event(sid, data):
# handle the message
return "OK", 123

Expand All @@ -163,11 +160,15 @@ namespace::
sio.connect('http://localhost:5000', namespaces=['/chat'])

To define event handlers on a namespace, the ``namespace`` argument must be
added to the ``on`` decorator::
added to the corresponding decorator::

@sio.event(namespace='/chat')
def my_custom_event(sid, data):
pass

@sio.on('connect', namespace='/chat')
def on_connect():
print('I\'m connected to the /chat namespace!')
print("I'm connected to the /chat namespace!")

Likewise, the client can emit an event to the server on a namespace by
providing its in the ``emit()`` call::
Expand Down
28 changes: 14 additions & 14 deletions docs/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ The example that follows shows a simple Python client:
sio = socketio.Client()
@sio.on('connect')
def on_connect():
@sio.event
def connect():
print('connection established')
@sio.on('my message')
def on_message(data):
@sio.event
def my_message(data):
print('message received with ', data)
sio.emit('my response', {'response': 'my response'})
@sio.on('disconnect')
def on_disconnect():
@sio.event
def disconnect():
print('disconnected from server')
sio.connect('http://localhost:5000')
Expand Down Expand Up @@ -71,15 +71,15 @@ asynchronous server:
'/': {'content_type': 'text/html', 'filename': 'index.html'}
})
@sio.on('connect')
@sio.event
def connect(sid, environ):
print('connect ', sid)
@sio.on('my message')
def message(sid, data):
@sio.event
def my_message(sid, data):
print('message ', data)
@sio.on('disconnect')
@sio.event
def disconnect(sid):
print('disconnect ', sid)
Expand All @@ -103,16 +103,16 @@ Uvicorn web server:
with open('index.html') as f:
return web.Response(text=f.read(), content_type='text/html')
@sio.on('connect', namespace='/chat')
@sio.event
def connect(sid, environ):
print("connect ", sid)
@sio.on('chat message', namespace='/chat')
async def message(sid, data):
@sio.event
async def chat_message(sid, data):
print("message ", data)
await sio.emit('reply', room=sid)
@sio.on('disconnect', namespace='/chat')
@sio.event
def disconnect(sid):
print('disconnect ', sid)
Expand Down
77 changes: 45 additions & 32 deletions docs/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,30 +135,39 @@ Defining Event Handlers
The Socket.IO protocol is event based. When a client wants to communicate with
the server it *emits* an event. Each event has a name, and a list of
arguments. The server registers event handler functions with the
:func:`socketio.Server.on` decorator::
:func:`socketio.Server.event` or :func:`socketio.Server.on` decorators::

@sio.event
def my_event(sid, data):
pass

@sio.on('my custom event')
def my_custom_event(sid, data):
def another_event(sid, data):
pass

In the first example the event name is obtained from the name of the handler
function. The second example is slightly more verbose, but it allows the event
name to be different than the function name or to include characters that are
illegal in function names, such as spaces.

For asyncio servers, event handlers can optionally be given as coroutines::

@sio.on('my custom event')
async def my_custom_event(sid, data):
@sio.event
async def my_event(sid, data):
pass

The ``sid`` argument is the Socket.IO session id, a unique identifier of each
client connection. All the events sent by a given client will have the same
``sid`` value.

The ``connect`` and ``disconnect`` are special; they are invoked automatically
when a client connects or disconnects from the server::
The ``connect`` and ``disconnect`` events are special; they are invoked
automatically when a client connects or disconnects from the server::

@sio.on('connect')
@sio.event
def connect(sid, environ):
print('connect ', sid)

@sio.on('disconnect')
@sio.event
def disconnect(sid):
print('disconnect ', sid)

Expand All @@ -172,9 +181,9 @@ headers. After inspecting the request, the connect event handler can return
Sometimes it is useful to pass data back to the client being rejected. In that
case instead of returning ``False``
:class:`socketio.exceptions.ConnectionRefusedError` can be raised, and all of
its argument will be sent to the client with the rejection::
its arguments will be sent to the client with the rejection message::

@sio.on('connect')
@sio.event
def connect(sid, environ):
raise ConnectionRefusedError('authentication failed')

Expand Down Expand Up @@ -210,8 +219,8 @@ has processed the event. While this is entirely managed by the client, the
server can provide a list of values that are to be passed on to the callback
function, simply by returning them from the handler function::

@sio.on('my event', namespace='/chat')
def my_event_handler(sid, data):
@sio.event
def my_event(sid, data):
# handle the message
return "OK", 123

Expand Down Expand Up @@ -240,6 +249,10 @@ that use multiple namespaces specify the correct namespace when setting up
their event handlers and rooms, using the optional ``namespace`` argument
available in all the methods in the :class:`socketio.Server` class::

@sio.event(namespace='/chat')
def my_custom_event(sid, data):
pass

@sio.on('my custom event', namespace='/chat')
def my_custom_event(sid, data):
pass
Expand Down Expand Up @@ -322,11 +335,11 @@ rooms as needed and can be moved between rooms as often as necessary.

::

@sio.on('chat')
@sio.event
def begin_chat(sid):
sio.enter_room(sid, 'chat_users')

@sio.on('exit_chat')
@sio.event
def exit_chat(sid):
sio.leave_room(sid, 'chat_users')

Expand All @@ -338,8 +351,8 @@ during the broadcast.

::

@sio.on('my message')
def message(sid, data):
@sio.event
def my_message(sid, data):
sio.emit('my reply', data, room='chat_users', skip_sid=sid)

User Sessions
Expand All @@ -353,52 +366,52 @@ of the connection, such as usernames or user ids.
The ``save_session()`` and ``get_session()`` methods are used to store and
retrieve information in the user session::

@sio.on('connect')
def on_connect(sid, environ):
@sio.event
def connect(sid, environ):
username = authenticate_user(environ)
sio.save_session(sid, {'username': username})

@sio.on('message')
def on_message(sid, data):
@sio.event
def message(sid, data):
session = sio.get_session(sid)
print('message from ', session['username'])

For the ``asyncio`` server, these methods are coroutines::


@sio.on('connect')
async def on_connect(sid, environ):
@sio.event
async def connect(sid, environ):
username = authenticate_user(environ)
await sio.save_session(sid, {'username': username})

@sio.on('message')
async def on_message(sid, data):
@sio.event
async def message(sid, data):
session = await sio.get_session(sid)
print('message from ', session['username'])

The session can also be manipulated with the `session()` context manager::

@sio.on('connect')
def on_connect(sid, environ):
@sio.event
def connect(sid, environ):
username = authenticate_user(environ)
with sio.session(sid) as session:
session['username'] = username

@sio.on('message')
def on_message(sid, data):
@sio.event
def message(sid, data):
with sio.session(sid) as session:
print('message from ', session['username'])

For the ``asyncio`` server, an asynchronous context manager is used::

@sio.on('connect')
def on_connect(sid, environ):
@sio.event
def connect(sid, environ):
username = authenticate_user(environ)
async with sio.session(sid) as session:
session['username'] = username

@sio.on('message')
def on_message(sid, data):
@sio.event
def message(sid, data):
async with sio.session(sid) as session:
print('message from ', session['username'])

Expand Down

0 comments on commit 70ebfdb

Please sign in to comment.