Search code examples
pythonpython-asyncioflask-socketiopython-socketio

How to use custom decorators in python-socketio server?


I'd like to use some custom decorators on event handlers in order to make working with sessions more comfortable.

Here is my event handler without custom decorator:

@sio.event
async def test(sid, data):
    async with sio.session(sid) as sess:
        room_id = sess["room_id"]
        print('test', room_id)
        await sio.emit("test", {'msg': 'msg'}, room=room_id)

Here is my event with custom decorator:

import functools


async def sess_decorator(func):
    @functools.wraps(func)
    async def wrapper(sid, data, *args, **kwargs):
        async with sio.session(sid) as sess:
            return func(sid, data, sess, *args, **kwargs)
        logger.error(f'Failed to get sess.\nsid: {sid}\ndata: {data}')

    return wrapper


@sio.event
@sess_decorator
async def test(sid, data, sess):
     room_id = sess["room_id"]
     print('test', room_id)
     await sio.emit("test", {'msg': 'msg'}, room=room_id)

The problem is after applying @sess_decorator event doesnt seem to trigger at all. And if I change an order of decorators it fails with following traceback before calling my decorator:

Traceback (most recent call last):
  File "/home/pata/.pyenv/versions/3.7.7/envs/cartel_env/lib/python3.7/site-packages/socketio/asyncio_server.py", line 476, in _handle_event_internal
    r = await server._trigger_event(data[0], namespace, sid, *data[1:])
  File "/home/pata/.pyenv/versions/3.7.7/envs/cartel_env/lib/python3.7/site-packages/socketio/asyncio_server.py", line 504, in _trigger_event
    ret = await self.handlers[namespace][event](*args)
TypeError: test() missing 1 required positional argument: 'sess'

Seems that there is some library design issue? Is there any other way to comlete same logic?


Solution

  • You have a couple of mistakes in how you implemented the decorator:

    • the decorator itself should not be an async function
    • the func argument is the original function, which is a coroutine, so it should be awaited

    The decorator with the fixes looks as follows:

    def sess_decorator(func):
        @functools.wraps(func)
        async def wrapper(sid, data, *args, **kwargs):
            async with sio.session(sid) as sess:
                return await func(sid, data, sess, *args, **kwargs)
            logger.error(f'Failed to get sess.\nsid: {sid}\ndata: {data}')
    
        return wrapper